feat(orgs): auto-create default teams on org creation (#513) #708

Merged
jmiller merged 1 commits from feature/default-teams into dev 2026-06-28 08:45:37 +00:00
2 changed files with 87 additions and 1 deletions
+1
View File
@@ -3,6 +3,7 @@
## [Unreleased]
### Added
- Auto-create default teams on org creation: Developers (write), Reviewers (read), CI/CD (actions+packages) (#513)
- Branch protection delete allowlist: configurable per-user/team/deploy-key allowlist for deleting protected branches (#696)
- Workflow subdirectory discovery: workflows in subdirectories of `.mokogitea/workflows/` are now auto-discovered (#693)
- API token scope `read:licensing` / `write:licensing` for licensing endpoints (#697)
+86 -1
View File
@@ -323,6 +323,60 @@ func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.Us
}
// CreateOrganization creates record of a new organization.
// DefaultTeamSpec defines a team to auto-create when a new organization is created.
type DefaultTeamSpec struct {
Name string
Description string
AccessMode perm.AccessMode
IncludesAllRepositories bool
CanCreateOrgRepo bool
Units map[unit.Type]perm.AccessMode
}
// DefaultOrgTeams is the list of teams created for every new organization
// (in addition to the mandatory Owners team). Override in tests or via init.
var DefaultOrgTeams = []DefaultTeamSpec{
{
Name: "Developers",
Description: "Members with write access to code, issues, and pull requests",
AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: true,
Units: map[unit.Type]perm.AccessMode{
unit.TypeCode: perm.AccessModeWrite,
unit.TypeIssues: perm.AccessModeWrite,
unit.TypePullRequests: perm.AccessModeWrite,
unit.TypeReleases: perm.AccessModeRead,
unit.TypeWiki: perm.AccessModeWrite,
unit.TypeProjects: perm.AccessModeWrite,
},
},
{
Name: "Reviewers",
Description: "Members with read access for code review",
AccessMode: perm.AccessModeRead,
IncludesAllRepositories: true,
Units: map[unit.Type]perm.AccessMode{
unit.TypeCode: perm.AccessModeRead,
unit.TypeIssues: perm.AccessModeRead,
unit.TypePullRequests: perm.AccessModeRead,
unit.TypeReleases: perm.AccessModeRead,
unit.TypeWiki: perm.AccessModeRead,
},
},
{
Name: "CI/CD",
Description: "Members with write access to actions and packages",
AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: true,
Units: map[unit.Type]perm.AccessMode{
unit.TypeCode: perm.AccessModeRead,
unit.TypeActions: perm.AccessModeWrite,
unit.TypePackages: perm.AccessModeWrite,
unit.TypeReleases: perm.AccessModeWrite,
},
},
}
func CreateOrganization(ctx context.Context, org *Organization, owner *user_model.User) (err error) {
if !owner.CanCreateOrganization() {
return ErrUserNotAllowedCreateOrg{}
@@ -348,7 +402,7 @@ func CreateOrganization(ctx context.Context, org *Organization, owner *user_mode
}
org.UseCustomAvatar = true
org.MaxRepoCreation = -1
org.NumTeams = 1
org.NumTeams = 1 + len(DefaultOrgTeams)
org.NumMembers = 1
org.Type = user_model.UserTypeOrganization
@@ -413,6 +467,37 @@ func CreateOrganization(ctx context.Context, org *Organization, owner *user_mode
}); err != nil {
return fmt.Errorf("insert team-user relation: %w", err)
}
for _, spec := range DefaultOrgTeams {
dt := &Team{
OrgID: org.ID,
LowerName: strings.ToLower(spec.Name),
Name: spec.Name,
Description: spec.Description,
AccessMode: spec.AccessMode,
IncludesAllRepositories: spec.IncludesAllRepositories,
CanCreateOrgRepo: spec.CanCreateOrgRepo,
}
if err = db.Insert(ctx, dt); err != nil {
return fmt.Errorf("insert default team %q: %w", spec.Name, err)
}
dtUnits := make([]TeamUnit, 0, len(spec.Units))
for tp, am := range spec.Units {
dtUnits = append(dtUnits, TeamUnit{
OrgID: org.ID,
TeamID: dt.ID,
Type: tp,
AccessMode: am,
})
}
if len(dtUnits) > 0 {
if err = db.Insert(ctx, &dtUnits); err != nil {
return fmt.Errorf("insert default team %q units: %w", spec.Name, err)
}
}
}
return nil
})
}