From dd0adb089376d98308eb971590788869c9874c3e Mon Sep 17 00:00:00 2001 From: chenyifan01 Date: Fri, 21 Oct 2022 17:50:05 +0800 Subject: [PATCH 1/3] #2924 add migrate submit api --- models/repo.go | 1 + modules/convert/convert.go | 1 + modules/structs/org.go | 1 + modules/structs/repo.go | 1 + routers/api/v1/admin/migrate_submit.go | 155 +++++++++++++++++++++++++ routers/api/v1/api.go | 1 + routers/api/v1/repo/migrate.go | 144 +++++++++++++++++++++++ routers/response/response_list.go | 6 + 8 files changed, 310 insertions(+) create mode 100644 routers/api/v1/admin/migrate_submit.go diff --git a/models/repo.go b/models/repo.go index 2c4fda39b..6009c776f 100755 --- a/models/repo.go +++ b/models/repo.go @@ -454,6 +454,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) AllowRebaseMerge: allowRebaseMerge, AllowSquash: allowSquash, AvatarURL: repo.avatarLink(e), + Status: int(repo.Status), } } diff --git a/modules/convert/convert.go b/modules/convert/convert.go index a542fe78b..9eb2c519d 100755 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -311,6 +311,7 @@ func ToOrganization(org *models.User) *api.Organization { Location: org.Location, Visibility: org.Visibility.String(), RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess, + NumRepos: org.NumRepos, } } diff --git a/modules/structs/org.go b/modules/structs/org.go index 4b79a4e70..191843f87 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -15,6 +15,7 @@ type Organization struct { Location string `json:"location"` Visibility string `json:"visibility"` RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` + NumRepos int `json:"num_repos"` } // CreateOrgOption options for creating an organization diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6e9ece4b0..03741d03b 100755 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -90,6 +90,7 @@ type Repository struct { AllowRebaseMerge bool `json:"allow_rebase_explicit"` AllowSquash bool `json:"allow_squash_merge"` AvatarURL string `json:"avatar_url"` + Status int `json:"status"` } // CreateRepoOption options when creating repository diff --git a/routers/api/v1/admin/migrate_submit.go b/routers/api/v1/admin/migrate_submit.go new file mode 100644 index 000000000..7680de0a0 --- /dev/null +++ b/routers/api/v1/admin/migrate_submit.go @@ -0,0 +1,155 @@ +package admin + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/migrations" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/task" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/response" + "fmt" + "net/http" + "net/url" + "strings" +) + +func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { + ctxUser, bizErr := checkContextUser(ctx, form.UID) + if bizErr != nil { + ctx.JSON(http.StatusOK, response.ResponseError(bizErr)) + return + } + + remoteAddr, err := form.ParseRemoteAddr(ctx.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + ctx.JSON(http.StatusOK, response.PARAM_ERROR) + case addrErr.IsPermissionDenied: + ctx.JSON(http.StatusOK, response.INSUFFICIENT_PERMISSION) + case addrErr.IsInvalidPath: + ctx.JSON(http.StatusOK, response.PARAM_ERROR) + default: + ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) + } + } else { + ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) + } + return + } + + var gitServiceType = structs.PlainGitService + u, err := url.Parse(form.CloneAddr) + if err == nil && strings.EqualFold(u.Host, "github.com") { + gitServiceType = structs.GithubService + } + + var opts = migrations.MigrateOptions{ + OriginalURL: form.CloneAddr, + GitServiceType: gitServiceType, + CloneAddr: remoteAddr, + RepoName: form.RepoName, + Alias: form.Alias, + Description: form.Description, + Private: form.Private || setting.Repository.ForcePrivate, + Mirror: form.Mirror, + AuthUsername: form.AuthUsername, + AuthPassword: form.AuthPassword, + Wiki: form.Wiki, + Issues: form.Issues, + Milestones: form.Milestones, + Labels: form.Labels, + Comments: true, + PullRequests: form.PullRequests, + Releases: form.Releases, + } + if opts.Mirror { + opts.Issues = false + opts.Milestones = false + opts.Labels = false + opts.Comments = false + opts.PullRequests = false + opts.Releases = false + } + + err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias) + if err != nil { + handleMigrateError(ctx, ctxUser, remoteAddr, err) + return + } + + err = task.MigrateRepository(ctx.User, ctxUser, opts) + if err == nil { + r := make(map[string]string) + r["OpenIUrl"] = strings.TrimSuffix(setting.AppURL, "/") + "/" + ctxUser.Name + "/" + opts.RepoName + r["OriginUrl"] = form.CloneAddr + ctx.JSON(http.StatusOK, response.SuccessWithData(r)) + return + } + + handleMigrateError(ctx, ctxUser, remoteAddr, err) +} + +func checkContextUser(ctx *context.APIContext, uid int64) (*models.User, *response.BizError) { + if uid == ctx.User.ID || uid == 0 { + return ctx.User, nil + } + + org, err := models.GetUserByID(uid) + if models.IsErrUserNotExist(err) { + return ctx.User, nil + } + + if err != nil { + return nil, response.SYSTEM_ERROR + } + + // Check ownership of organization. + if !org.IsOrganization() { + return nil, nil + } + if !ctx.User.IsAdmin { + canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) + if err != nil { + return nil, response.NewBizError(err) + } else if !canCreate { + return nil, response.INSUFFICIENT_PERMISSION + } + } + return org, nil +} + +func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) { + switch { + case models.IsErrRepoAlreadyExist(err): + ctx.JSON(http.StatusOK, response.ServerError("The repository with the same name already exists.")) + case migrations.IsRateLimitError(err): + ctx.JSON(http.StatusOK, response.ServerError("Remote visit addressed rate limitation.")) + case migrations.IsTwoFactorAuthError(err): + ctx.JSON(http.StatusOK, response.ServerError("Remote visit required two factors authentication.")) + case models.IsErrReachLimitOfRepo(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))) + case models.IsErrNameReserved(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))) + case models.IsErrNameCharsNotAllowed(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))) + case models.IsErrNamePatternNotAllowed(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))) + default: + err = util.URLSanitizedError(err, remoteAddr) + if strings.Contains(err.Error(), "Authentication failed") || + strings.Contains(err.Error(), "Bad credentials") || + strings.Contains(err.Error(), "could not read Username") { + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Authentication failed: %v.", err))) + } else if strings.Contains(err.Error(), "fatal:") { + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Migration failed: %v.", err))) + } else { + ctx.JSON(http.StatusOK, response.ServerError(err.Error())) + } + } +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 8e1d725ed..4160c2430 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -702,6 +702,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/issues/search", repo.SearchIssues) m.Post("/migrate", reqToken(), bind(auth.MigrateRepoForm{}), repo.Migrate) + m.Post("/migrate/submit", reqToken(), bind(auth.MigrateRepoForm{}), repo.MigrateSubmit) m.Group("/:username/:reponame", func() { m.Combo("").Get(reqAnyRepoReader(), repo.Get). diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index fd0db7814..ed6ca69e2 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -6,6 +6,8 @@ package repo import ( "bytes" + "code.gitea.io/gitea/modules/task" + "code.gitea.io/gitea/routers/response" "errors" "fmt" "net/http" @@ -216,3 +218,145 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA } } } + +func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { + ctxUser, bizErr := checkContextUser(ctx, form.UID) + if bizErr != nil { + ctx.JSON(http.StatusOK, response.ResponseError(bizErr)) + return + } + + remoteAddr, err := form.ParseRemoteAddr(ctx.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + ctx.JSON(http.StatusOK, response.PARAM_ERROR) + case addrErr.IsPermissionDenied: + ctx.JSON(http.StatusOK, response.INSUFFICIENT_PERMISSION) + case addrErr.IsInvalidPath: + ctx.JSON(http.StatusOK, response.PARAM_ERROR) + default: + ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) + } + } else { + ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) + } + return + } + + var gitServiceType = api.PlainGitService + u, err := url.Parse(form.CloneAddr) + if err == nil && strings.EqualFold(u.Host, "github.com") { + gitServiceType = api.GithubService + } + + var opts = migrations.MigrateOptions{ + OriginalURL: form.CloneAddr, + GitServiceType: gitServiceType, + CloneAddr: remoteAddr, + RepoName: form.RepoName, + Alias: form.Alias, + Description: form.Description, + Private: form.Private || setting.Repository.ForcePrivate, + Mirror: form.Mirror, + AuthUsername: form.AuthUsername, + AuthPassword: form.AuthPassword, + Wiki: form.Wiki, + Issues: form.Issues, + Milestones: form.Milestones, + Labels: form.Labels, + Comments: true, + PullRequests: form.PullRequests, + Releases: form.Releases, + } + if opts.Mirror { + opts.Issues = false + opts.Milestones = false + opts.Labels = false + opts.Comments = false + opts.PullRequests = false + opts.Releases = false + } + + err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias) + if err != nil { + handleMigrateError4Api(ctx, ctxUser, remoteAddr, err) + return + } + + err = task.MigrateRepository(ctx.User, ctxUser, opts) + if err == nil { + r := make(map[string]string) + r["OpenIUrl"] = strings.TrimSuffix(setting.AppURL, "/") + "/" + ctxUser.Name + "/" + opts.RepoName + r["OriginUrl"] = form.CloneAddr + ctx.JSON(http.StatusOK, response.SuccessWithData(r)) + return + } + + handleMigrateError4Api(ctx, ctxUser, remoteAddr, err) +} + +func checkContextUser(ctx *context.APIContext, uid int64) (*models.User, *response.BizError) { + if uid == ctx.User.ID || uid == 0 { + return ctx.User, nil + } + + org, err := models.GetUserByID(uid) + if models.IsErrUserNotExist(err) { + return ctx.User, nil + } + + if err != nil { + return nil, response.SYSTEM_ERROR + } + + // Check ownership of organization. + if !org.IsOrganization() { + return nil, nil + } + if !ctx.User.IsAdmin { + canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) + if err != nil { + return nil, response.NewBizError(err) + } else if !canCreate { + return nil, response.INSUFFICIENT_PERMISSION + } + } + return org, nil +} + +func handleMigrateError4Api(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) { + switch { + case models.IsErrRepoAlreadyExist(err): + ctx.JSON(http.StatusOK, response.Error(3, "The repository with the same name already exists.")) + case migrations.IsRateLimitError(err): + ctx.JSON(http.StatusOK, response.ServerError("Remote visit addressed rate limitation.")) + case migrations.IsTwoFactorAuthError(err): + ctx.JSON(http.StatusOK, response.ServerError("Remote visit required two factors authentication.")) + case models.IsErrReachLimitOfRepo(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))) + case models.IsErrNameReserved(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))) + case models.IsErrNameCharsNotAllowed(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))) + case models.IsErrNamePatternNotAllowed(err): + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))) + default: + err = util.URLSanitizedError(err, remoteAddr) + if strings.Contains(err.Error(), "Authentication failed") || + strings.Contains(err.Error(), "Bad credentials") || + strings.Contains(err.Error(), "could not read Username") { + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Authentication failed: %v.", err))) + } else if strings.Contains(err.Error(), "fatal:") { + ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Migration failed: %v.", err))) + } else { + ctx.JSON(http.StatusOK, response.ServerError(err.Error())) + } + } +} + +func QueryRepoSatus(ctx *context.APIContext, form auth.MigrateRepoForm) { + +} diff --git a/routers/response/response_list.go b/routers/response/response_list.go index 6514f3edd..138b0e294 100644 --- a/routers/response/response_list.go +++ b/routers/response/response_list.go @@ -1,5 +1,11 @@ package response +//repo response var RESOURCE_QUEUE_NOT_AVAILABLE = &BizError{Code: 1001, Err: "resource queue not available"} var SPECIFICATION_NOT_EXIST = &BizError{Code: 1002, Err: "specification not exist"} var SPECIFICATION_NOT_AVAILABLE = &BizError{Code: 1003, Err: "specification not available"} + +//common response +var SYSTEM_ERROR = &BizError{Code: 9009, Err: "System error.Please try again later"} +var INSUFFICIENT_PERMISSION = &BizError{Code: 9003, Err: "insufficient permissions"} +var PARAM_ERROR = &BizError{Code: 9001, Err: "param error permissions"} From 7e07546f565c3f77d1ce24e953d2d962e233b1cb Mon Sep 17 00:00:00 2001 From: chenyifan01 Date: Wed, 26 Oct 2022 09:56:46 +0800 Subject: [PATCH 2/3] add log --- routers/api/v1/repo/migrate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index ed6ca69e2..2f28b0bd3 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -220,6 +220,7 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA } func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { + log.Info("receive MigrateSubmit request") ctxUser, bizErr := checkContextUser(ctx, form.UID) if bizErr != nil { ctx.JSON(http.StatusOK, response.ResponseError(bizErr)) From fba3ca7ec439063f8d09dfdd851f74574151a553 Mon Sep 17 00:00:00 2001 From: chenyifan01 Date: Tue, 1 Nov 2022 10:13:31 +0800 Subject: [PATCH 3/3] remove migrate_submit.go --- routers/api/v1/admin/migrate_submit.go | 155 ------------------------- 1 file changed, 155 deletions(-) delete mode 100644 routers/api/v1/admin/migrate_submit.go diff --git a/routers/api/v1/admin/migrate_submit.go b/routers/api/v1/admin/migrate_submit.go deleted file mode 100644 index 7680de0a0..000000000 --- a/routers/api/v1/admin/migrate_submit.go +++ /dev/null @@ -1,155 +0,0 @@ -package admin - -import ( - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/auth" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/migrations" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/task" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/routers/response" - "fmt" - "net/http" - "net/url" - "strings" -) - -func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { - ctxUser, bizErr := checkContextUser(ctx, form.UID) - if bizErr != nil { - ctx.JSON(http.StatusOK, response.ResponseError(bizErr)) - return - } - - remoteAddr, err := form.ParseRemoteAddr(ctx.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - ctx.JSON(http.StatusOK, response.PARAM_ERROR) - case addrErr.IsPermissionDenied: - ctx.JSON(http.StatusOK, response.INSUFFICIENT_PERMISSION) - case addrErr.IsInvalidPath: - ctx.JSON(http.StatusOK, response.PARAM_ERROR) - default: - ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) - } - } else { - ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) - } - return - } - - var gitServiceType = structs.PlainGitService - u, err := url.Parse(form.CloneAddr) - if err == nil && strings.EqualFold(u.Host, "github.com") { - gitServiceType = structs.GithubService - } - - var opts = migrations.MigrateOptions{ - OriginalURL: form.CloneAddr, - GitServiceType: gitServiceType, - CloneAddr: remoteAddr, - RepoName: form.RepoName, - Alias: form.Alias, - Description: form.Description, - Private: form.Private || setting.Repository.ForcePrivate, - Mirror: form.Mirror, - AuthUsername: form.AuthUsername, - AuthPassword: form.AuthPassword, - Wiki: form.Wiki, - Issues: form.Issues, - Milestones: form.Milestones, - Labels: form.Labels, - Comments: true, - PullRequests: form.PullRequests, - Releases: form.Releases, - } - if opts.Mirror { - opts.Issues = false - opts.Milestones = false - opts.Labels = false - opts.Comments = false - opts.PullRequests = false - opts.Releases = false - } - - err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias) - if err != nil { - handleMigrateError(ctx, ctxUser, remoteAddr, err) - return - } - - err = task.MigrateRepository(ctx.User, ctxUser, opts) - if err == nil { - r := make(map[string]string) - r["OpenIUrl"] = strings.TrimSuffix(setting.AppURL, "/") + "/" + ctxUser.Name + "/" + opts.RepoName - r["OriginUrl"] = form.CloneAddr - ctx.JSON(http.StatusOK, response.SuccessWithData(r)) - return - } - - handleMigrateError(ctx, ctxUser, remoteAddr, err) -} - -func checkContextUser(ctx *context.APIContext, uid int64) (*models.User, *response.BizError) { - if uid == ctx.User.ID || uid == 0 { - return ctx.User, nil - } - - org, err := models.GetUserByID(uid) - if models.IsErrUserNotExist(err) { - return ctx.User, nil - } - - if err != nil { - return nil, response.SYSTEM_ERROR - } - - // Check ownership of organization. - if !org.IsOrganization() { - return nil, nil - } - if !ctx.User.IsAdmin { - canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) - if err != nil { - return nil, response.NewBizError(err) - } else if !canCreate { - return nil, response.INSUFFICIENT_PERMISSION - } - } - return org, nil -} - -func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) { - switch { - case models.IsErrRepoAlreadyExist(err): - ctx.JSON(http.StatusOK, response.ServerError("The repository with the same name already exists.")) - case migrations.IsRateLimitError(err): - ctx.JSON(http.StatusOK, response.ServerError("Remote visit addressed rate limitation.")) - case migrations.IsTwoFactorAuthError(err): - ctx.JSON(http.StatusOK, response.ServerError("Remote visit required two factors authentication.")) - case models.IsErrReachLimitOfRepo(err): - ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))) - case models.IsErrNameReserved(err): - ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))) - case models.IsErrNameCharsNotAllowed(err): - ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))) - case models.IsErrNamePatternNotAllowed(err): - ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))) - default: - err = util.URLSanitizedError(err, remoteAddr) - if strings.Contains(err.Error(), "Authentication failed") || - strings.Contains(err.Error(), "Bad credentials") || - strings.Contains(err.Error(), "could not read Username") { - ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Authentication failed: %v.", err))) - } else if strings.Contains(err.Error(), "fatal:") { - ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Migration failed: %v.", err))) - } else { - ctx.JSON(http.StatusOK, response.ServerError(err.Error())) - } - } -}