mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-04 03:35:05 +02:00
add group pages to web router
- `/{username}/groups/{group_id}` -> show a group's homepage with its repositories
- `/{org}/groups/new` -> create a new group in an org
- `/{org}/groups/{group_id}` -> show a group in an org
- `/{org}/groups/{group_id}/teams` -> show/edit teams with access to a group
- `/{org}/groups/{group_id}/teams/{team}/edit` -> edit a team's access to a group
- `/{org}/group/{group_id}/settings` -> show/edit a group's settings
- `/group/search` -> search for groups with parameters. return matching groups and repos contained within them
This commit is contained in:
parent
97576a41c4
commit
efe8193237
45
routers/web/group/edit.go
Normal file
45
routers/web/group/edit.go
Normal file
@ -0,0 +1,45 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
)
|
||||
|
||||
func MoveGroupItem(ctx *context.Context) {
|
||||
form := &forms.MovedGroupItemForm{}
|
||||
if err := json.NewDecoder(ctx.Req.Body).Decode(form); err != nil {
|
||||
ctx.ServerError("DecodeMovedGroupItemForm", err)
|
||||
return
|
||||
}
|
||||
if form.IsGroup {
|
||||
group, err := group_model.GetGroupByID(ctx, form.ItemID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetGroupByID", err)
|
||||
return
|
||||
}
|
||||
if group.ParentGroupID != form.NewParent {
|
||||
if err = group_model.MoveGroup(ctx, group, form.NewParent, form.NewPos); err != nil {
|
||||
ctx.ServerError("MoveGroup", err)
|
||||
return
|
||||
}
|
||||
if err = group_service.RecalculateGroupAccess(ctx, group, false); err != nil {
|
||||
ctx.ServerError("RecalculateGroupAccess", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, form.ItemID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
}
|
||||
if repo.GroupID != form.NewParent {
|
||||
if err = group_service.MoveRepositoryToGroup(ctx, repo, form.NewParent, form.NewPos); err != nil {
|
||||
ctx.ServerError("MoveRepositoryToGroup", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.JSONOK()
|
||||
}
|
||||
117
routers/web/group/home.go
Normal file
117
routers/web/group/home.go
Normal file
@ -0,0 +1,117 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
shared_group_model "code.gitea.io/gitea/models/shared/group"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
shared_group "code.gitea.io/gitea/routers/web/shared/group"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tplGroupHome = "group/home"
|
||||
)
|
||||
|
||||
func Home(ctx *context.Context) {
|
||||
org := ctx.Org.Organization
|
||||
|
||||
ctx.Data["PageIsUserProfile"] = true
|
||||
ctx.Data["Title"] = org.DisplayName()
|
||||
|
||||
var orderBy db.SearchOrderBy
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok {
|
||||
sortOrder = setting.UI.ExploreDefaultSort
|
||||
}
|
||||
ctx.Data["SortType"] = sortOrder
|
||||
orderBy = repo_model.OrderByFlatMap[sortOrder]
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
ctx.Data["Keyword"] = keyword
|
||||
|
||||
language := ctx.FormTrim("language")
|
||||
ctx.Data["Language"] = language
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
archived := ctx.FormOptionalBool("archived")
|
||||
ctx.Data["IsArchived"] = archived
|
||||
|
||||
fork := ctx.FormOptionalBool("fork")
|
||||
ctx.Data["IsFork"] = fork
|
||||
|
||||
mirror := ctx.FormOptionalBool("mirror")
|
||||
ctx.Data["IsMirror"] = mirror
|
||||
|
||||
template := ctx.FormOptionalBool("template")
|
||||
ctx.Data["IsTemplate"] = template
|
||||
|
||||
private := ctx.FormOptionalBool("private")
|
||||
ctx.Data["IsPrivate"] = private
|
||||
|
||||
err := shared_group.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
Doer: ctx.Doer,
|
||||
OrgID: org.ID,
|
||||
IsDoerMember: ctx.Org.IsMember,
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||
}
|
||||
|
||||
members, err := shared_group_model.FindGroupMembers(ctx, ctx.RepoGroup.Group.ID, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindOrgMembers", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.RepoGroup.Teams
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.RepoGroup.IsMember || len(members) > 0
|
||||
ctx.Data["PageIsViewRepositories"] = true
|
||||
|
||||
var (
|
||||
repos []*repo_model.Repository
|
||||
count int64
|
||||
)
|
||||
repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
PageSize: setting.UI.User.RepoPagingNum,
|
||||
Page: page,
|
||||
},
|
||||
Keyword: keyword,
|
||||
OwnerID: org.ID,
|
||||
OrderBy: orderBy,
|
||||
Private: ctx.IsSigned,
|
||||
Actor: ctx.Doer,
|
||||
Language: language,
|
||||
IncludeDescription: setting.UI.SearchRepoDescription,
|
||||
Archived: archived,
|
||||
Fork: fork,
|
||||
Mirror: mirror,
|
||||
Template: template,
|
||||
IsPrivate: private,
|
||||
GroupID: ctx.RepoGroup.Group.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("SearchRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Repos"] = repos
|
||||
ctx.Data["Total"] = count
|
||||
|
||||
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||
pager.AddParamFromRequest(ctx.Req)
|
||||
ctx.Data["Page"] = pager
|
||||
ctx.HTML(http.StatusOK, tplGroupHome)
|
||||
}
|
||||
89
routers/web/group/new.go
Normal file
89
routers/web/group/new.go
Normal file
@ -0,0 +1,89 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
)
|
||||
|
||||
const tplGroupNew = "group/create"
|
||||
|
||||
func NewGroup(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||
ctx.Data["PageIsNewGroup"] = true
|
||||
if ctx.RepoGroup.Group != nil {
|
||||
ctx.Data["Group"] = &group_model.Group{ParentGroupID: ctx.RepoGroup.Group.ID}
|
||||
} else {
|
||||
ctx.Data["Group"] = &group_model.Group{}
|
||||
}
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := group_model.FindGroupsOptions{
|
||||
ActorID: ctx.Doer.ID,
|
||||
CanCreateIn: optional.Some(true),
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
}
|
||||
cond := group_model.AccessibleGroupCondition(ctx.Doer, unit_model.TypeInvalid, perm.AccessModeWrite)
|
||||
cond = cond.And(opts.ToConds())
|
||||
groups, err := group_model.FindGroupsByCond(ctx, &group_model.FindGroupsOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
ParentGroupID: -1,
|
||||
}, cond)
|
||||
for _, g := range groups {
|
||||
err = g.LoadAccessibleSubgroups(ctx, true, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadAccessibleSubgroups", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
ctx.ServerError("FindGroupsByCond", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Groups"] = groups
|
||||
ctx.HTML(http.StatusOK, tplGroupNew)
|
||||
}
|
||||
func NewGroupPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateGroupForm)
|
||||
log.GetLogger(log.DEFAULT).Info("what? %+v", form)
|
||||
g := &group_model.Group{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
Name: form.GroupName,
|
||||
Description: form.Description,
|
||||
OwnerName: ctx.Org.Organization.Name,
|
||||
ParentGroupID: form.ParentGroupID,
|
||||
}
|
||||
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||
ctx.Data["PageIsGroupNew"] = true
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
ctx.Data["Group"] = g
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplGroupNew)
|
||||
return
|
||||
}
|
||||
|
||||
if err := group_service.NewGroup(ctx, g); err != nil {
|
||||
ctx.Data["Err_GroupName"] = true
|
||||
ctx.ServerError("NewGroup", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Group created: %s/%s", ctx.Org.Organization.Name, g.Name)
|
||||
ctx.Redirect(g.GroupLink())
|
||||
}
|
||||
155
routers/web/group/search.go
Normal file
155
routers/web/group/search.go
Normal file
@ -0,0 +1,155 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
)
|
||||
|
||||
func toSearchRepoOptions(ctx *context.Context) (opts *repo_model.SearchRepoOptions) {
|
||||
page := ctx.FormInt("page")
|
||||
opts = &repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
},
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
OwnerID: ctx.FormInt64("uid"),
|
||||
PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
|
||||
TeamID: ctx.FormInt64("team_id"),
|
||||
TopicOnly: ctx.FormBool("topic"),
|
||||
Collaborate: optional.None[bool](),
|
||||
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
|
||||
Template: optional.None[bool](),
|
||||
StarredByID: ctx.FormInt64("starredBy"),
|
||||
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||
}
|
||||
if ctx.FormString("template") != "" {
|
||||
opts.Template = optional.Some(ctx.FormBool("template"))
|
||||
}
|
||||
|
||||
if ctx.FormBool("exclusive") {
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
|
||||
mode := ctx.FormString("mode")
|
||||
switch mode {
|
||||
case "source":
|
||||
opts.Fork = optional.Some(false)
|
||||
opts.Mirror = optional.Some(false)
|
||||
case "fork":
|
||||
opts.Fork = optional.Some(true)
|
||||
case "mirror":
|
||||
opts.Mirror = optional.Some(true)
|
||||
case "collaborative":
|
||||
opts.Mirror = optional.Some(false)
|
||||
opts.Collaborate = optional.Some(true)
|
||||
case "":
|
||||
default:
|
||||
estr := fmt.Sprintf("Invalid search mode: \"%s\"", mode)
|
||||
ctx.Status(http.StatusUnprocessableEntity)
|
||||
ctx.ServerError("toSearchRepoOptions", errors.New(estr))
|
||||
return nil
|
||||
}
|
||||
|
||||
if ctx.FormString("archived") != "" {
|
||||
opts.Archived = optional.Some(ctx.FormBool("archived"))
|
||||
}
|
||||
|
||||
if ctx.FormString("is_private") != "" {
|
||||
opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
|
||||
}
|
||||
|
||||
sortMode := ctx.FormString("sort")
|
||||
if len(sortMode) > 0 {
|
||||
sortOrder := ctx.FormString("order")
|
||||
if len(sortOrder) == 0 {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||
opts.OrderBy = orderBy
|
||||
} else {
|
||||
estr := fmt.Errorf("Invalid sort mode: \"%s\"", sortMode)
|
||||
ctx.Status(http.StatusUnprocessableEntity)
|
||||
ctx.ServerError("toSearchRepoOptions", estr)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
estr := fmt.Errorf("Invalid sort order: \"%s\"", sortOrder)
|
||||
ctx.Status(http.StatusUnprocessableEntity)
|
||||
ctx.ServerError("toSearchRepoOptions", estr)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SearchGroup(ctx *context.Context) {
|
||||
gid := ctx.FormInt64("group_id")
|
||||
var (
|
||||
group *group_model.Group
|
||||
err error
|
||||
canSee bool = true
|
||||
oid int64 = ctx.FormInt64("uid")
|
||||
)
|
||||
if gid > 0 {
|
||||
group, err = group_model.GetGroupByID(ctx, gid)
|
||||
if err != nil && !group_model.IsErrGroupNotExist(err) {
|
||||
ctx.ServerError("GetGroupByID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if group != nil {
|
||||
canSee, err = group.CanAccess(ctx, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("GroupCanAccess", err)
|
||||
return
|
||||
}
|
||||
oid = group.OwnerID
|
||||
}
|
||||
if !canSee {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
subgroupOpts := &group_model.FindGroupsOptions{
|
||||
ParentGroupID: gid,
|
||||
ActorID: ctx.Doer.ID,
|
||||
OwnerID: oid,
|
||||
}
|
||||
if gid == 0 {
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
subgroupOpts.ListOptions = db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
}
|
||||
}
|
||||
sro := toSearchRepoOptions(ctx)
|
||||
rgw, err := group_service.SearchRepoGroupWeb(group, &group_service.WebSearchOptions{
|
||||
OrgID: oid,
|
||||
GroupOpts: subgroupOpts,
|
||||
RepoOpts: *sro,
|
||||
Actor: ctx.Doer,
|
||||
Recurse: ctx.FormBool("recurse"),
|
||||
Ctx: ctx,
|
||||
Locale: ctx.Locale,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("SearchRepoGroupWeb", err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, rgw)
|
||||
}
|
||||
143
routers/web/group/setting.go
Normal file
143
routers/web/group/setting.go
Normal file
@ -0,0 +1,143 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
shared_group "code.gitea.io/gitea/routers/web/shared/group"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tplSettingsOptions templates.TplName = "group/settings/options"
|
||||
)
|
||||
|
||||
func RedirectToDefaultSetting(ctx *context.Context) {
|
||||
ctx.Redirect(ctx.RepoGroup.OrgGroupLink + "/settings/actions/runners")
|
||||
}
|
||||
|
||||
// Settings render the main settings page
|
||||
func Settings(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("group.settings")
|
||||
ctx.Data["PageIsGroupSettings"] = true
|
||||
ctx.Data["PageIsSettingsOptions"] = true
|
||||
ctx.Data["CurrentVisibility"] = ctx.RepoGroup.Group.Visibility
|
||||
ctx.Data["ContextUser"] = ctx.ContextUser
|
||||
|
||||
err := shared_group.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||
}
|
||||
|
||||
func SettingsPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.UpdateGroupSettingForm)
|
||||
ctx.Data["Title"] = ctx.Tr("group.settings")
|
||||
ctx.Data["PageIsGroupSettings"] = true
|
||||
ctx.Data["PageIsSettingsOptions"] = true
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||
return
|
||||
}
|
||||
group := ctx.RepoGroup.Group
|
||||
|
||||
opts := &group_service.UpdateOptions{
|
||||
Description: optional.Some(form.Description),
|
||||
Visibility: optional.Some(form.Visibility),
|
||||
}
|
||||
if form.Name != group.Name {
|
||||
opts.Name = optional.Some(form.Name)
|
||||
}
|
||||
visibilityChanged := group.Visibility != form.Visibility
|
||||
if err := group_service.UpdateGroup(ctx, group, opts); err != nil {
|
||||
ctx.ServerError("UpdateGroup", err)
|
||||
return
|
||||
}
|
||||
if visibilityChanged {
|
||||
repos, _, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||
Actor: ctx.ContextUser,
|
||||
Private: true,
|
||||
GroupID: group.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("SearchRepositories", err)
|
||||
return
|
||||
}
|
||||
for _, repo := range repos {
|
||||
if err = repo_service.UpdateRepository(ctx, repo, true); err != nil {
|
||||
ctx.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Trace("Group setting updated: '%s'", group.Name)
|
||||
ctx.Flash.Success(ctx.Tr("group.settings.update_setting_success"))
|
||||
ctx.Redirect(ctx.RepoGroup.OrgGroupLink + "/settings")
|
||||
}
|
||||
|
||||
// SettingsAvatar response for change avatar on settings page
|
||||
func SettingsAvatar(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AvatarForm)
|
||||
form.Source = forms.AvatarLocal
|
||||
if err := updateAvatarSetting(ctx, form, ctx.RepoGroup.Group); err != nil {
|
||||
ctx.Flash.Error(err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("group.settings.update_avatar_success"))
|
||||
}
|
||||
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings")
|
||||
}
|
||||
|
||||
// SettingsDeleteAvatar response for delete avatar on settings page
|
||||
func SettingsDeleteAvatar(ctx *context.Context) {
|
||||
if err := user_service.DeleteAvatar(ctx, ctx.Org.Organization.AsUser()); err != nil {
|
||||
ctx.Flash.Error(err.Error())
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(ctx.RepoGroup.OrgGroupLink + "/settings")
|
||||
}
|
||||
|
||||
func updateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, group *group_model.Group) error {
|
||||
if form.Avatar != nil && form.Avatar.Filename != "" {
|
||||
fr, err := form.Avatar.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Avatar.Open: %w", err)
|
||||
}
|
||||
defer fr.Close()
|
||||
|
||||
if form.Avatar.Size > setting.Avatar.MaxFileSize {
|
||||
return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_is_too_big", form.Avatar.Size/1024, setting.Avatar.MaxFileSize/1024))
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(fr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.ReadAll: %w", err)
|
||||
}
|
||||
|
||||
st := typesniffer.DetectContentType(data)
|
||||
if !(st.IsImage() && !st.IsSvgImage()) {
|
||||
return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_not_a_image"))
|
||||
}
|
||||
if err = group_service.UploadAvatar(ctx, group, data); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
216
routers/web/group/team.go
Normal file
216
routers/web/group/team.go
Normal file
@ -0,0 +1,216 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
web_org "code.gitea.io/gitea/routers/web/org"
|
||||
shared_group "code.gitea.io/gitea/routers/web/shared/group"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
tplTeamEdit = "group/team/new"
|
||||
tplTeams = "group/team/teams"
|
||||
)
|
||||
|
||||
func MinUnitAccessMode(unitsMap map[unit_model.Type]perm.AccessMode) perm.AccessMode {
|
||||
res := perm.AccessModeNone
|
||||
for t, mode := range unitsMap {
|
||||
// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
|
||||
if t == unit_model.TypeExternalTracker || t == unit_model.TypeExternalWiki {
|
||||
continue
|
||||
}
|
||||
|
||||
// get the minial permission great than AccessModeNone except all are AccessModeNone
|
||||
if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
|
||||
res = mode
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func SearchTeamCandidates(ctx *context.Context) {
|
||||
teams, _, err := org_model.SearchTeam(ctx, &org_model.SearchTeamOptions{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
ListOptions: db.ListOptions{
|
||||
PageSize: setting.UI.MembersPagingNum,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("Unable to search teams", err)
|
||||
return
|
||||
}
|
||||
apiTeams, err := convert.ToTeams(ctx, teams, true)
|
||||
if err != nil {
|
||||
ctx.ServerError("Unable to search teams", err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]any{"data": apiTeams})
|
||||
}
|
||||
|
||||
func Teams(ctx *context.Context) {
|
||||
group := ctx.RepoGroup.Group
|
||||
ctx.Data["Title"] = group.Name
|
||||
ctx.Data["PageIsGroupTeams"] = true
|
||||
for _, t := range ctx.RepoGroup.Teams {
|
||||
if err := t.LoadMembers(ctx); err != nil {
|
||||
ctx.ServerError("GetMembers", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["Teams"] = ctx.RepoGroup.Teams
|
||||
|
||||
err := shared_group.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderOrgHeader", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplTeams)
|
||||
}
|
||||
|
||||
func EditTeam(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.RepoGroup.Group.Name
|
||||
ctx.Data["PageIsGroupTeams"] = true
|
||||
if err := ctx.RepoGroup.Team.LoadUnits(ctx); err != nil {
|
||||
ctx.ServerError("LoadUnits", err)
|
||||
return
|
||||
}
|
||||
if err := shared_group.LoadHeaderCount(ctx); err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["GroupTeam"] = ctx.RepoGroup.GroupTeam
|
||||
ctx.Data["Team"] = ctx.RepoGroup.Team
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
ctx.HTML(http.StatusOK, tplTeamEdit)
|
||||
}
|
||||
|
||||
func EditTeamPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateGroupTeamForm)
|
||||
t := ctx.RepoGroup.Team
|
||||
gt := ctx.RepoGroup.GroupTeam
|
||||
newAccessMode := perm.ParseAccessMode(form.Permission)
|
||||
unitPerms := web_org.GetUnitPerms(ctx.Req.Form, newAccessMode)
|
||||
if newAccessMode < perm.AccessModeAdmin {
|
||||
newAccessMode = MinUnitAccessMode(unitPerms)
|
||||
}
|
||||
ctx.Data["Title"] = ctx.RepoGroup.Group.Name
|
||||
ctx.Data["PageIsGroupTeams"] = true
|
||||
ctx.Data["Team"] = t
|
||||
ctx.Data["GroupTeam"] = gt
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
if !t.IsOwnerTeam() {
|
||||
if gt.AccessMode != newAccessMode {
|
||||
gt.AccessMode = newAccessMode
|
||||
}
|
||||
if gt.CanCreateIn != form.CanCreateRepoOrSubGroup {
|
||||
gt.CanCreateIn = form.CanCreateRepoOrSubGroup
|
||||
}
|
||||
} else {
|
||||
gt.CanCreateIn = true
|
||||
}
|
||||
units := make([]*group_model.RepoGroupUnit, 0, len(unitPerms))
|
||||
for tp, perm := range unitPerms {
|
||||
units = append(units, &group_model.RepoGroupUnit{
|
||||
GroupID: gt.GroupID,
|
||||
TeamID: t.ID,
|
||||
Type: tp,
|
||||
AccessMode: perm,
|
||||
})
|
||||
}
|
||||
gt.Units = units
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplTeamEdit)
|
||||
return
|
||||
}
|
||||
if gt.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 {
|
||||
ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamEdit, &form)
|
||||
return
|
||||
}
|
||||
if err := group_service.UpdateGroupTeam(ctx, gt); err != nil {
|
||||
ctx.ServerError("UpdateGroupTeam", err)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/teams/")
|
||||
}
|
||||
|
||||
func TeamAddPost(ctx *context.Context) {
|
||||
if !ctx.RepoGroup.IsGroupAdmin || !ctx.RepoGroup.IsOwner {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
group := ctx.RepoGroup.Group
|
||||
tname := strings.ToLower(ctx.FormTrim("tname"))
|
||||
t, err := org_model.GetTeam(ctx, group.OwnerID, tname)
|
||||
if err != nil {
|
||||
if org_model.IsErrTeamNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
|
||||
ctx.Redirect(ctx.RepoGroup.OrgGroupLink + "/teams")
|
||||
} else {
|
||||
ctx.ServerError("GetTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
has := group_model.HasTeamGroup(ctx, group.OwnerID, t.ID, group.ID)
|
||||
if has {
|
||||
ctx.Flash.Error(ctx.Tr("org.group.add_duplicate_team"))
|
||||
} else {
|
||||
parentGroup, err := group_model.FindGroupTeamByTeamID(ctx, group.ID, t.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindGroupTeamByTeamID", err)
|
||||
return
|
||||
}
|
||||
mode := t.AccessMode
|
||||
canCreateIn := t.CanCreateOrgRepo
|
||||
if parentGroup != nil {
|
||||
mode = max(t.AccessMode, parentGroup.AccessMode)
|
||||
canCreateIn = parentGroup.CanCreateIn || t.CanCreateOrgRepo
|
||||
}
|
||||
if err = group.LoadParentGroup(ctx); err != nil {
|
||||
ctx.ServerError("LoadParentGroup", err)
|
||||
return
|
||||
}
|
||||
err = group_model.AddTeamGroup(ctx, ctx.RepoGroup.Group.OwnerID, t.ID, ctx.RepoGroup.Group.ID, mode, canCreateIn)
|
||||
if err != nil {
|
||||
ctx.ServerError("AddTeamGroup", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Redirect(group.OrgGroupLink() + "/teams")
|
||||
}
|
||||
|
||||
func TeamRemove(ctx *context.Context) {
|
||||
if !ctx.RepoGroup.IsGroupAdmin || !ctx.RepoGroup.IsOwner {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
org := ctx.Org.Organization.ID
|
||||
group := ctx.RepoGroup.Group
|
||||
team, err := org_model.GetTeam(ctx, org, ctx.PathParam("team"))
|
||||
if err != nil {
|
||||
if org_model.IsErrTeamNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.ServerError("GetTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = group_model.RemoveTeamGroup(ctx, org, team.ID, group.ID); err != nil {
|
||||
ctx.ServerError("RemoveTeamGroup", err)
|
||||
return
|
||||
}
|
||||
ctx.JSONRedirect(ctx.RepoGroup.OrgGroupLink + "/teams")
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/routers/web/group"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -922,6 +923,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
|
||||
m.Group("/org", func() {
|
||||
m.Group("/{org}", func() {
|
||||
m.Get("/-/search_team_candidates", optExploreSignIn, group.SearchTeamCandidates)
|
||||
m.Get("/members", org.Members)
|
||||
}, context.OrgAssignment(context.OrgAssignmentOptions{}))
|
||||
}, optSignIn)
|
||||
@ -951,6 +953,31 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Get("/milestones/{team}", reqMilestonesDashboardPageEnabled, user.Milestones)
|
||||
m.Post("/members/action/{action}", org.MembersAction)
|
||||
m.Get("/teams", org.Teams)
|
||||
|
||||
m.Group("/groups", func() {
|
||||
m.Combo("/new").
|
||||
Get(group.NewGroup).
|
||||
Post(web.Bind(forms.CreateGroupForm{}), group.NewGroupPost)
|
||||
m.Group("/{group_id}", func() {
|
||||
m.Get("/teams", group.Teams)
|
||||
m.Post("/teams/add", group.TeamAddPost)
|
||||
m.Combo("/teams/{team}/edit").
|
||||
Get(group.EditTeam).
|
||||
Post(web.Bind(forms.CreateGroupTeamForm{}), group.EditTeamPost)
|
||||
m.Post("/teams/{team}/remove", group.TeamRemove)
|
||||
m.Group("/settings", func() {
|
||||
m.Combo("").
|
||||
Get(group.Settings).
|
||||
Post(web.Bind(forms.UpdateGroupSettingForm{}), group.SettingsPost)
|
||||
m.Post("/avatar", web.Bind(forms.AvatarForm{}), group.SettingsAvatar)
|
||||
m.Post("/avatar/delete", group.SettingsDeleteAvatar)
|
||||
}, ctxDataSet("PageIsGroupSettings", true))
|
||||
}, context.GroupAssignment(context.GroupAssignmentOptions{
|
||||
RequireMember: true,
|
||||
RequireOwner: false,
|
||||
RequireGroupAdmin: true,
|
||||
}))
|
||||
})
|
||||
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireMember: true, RequireTeamMember: true}))
|
||||
|
||||
m.Group("/{org}", func() {
|
||||
@ -1053,6 +1080,11 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
}, reqSignIn)
|
||||
// end "/org": most org routes
|
||||
|
||||
m.Group("/group", func() {
|
||||
m.Get("/search", group.SearchGroup)
|
||||
}, reqSignIn)
|
||||
// end "/group": search
|
||||
|
||||
m.Group("/repo", func() {
|
||||
m.Get("/create", repo.Create)
|
||||
m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost)
|
||||
@ -1126,6 +1158,18 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
|
||||
}, optSignIn, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{}))
|
||||
// end "/{username}/-": packages, projects, code
|
||||
m.Group("/{username}/groups", func() {
|
||||
m.Group("/{group_id}", func() {
|
||||
m.Get("", group.Home)
|
||||
}, context.GroupAssignment(context.GroupAssignmentOptions{}))
|
||||
}, optSignIn, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{}))
|
||||
|
||||
m.Group("/{username}/groups", func() {
|
||||
m.Post("/items/move", group.MoveGroupItem)
|
||||
}, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{
|
||||
RequireMember: true,
|
||||
}))
|
||||
// end "/{username}/groups"
|
||||
|
||||
repoDashFn := func() {
|
||||
m.Group("/migrate", func() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user