@@ -140,6 +140,7 @@ func NewRepoContext() { | |||
// RepositoryStatus defines the status of repository | |||
type RepositoryStatus int | |||
type RepoBlockChainStatus int | |||
type RepoType int | |||
// all kinds of RepositoryStatus | |||
const ( | |||
@@ -153,6 +154,11 @@ const ( | |||
RepoBlockChainFailed | |||
) | |||
const ( | |||
RepoNormal RepoType = iota | |||
RepoCourse | |||
) | |||
// Repository represents a git repository. | |||
type Repository struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
@@ -166,7 +172,8 @@ type Repository struct { | |||
OriginalServiceType api.GitServiceType `xorm:"index"` | |||
OriginalURL string `xorm:"VARCHAR(2048)"` | |||
DefaultBranch string | |||
CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"` | |||
Creator *User `xorm:"-"` | |||
NumWatches int | |||
NumStars int | |||
NumForks int | |||
@@ -175,11 +182,12 @@ type Repository struct { | |||
NumOpenIssues int `xorm:"-"` | |||
NumPulls 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"` | |||
IsEmpty bool `xorm:"INDEX"` | |||
@@ -1035,6 +1043,8 @@ type CreateRepoOptions struct { | |||
IsMirror bool | |||
AutoInit bool | |||
Status RepositoryStatus | |||
IsCourse bool | |||
Topics []string | |||
} | |||
// GetRepoInitFile returns repository init files | |||
@@ -1080,7 +1090,7 @@ func IsUsableRepoAlias(name string) error { | |||
} | |||
// 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 { | |||
return err | |||
} | |||
@@ -1094,7 +1104,10 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
} else if has { | |||
return ErrRepoAlreadyExist{u.Name, repo.Name} | |||
} | |||
isCourse := isCourse(opts) | |||
if isCourse { | |||
repo.CreatorID = doer.ID | |||
} | |||
if _, err = ctx.e.Insert(repo); err != nil { | |||
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}, | |||
}) | |||
} 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 { | |||
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 { | |||
units = append(units, RepoUnit{ | |||
RepoID: repo.ID, | |||
@@ -1146,11 +1165,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
Config: &BlockChainConfig{EnableBlockChain: true}, | |||
}) | |||
} 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 { | |||
units = append(units, RepoUnit{ | |||
RepoID: repo.ID, | |||
@@ -1220,6 +1241,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
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 { | |||
sess := x.Where("id > 0") | |||
@@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error { | |||
set := make(map[int64]struct{}) | |||
repoIDs := make([]int64, len(repos)) | |||
setCreator := make(map[int64]struct{}) | |||
for i := range repos { | |||
set[repos[i].OwnerID] = struct{}{} | |||
repoIDs[i] = repos[i].ID | |||
setCreator[repos[i].CreatorID] = struct{}{} | |||
} | |||
// Load owners. | |||
@@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error { | |||
Find(&users); err != nil { | |||
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 { | |||
repos[i].Owner = users[repos[i].OwnerID] | |||
repos[i].Creator = creators[repos[i].CreatorID] | |||
} | |||
// Load primary language. | |||
@@ -174,6 +187,10 @@ type SearchRepoOptions struct { | |||
// True -> include just has milestones | |||
// False -> include just has no milestone | |||
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 | |||
@@ -351,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||
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 { | |||
cond = cond.And(accessibleRepositoryCondition(opts.Actor)) | |||
} | |||
@@ -728,3 +728,15 @@ type DeadlineForm struct { | |||
func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
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) | |||
} |
@@ -22,6 +22,10 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m | |||
Limit: u.MaxRepoCreation, | |||
} | |||
} | |||
var RepoType = models.RepoNormal | |||
if opts.IsCourse { | |||
RepoType = models.RepoCourse | |||
} | |||
repo := &models.Repository{ | |||
OwnerID: u.ID, | |||
@@ -38,10 +42,14 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m | |||
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, | |||
Status: opts.Status, | |||
IsEmpty: !opts.AutoInit, | |||
RepoType: RepoType, | |||
} | |||
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 | |||
} | |||
@@ -50,6 +50,7 @@ repository = Repository | |||
organization = Organization | |||
mirror = Mirror | |||
new_repo = New Repository | |||
new_course=Publish Course | |||
new_migrate = New Migration | |||
new_dataset = New 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. | |||
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. | |||
2fa_auth_required = Remote visit required two factors authentication. | |||
org_name_been_taken = The organization name is already taken. | |||
@@ -802,6 +804,8 @@ readme = README | |||
readme_helper = Select a README file template. | |||
auto_init = Initialize Repository (Adds .gitignore, License and README) | |||
create_repo = Create Repository | |||
create_course = Publish Course | |||
failed_to_create_course=Fail to publish course, please try again later. | |||
default_branch = Default Branch | |||
mirror_prune = Prune | |||
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. | |||
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.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.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 | |||
migrate_type = Migration Type | |||
@@ -50,6 +50,7 @@ repository=项目 | |||
organization=组织 | |||
mirror=镜像 | |||
new_repo=创建项目 | |||
new_course=发布课程 | |||
new_dataset=创建数据集 | |||
new_migrate=迁移外部项目 | |||
edit_dataset = Edit Dataset | |||
@@ -395,6 +396,7 @@ lang_select_error=从列表中选出语言 | |||
username_been_taken=用户名已被使用。 | |||
repo_name_been_taken=项目名称或项目路径已被使用。 | |||
course_name_been_taken=课程名称或路径已被使用。 | |||
visit_rate_limit=远程访问达到速度限制。 | |||
2fa_auth_required=远程访问需要双重验证。 | |||
org_name_been_taken=组织名称已被使用。 | |||
@@ -808,6 +810,8 @@ readme=自述 | |||
readme_helper=选择自述文件模板。 | |||
auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) | |||
create_repo=创建项目 | |||
create_course=发布课程 | |||
failed_to_create_course=发布课程失败,请稍后再试。 | |||
default_branch=默认分支 | |||
mirror_prune=修剪 | |||
mirror_prune_desc=删除过时的远程跟踪引用 | |||
@@ -980,8 +984,12 @@ archive.issue.nocomment=此项目已存档,您不能在此任务添加评论 | |||
archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 | |||
form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 | |||
form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。 | |||
form.name_reserved=项目名称 '%s' 是被保留的。 | |||
form.course_name_reserved=课程名称 '%s' 是被保留的。 | |||
form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 | |||
form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。 | |||
add_course_org_fail=加入组织失败,请稍后重试。 | |||
need_auth=需要授权验证 | |||
migrate_type=迁移类型 | |||
@@ -7,11 +7,11 @@ package routers | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"io/ioutil" | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/services/repository" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
@@ -133,6 +133,7 @@ type RepoSearchOptions struct { | |||
Restricted bool | |||
PageSize int | |||
TplName base.TplName | |||
Course util.OptionalBool | |||
} | |||
var ( | |||
@@ -211,6 +212,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||
AllLimited: true, | |||
TopicName: topic, | |||
IncludeDescription: setting.UI.SearchRepoDescription, | |||
Course: opts.Course, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("SearchRepository", err) | |||
@@ -559,7 +561,7 @@ func NotFound(ctx *context.Context) { | |||
func RecommendOrgFromPromote(ctx *context.Context) { | |||
url := setting.RecommentRepoAddr + "organizations" | |||
result, err := recommendFromPromote(url) | |||
result, err := repository.RecommendFromPromote(url) | |||
if err != nil { | |||
ctx.ServerError("500", err) | |||
return | |||
@@ -586,62 +588,11 @@ func RecommendOrgFromPromote(ctx *context.Context) { | |||
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) { | |||
url := setting.RecommentRepoAddr + "projects" | |||
result, err := recommendFromPromote(url) | |||
result, err := repository.GetRecommendRepoFromPromote("projects") | |||
if err != nil { | |||
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) | |||
} |
@@ -7,6 +7,10 @@ package org | |||
import ( | |||
"strings" | |||
"code.gitea.io/gitea/services/repository" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
@@ -14,7 +18,8 @@ import ( | |||
) | |||
const ( | |||
tplOrgHome base.TplName = "org/home" | |||
tplOrgHome base.TplName = "org/home" | |||
tplOrgCourseHome base.TplName = "org/home_courses" | |||
) | |||
// Home show organization home page | |||
@@ -59,10 +64,16 @@ func Home(ctx *context.Context) { | |||
case "fewestforks": | |||
orderBy = models.SearchOrderByForks | |||
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"), " ") | |||
ctx.Data["Keyword"] = keyword | |||
@@ -76,9 +87,21 @@ func Home(ctx *context.Context) { | |||
count int64 | |||
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{ | |||
ListOptions: models.ListOptions{ | |||
PageSize: setting.UI.User.RepoPagingNum, | |||
PageSize: pageSize, | |||
Page: page, | |||
}, | |||
Keyword: keyword, | |||
@@ -87,6 +110,7 @@ func Home(ctx *context.Context) { | |||
Private: ctx.IsSigned, | |||
Actor: ctx.User, | |||
IncludeDescription: setting.UI.SearchRepoDescription, | |||
Course: CourseOptional, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("SearchRepository", err) | |||
@@ -138,5 +162,10 @@ func Home(ctx *context.Context) { | |||
} | |||
ctx.Data["tags"] = tags | |||
ctx.HTML(200, tplOrgHome) | |||
if setting.Course.OrgName == org.Name { | |||
ctx.HTML(200, tplOrgCourseHome) | |||
} else { | |||
ctx.HTML(200, tplOrgHome) | |||
} | |||
} |
@@ -17,7 +17,8 @@ import ( | |||
const ( | |||
// 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 | |||
@@ -64,8 +65,11 @@ func Members(ctx *context.Context) { | |||
ctx.Data["MembersIsPublicMember"] = membersIsPublic | |||
ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) | |||
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 | |||
@@ -10,6 +10,8 @@ import ( | |||
"path" | |||
"strings" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/auth" | |||
"code.gitea.io/gitea/modules/base" | |||
@@ -22,7 +24,8 @@ import ( | |||
const ( | |||
// 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 base.TplName = "org/team/new" | |||
// tplTeamMembers template path for showing team members page | |||
@@ -44,8 +47,12 @@ func Teams(ctx *context.Context) { | |||
} | |||
} | |||
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 | |||
@@ -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) | |||
} | |||
} |
@@ -708,6 +708,13 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
}, reqSignIn) | |||
// ***** 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 ***** | |||
m.Group("/repo", func() { | |||
m.Get("/create", repo.Create) | |||
@@ -5,12 +5,17 @@ | |||
package repository | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/notification" | |||
repo_module "code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/setting" | |||
pull_service "code.gitea.io/gitea/services/pull" | |||
"fmt" | |||
) | |||
// 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 | |||
} | |||
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 | |||
} |