@@ -1,7 +1,6 @@ | |||||
package models | package models | ||||
import ( | import ( | ||||
"fmt" | |||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"time" | "time" | ||||
@@ -17,7 +16,7 @@ const ( | |||||
TechMigrateFailed = 4 | TechMigrateFailed = 4 | ||||
) | ) | ||||
const DefaultTechStatus = 2 | |||||
const DefaultTechApprovedStatus = TechShow | |||||
type TechConvergeBaseInfo struct { | type TechConvergeBaseInfo struct { | ||||
ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
@@ -61,6 +60,35 @@ func (t *TechConvergeBaseInfo) Brief() *TechConvergeBrief { | |||||
AllInstitution: t.AllInstitution, | AllInstitution: t.AllInstitution, | ||||
} | } | ||||
} | } | ||||
func (t *TechConvergeBaseInfo) IsValidInstitution(institution string) bool { | |||||
if t.AllInstitution == "" && t.Institution == "" { | |||||
return false | |||||
} | |||||
allInstitution := make([]string, 0) | |||||
if t.AllInstitution != "" { | |||||
allInstitution = strings.Split(t.AllInstitution, ",") | |||||
} | |||||
if t.Institution != "" { | |||||
allInstitution = append(allInstitution, t.Institution) | |||||
} | |||||
newInstitution := strings.Split(institution, ",") | |||||
total := len(newInstitution) | |||||
matched := 0 | |||||
for _, n := range newInstitution { | |||||
for _, s := range allInstitution { | |||||
if s == n { | |||||
matched++ | |||||
break | |||||
} | |||||
} | |||||
} | |||||
if matched == total { | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
type RepoConvergeInfo struct { | type RepoConvergeInfo struct { | ||||
ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
@@ -106,7 +134,7 @@ type ErrTechConvergeBaseInfoNotExist struct { | |||||
} | } | ||||
func (err ErrTechConvergeBaseInfoNotExist) Error() string { | func (err ErrTechConvergeBaseInfoNotExist) Error() string { | ||||
return fmt.Sprintf("TechConvergeBaseInfo does not exist [id: %s]", err.ID) | |||||
return "tech.tech_not_exist" | |||||
} | } | ||||
func IsErrTechConvergeBaseInfoNotExist(err error) bool { | func IsErrTechConvergeBaseInfoNotExist(err error) bool { | ||||
@@ -269,7 +297,7 @@ type FindTechOpt struct { | |||||
} | } | ||||
func FindTech(opt FindTechOpt) ([]*TechConvergeBaseInfo, error) { | func FindTech(opt FindTechOpt) ([]*TechConvergeBaseInfo, error) { | ||||
var cond builder.Cond | |||||
var cond = builder.NewCond() | |||||
if opt.TechNo != "" { | if opt.TechNo != "" { | ||||
cond = cond.And(builder.Like{"project_number", opt.TechNo}) | cond = cond.And(builder.Like{"project_number", opt.TechNo}) | ||||
} | } | ||||
@@ -289,8 +317,8 @@ func FindTech(opt FindTechOpt) ([]*TechConvergeBaseInfo, error) { | |||||
} | } | ||||
func GetTechByTechNo(techNo string) (*TechConvergeBaseInfo, error) { | func GetTechByTechNo(techNo string) (*TechConvergeBaseInfo, error) { | ||||
var tech *TechConvergeBaseInfo | |||||
has, err := x.Where("project_number = ?", techNo).Get(&tech) | |||||
var tech = &TechConvergeBaseInfo{} | |||||
has, err := x.Where("project_number = ?", techNo).Get(tech) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} else if !has { | } else if !has { | ||||
@@ -299,3 +327,35 @@ func GetTechByTechNo(techNo string) (*TechConvergeBaseInfo, error) { | |||||
return tech, nil | return tech, nil | ||||
} | } | ||||
type GetRepoConvergeOpts struct { | |||||
RepoId int64 | |||||
BaseInfoId int64 | |||||
Status []int | |||||
} | |||||
func GetRepoConverge(opts GetRepoConvergeOpts) ([]*RepoConvergeInfo, error) { | |||||
r := make([]*RepoConvergeInfo, 0) | |||||
cond := builder.NewCond() | |||||
if opts.RepoId > 0 { | |||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoId}) | |||||
} | |||||
if opts.BaseInfoId > 0 { | |||||
cond = cond.And(builder.Eq{"base_info_id": opts.BaseInfoId}) | |||||
} | |||||
if len(opts.Status) > 0 { | |||||
cond = cond.And(builder.In("status", opts.Status)) | |||||
} | |||||
err := x.Where(cond).Find(&r) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return r, nil | |||||
} | |||||
func UpdateRepoConvergeStatus(id int64, status int) (int64, error) { | |||||
return x.ID(id).Update(&RepoConvergeInfo{ | |||||
Status: status, | |||||
}) | |||||
} |
@@ -1,11 +1,18 @@ | |||||
package structs | package structs | ||||
type TechRepo struct { | |||||
Url string `json:"url"` | |||||
type NotOpenITechRepo struct { | |||||
Url string `json:"url" binding:"Required"` | |||||
TechNo string `json:"no"` | |||||
Institution string `json:"institution"` | |||||
UID int64 `json:"uid"` //启智项目uid | |||||
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` | |||||
Alias string `json:"alias" binding:"Required;AlphaDashDotChinese;MaxSize(100)"` | |||||
Topics string `json:"topics"` //关键词 | |||||
Description string `json:"description" binding:"MaxSize(255)"` | |||||
} | |||||
type OpenITechRepo struct { | |||||
Url string `json:"url" binding:"Required"` | |||||
TechNo string `json:"no"` | TechNo string `json:"no"` | ||||
Institution string `json:"institution"` | Institution string `json:"institution"` | ||||
uid string `json:"uid"` //启智项目uid | |||||
repo_name string `json:"repo_name"` //启智项目名称 | |||||
topics string `json:"topics"` //关键词 | |||||
description string `json:"description"` //简介 | |||||
} | } |
@@ -3363,3 +3363,10 @@ get_file_fail= Can not get the file content, please try again later. | |||||
content_type_unsupported=The format of the file or file content is wrong. | content_type_unsupported=The format of the file or file content is wrong. | ||||
sql_err=Fail to process data, please try again later. | sql_err=Fail to process data, please try again later. | ||||
[tech] | |||||
incorrect_openi_format = The OpenI address format is incorrect | |||||
openi_repo_not_exist = OpenI repository is not exists | |||||
tech_not_exist = The project approval number does not exist | |||||
institution_not_valid = The submitted contributing unit is not among the participating units of the technology project | |||||
repo_converge_exists = The technology project [%s] already has [%s], please do not submit it again | |||||
to_migrate_repo_exists = The project has been migrated to OpenI, please use the OpenI way to submit |
@@ -3383,5 +3383,13 @@ get_file_fail= 获取上传文件失败。 | |||||
content_type_unsupported=上传文件的格式有误。 | content_type_unsupported=上传文件的格式有误。 | ||||
sql_err=数据处理错误,请稍后再试。 | sql_err=数据处理错误,请稍后再试。 | ||||
[tech] | |||||
incorrect_openi_format = 启智项目地址格式错误 | |||||
openi_repo_not_exist = 启智项目不存在 | |||||
tech_not_exist = 项目立项编号不存在 | |||||
institution_not_valid = 当前提交的贡献单位不在该科技项目的参与单位中 | |||||
repo_converge_exists = 科技项目[%s]中已存在[%s],请勿重复提交 | |||||
to_migrate_repo_exists = 该项目已迁移到启智,请使用启智社区方式提交申请 | |||||
@@ -543,6 +543,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
m.Get("/filter", tech.GetFilterInfo) | m.Get("/filter", tech.GetFilterInfo) | ||||
m.Get("/search", tech.SearchTechProjectInfo) | m.Get("/search", tech.SearchTechProjectInfo) | ||||
m.Get("/repo_search", tech.SearchRepoInfo) | m.Get("/repo_search", tech.SearchRepoInfo) | ||||
m.Post("/openi", bind(api.OpenITechRepo{}), tech.CommitOpenIRepo) | |||||
m.Post("/no_openi", bind(api.NotOpenITechRepo{}), tech.CommitNotOpenIRepo) | |||||
}, reqToken()) | }, reqToken()) | ||||
@@ -6,8 +6,8 @@ package repo | |||||
import ( | import ( | ||||
"bytes" | "bytes" | ||||
"code.gitea.io/gitea/modules/task" | |||||
"code.gitea.io/gitea/routers/response" | "code.gitea.io/gitea/routers/response" | ||||
"code.gitea.io/gitea/services/repository" | |||||
"errors" | "errors" | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
@@ -221,143 +221,9 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA | |||||
func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { | func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { | ||||
log.Info("receive MigrateSubmit request") | log.Info("receive MigrateSubmit request") | ||||
ctxUser, bizErr := checkContextUser(ctx, form.UID) | |||||
if bizErr != nil { | |||||
ctx.JSON(http.StatusOK, response.ResponseError(bizErr)) | |||||
return | |||||
} | |||||
remoteAddr, err := form.ParseRemoteAddr(ctx.User) | |||||
err := repository.MigrateSubmit(ctx.User, form) | |||||
if err != nil { | 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, | |||||
ctx.JSON(http.StatusOK, err) | |||||
} | } | ||||
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) { | |||||
ctx.JSON(http.StatusOK, response.Success()) | |||||
} | } |
@@ -2,25 +2,45 @@ package tech | |||||
import ( | import ( | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/auth" | |||||
"code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
"code.gitea.io/gitea/routers/response" | "code.gitea.io/gitea/routers/response" | ||||
"code.gitea.io/gitea/services/repository" | "code.gitea.io/gitea/services/repository" | ||||
techService "code.gitea.io/gitea/services/tech" | |||||
"fmt" | |||||
"net/http" | "net/http" | ||||
) | ) | ||||
//CommitOpenIRepo 新建启智项目申请页面提交 | //CommitOpenIRepo 新建启智项目申请页面提交 | ||||
func CommitOpenIRepo(ctx *context.APIContext, form api.TechRepo) { | |||||
func CommitOpenIRepo(ctx *context.APIContext, form api.OpenITechRepo) { | |||||
//解析项目路径,查找项目 | //解析项目路径,查找项目 | ||||
repo, err := repository.FindRepoByUrl(form.Url) | repo, err := repository.FindRepoByUrl(form.Url) | ||||
if err != nil { | if err != nil { | ||||
ctx.JSON(http.StatusOK, response.OuterServerError(err.Error())) | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | return | ||||
} | } | ||||
//查找项目编号 | //查找项目编号 | ||||
tech, err := models.GetTechByTechNo(form.TechNo) | tech, err := models.GetTechByTechNo(form.TechNo) | ||||
if err != nil { | if err != nil { | ||||
ctx.JSON(http.StatusOK, response.OuterServerError(err.Error())) | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | |||||
} | |||||
//判断承接单位是否在科技项目的参与单位中 | |||||
if !tech.IsValidInstitution(form.Institution) { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr("tech.institution_not_valid"))) | |||||
return | |||||
} | |||||
//判断是否已经存在了该项目 | |||||
exist, err := techService.IsValidRepoConvergeExists(repo.ID, tech.ID) | |||||
if err != nil { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | |||||
} | |||||
if exist { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(fmt.Sprintf(ctx.Tr("tech.repo_converge_exists"), tech.ProjectName, repo.Alias))) | |||||
return | return | ||||
} | } | ||||
@@ -31,27 +51,83 @@ func CommitOpenIRepo(ctx *context.APIContext, form api.TechRepo) { | |||||
BaseInfoID: tech.ID, | BaseInfoID: tech.ID, | ||||
Institution: form.Institution, | Institution: form.Institution, | ||||
UID: ctx.User.ID, | UID: ctx.User.ID, | ||||
Status: models.DefaultTechStatus, | |||||
Status: models.DefaultTechApprovedStatus, | |||||
} | } | ||||
err = rci.InsertOrUpdate() | err = rci.InsertOrUpdate() | ||||
if err != nil { | if err != nil { | ||||
ctx.JSON(http.StatusOK, response.OuterServerError(err.Error())) | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | return | ||||
} | } | ||||
ctx.JSON(http.StatusOK, response.OuterSuccess()) | ctx.JSON(http.StatusOK, response.OuterSuccess()) | ||||
return | |||||
} | } | ||||
//CommitNotOpenIRepo 新建非启智项目申请页面提交 | //CommitNotOpenIRepo 新建非启智项目申请页面提交 | ||||
func CommitNotOpenIRepo(ctx *context.APIContext, form api.TechRepo) { | |||||
////查找项目编号 | |||||
//tech, err := models.GetTechByTechNo(form.TechNo) | |||||
//if err != nil { | |||||
// ctx.JSON(http.StatusOK, response.OuterServerError(err.Error())) | |||||
// return | |||||
//} | |||||
// | |||||
////调用迁移接口 | |||||
return | |||||
func CommitNotOpenIRepo(ctx *context.APIContext, form api.NotOpenITechRepo) { | |||||
//触发更新迁移状态 | |||||
go techService.UpdateTechMigrateStatus() | |||||
//查找项目编号 | |||||
tech, err := models.GetTechByTechNo(form.TechNo) | |||||
if err != nil { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | |||||
} | |||||
//判断承接单位是否在科技项目的参与单位中 | |||||
if !tech.IsValidInstitution(form.Institution) { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr("tech.institution_not_valid"))) | |||||
return | |||||
} | |||||
//调用迁移接口 | |||||
bizErr := repository.MigrateSubmit(ctx.User, auth.MigrateRepoForm{ | |||||
CloneAddr: form.Url, | |||||
UID: form.UID, | |||||
RepoName: form.RepoName, | |||||
Alias: form.Alias, | |||||
Description: form.Description, | |||||
Labels: true, | |||||
Mirror: true, | |||||
}) | |||||
if bizErr != nil { | |||||
if bizErr.Code == 3 { | |||||
ctx.JSON(http.StatusOK, response.OuterError(bizErr.Code, ctx.Tr("tech.to_migrate_repo_exists"))) | |||||
return | |||||
} | |||||
ctx.JSON(http.StatusOK, response.OuterError(bizErr.Code, ctx.Tr(bizErr.Err))) | |||||
return | |||||
} | |||||
repo, err := models.GetRepositoryByName(form.UID, form.RepoName) | |||||
if err != nil { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | |||||
} | |||||
//判断是否已经存在了该项目 | |||||
exist, err := techService.IsValidRepoConvergeExists(repo.ID, tech.ID) | |||||
if err != nil { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | |||||
} | |||||
if exist { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(fmt.Sprintf(ctx.Tr("tech.repo_converge_exists"), tech.ProjectName, repo.Alias))) | |||||
return | |||||
} | |||||
//写入数据库 | |||||
rci := &models.RepoConvergeInfo{ | |||||
RepoID: repo.ID, | |||||
Url: form.Url, | |||||
BaseInfoID: tech.ID, | |||||
Institution: form.Institution, | |||||
UID: ctx.User.ID, | |||||
Status: models.DefaultTechApprovedStatus, | |||||
} | |||||
err = rci.InsertOrUpdate() | |||||
if err != nil { | |||||
ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
return | |||||
} | |||||
ctx.JSON(http.StatusOK, response.OuterSuccess()) | |||||
} | } |
@@ -12,3 +12,7 @@ func (b BizError) Error() string { | |||||
func NewBizError(err error) *BizError { | func NewBizError(err error) *BizError { | ||||
return &BizError{Code: RESPONSE_CODE_ERROR_DEFAULT, Err: err.Error()} | return &BizError{Code: RESPONSE_CODE_ERROR_DEFAULT, Err: err.Error()} | ||||
} | } | ||||
func BuildBizError(code int, msg string) *BizError { | |||||
return &BizError{Code: code, Err: msg} | |||||
} |
@@ -0,0 +1,146 @@ | |||||
package repository | |||||
import ( | |||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/auth" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/migrations" | |||||
"code.gitea.io/gitea/modules/setting" | |||||
api "code.gitea.io/gitea/modules/structs" | |||||
"code.gitea.io/gitea/modules/task" | |||||
"code.gitea.io/gitea/modules/util" | |||||
"code.gitea.io/gitea/routers/response" | |||||
"errors" | |||||
"fmt" | |||||
"net/url" | |||||
"strings" | |||||
) | |||||
func MigrateSubmit(currentUser *models.User, form auth.MigrateRepoForm) *response.BizError { | |||||
log.Info("receive MigrateSubmit request") | |||||
ctxUser, bizErr := checkMigrateUser(currentUser, form.UID) | |||||
if bizErr != nil { | |||||
return bizErr | |||||
} | |||||
remoteAddr, err := form.ParseRemoteAddr(currentUser) | |||||
if err != nil { | |||||
if models.IsErrInvalidCloneAddr(err) { | |||||
addrErr := err.(models.ErrInvalidCloneAddr) | |||||
switch { | |||||
case addrErr.IsURLError: | |||||
return response.PARAM_ERROR | |||||
case addrErr.IsPermissionDenied: | |||||
return response.INSUFFICIENT_PERMISSION | |||||
case addrErr.IsInvalidPath: | |||||
return response.PARAM_ERROR | |||||
default: | |||||
} | |||||
} | |||||
return response.NewBizError(err) | |||||
} | |||||
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(currentUser, ctxUser, opts.RepoName, opts.Alias) | |||||
if err != nil { | |||||
return handleMigrateError4Api(ctxUser, remoteAddr, err) | |||||
} | |||||
err = task.MigrateRepository(currentUser, ctxUser, opts) | |||||
if err != nil { | |||||
return handleMigrateError4Api(ctxUser, remoteAddr, err) | |||||
} | |||||
return nil | |||||
} | |||||
func checkMigrateUser(currentUser *models.User, uid int64) (*models.User, *response.BizError) { | |||||
if uid == currentUser.ID || uid == 0 { | |||||
return currentUser, nil | |||||
} | |||||
org, err := models.GetUserByID(uid) | |||||
if models.IsErrUserNotExist(err) { | |||||
return currentUser, nil | |||||
} | |||||
if err != nil { | |||||
return nil, response.SYSTEM_ERROR | |||||
} | |||||
// Check ownership of organization. | |||||
if !org.IsOrganization() { | |||||
return nil, nil | |||||
} | |||||
if !currentUser.IsAdmin { | |||||
canCreate, err := org.CanCreateOrgRepo(currentUser.ID) | |||||
if err != nil { | |||||
return nil, response.NewBizError(err) | |||||
} else if !canCreate { | |||||
return nil, response.INSUFFICIENT_PERMISSION | |||||
} | |||||
} | |||||
return org, nil | |||||
} | |||||
func handleMigrateError4Api(repoOwner *models.User, remoteAddr string, err error) *response.BizError { | |||||
switch { | |||||
case models.IsErrRepoAlreadyExist(err): | |||||
return response.BuildBizError(3, "The repository with the same name already exists.") | |||||
case migrations.IsRateLimitError(err): | |||||
return response.NewBizError(errors.New("Remote visit addressed rate limitation.")) | |||||
case migrations.IsTwoFactorAuthError(err): | |||||
return response.NewBizError(errors.New("Remote visit required two factors authentication.")) | |||||
case models.IsErrReachLimitOfRepo(err): | |||||
return response.NewBizError(errors.New(fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))) | |||||
case models.IsErrNameReserved(err): | |||||
return response.NewBizError(errors.New(fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))) | |||||
case models.IsErrNameCharsNotAllowed(err): | |||||
return response.NewBizError(errors.New(fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))) | |||||
case models.IsErrNamePatternNotAllowed(err): | |||||
return response.NewBizError(errors.New(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") { | |||||
return response.NewBizError(errors.New((fmt.Sprintf("Authentication failed: %v.", err)))) | |||||
} else if strings.Contains(err.Error(), "fatal:") { | |||||
return response.NewBizError(errors.New((fmt.Sprintf("Migration failed: %v.", err)))) | |||||
} | |||||
} | |||||
return response.NewBizError(err) | |||||
} |
@@ -3,6 +3,7 @@ package repository | |||||
import ( | import ( | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"errors" | |||||
"net/url" | "net/url" | ||||
"strings" | "strings" | ||||
) | ) | ||||
@@ -10,10 +11,13 @@ import ( | |||||
func FindRepoByUrl(url string) (*models.Repository, error) { | func FindRepoByUrl(url string) (*models.Repository, error) { | ||||
ownerName, repoName := parseOpenIUrl(url) | ownerName, repoName := parseOpenIUrl(url) | ||||
if ownerName == "" || repoName == "" { | if ownerName == "" || repoName == "" { | ||||
return nil, nil | |||||
return nil, errors.New("tech.incorrect_openi_format") | |||||
} | } | ||||
r, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | r, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | ||||
if err != nil { | if err != nil { | ||||
if models.IsErrRepoNotExist(err) { | |||||
return nil, errors.New("tech.openi_repo_not_exist") | |||||
} | |||||
return nil, err | return nil, err | ||||
} | } | ||||
return r, nil | return r, nil | ||||
@@ -25,7 +29,7 @@ func parseOpenIUrl(u string) (string, string) { | |||||
if err != nil { | if err != nil { | ||||
return "", "" | return "", "" | ||||
} | } | ||||
if url.Host != setting.AppURL { | |||||
if !strings.Contains(setting.AppURL, url.Host) { | |||||
return "", "" | return "", "" | ||||
} | } | ||||
@@ -13,3 +13,39 @@ func FindTech(opt models.FindTechOpt) ([]*models.TechConvergeBrief, error) { | |||||
} | } | ||||
return r, nil | return r, nil | ||||
} | } | ||||
func IsValidRepoConvergeExists(repoId, baseInfoId int64) (bool, error) { | |||||
list, err := models.GetRepoConverge(models.GetRepoConvergeOpts{ | |||||
Status: []int{models.TechHide, models.TechShow, models.TechMigrating}, | |||||
RepoId: repoId, | |||||
BaseInfoId: baseInfoId, | |||||
}) | |||||
if err != nil { | |||||
return false, err | |||||
} | |||||
return len(list) > 0, nil | |||||
} | |||||
func UpdateTechMigrateStatus() error { | |||||
migratingRepos, err := models.GetRepoConverge(models.GetRepoConvergeOpts{ | |||||
Status: []int{models.TechMigrating}, | |||||
}) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
for _, r := range migratingRepos { | |||||
repo, err := models.GetRepositoryByID(r.RepoID) | |||||
if err != nil { | |||||
if models.IsErrRepoNotExist(err) { | |||||
models.UpdateRepoConvergeStatus(r.ID, models.TechMigrateFailed) | |||||
continue | |||||
} | |||||
continue | |||||
} | |||||
if repo.Status == models.RepositoryReady { | |||||
models.UpdateRepoConvergeStatus(r.ID, models.DefaultTechApprovedStatus) | |||||
continue | |||||
} | |||||
} | |||||
return nil | |||||
} |