Files
Gogs/internal/route/api/v1/adapters.go
ᴊᴏᴇ ᴄʜᴇɴ a1fa62b270 all: decouple API types from go-gogs-client SDK (#8171)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-10 10:56:17 -05:00

304 lines
7.7 KiB
Go

package v1
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/gogs/git-module"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/database"
"gogs.io/gogs/internal/markup"
"gogs.io/gogs/internal/route/api/v1/types"
)
// toAllowedPageSize makes sure page size is in allowed range.
func toAllowedPageSize(size int) int {
if size <= 0 {
size = 10
} else if size > conf.API.MaxResponseItems {
size = conf.API.MaxResponseItems
}
return size
}
// toUser converts a database user to an API user with the full name sanitized
// for safe HTML rendering.
func toUser(u *database.User) *types.User {
return &types.User{
ID: u.ID,
UserName: u.Name,
Login: u.Name,
FullName: markup.Sanitize(u.FullName),
Email: u.Email,
AvatarURL: u.AvatarURL(),
}
}
func toUserEmail(email *database.EmailAddress) *types.UserEmail {
return &types.UserEmail{
Email: email.Email,
Verified: email.IsActivated,
Primary: email.IsPrimary,
}
}
func toBranch(b *database.Branch, c *git.Commit) *types.RepositoryBranch {
return &types.RepositoryBranch{
Name: b.Name,
Commit: toPayloadCommit(c),
}
}
func toTag(b *database.Tag, c *git.Commit) *tag {
return &tag{
Name: b.Name,
Commit: toPayloadCommit(c),
}
}
func toPayloadCommit(c *git.Commit) *types.WebhookPayloadCommit {
authorUsername := ""
author, err := database.Handle.Users().GetByEmail(context.TODO(), c.Author.Email)
if err == nil {
authorUsername = author.Name
}
committerUsername := ""
committer, err := database.Handle.Users().GetByEmail(context.TODO(), c.Committer.Email)
if err == nil {
committerUsername = committer.Name
}
return &types.WebhookPayloadCommit{
ID: c.ID.String(),
Message: c.Message,
URL: "Not implemented",
Author: &types.WebhookPayloadUser{
Name: c.Author.Name,
Email: c.Author.Email,
UserName: authorUsername,
},
Committer: &types.WebhookPayloadUser{
Name: c.Committer.Name,
Email: c.Committer.Email,
UserName: committerUsername,
},
Timestamp: c.Author.When,
}
}
func toUserPublicKey(apiLink string, key *database.PublicKey) *types.UserPublicKey {
return &types.UserPublicKey{
ID: key.ID,
Key: key.Content,
URL: apiLink + strconv.FormatInt(key.ID, 10),
Title: key.Name,
Created: key.Created,
}
}
func toRepositoryHook(repoLink string, w *database.Webhook) *types.RepositoryHook {
config := map[string]string{
"url": w.URL,
"content_type": w.ContentType.Name(),
}
if w.HookTaskType == database.SLACK {
s := w.SlackMeta()
config["channel"] = s.Channel
config["username"] = s.Username
config["icon_url"] = s.IconURL
config["color"] = s.Color
}
return &types.RepositoryHook{
ID: w.ID,
Type: w.HookTaskType.Name(),
URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID),
Active: w.IsActive,
Config: config,
Events: w.EventsArray(),
Updated: w.Updated,
Created: w.Created,
}
}
func toDeployKey(apiLink string, key *database.DeployKey) *types.RepositoryDeployKey {
return &types.RepositoryDeployKey{
ID: key.ID,
Key: key.Content,
URL: apiLink + strconv.FormatInt(key.ID, 10),
Title: key.Name,
Created: key.Created,
ReadOnly: true, // All deploy keys are read-only.
}
}
func toOrganization(org *database.User) *types.Organization {
return &types.Organization{
ID: org.ID,
AvatarURL: org.AvatarURL(),
UserName: org.Name,
FullName: org.FullName,
Description: org.Description,
Website: org.Website,
Location: org.Location,
}
}
func toOrganizationTeam(team *database.Team) *types.OrganizationTeam {
return &types.OrganizationTeam{
ID: team.ID,
Name: team.Name,
Description: team.Description,
Permission: team.Authorize.String(),
}
}
func toIssueLabel(l *database.Label) *types.IssueLabel {
return &types.IssueLabel{
ID: l.ID,
Name: l.Name,
Color: strings.TrimLeft(l.Color, "#"),
}
}
func issueState(isClosed bool) types.IssueStateType {
if isClosed {
return types.IssueStateClosed
}
return types.IssueStateOpen
}
// toIssue converts a database issue to an API issue.
// It assumes the following fields have been assigned with valid values:
// Required - Poster, Labels
// Optional - Milestone, Assignee, PullRequest
func toIssue(issue *database.Issue) *types.Issue {
labels := make([]*types.IssueLabel, len(issue.Labels))
for i := range issue.Labels {
labels[i] = toIssueLabel(issue.Labels[i])
}
apiIssue := &types.Issue{
ID: issue.ID,
Index: issue.Index,
Poster: toUser(issue.Poster),
Title: issue.Title,
Body: issue.Content,
Labels: labels,
State: issueState(issue.IsClosed),
Comments: issue.NumComments,
Created: issue.Created,
Updated: issue.Updated,
}
if issue.Milestone != nil {
apiIssue.Milestone = toIssueMilestone(issue.Milestone)
}
if issue.Assignee != nil {
apiIssue.Assignee = toUser(issue.Assignee)
}
if issue.IsPull {
apiIssue.PullRequest = &types.PullRequestMeta{
HasMerged: issue.PullRequest.HasMerged,
}
if issue.PullRequest.HasMerged {
apiIssue.PullRequest.Merged = &issue.PullRequest.Merged
}
}
return apiIssue
}
func toIssueComment(c *database.Comment) *types.IssueComment {
return &types.IssueComment{
ID: c.ID,
HTMLURL: c.HTMLURL(),
Poster: toUser(c.Poster),
Body: c.Content,
Created: c.Created,
Updated: c.Updated,
}
}
func toIssueMilestone(m *database.Milestone) *types.IssueMilestone {
ms := &types.IssueMilestone{
ID: m.ID,
State: issueState(m.IsClosed),
Title: m.Name,
Description: m.Content,
OpenIssues: m.NumOpenIssues,
ClosedIssues: m.NumClosedIssues,
}
if m.IsClosed {
ms.Closed = &m.ClosedDate
}
if m.Deadline.Year() < 9999 {
ms.Deadline = &m.Deadline
}
return ms
}
// toRelease converts a database release to an API release.
// It assumes the Publisher field has been assigned.
func toRelease(r *database.Release) *types.RepositoryRelease {
return &types.RepositoryRelease{
ID: r.ID,
TagName: r.TagName,
TargetCommitish: r.Target,
Name: r.Title,
Body: r.Note,
Draft: r.IsDraft,
Prerelease: r.IsPrerelease,
Author: toUser(r.Publisher),
Created: r.Created,
}
}
func toRepositoryCollaborator(c *database.Collaborator) *types.RepositoryCollaborator {
return &types.RepositoryCollaborator{
User: toUser(c.User),
Permissions: types.RepositoryPermission{
Admin: c.Collaboration.Mode >= database.AccessModeAdmin,
Push: c.Collaboration.Mode >= database.AccessModeWrite,
Pull: c.Collaboration.Mode >= database.AccessModeRead,
},
}
}
// toRepository converts a database repository to an API repository.
// It assumes the Owner field has been loaded on the repo.
func toRepository(repo *database.Repository, perm *types.RepositoryPermission) *types.Repository {
cloneLink := repo.CloneLink()
apiRepo := &types.Repository{
ID: repo.ID,
Owner: toUser(repo.Owner),
Name: repo.Name,
FullName: repo.FullName(),
Description: repo.Description,
Private: repo.IsPrivate,
Fork: repo.IsFork,
Empty: repo.IsBare,
Mirror: repo.IsMirror,
Size: repo.Size,
HTMLURL: repo.HTMLURL(),
SSHURL: cloneLink.SSH,
CloneURL: cloneLink.HTTPS,
Website: repo.Website,
Stars: repo.NumStars,
Forks: repo.NumForks,
Watchers: repo.NumWatches,
OpenIssues: repo.NumOpenIssues,
DefaultBranch: repo.DefaultBranch,
Created: repo.Created,
Updated: repo.Updated,
Permissions: perm,
}
if repo.IsFork {
p := &types.RepositoryPermission{Pull: true}
apiRepo.Parent = toRepository(repo.BaseRepo, p)
}
return apiRepo
}