Browse Source

千校计划后端代码合入

tags/v1.22.1.3^2
ychao_1983 3 years ago
parent
commit
c802b47c27
13 changed files with 447 additions and 93 deletions
  1. +52
    -23
      models/repo.go
  2. +21
    -0
      models/repo_list.go
  3. +12
    -0
      modules/auth/repo_form.go
  4. +9
    -1
      modules/repository/create.go
  5. +8
    -0
      options/locale/locale_en-US.ini
  6. +8
    -0
      options/locale/locale_zh-CN.ini
  7. +8
    -57
      routers/home.go
  8. +35
    -6
      routers/org/home.go
  9. +7
    -3
      routers/org/members.go
  10. +9
    -2
      routers/org/teams.go
  11. +189
    -0
      routers/repo/course.go
  12. +7
    -0
      routers/routes/routes.go
  13. +82
    -1
      services/repository/repository.go

+ 52
- 23
models/repo.go View File

@@ -140,6 +140,7 @@ func NewRepoContext() {
// RepositoryStatus defines the status of repository // RepositoryStatus defines the status of repository
type RepositoryStatus int type RepositoryStatus int
type RepoBlockChainStatus int type RepoBlockChainStatus int
type RepoType int


// all kinds of RepositoryStatus // all kinds of RepositoryStatus
const ( const (
@@ -153,6 +154,11 @@ const (
RepoBlockChainFailed RepoBlockChainFailed
) )


const (
RepoNormal RepoType = iota
RepoCourse
)

// Repository represents a git repository. // Repository represents a git repository.
type Repository struct { type Repository struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@@ -166,7 +172,8 @@ type Repository struct {
OriginalServiceType api.GitServiceType `xorm:"index"` OriginalServiceType api.GitServiceType `xorm:"index"`
OriginalURL string `xorm:"VARCHAR(2048)"` OriginalURL string `xorm:"VARCHAR(2048)"`
DefaultBranch string DefaultBranch string

CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
Creator *User `xorm:"-"`
NumWatches int NumWatches int
NumStars int NumStars int
NumForks int NumForks int
@@ -175,11 +182,12 @@ type Repository struct {
NumOpenIssues int `xorm:"-"` NumOpenIssues int `xorm:"-"`
NumPulls int NumPulls int
NumClosedPulls int NumClosedPulls int
NumOpenPulls int `xorm:"-"`
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumOpenMilestones int `xorm:"-"`
NumCommit int64 `xorm:"NOT NULL DEFAULT 0"`
NumOpenPulls int `xorm:"-"`
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumOpenMilestones int `xorm:"-"`
NumCommit int64 `xorm:"NOT NULL DEFAULT 0"`
RepoType RepoType `xorm:"NOT NULL DEFAULT 0"`


IsPrivate bool `xorm:"INDEX"` IsPrivate bool `xorm:"INDEX"`
IsEmpty bool `xorm:"INDEX"` IsEmpty bool `xorm:"INDEX"`
@@ -1035,6 +1043,8 @@ type CreateRepoOptions struct {
IsMirror bool IsMirror bool
AutoInit bool AutoInit bool
Status RepositoryStatus Status RepositoryStatus
IsCourse bool
Topics []string
} }


// GetRepoInitFile returns repository init files // GetRepoInitFile returns repository init files
@@ -1080,7 +1090,7 @@ func IsUsableRepoAlias(name string) error {
} }


// CreateRepository creates a repository for the user/organization. // CreateRepository creates a repository for the user/organization.
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) {
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, opts ...CreateRepoOptions) (err error) {
if err = IsUsableRepoName(repo.Name); err != nil { if err = IsUsableRepoName(repo.Name); err != nil {
return err return err
} }
@@ -1094,7 +1104,10 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
} else if has { } else if has {
return ErrRepoAlreadyExist{u.Name, repo.Name} return ErrRepoAlreadyExist{u.Name, repo.Name}
} }

isCourse := isCourse(opts)
if isCourse {
repo.CreatorID = doer.ID
}
if _, err = ctx.e.Insert(repo); err != nil { if _, err = ctx.e.Insert(repo); err != nil {
return err return err
} }
@@ -1128,17 +1141,23 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true},
}) })
} else if tp == UnitTypeDatasets { } else if tp == UnitTypeDatasets {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &DatasetConfig{EnableDataset: true},
})
if !isCourse {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &DatasetConfig{EnableDataset: true},
})
}

} else if tp == UnitTypeCloudBrain { } else if tp == UnitTypeCloudBrain {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &CloudBrainConfig{EnableCloudBrain: true},
})
if !isCourse {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &CloudBrainConfig{EnableCloudBrain: true},
})
}

} else if tp == UnitTypeBlockChain { } else if tp == UnitTypeBlockChain {
units = append(units, RepoUnit{ units = append(units, RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
@@ -1146,11 +1165,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Config: &BlockChainConfig{EnableBlockChain: true}, Config: &BlockChainConfig{EnableBlockChain: true},
}) })
} else if tp == UnitTypeModelManage { } else if tp == UnitTypeModelManage {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &ModelManageConfig{EnableModelManage: true},
})
if !isCourse {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &ModelManageConfig{EnableModelManage: true},
})
}
} else { } else {
units = append(units, RepoUnit{ units = append(units, RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
@@ -1220,6 +1241,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
return nil return nil
} }


func isCourse(opts []CreateRepoOptions) bool {
var isCourse = false
if len(opts) > 0 {
isCourse = opts[0].IsCourse
}
return isCourse
}

func countRepositories(userID int64, private bool) int64 { func countRepositories(userID int64, private bool) int64 {
sess := x.Where("id > 0") sess := x.Where("id > 0")




+ 21
- 0
models/repo_list.go View File

@@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error {


set := make(map[int64]struct{}) set := make(map[int64]struct{})
repoIDs := make([]int64, len(repos)) repoIDs := make([]int64, len(repos))
setCreator := make(map[int64]struct{})
for i := range repos { for i := range repos {
set[repos[i].OwnerID] = struct{}{} set[repos[i].OwnerID] = struct{}{}
repoIDs[i] = repos[i].ID repoIDs[i] = repos[i].ID
setCreator[repos[i].CreatorID] = struct{}{}

} }


// Load owners. // Load owners.
@@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error {
Find(&users); err != nil { Find(&users); err != nil {
return fmt.Errorf("find users: %v", err) return fmt.Errorf("find users: %v", err)
} }
//Load creator
creators := make(map[int64]*User, len(set))
if err := e.
Where("id > 0").
In("id", keysInt64(setCreator)).
Find(&creators); err != nil {
return fmt.Errorf("find create repo users: %v", err)
}

for i := range repos { for i := range repos {
repos[i].Owner = users[repos[i].OwnerID] repos[i].Owner = users[repos[i].OwnerID]
repos[i].Creator = creators[repos[i].CreatorID]
} }


// Load primary language. // Load primary language.
@@ -174,6 +187,10 @@ type SearchRepoOptions struct {
// True -> include just has milestones // True -> include just has milestones
// False -> include just has no milestone // False -> include just has no milestone
HasMilestones util.OptionalBool HasMilestones util.OptionalBool
// None -> include all repos
// True -> include just courses
// False -> include just no courses
Course util.OptionalBool
} }


//SearchOrderBy is used to sort the result //SearchOrderBy is used to sort the result
@@ -351,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue})
} }


if opts.Course == util.OptionalBoolTrue {
cond = cond.And(builder.Eq{"repo_type": RepoCourse})
}

if opts.Actor != nil && opts.Actor.IsRestricted { if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(accessibleRepositoryCondition(opts.Actor)) cond = cond.And(accessibleRepositoryCondition(opts.Actor))
} }


+ 12
- 0
modules/auth/repo_form.go View File

@@ -728,3 +728,15 @@ type DeadlineForm struct {
func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) return validate(errs, ctx.Data, f, ctx.Locale)
} }

type CreateCourseForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"`
Topics string
Description string `binding:"MaxSize(1024)"`
}

// Validate validates the fields
func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

+ 9
- 1
modules/repository/create.go View File

@@ -22,6 +22,10 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
Limit: u.MaxRepoCreation, Limit: u.MaxRepoCreation,
} }
} }
var RepoType = models.RepoNormal
if opts.IsCourse {
RepoType = models.RepoCourse
}


repo := &models.Repository{ repo := &models.Repository{
OwnerID: u.ID, OwnerID: u.ID,
@@ -38,10 +42,14 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status, Status: opts.Status,
IsEmpty: !opts.AutoInit, IsEmpty: !opts.AutoInit,
RepoType: RepoType,
} }


err = models.WithTx(func(ctx models.DBContext) error { err = models.WithTx(func(ctx models.DBContext) error {
if err = models.CreateRepository(ctx, doer, u, repo); err != nil {
if err = models.CreateRepository(ctx, doer, u, repo, opts); err != nil {
return err
}
if err = models.SaveTopics(repo.ID, opts.Topics...); err != nil {
return err return err
} }




+ 8
- 0
options/locale/locale_en-US.ini View File

@@ -50,6 +50,7 @@ repository = Repository
organization = Organization organization = Organization
mirror = Mirror mirror = Mirror
new_repo = New Repository new_repo = New Repository
new_course=Publish Course
new_migrate = New Migration new_migrate = New Migration
new_dataset = New Dataset new_dataset = New Dataset
edit_dataset = Edit Dataset edit_dataset = Edit Dataset
@@ -390,6 +391,7 @@ lang_select_error = Select a language from the list.


username_been_taken = The username is already taken. username_been_taken = The username is already taken.
repo_name_been_taken = The repository name or path is already used. repo_name_been_taken = The repository name or path is already used.
course_name_been_taken=The course path is already used.
visit_rate_limit = Remote visit addressed rate limitation. visit_rate_limit = Remote visit addressed rate limitation.
2fa_auth_required = Remote visit required two factors authentication. 2fa_auth_required = Remote visit required two factors authentication.
org_name_been_taken = The organization name is already taken. org_name_been_taken = The organization name is already taken.
@@ -802,6 +804,8 @@ readme = README
readme_helper = Select a README file template. readme_helper = Select a README file template.
auto_init = Initialize Repository (Adds .gitignore, License and README) auto_init = Initialize Repository (Adds .gitignore, License and README)
create_repo = Create Repository create_repo = Create Repository
create_course = Publish Course
failed_to_create_course=Fail to publish course, please try again later.
default_branch = Default Branch default_branch = Default Branch
mirror_prune = Prune mirror_prune = Prune
mirror_prune_desc = Remove obsolete remote-tracking references mirror_prune_desc = Remove obsolete remote-tracking references
@@ -968,8 +972,12 @@ archive.issue.nocomment = This repo is archived. You cannot comment on issues.
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.


form.reach_limit_of_creation = You have already reached your limit of %d repositories. form.reach_limit_of_creation = You have already reached your limit of %d repositories.
form.reach_limit_of_course_creation=You have already reached your limit of %d courses or repositories.
form.name_reserved = The repository name '%s' is reserved. form.name_reserved = The repository name '%s' is reserved.
form.course_name_reserved=The course name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
form.course_name_pattern_not_allowed=The pattern '%s' is not allowed in a course name.
add_course_org_fail=Fail to add organization, please try again later.


need_auth = Clone Authorization need_auth = Clone Authorization
migrate_type = Migration Type migrate_type = Migration Type


+ 8
- 0
options/locale/locale_zh-CN.ini View File

@@ -50,6 +50,7 @@ repository=项目
organization=组织 organization=组织
mirror=镜像 mirror=镜像
new_repo=创建项目 new_repo=创建项目
new_course=发布课程
new_dataset=创建数据集 new_dataset=创建数据集
new_migrate=迁移外部项目 new_migrate=迁移外部项目
edit_dataset = Edit Dataset edit_dataset = Edit Dataset
@@ -395,6 +396,7 @@ lang_select_error=从列表中选出语言


username_been_taken=用户名已被使用。 username_been_taken=用户名已被使用。
repo_name_been_taken=项目名称或项目路径已被使用。 repo_name_been_taken=项目名称或项目路径已被使用。
course_name_been_taken=课程名称或路径已被使用。
visit_rate_limit=远程访问达到速度限制。 visit_rate_limit=远程访问达到速度限制。
2fa_auth_required=远程访问需要双重验证。 2fa_auth_required=远程访问需要双重验证。
org_name_been_taken=组织名称已被使用。 org_name_been_taken=组织名称已被使用。
@@ -808,6 +810,8 @@ readme=自述
readme_helper=选择自述文件模板。 readme_helper=选择自述文件模板。
auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) auto_init=初始化存储库 (添加. gitignore、许可证和自述文件)
create_repo=创建项目 create_repo=创建项目
create_course=发布课程
failed_to_create_course=发布课程失败,请稍后再试。
default_branch=默认分支 default_branch=默认分支
mirror_prune=修剪 mirror_prune=修剪
mirror_prune_desc=删除过时的远程跟踪引用 mirror_prune_desc=删除过时的远程跟踪引用
@@ -980,8 +984,12 @@ archive.issue.nocomment=此项目已存档,您不能在此任务添加评论
archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。


form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。
form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。
form.name_reserved=项目名称 '%s' 是被保留的。 form.name_reserved=项目名称 '%s' 是被保留的。
form.course_name_reserved=课程名称 '%s' 是被保留的。
form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。
form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。
add_course_org_fail=加入组织失败,请稍后重试。


need_auth=需要授权验证 need_auth=需要授权验证
migrate_type=迁移类型 migrate_type=迁移类型


+ 8
- 57
routers/home.go View File

@@ -7,11 +7,11 @@ package routers


import ( import (
"bytes" "bytes"
"fmt"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"


"code.gitea.io/gitea/services/repository"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -133,6 +133,7 @@ type RepoSearchOptions struct {
Restricted bool Restricted bool
PageSize int PageSize int
TplName base.TplName TplName base.TplName
Course util.OptionalBool
} }


var ( var (
@@ -211,6 +212,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
AllLimited: true, AllLimited: true,
TopicName: topic, TopicName: topic,
IncludeDescription: setting.UI.SearchRepoDescription, IncludeDescription: setting.UI.SearchRepoDescription,
Course: opts.Course,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepository", err) ctx.ServerError("SearchRepository", err)
@@ -559,7 +561,7 @@ func NotFound(ctx *context.Context) {


func RecommendOrgFromPromote(ctx *context.Context) { func RecommendOrgFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "organizations" url := setting.RecommentRepoAddr + "organizations"
result, err := recommendFromPromote(url)
result, err := repository.RecommendFromPromote(url)
if err != nil { if err != nil {
ctx.ServerError("500", err) ctx.ServerError("500", err)
return return
@@ -586,62 +588,11 @@ func RecommendOrgFromPromote(ctx *context.Context) {
ctx.JSON(200, resultOrg) ctx.JSON(200, resultOrg)
} }


func recommendFromPromote(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil || resp.StatusCode != 200 {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}
bytes, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}

allLineStr := string(bytes)
lines := strings.Split(allLineStr, "\n")
result := make([]string, len(lines))
for i, line := range lines {
log.Info("i=" + fmt.Sprint(i) + " line=" + line)
result[i] = strings.Trim(line, " ")
}
return result, nil
}

func RecommendRepoFromPromote(ctx *context.Context) { func RecommendRepoFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "projects"
result, err := recommendFromPromote(url)
result, err := repository.GetRecommendRepoFromPromote("projects")
if err != nil { if err != nil {
ctx.ServerError("500", err) ctx.ServerError("500", err)
return
}
resultRepo := make([]map[string]interface{}, 0)
//resultRepo := make([]*models.Repository, 0)
for _, repoName := range result {
tmpIndex := strings.Index(repoName, "/")
if tmpIndex == -1 {
log.Info("error repo name format.")
} else {
ownerName := strings.Trim(repoName[0:tmpIndex], " ")
repoName := strings.Trim(repoName[tmpIndex+1:], " ")
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err == nil {
repoMap := make(map[string]interface{})
repoMap["ID"] = fmt.Sprint(repo.ID)
repoMap["Name"] = repo.Name
repoMap["OwnerName"] = repo.OwnerName
repoMap["NumStars"] = repo.NumStars
repoMap["NumForks"] = repo.NumForks
repoMap["Description"] = repo.Description
repoMap["NumWatchs"] = repo.NumWatches
repoMap["Topics"] = repo.Topics
repoMap["Avatar"] = repo.RelAvatarLink()
resultRepo = append(resultRepo, repoMap)
} else {
log.Info("query repo error," + err.Error())
}
}
} else {
ctx.JSON(200, result)
} }
ctx.JSON(200, resultRepo)
} }

+ 35
- 6
routers/org/home.go View File

@@ -7,6 +7,10 @@ package org
import ( import (
"strings" "strings"


"code.gitea.io/gitea/services/repository"

"code.gitea.io/gitea/modules/util"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@@ -14,7 +18,8 @@ import (
) )


const ( const (
tplOrgHome base.TplName = "org/home"
tplOrgHome base.TplName = "org/home"
tplOrgCourseHome base.TplName = "org/home_courses"
) )


// Home show organization home page // Home show organization home page
@@ -59,10 +64,16 @@ func Home(ctx *context.Context) {
case "fewestforks": case "fewestforks":
orderBy = models.SearchOrderByForks orderBy = models.SearchOrderByForks
default: default:
ctx.Data["SortType"] = "recentupdate"
orderBy = models.SearchOrderByRecentUpdated
}
if setting.Course.OrgName == org.Name {
ctx.Data["SortType"] = "newest"
orderBy = models.SearchOrderByNewest


} else {
ctx.Data["SortType"] = "recentupdate"
orderBy = models.SearchOrderByRecentUpdated
}
}
orderBy = orderBy + ",id"
keyword := strings.Trim(ctx.Query("q"), " ") keyword := strings.Trim(ctx.Query("q"), " ")
ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword


@@ -76,9 +87,21 @@ func Home(ctx *context.Context) {
count int64 count int64
err error err error
) )
pageSize := setting.UI.User.RepoPagingNum
var CourseOptional util.OptionalBool = util.OptionalBoolNone
if setting.Course.OrgName == org.Name {
pageSize = 15
CourseOptional = util.OptionalBoolTrue
recommendCourses, _ := repository.GetRecommendRepoFromPromote("courses")
ctx.Data["RecommendCourses"] = recommendCourses
recommendCourseKeyWords, _ := repository.GetRecommendCourseKeyWords()
ctx.Data["CoursesKeywords"] = recommendCourseKeyWords

}

repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
ListOptions: models.ListOptions{ ListOptions: models.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
PageSize: pageSize,
Page: page, Page: page,
}, },
Keyword: keyword, Keyword: keyword,
@@ -87,6 +110,7 @@ func Home(ctx *context.Context) {
Private: ctx.IsSigned, Private: ctx.IsSigned,
Actor: ctx.User, Actor: ctx.User,
IncludeDescription: setting.UI.SearchRepoDescription, IncludeDescription: setting.UI.SearchRepoDescription,
Course: CourseOptional,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepository", err) ctx.ServerError("SearchRepository", err)
@@ -138,5 +162,10 @@ func Home(ctx *context.Context) {
} }


ctx.Data["tags"] = tags ctx.Data["tags"] = tags
ctx.HTML(200, tplOrgHome)
if setting.Course.OrgName == org.Name {
ctx.HTML(200, tplOrgCourseHome)
} else {
ctx.HTML(200, tplOrgHome)
}

} }

+ 7
- 3
routers/org/members.go View File

@@ -17,7 +17,8 @@ import (


const ( const (
// tplMembers template for organization members page // tplMembers template for organization members page
tplMembers base.TplName = "org/member/members"
tplMembers base.TplName = "org/member/members"
tplCourseMembers base.TplName = "org/member/course_members"
) )


// Members render organization users page // Members render organization users page
@@ -64,8 +65,11 @@ func Members(ctx *context.Context) {
ctx.Data["MembersIsPublicMember"] = membersIsPublic ctx.Data["MembersIsPublicMember"] = membersIsPublic
ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID)
ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus() ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus()

ctx.HTML(200, tplMembers)
if setting.Course.OrgName == org.Name {
ctx.HTML(200, tplCourseMembers)
} else {
ctx.HTML(200, tplMembers)
}
} }


// MembersAction response for operation to a member of organization // MembersAction response for operation to a member of organization


+ 9
- 2
routers/org/teams.go View File

@@ -10,6 +10,8 @@ import (
"path" "path"
"strings" "strings"


"code.gitea.io/gitea/modules/setting"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@@ -22,7 +24,8 @@ import (


const ( const (
// tplTeams template path for teams list page // tplTeams template path for teams list page
tplTeams base.TplName = "org/team/teams"
tplTeams base.TplName = "org/team/teams"
tplCourseTeams base.TplName = "org/team/courseTeams"
// tplTeamNew template path for create new team page // tplTeamNew template path for create new team page
tplTeamNew base.TplName = "org/team/new" tplTeamNew base.TplName = "org/team/new"
// tplTeamMembers template path for showing team members page // tplTeamMembers template path for showing team members page
@@ -44,8 +47,12 @@ func Teams(ctx *context.Context) {
} }
} }
ctx.Data["Teams"] = org.Teams ctx.Data["Teams"] = org.Teams
if setting.Course.OrgName == org.Name {
ctx.HTML(200, tplCourseTeams)
} else {
ctx.HTML(200, tplTeams)
}


ctx.HTML(200, tplTeams)
} }


// TeamsAction response for join, leave, remove, add operations to team // TeamsAction response for join, leave, remove, add operations to team


+ 189
- 0
routers/repo/course.go View File

@@ -0,0 +1,189 @@
package repo

import (
"net/http"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
repo_service "code.gitea.io/gitea/services/repository"
)

const (
tplCreateCourse base.TplName = "repo/createCourse"
)

func CreateCourse(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_course")

ctx.Data["Owner"] = setting.Course.OrgName

ctx.HTML(200, tplCreateCourse)

}

func CreateCoursePost(ctx *context.Context, form auth.CreateCourseForm) {
ctx.Data["Title"] = ctx.Tr("new_course")

if ctx.Written() {
return
}

var topics = make([]string, 0)
var topicsStr = strings.TrimSpace(form.Topics)
if len(topicsStr) > 0 {
topics = strings.Split(topicsStr, ",")
}

validTopics, invalidTopics := models.SanitizeAndValidateTopics(topics)

if len(validTopics) > 25 {
ctx.RenderWithErr(ctx.Tr("repo.topic.count_prompt"), tplCreateCourse, form)
return
}

if len(invalidTopics) > 0 {
ctx.RenderWithErr(ctx.Tr("repo.topic.format_prompt"), tplCreateCourse, form)
return
}

var repo *models.Repository
var err error

if setting.Course.OrgName == "" || setting.Course.TeamName == "" {
log.Error("then organization name or team name of course is empty.")
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}

org, team, err := getOrgAndTeam()

if err != nil {
log.Error("Failed to get team from db.", err)
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}
isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID})
if err != nil {
log.Error("Failed to get user in team from db.")
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}

if !isInTeam {
err = models.AddTeamMember(team, ctx.User.ID)
if err != nil {
log.Error("Failed to add user to team.")
ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
return
}
}

if ctx.HasError() {
ctx.HTML(200, tplCreateCourse)
return
}

repo, err = repo_service.CreateRepository(ctx.User, org, models.CreateRepoOptions{
Name: form.RepoName,
Alias: form.Alias,
Description: form.Description,
Gitignores: "",
IssueLabels: "",
License: "",
Readme: "Default",
IsPrivate: false,
DefaultBranch: "master",
AutoInit: true,
IsCourse: true,
Topics: validTopics,
})
if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, org.Name, repo.Name)
ctx.Redirect(setting.AppSubURL + "/" + org.Name + "/" + repo.Name)
return
}

handleCreateCourseError(ctx, org, err, "CreateCoursePost", tplCreateCourse, &form)
}

func AddCourseOrg(ctx *context.Context) {

_, team, err := getOrgAndTeam()

if err != nil {
log.Error("Failed to get team from db.", err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"message": ctx.Tr("repo.addCourseOrgFail"),
})
return
}
isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID})
if err != nil {
log.Error("Failed to get user in team from db.", err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"message": ctx.Tr("repo.add_course_org_fail"),
})
return
}

if !isInTeam {
err = models.AddTeamMember(team, ctx.User.ID)
if err != nil {
log.Error("Failed to add user to team.", err)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"message": ctx.Tr("repo.add_course_org_fail"),
})
return
}
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 0,
"message": "",
})

}

func getOrgAndTeam() (*models.User, *models.Team, error) {
org, err := models.GetUserByName(setting.Course.OrgName)

if err != nil {
log.Error("Failed to get organization from db.", err)
return nil, nil, err
}

team, err := models.GetTeam(org.ID, setting.Course.TeamName)

if err != nil {
log.Error("Failed to get team from db.", err)

return nil, nil, err
}
return org, team, nil
}

func handleCreateCourseError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) {
switch {
case models.IsErrReachLimitOfRepo(err):
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_course_creation", owner.MaxCreationLimit()), tpl, form)
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.course_name_been_taken"), tpl, form)
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.course_name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.course_name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
default:
ctx.ServerError(name, err)
}
}

+ 7
- 0
routers/routes/routes.go View File

@@ -708,6 +708,13 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqSignIn) }, reqSignIn)
// ***** END: Organization ***** // ***** END: Organization *****


m.Group("/course", func() {
m.Get("/create", repo.CreateCourse)
m.Post("/create", bindIgnErr(auth.CreateCourseForm{}), repo.CreateCoursePost)
m.Get("/addOrg", repo.AddCourseOrg)

}, reqSignIn)

// ***** START: Repository ***** // ***** START: Repository *****
m.Group("/repo", func() { m.Group("/repo", func() {
m.Get("/create", repo.Create) m.Get("/create", repo.Create)


+ 82
- 1
services/repository/repository.go View File

@@ -5,12 +5,17 @@
package repository package repository


import ( import (
"fmt"
"io/ioutil"
"net/http"
"strings"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
"fmt"
) )


// CreateRepository creates a repository for the user/organization. // CreateRepository creates a repository for the user/organization.
@@ -86,3 +91,79 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo


return repo, nil return repo, nil
} }

func GetRecommendCourseKeyWords() ([]string, error) {

url := setting.RecommentRepoAddr + "course_keywords"
result, err := RecommendFromPromote(url)

if err != nil {
return []string{}, err
}
return result, err

}

func GetRecommendRepoFromPromote(filename string) ([]map[string]interface{}, error) {
resultRepo := make([]map[string]interface{}, 0)
url := setting.RecommentRepoAddr + filename
result, err := RecommendFromPromote(url)

if err != nil {

return resultRepo, err
}

//resultRepo := make([]*models.Repository, 0)
for _, repoName := range result {
tmpIndex := strings.Index(repoName, "/")
if tmpIndex == -1 {
log.Info("error repo name format.")
} else {
ownerName := strings.Trim(repoName[0:tmpIndex], " ")
repoName := strings.Trim(repoName[tmpIndex+1:], " ")
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err == nil {
repoMap := make(map[string]interface{})
repoMap["ID"] = fmt.Sprint(repo.ID)
repoMap["Name"] = repo.Name
repoMap["Alias"] = repo.Alias
repoMap["Creator"] = repo.Creator
repoMap["OwnerName"] = repo.OwnerName
repoMap["NumStars"] = repo.NumStars
repoMap["NumForks"] = repo.NumForks
repoMap["Description"] = repo.Description
repoMap["NumWatchs"] = repo.NumWatches
repoMap["Topics"] = repo.Topics
repoMap["Avatar"] = repo.RelAvatarLink()
resultRepo = append(resultRepo, repoMap)
} else {
log.Info("query repo error," + err.Error())
}
}
}
return resultRepo, nil
}

func RecommendFromPromote(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil || resp.StatusCode != 200 {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}
bytes, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Info("Get organizations url error=" + err.Error())
return nil, err
}

allLineStr := string(bytes)
lines := strings.Split(allLineStr, "\n")
result := make([]string, len(lines))
for i, line := range lines {
log.Info("i=" + fmt.Sprint(i) + " line=" + line)
result[i] = strings.Trim(line, " ")
}
return result, nil
}

Loading…
Cancel
Save