0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-14 13:08:11 +02:00

update group model

- add `SortOrder` field to `Group` struct (to allow drag-and-drop reordering to persist across refreshes)
- add method to return `/org/` prefixed url to group
- refactor `FindGroupsByCond` to take `FindGroupOptions` as an argument to be chained to the provided condition
- ensure that found groups are sorted by their `SortOrder` field
- modify `LoadParentGroup` method to immediately return nil if `ParentGroupID` is 0
- add permission-checking utility methods `CanAccess`, `IsOwnedBy`,`CanCreateIn` and `IsAdminOf`
- add `ShortName` method that returns an abbreviated group name
- add `GetGroupByRepoID`
- create `CountGroups` function
- create `UpdateGroupOwnerName` helper function to be called when a user changes their username
- refactor `MoveGroup` to allow moving a group to the "root" level (`ParentGroupID` = 0)
This commit is contained in:
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 2025-01-09 16:45:30 -05:00
parent bc4e4840b2
commit d8cedf18d8
No known key found for this signature in database
GPG Key ID: 924A5F6AF051E87C

View File

@ -1,18 +1,21 @@
package group package group
import ( import (
"context"
"fmt"
"net/url"
"slices"
"strconv"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"context"
"errors"
"fmt"
"net/url"
"strconv"
"xorm.io/builder" "xorm.io/builder"
) )
@ -23,16 +26,17 @@ type Group struct {
OwnerName string OwnerName string
Owner *user_model.User `xorm:"-"` Owner *user_model.User `xorm:"-"`
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"INDEX NOT NULL"` Name string `xorm:"TEXT INDEX NOT NULL"`
FullName string `xorm:"TEXT"` // displayed in places like navigation menus
Description string `xorm:"TEXT"` Description string `xorm:"TEXT"`
IsPrivate bool IsPrivate bool
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"` Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
Avatar string `xorm:"VARCHAR(64)"` Avatar string `xorm:"VARCHAR(64)"`
ParentGroupID int64 `xorm:"INDEX DEFAULT NULL"` ParentGroupID int64 `xorm:"DEFAULT NULL"`
ParentGroup *Group `xorm:"-"` ParentGroup *Group `xorm:"-"`
Subgroups GroupList `xorm:"-"` Subgroups GroupList `xorm:"-"`
SortOrder int `xorm:"INDEX"`
} }
// GroupLink returns the link to this group // GroupLink returns the link to this group
@ -40,6 +44,10 @@ func (g *Group) GroupLink() string {
return setting.AppSubURL + "/" + url.PathEscape(g.OwnerName) + "/groups/" + strconv.FormatInt(g.ID, 10) return setting.AppSubURL + "/" + url.PathEscape(g.OwnerName) + "/groups/" + strconv.FormatInt(g.ID, 10)
} }
func (g *Group) OrgGroupLink() string {
return setting.AppSubURL + "/org/" + url.PathEscape(g.OwnerName) + "/groups/" + strconv.FormatInt(g.ID, 10)
}
func (Group) TableName() string { return "repo_group" } func (Group) TableName() string { return "repo_group" }
func init() { func init() {
@ -58,10 +66,15 @@ func (g *Group) doLoadSubgroups(ctx context.Context, recursive bool, cond builde
return nil return nil
} }
var err error var err error
g.Subgroups, err = FindGroupsByCond(ctx, cond, g.ID) g.Subgroups, err = FindGroupsByCond(ctx, &FindGroupsOptions{
ParentGroupID: g.ID,
}, cond)
if err != nil { if err != nil {
return err return err
} }
slices.SortStableFunc(g.Subgroups, func(a, b *Group) int {
return a.SortOrder - b.SortOrder
})
if recursive { if recursive {
for _, group := range g.Subgroups { for _, group := range g.Subgroups {
err = group.doLoadSubgroups(ctx, recursive, cond, currentLevel+1) err = group.doLoadSubgroups(ctx, recursive, cond, currentLevel+1)
@ -96,6 +109,9 @@ func (g *Group) LoadParentGroup(ctx context.Context) error {
if g.ParentGroup != nil { if g.ParentGroup != nil {
return nil return nil
} }
if g.ParentGroupID == 0 {
return nil
}
parentGroup, err := GetGroupByID(ctx, g.ParentGroupID) parentGroup, err := GetGroupByID(ctx, g.ParentGroupID)
if err != nil { if err != nil {
return err return err
@ -113,7 +129,46 @@ func (g *Group) LoadOwner(ctx context.Context) error {
return err return err
} }
func (g *Group) GetGroupByID(ctx context.Context, id int64) (*Group, error) { func (g *Group) CanAccess(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where(UserOrgTeamPermCond("id", userID, perm.AccessModeRead)).Table("repo_group").Exist()
}
func (g *Group) IsOwnedBy(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where("team_user.uid = ?", userID).
Join("INNER", "team_user", "team_user.team_id = group_team.team_id").
And("group_team.access_mode = ?", perm.AccessModeOwner).
And("group_team.group_id = ?", g.ID).
Table("group_team").
Exist()
}
func (g *Group) CanCreateIn(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where("team_user.uid = ?", userID).
Join("INNER", "team_user", "team_user.team_id = group_team.team_id").
And("group_team.group_id = ?", g.ID).
And("group_team.can_create_in = ?", true).
Table("group_team").
Exist()
}
func (g *Group) IsAdminOf(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where("team_user.uid = ?", userID).
Join("INNER", "team_user", "team_user.team_id = group_team.team_id").
And("group_team.group_id = ?", g.ID).
And("group_team.access_mode >= ?", perm.AccessModeAdmin).
Table("group_team").
Exist()
}
func (g *Group) ShortName(length int) string {
return util.EllipsisDisplayString(g.Name, length)
}
func GetGroupByID(ctx context.Context, id int64) (*Group, error) {
group := new(Group) group := new(Group)
has, err := db.GetEngine(ctx).ID(id).Get(group) has, err := db.GetEngine(ctx).ID(id).Get(group)
@ -125,10 +180,32 @@ func (g *Group) GetGroupByID(ctx context.Context, id int64) (*Group, error) {
return group, nil return group, nil
} }
func GetGroupByRepoID(ctx context.Context, repoID int64) (*Group, error) {
group := new(Group)
_, err := db.GetEngine(ctx).
In("id", builder.
Select("group_id").
From("repo").
Where(builder.Eq{"id": repoID})).
Get(group)
return group, err
}
func ParentGroupCondByRepoID(ctx context.Context, repoID int64, idStr string) builder.Cond {
g, err := GetGroupByRepoID(ctx, repoID)
if err != nil {
return builder.In(idStr)
}
return ParentGroupCond(idStr, g.ID)
}
type FindGroupsOptions struct { type FindGroupsOptions struct {
db.ListOptions db.ListOptions
OwnerID int64 OwnerID int64
ParentGroupID int64 ParentGroupID int64
CanCreateIn optional.Option[bool]
ActorID int64
Name string
} }
func (opts FindGroupsOptions) ToConds() builder.Cond { func (opts FindGroupsOptions) ToConds() builder.Cond {
@ -160,23 +237,47 @@ func FindGroups(ctx context.Context, opts *FindGroupsOptions) (GroupList, error)
if opts.Page > 0 { if opts.Page > 0 {
sess = db.SetSessionPagination(sess, opts) sess = db.SetSessionPagination(sess, opts)
} }
groups := make([]*Group, 0, 10) groups := make([]*Group, 0, 10)
return groups, sess. return groups, sess.
Asc("repo_group.id"). Asc("repo_group.sort_order").
Find(&groups) Find(&groups)
} }
func FindGroupsByCond(ctx context.Context, cond builder.Cond, parentGroupID int64) (GroupList, error) { func findGroupsByCond(ctx context.Context, opts *FindGroupsOptions, cond builder.Cond) db.Engine {
if parentGroupID > 0 { if opts.Page <= 0 {
cond = cond.And(builder.Eq{"repo_group.id": parentGroupID}) opts.Page = 1
} else {
cond = cond.And(builder.IsNull{"repo_group.id"})
} }
sess := db.GetEngine(ctx).Where(cond)
groups := make([]*Group, 0) sess := db.GetEngine(ctx).Where(cond.And(opts.ToConds()))
return groups, sess. if opts.PageSize > 0 {
Asc("repo_group.id"). sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
Find(&groups) }
return sess.Asc("sort_order")
}
func FindGroupsByCond(ctx context.Context, opts *FindGroupsOptions, cond builder.Cond) (GroupList, error) {
defaultSize := 50
if opts.PageSize > 0 {
defaultSize = opts.PageSize
}
sess := findGroupsByCond(ctx, opts, cond)
groups := make([]*Group, 0, defaultSize)
if err := sess.Find(&groups); err != nil {
return nil, err
}
return groups, nil
}
func CountGroups(ctx context.Context, opts *FindGroupsOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.ToConds()).Count(new(Group))
}
func UpdateGroupOwnerName(ctx context.Context, oldUser, newUser string) error {
if _, err := db.GetEngine(ctx).Exec("UPDATE `repo_group` SET owner_name=? WHERE owner_name=?", newUser, oldUser); err != nil {
return fmt.Errorf("change group owner name: %w", err)
}
return nil
} }
// GetParentGroupChain returns a slice containing a group and its ancestors // GetParentGroupChain returns a slice containing a group and its ancestors
@ -225,20 +326,21 @@ func ParentGroupCond(idStr string, groupID int64) builder.Cond {
func MoveGroup(ctx context.Context, group *Group, newParent int64, newSortOrder int) error { func MoveGroup(ctx context.Context, group *Group, newParent int64, newSortOrder int) error {
sess := db.GetEngine(ctx) sess := db.GetEngine(ctx)
ng, err := GetGroupByID(ctx, newParent) ng, err := GetGroupByID(ctx, newParent)
if err != nil { if !IsErrGroupNotExist(err) {
return err return err
} }
if ng.OwnerID != group.OwnerID { if ng != nil {
return fmt.Errorf("group[%d]'s ownerID is not equal to new paretn group[%d]'s owner ID", group.ID, ng.ID) if ng.OwnerID != group.OwnerID {
return fmt.Errorf("group[%d]'s ownerID is not equal to new parent group[%d]'s owner ID", group.ID, ng.ID)
}
} }
group.ParentGroupID = newParent group.ParentGroupID = newParent
group.SortOrder = newSortOrder group.SortOrder = newSortOrder
if _, err = sess.Table(group.TableName()). if _, err = sess.Table(group.TableName()).
Where("id = ?", group.ID). ID(group.ID).
MustCols("parent_group_id"). AllCols().
Update(group, &Group{ Update(group); err != nil {
ID: group.ID,
}); err != nil {
return err return err
} }
if group.ParentGroup != nil && newParent != 0 { if group.ParentGroup != nil && newParent != 0 {