mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
- `internal/services`: typed Gitea workflows built on `gitea.dev/sdk`, independent of CLI flags and terminal I/O: issues, pull requests, reviews, comments, labels, milestones, releases and assets, repositories, branches, wiki, Actions (workflows, runs, jobs, logs, artifacts, secrets, variables), organizations, teams, webhooks, notifications, SSH keys, tracked time, admin users, search and status. - SDK types are mapped to tea's own output types, so `--json` stays a stable contract. Requests the SDK cannot express yet (e.g. pull list filters, multi-status run lists, org label archiving) go through small raw helpers, each marked `TODO(sdk)` with the missing SDK piece. - Targets Gitea 28 or newer: no version or API-schema probing. List helpers fill pages capped by `MAX_RESPONSE_ITEMS`. Nothing imports this package yet. Reviewed-on: https://gitea.com/gitea/tea/pulls/1125 Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
175 lines
6.6 KiB
Go
175 lines
6.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
gitea "gitea.dev/sdk"
|
|
"gitea.dev/tea/internal/transport"
|
|
)
|
|
|
|
// Team is an organization's access group and its permission policy.
|
|
type Team struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Organization string `json:"organization"`
|
|
Permission string `json:"permission"`
|
|
CanCreateRepo bool `json:"can_create_repo"`
|
|
AllRepositories bool `json:"all_repositories"`
|
|
Units map[string]string `json:"units"`
|
|
}
|
|
|
|
func teamFrom(team *gitea.Team) Team {
|
|
result := Team{
|
|
ID: team.ID, Name: team.Name, Description: team.Description, Permission: string(team.Permission),
|
|
CanCreateRepo: team.CanCreateOrgRepo, AllRepositories: team.IncludesAllRepositories, Units: team.UnitsMap,
|
|
}
|
|
if result.Units == nil {
|
|
result.Units = map[string]string{}
|
|
}
|
|
if team.Organization != nil {
|
|
result.Organization = organizationFrom(team.Organization).Name
|
|
}
|
|
return result
|
|
}
|
|
|
|
// TeamOptions preserves omitted fields when changing a team.
|
|
type TeamOptions struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
Permission *string `json:"permission,omitempty"`
|
|
CanCreateRepo *bool `json:"can_create_org_repo,omitempty"`
|
|
AllRepositories *bool `json:"includes_all_repositories,omitempty"`
|
|
Units map[string]string `json:"units_map,omitempty"`
|
|
}
|
|
|
|
// Teams implements team, member and repository access operations.
|
|
type Teams struct{ Client *transport.Client }
|
|
|
|
// List lists teams in one organization.
|
|
func (s Teams) List(ctx context.Context, org string, limit, page int) ([]Team, error) {
|
|
teams, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) ([]*gitea.Team, *gitea.Response, error) {
|
|
return c.Organizations.ListOrgTeams(ctx, org, gitea.ListTeamsOptions{ListOptions: listOptions(limit, page)})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return convertAll(teams, teamFrom), nil
|
|
}
|
|
|
|
// ByName indexes every team of an organization by lower-case name, which Gitea keeps unique.
|
|
func (s Teams) ByName(ctx context.Context, org string) (map[string]Team, error) {
|
|
teams, err := listAll(func(page int) ([]Team, error) { return s.List(ctx, org, 50, page) })
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[string]Team, len(teams))
|
|
for _, team := range teams {
|
|
result[strings.ToLower(team.Name)] = team
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// View retrieves a team and its current permissions.
|
|
func (s Teams) View(ctx context.Context, id int64) (Team, error) {
|
|
team, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Team, *gitea.Response, error) {
|
|
return c.Organizations.GetTeam(ctx, id)
|
|
})
|
|
if err != nil {
|
|
return Team{}, err
|
|
}
|
|
return teamFrom(team), nil
|
|
}
|
|
|
|
// Create creates an organization team with explicit initial permissions.
|
|
func (s Teams) Create(ctx context.Context, org string, opts TeamOptions) (Team, error) {
|
|
team, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Team, *gitea.Response, error) {
|
|
return c.Organizations.CreateTeam(ctx, org, gitea.CreateTeamOption{
|
|
Name: deref(opts.Name), Description: deref(opts.Description), Permission: gitea.AccessMode(deref(opts.Permission)),
|
|
CanCreateOrgRepo: deref(opts.CanCreateRepo), IncludesAllRepositories: deref(opts.AllRepositories), UnitsMap: opts.Units,
|
|
})
|
|
})
|
|
if err != nil {
|
|
return Team{}, err
|
|
}
|
|
return teamFrom(team), nil
|
|
}
|
|
|
|
// Edit preserves unit permissions when changing access to all repositories.
|
|
func (s Teams) Edit(ctx context.Context, id int64, opts TeamOptions) (Team, error) {
|
|
if opts.AllRepositories != nil && opts.Permission == nil {
|
|
current, err := s.View(ctx, id)
|
|
if err != nil {
|
|
return Team{}, err
|
|
}
|
|
if current.Permission == "owner" {
|
|
return Team{}, &CapabilityError{Feature: "owner-team repository policy editing", Reason: "owner teams already manage all organization repositories"}
|
|
}
|
|
opts.Permission = ¤t.Permission
|
|
if opts.Units == nil {
|
|
opts.Units = current.Units
|
|
}
|
|
}
|
|
var team gitea.Team
|
|
// TODO(sdk): EditTeamOption requires a name of at most 30 characters and a permission, and EditTeam should return the team.
|
|
if err := sendJSON(ctx, s.Client, http.MethodPatch, fmt.Sprintf("teams/%d", id), opts, &team); err != nil {
|
|
return Team{}, err
|
|
}
|
|
return teamFrom(&team), nil
|
|
}
|
|
|
|
// Delete deletes a team and its associated access grants.
|
|
func (s Teams) Delete(ctx context.Context, id int64) error {
|
|
return do(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Response, error) {
|
|
return c.Organizations.DeleteTeam(ctx, id)
|
|
})
|
|
}
|
|
|
|
// Members lists members of a team.
|
|
func (s Teams) Members(ctx context.Context, id int64, limit, page int) ([]OrganizationMember, error) {
|
|
users, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) ([]*gitea.User, *gitea.Response, error) {
|
|
return c.Organizations.ListTeamMembers(ctx, id, gitea.ListTeamMembersOptions{ListOptions: listOptions(limit, page)})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return convertAll(users, memberFrom), nil
|
|
}
|
|
|
|
// SetMember adds or removes one team membership.
|
|
func (s Teams) SetMember(ctx context.Context, id int64, username string, add bool) error {
|
|
return do(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Response, error) {
|
|
if add {
|
|
return c.Organizations.AddTeamMember(ctx, id, username)
|
|
}
|
|
return c.Organizations.RemoveTeamMember(ctx, id, username)
|
|
})
|
|
}
|
|
|
|
// Repositories lists repositories accessible to a team.
|
|
func (s Teams) Repositories(ctx context.Context, id int64, limit, page int) ([]Repository, error) {
|
|
repos, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) ([]*gitea.Repository, *gitea.Response, error) {
|
|
return c.Organizations.ListTeamRepositories(ctx, id, gitea.ListTeamRepositoriesOptions{ListOptions: listOptions(limit, page)})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return convertAll(repos, repositoryFrom), nil
|
|
}
|
|
|
|
// SetRepository adds or removes team access to a repository.
|
|
func (s Teams) SetRepository(ctx context.Context, id int64, repository string, add bool) error {
|
|
return repoDo(ctx, s.Client, repository, func(ctx context.Context, c *gitea.Client, owner, name string) (*gitea.Response, error) {
|
|
if add {
|
|
return c.Organizations.AddTeamRepository(ctx, id, owner, name)
|
|
}
|
|
return c.Organizations.RemoveTeamRepository(ctx, id, owner, name)
|
|
})
|
|
}
|