Skip to content

Commit 6f8bca2

Browse files
committed
Manual sync GitLab MR !2740
1 parent 73cc4f6 commit 6f8bca2

18 files changed

Lines changed: 761 additions & 1 deletion

File tree

_mocks/opencsg.com/csghub-server/component/mock_RepoComponent.go

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/handler/repo.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,68 @@ func (h *RepoHandler) ChangePath(ctx *gin.Context) {
19701970
httpbase.OK(ctx, nil)
19711971
}
19721972

1973+
// TransferOwnership godoc
1974+
// @Security ApiKey
1975+
// @Summary Transfer repository ownership to another namespace
1976+
// @Tags Repository
1977+
// @Accept json
1978+
// @Produce json
1979+
// @Param namespace path string true "current namespace"
1980+
// @Param name path string true "repository name"
1981+
// @Param body body types.TransferRepoReq true "transfer request"
1982+
// @Success 200 {object} types.Response{} "OK"
1983+
// @Failure 400 {object} types.APIBadRequest "Bad request"
1984+
// @Failure 403 {object} types.APIForbidden "Forbidden"
1985+
// @Failure 500 {object} types.APIInternalServerError "Internal server error"
1986+
// @Router /{repo_type}/{namespace}/{name}/transfer [post]
1987+
func (h *RepoHandler) TransferOwnership(ctx *gin.Context) {
1988+
var req types.TransferRepoReq
1989+
if err := ctx.ShouldBindJSON(&req); err != nil {
1990+
slog.ErrorContext(ctx.Request.Context(), "invalid request body", slog.Any("error", err))
1991+
httpbase.BadRequest(ctx, err.Error())
1992+
return
1993+
}
1994+
1995+
namespace, name, err := common.GetNamespaceAndNameFromContext(ctx)
1996+
if err != nil {
1997+
slog.ErrorContext(ctx.Request.Context(), "invalid request body", slog.Any("error", err))
1998+
httpbase.BadRequest(ctx, err.Error())
1999+
return
2000+
}
2001+
req.Namespace = namespace
2002+
req.Name = name
2003+
req.CurrentUser = httpbase.GetCurrentUser(ctx)
2004+
req.RepoType = common.RepoTypeFromContext(ctx)
2005+
2006+
err = h.c.TransferOwnership(ctx.Request.Context(), req)
2007+
if err != nil {
2008+
if errors.Is(err, errorx.ErrNoSourceTransferPermission) ||
2009+
errors.Is(err, errorx.ErrNoTargetTransferPermission) {
2010+
slog.ErrorContext(ctx.Request.Context(), "forbidden to transfer ownership", slog.Any("error", err))
2011+
httpbase.ForbiddenError(ctx, err)
2012+
return
2013+
}
2014+
if errors.Is(err, errorx.ErrTransferSameNamespace) ||
2015+
errors.Is(err, errorx.ErrTransferTargetExists) ||
2016+
errors.Is(err, errorx.ErrTransferNotSupported) ||
2017+
errors.Is(err, errorx.ErrBadRequest) ||
2018+
errors.Is(err, errorx.ErrChangePathBlocked) {
2019+
slog.ErrorContext(ctx.Request.Context(), "bad request for transfer ownership", slog.Any("error", err))
2020+
httpbase.BadRequestWithExt(ctx, err)
2021+
return
2022+
}
2023+
if errors.Is(err, errorx.ErrForbidden) {
2024+
slog.ErrorContext(ctx.Request.Context(), "forbidden to transfer ownership", slog.Any("error", err))
2025+
httpbase.ForbiddenError(ctx, err)
2026+
return
2027+
}
2028+
slog.ErrorContext(ctx.Request.Context(), "failed to transfer ownership", slog.Any("error", err))
2029+
httpbase.ServerError(ctx, err)
2030+
return
2031+
}
2032+
httpbase.OK(ctx, nil)
2033+
}
2034+
19732035
// GetRepos godoc
19742036
// @Security ApiKey
19752037
// @Summary Get repo paths with search query

api/handler/repo_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,3 +2038,144 @@ func TestRepoHandler_BatchGetRepoExtra(t *testing.T) {
20382038
tester.ResponseEqCode(t, http.StatusInternalServerError)
20392039
})
20402040
}
2041+
2042+
func TestRepoHandler_TransferOwnership_Success(t *testing.T) {
2043+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2044+
return rp.TransferOwnership
2045+
})
2046+
2047+
tester.WithUser()
2048+
tester.WithKV("repo_type", types.ModelRepo)
2049+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2050+
2051+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), types.TransferRepoReq{
2052+
RepoType: types.ModelRepo,
2053+
Namespace: "u",
2054+
Name: "r",
2055+
NewNamespace: "targetns",
2056+
CurrentUser: "u",
2057+
}).Return(nil)
2058+
2059+
tester.Execute()
2060+
2061+
tester.ResponseEqCode(t, http.StatusOK)
2062+
}
2063+
2064+
func TestRepoHandler_TransferOwnership_BadRequest(t *testing.T) {
2065+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2066+
return rp.TransferOwnership
2067+
})
2068+
2069+
tester.WithUser()
2070+
tester.WithKV("repo_type", types.ModelRepo)
2071+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2072+
2073+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2074+
Return(errorx.ErrBadRequest)
2075+
2076+
tester.Execute()
2077+
2078+
tester.ResponseEqCode(t, http.StatusBadRequest)
2079+
}
2080+
2081+
func TestRepoHandler_TransferOwnership_Forbidden(t *testing.T) {
2082+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2083+
return rp.TransferOwnership
2084+
})
2085+
2086+
tester.WithUser()
2087+
tester.WithKV("repo_type", types.ModelRepo)
2088+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2089+
2090+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2091+
Return(errorx.ErrForbiddenMsg("users do not have permission"))
2092+
2093+
tester.Execute()
2094+
2095+
tester.ResponseEqCode(t, http.StatusForbidden)
2096+
}
2097+
2098+
func TestRepoHandler_TransferOwnership_ServerError(t *testing.T) {
2099+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2100+
return rp.TransferOwnership
2101+
})
2102+
2103+
tester.WithUser()
2104+
tester.WithKV("repo_type", types.ModelRepo)
2105+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2106+
2107+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2108+
Return(errors.New("internal error"))
2109+
2110+
tester.Execute()
2111+
2112+
tester.ResponseEqCode(t, http.StatusInternalServerError)
2113+
}
2114+
2115+
func TestRepoHandler_TransferOwnership_NoSourcePermission(t *testing.T) {
2116+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2117+
return rp.TransferOwnership
2118+
})
2119+
2120+
tester.WithUser()
2121+
tester.WithKV("repo_type", types.ModelRepo)
2122+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2123+
2124+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2125+
Return(errorx.ErrNoSourceTransferPermission)
2126+
2127+
tester.Execute()
2128+
2129+
tester.ResponseEqCode(t, http.StatusForbidden)
2130+
}
2131+
2132+
func TestRepoHandler_TransferOwnership_SameNamespace(t *testing.T) {
2133+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2134+
return rp.TransferOwnership
2135+
})
2136+
2137+
tester.WithUser()
2138+
tester.WithKV("repo_type", types.ModelRepo)
2139+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "same"})
2140+
2141+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2142+
Return(errorx.ErrTransferSameNamespace)
2143+
2144+
tester.Execute()
2145+
2146+
tester.ResponseEqCode(t, http.StatusBadRequest)
2147+
}
2148+
2149+
func TestRepoHandler_TransferOwnership_TargetExists(t *testing.T) {
2150+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2151+
return rp.TransferOwnership
2152+
})
2153+
2154+
tester.WithUser()
2155+
tester.WithKV("repo_type", types.ModelRepo)
2156+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2157+
2158+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2159+
Return(errorx.ErrTransferTargetExists)
2160+
2161+
tester.Execute()
2162+
2163+
tester.ResponseEqCode(t, http.StatusBadRequest)
2164+
}
2165+
2166+
func TestRepoHandler_TransferOwnership_NotSupported(t *testing.T) {
2167+
tester := NewRepoTester(t).WithHandleFunc(func(rp *RepoHandler) gin.HandlerFunc {
2168+
return rp.TransferOwnership
2169+
})
2170+
2171+
tester.WithUser()
2172+
tester.WithKV("repo_type", types.ModelRepo)
2173+
tester.WithBody(t, types.TransferRepoReq{NewNamespace: "targetns"})
2174+
2175+
tester.mocks.repo.EXPECT().TransferOwnership(tester.Ctx(), mock.Anything).
2176+
Return(errorx.ErrTransferNotSupported)
2177+
2178+
tester.Execute()
2179+
2180+
tester.ResponseEqCode(t, http.StatusBadRequest)
2181+
}

api/router/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,7 @@ func createModelRoutes(config *config.Config,
658658
{
659659
modelsGroup.GET("/:namespace/:name/branches", repoCommonHandler.Branches)
660660
modelsGroup.GET("/:namespace/:name/tags", repoCommonHandler.Tags)
661+
modelsGroup.POST("/:namespace/:name/transfer", middlewareCollection.Auth.NeedLogin, repoCommonHandler.TransferOwnership)
661662
modelsGroup.POST("/:namespace/:name/preupload/:revision", middlewareCollection.Auth.NeedPhoneVerified, repoCommonHandler.Preupload)
662663
// update tags of a certain category
663664
modelsGroup.GET("/:namespace/:name/all_files", cache.Cache(memoryStore, time.Minute*2, middleware.CacheRepoInfo()), repoCommonHandler.AllFiles)
@@ -807,6 +808,7 @@ func createDatasetRoutes(
807808

808809
datasetsGroup.GET("/:namespace/:name/branches", middleware.MustLogin(), repoCommonHandler.Branches)
809810
datasetsGroup.GET("/:namespace/:name/tags", middleware.MustLogin(), repoCommonHandler.Tags)
811+
datasetsGroup.POST("/:namespace/:name/transfer", middleware.MustLogin(), repoCommonHandler.TransferOwnership)
810812
datasetsGroup.POST("/:namespace/:name/preupload/:revision", middlewareCollection.Auth.NeedPhoneVerified, repoCommonHandler.Preupload)
811813
// update tags of a certain category
812814
datasetsGroup.GET("/:namespace/:name/all_files", middleware.MustLogin(), cache.Cache(memoryStore, time.Minute*2, middleware.CacheRepoInfo()), repoCommonHandler.AllFiles)
@@ -860,6 +862,7 @@ func createCodeRoutes(
860862
codesGroup.GET("/:namespace/:name/relations", codeHandler.Relations)
861863
codesGroup.GET("/:namespace/:name/branches", repoCommonHandler.Branches)
862864
codesGroup.GET("/:namespace/:name/tags", repoCommonHandler.Tags)
865+
codesGroup.POST("/:namespace/:name/transfer", middlewareCollection.Auth.NeedLogin, repoCommonHandler.TransferOwnership)
863866
codesGroup.POST("/:namespace/:name/preupload/:revision", middlewareCollection.Auth.NeedPhoneVerified, repoCommonHandler.Preupload)
864867

865868
// update tags of a certain category

api/router/api_mcp.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ func CreateMCPServerRoutes(
3131
mcpGroup.GET("/:namespace/:name/all_files", repoCommonHandler.AllFiles)
3232
mcpGroup.GET("/:namespace/:name/branches", repoCommonHandler.Branches)
3333
mcpGroup.GET("/:namespace/:name/tags", repoCommonHandler.Tags)
34-
mcpGroup.POST("/:namespace/:name/preupload/:revision", repoCommonHandler.Preupload)
34+
mcpGroup.POST("/:namespace/:name/transfer", middlewareCollection.Auth.NeedLogin, repoCommonHandler.TransferOwnership)
35+
mcpGroup.POST("/:namespace/:name/preupload/:revision", repoCommonHandler.Preupload)
3536
mcpGroup.POST("/:namespace/:name/tags/:category", middlewareCollection.Auth.NeedLogin, repoCommonHandler.UpdateTags)
3637
mcpGroup.GET("/:namespace/:name/last_commit", repoCommonHandler.LastCommit)
3738
mcpGroup.GET("/:namespace/:name/commit/:commit_id", repoCommonHandler.CommitWithDiff)

common/errorx/error_auth.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const (
2222
quotaExceeded
2323
//need old token, often reported when user refresh token
2424
needOldToken
25+
noSourceTransferPermission
26+
noTargetTransferPermission
2527
)
2628

2729
var (
@@ -206,6 +208,30 @@ var (
206208
//
207209
// zh-HK: 必須攜帶舊Token
208210
ErrNeedOldToken error = CustomError{prefix: errAuthPrefix, code: needOldToken}
211+
// users do not have permission to transfer repo from the source namespace
212+
//
213+
// Description: The user does not have write permission on the source namespace and cannot transfer the repository away from it.
214+
//
215+
// Description_ZH: 用户对源命名空间没有写权限,无法将仓库从该命名空间转出。
216+
//
217+
// en-US: Users do not have permission to transfer repo from this namespace
218+
//
219+
// zh-CN: 用户没有权限从此命名空间转移仓库
220+
//
221+
// zh-HK: 用戶沒有權限從此命名空間轉移倉庫
222+
ErrNoSourceTransferPermission = CustomError{prefix: errAuthPrefix, code: noSourceTransferPermission}
223+
// users do not have permission to transfer repo to the target namespace
224+
//
225+
// Description: The user does not have write permission on the target namespace and cannot transfer the repository to it.
226+
//
227+
// Description_ZH: 用户对目标命名空间没有写权限,无法将仓库转移到该命名空间。
228+
//
229+
// en-US: Users do not have permission to transfer repo to this namespace
230+
//
231+
// zh-CN: 用户没有权限将仓库转移到此命名空间
232+
//
233+
// zh-HK: 用戶沒有權限將倉庫轉移到此命名空間
234+
ErrNoTargetTransferPermission = CustomError{prefix: errAuthPrefix, code: noTargetTransferPermission}
209235
)
210236

211237
/*

common/errorx/error_req.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const (
2222
errLimitedIPLocation
2323
errCaptchaIncorrect
2424
errTargetNamespaceNotFound
25+
errTransferSameNamespace
26+
errTransferTargetExists
27+
errTransferNotSupported
2528
)
2629

2730
var (
@@ -162,6 +165,42 @@ var (
162165
//
163166
// zh-HK: 目標命名空間不存在
164167
ErrTargetNamespaceNotFound = CustomError{prefix: errReqPrefix, code: errTargetNamespaceNotFound}
168+
// the transfer target namespace is the same as the source namespace
169+
//
170+
// Description: The target namespace for transfer is the same as the current namespace. Ownership transfer requires a different namespace.
171+
//
172+
// Description_ZH: 转移目标命名空间与当前命名空间相同,所有权转移需要不同的命名空间。
173+
//
174+
// en-US: New namespace must be different from current namespace
175+
//
176+
// zh-CN: 新命名空间必须与当前命名空间不同
177+
//
178+
// zh-HK: 新命名空間必須與當前命名空間不同
179+
ErrTransferSameNamespace = CustomError{prefix: errReqPrefix, code: errTransferSameNamespace}
180+
// a repository with the same name already exists in the target namespace
181+
//
182+
// Description: A repository with the same name already exists in the target namespace. The transfer cannot proceed because of the naming conflict.
183+
//
184+
// Description_ZH: 目标命名空间中已存在同名的仓库,由于命名冲突,无法进行转移。
185+
//
186+
// en-US: A repository with the same name already exists in the target namespace
187+
//
188+
// zh-CN: 目标命名空间中已存在同名仓库
189+
//
190+
// zh-HK: 目標命名空間中已存在同名倉庫
191+
ErrTransferTargetExists = CustomError{prefix: errReqPrefix, code: errTransferTargetExists}
192+
// the repository is not supported for ownership transfer
193+
//
194+
// Description: The repository cannot be transferred because it does not have a hashed path. Only repositories with hashed paths support ownership transfer.
195+
//
196+
// Description_ZH: 该仓库不支持所有权转移,因为它没有哈希路径。只有具有哈希路径的仓库才支持所有权转移。
197+
//
198+
// en-US: Repository not supported to transfer ownership
199+
//
200+
// zh-CN: 该仓库不支持转移所有权
201+
//
202+
// zh-HK: 該倉庫不支持轉移所有權
203+
ErrTransferNotSupported = CustomError{prefix: errReqPrefix, code: errTransferNotSupported}
165204
)
166205

167206
func BadRequest(originErr error, ext context) error {

0 commit comments

Comments
 (0)