Skip to content

Commit 590879e

Browse files
authored
Merge pull request #433 from chaitin/feat-gitea-repos
feat: 实现 gitea 的仓库列表接口
2 parents cdb8197 + e6b0670 commit 590879e

8 files changed

Lines changed: 107 additions & 61 deletions

File tree

backend/biz/git/usecase/identity.go

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,40 +64,33 @@ func (u *GitIdentityUsecase) Get(ctx context.Context, uid uuid.UUID, id uuid.UUI
6464
}
6565
gi := cvt.From(identity, &domain.GitIdentity{})
6666

67-
// PAT 模式:获取 GitHub 授权仓库列表
68-
if identity.Platform == consts.GitPlatformGithub && identity.AccessToken != "" && u.gh != nil {
69-
repos, err := u.gh.GetAuthorizedRepositories(ctx, identity.AccessToken)
70-
if err != nil {
71-
u.logger.WarnContext(ctx, "failed to get github authorized repositories", "error", err, "identity_id", id)
72-
} else {
73-
gi.AuthorizedRepositories = make([]domain.AuthRepository, 0, len(repos))
74-
for _, r := range repos {
75-
gi.AuthorizedRepositories = append(gi.AuthorizedRepositories, domain.AuthRepository{
76-
FullName: r.FullName,
77-
URL: r.URL,
78-
Description: r.Description,
79-
})
80-
}
81-
}
67+
// PAT 模式:获取授权仓库列表
68+
if identity.AccessToken == "" {
69+
return gi, nil
8270
}
83-
84-
// GitLab PAT 模式:获取授权仓库列表
85-
if identity.Platform == consts.GitPlatformGitLab && identity.AccessToken != "" {
86-
glClient := gitlab.NewGitlab(identity.BaseURL, identity.AccessToken, u.logger)
87-
if glClient != nil {
88-
glRepos, err := glClient.GetAuthorizedRepositories(ctx, identity.AccessToken, false)
89-
if err != nil {
90-
u.logger.WarnContext(ctx, "failed to get gitlab repositories", "error", err, "identity_id", id)
91-
} else {
92-
gi.AuthorizedRepositories = make([]domain.AuthRepository, 0, len(glRepos))
93-
for _, r := range glRepos {
94-
gi.AuthorizedRepositories = append(gi.AuthorizedRepositories, domain.AuthRepository{
95-
FullName: r.FullName,
96-
URL: r.URL,
97-
Description: r.Description,
98-
})
99-
}
100-
}
71+
var client domain.GitPlatformClient[domain.AuthRepository]
72+
switch identity.Platform {
73+
case consts.GitPlatformGithub:
74+
client = u.gh
75+
case consts.GitPlatformGitLab:
76+
client = gitlab.NewGitlab(identity.BaseURL, identity.AccessToken, u.logger)
77+
case consts.GitPlatformGitea:
78+
client = gitea.NewGitea(u.logger, identity.BaseURL)
79+
default:
80+
// 该平台不支持获取授权仓库列表
81+
return gi, nil
82+
}
83+
repos, err := client.GetAuthorizedRepositories(ctx, identity.AccessToken)
84+
if err != nil {
85+
u.logger.WarnContext(ctx, "failed to get authorized repositories", "error", err, "platform", identity.Platform, "identity_id", id)
86+
} else {
87+
gi.AuthorizedRepositories = make([]domain.AuthRepository, 0, len(repos))
88+
for _, r := range repos {
89+
gi.AuthorizedRepositories = append(gi.AuthorizedRepositories, domain.AuthRepository{
90+
FullName: r.FullName,
91+
URL: r.URL,
92+
Description: r.Description,
93+
})
10194
}
10295
}
10396

backend/biz/project/usecase/project.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func NewProjectUsecase(i *do.Injector) (domain.ProjectUsecase, error) {
6363
cfg: cfg,
6464
gh: github.NewGithub(logger),
6565
gte: gitee.NewGitee(cfg.Gitee.BaseURL, logger),
66-
gta: gitea.NewGitea(logger),
66+
gta: gitea.NewGitea(logger, cfg.GetGiteaBaseURL()),
6767
glDomestic: glDomestic,
6868
glInternational: glInternational,
6969
tokenCache: cache.New(repoTokenCacheTTL, 10*time.Minute),

backend/domain/gitidentity.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ type AuthRepository struct {
3838
Description string `json:"description"`
3939
}
4040

41+
// GitPlatformClient 各 Git 平台的统一客户端接口
42+
type GitPlatformClient[T any] interface {
43+
// GetAuthorizedRepositories 获取 PAT 可访问的仓库列表
44+
GetAuthorizedRepositories(ctx context.Context, token string) ([]T, error)
45+
}
46+
47+
// AuthRepositoryInterface 用于约束平台客户端返回的仓库类型
48+
type AuthRepositoryInterface interface {
49+
~struct {
50+
FullName string
51+
URL string
52+
Description string
53+
}
54+
}
55+
4156
// GitIdentity Git 身份认证
4257
type GitIdentity struct {
4358
ID uuid.UUID `json:"id"`

backend/pkg/git/gitea/gitea.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ import (
1313

1414
// Gitea 客户端
1515
type Gitea struct {
16-
logger *slog.Logger
16+
logger *slog.Logger
17+
baseURL string
1718
}
1819

1920
// NewGitea 创建 Gitea 客户端
20-
func NewGitea(logger *slog.Logger) *Gitea {
21+
func NewGitea(logger *slog.Logger, baseURL string) *Gitea {
22+
if baseURL == "" {
23+
baseURL = "https://gitea.com"
24+
}
25+
baseURL = strings.TrimSuffix(baseURL, "/")
2126
return &Gitea{
22-
logger: logger.With("module", "gitea"),
27+
logger: logger.With("module", "gitea"),
28+
baseURL: baseURL,
2329
}
2430
}
2531

backend/pkg/git/gitea/operation.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"sort"
1313
"strings"
1414
"time"
15+
16+
domainpkg "github.com/chaitin/MonkeyCode/backend/domain"
1517
)
1618

1719
// giteaAPIGet 通用 Gitea API GET 请求
@@ -285,6 +287,44 @@ func ListBranches(ctx context.Context, baseURL, token, owner, repo string, page,
285287
return result, nil
286288
}
287289

290+
// GetAuthorizedRepositories 获取 token 可访问的仓库列表
291+
func (g *Gitea) GetAuthorizedRepositories(ctx context.Context, token string) ([]domainpkg.AuthRepository, error) {
292+
apiURL := fmt.Sprintf("%s/api/v1/user/repos?limit=100", g.baseURL)
293+
var all []domainpkg.AuthRepository
294+
page := 1
295+
for {
296+
pagedURL := fmt.Sprintf("%s&page=%d", apiURL, page)
297+
body, err := giteaAPIGet(ctx, pagedURL, token)
298+
if err != nil {
299+
return nil, fmt.Errorf("list repos: %w", err)
300+
}
301+
type giteaRepo struct {
302+
FullName string `json:"full_name"`
303+
CloneURL string `json:"clone_url"`
304+
Description string `json:"description"`
305+
}
306+
var repos []giteaRepo
307+
if err := json.Unmarshal(body, &repos); err != nil {
308+
return nil, fmt.Errorf("unmarshal repos: %w", err)
309+
}
310+
if len(repos) == 0 {
311+
break
312+
}
313+
for _, r := range repos {
314+
all = append(all, domainpkg.AuthRepository{
315+
FullName: r.FullName,
316+
URL: r.CloneURL,
317+
Description: r.Description,
318+
})
319+
}
320+
if len(repos) < 100 {
321+
break
322+
}
323+
page++
324+
}
325+
return all, nil
326+
}
327+
288328
func giteaTypeToMode(entryType string) int {
289329
switch entryType {
290330
case "tree":

backend/pkg/git/github/github.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@ import (
1111

1212
"github.com/google/go-github/v74/github"
1313
"golang.org/x/oauth2"
14+
15+
domainpkg "github.com/chaitin/MonkeyCode/backend/domain"
1416
)
1517

1618
// Github GitHub 客户端(PAT 模式)
1719
type Github struct {
18-
logger *slog.Logger
20+
logger *slog.Logger
21+
baseURL string
1922
}
2023

2124
// NewGithub 创建 GitHub 客户端
2225
func NewGithub(logger *slog.Logger) *Github {
2326
return &Github{
24-
logger: logger.With("module", "github"),
27+
logger: logger.With("module", "github"),
28+
baseURL: "https://github.com",
2529
}
2630
}
2731

@@ -139,20 +143,20 @@ func (g *Github) GetRepoDescription(ctx context.Context, token, owner, repo stri
139143
}
140144

141145
// GetAuthorizedRepositories 获取 PAT 可访问的仓库列表
142-
func (g *Github) GetAuthorizedRepositories(ctx context.Context, token string) ([]AuthRepository, error) {
146+
func (g *Github) GetAuthorizedRepositories(ctx context.Context, token string) ([]domainpkg.AuthRepository, error) {
143147
client := newClientWithToken(ctx, token)
144148
opts := &github.RepositoryListByAuthenticatedUserOptions{
145149
ListOptions: github.ListOptions{PerPage: 100},
146150
Sort: "updated",
147151
}
148-
var all []AuthRepository
152+
var all []domainpkg.AuthRepository
149153
for {
150154
repos, resp, err := client.Repositories.ListByAuthenticatedUser(ctx, opts)
151155
if err != nil {
152156
return nil, fmt.Errorf("list repos: %w", err)
153157
}
154158
for _, r := range repos {
155-
all = append(all, AuthRepository{
159+
all = append(all, domainpkg.AuthRepository{
156160
FullName: r.GetFullName(),
157161
URL: r.GetCloneURL(),
158162
Description: r.GetDescription(),
@@ -166,13 +170,6 @@ func (g *Github) GetAuthorizedRepositories(ctx context.Context, token string) ([
166170
return all, nil
167171
}
168172

169-
// AuthRepository 授权仓库信息
170-
type AuthRepository struct {
171-
FullName string `json:"full_name"`
172-
URL string `json:"url"`
173-
Description string `json:"description"`
174-
}
175-
176173
// DeleteWebhookByURL 根据 webhook URL 精确匹配删除 GitHub 仓库上的 webhook
177174
func (g *Github) DeleteWebhookByURL(ctx context.Context, token, owner, repo, webhookURL string) error {
178175
client := newClientWithToken(ctx, token)

backend/pkg/git/gitlab/operation.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"strings"
1212

1313
gitlab "gitlab.com/gitlab-org/api/client-go"
14+
15+
domainpkg "github.com/chaitin/MonkeyCode/backend/domain"
1416
)
1517

1618
// ParseProjectPath 从仓库 URL 解析出项目路径(path_with_namespace)
@@ -48,15 +50,15 @@ func (g *Gitlab) GetRepoDescription(ctx context.Context, token, repoURL string,
4850
}
4951

5052
// GetAuthorizedRepositories 获取 token 可访问的仓库列表
51-
func (g *Gitlab) GetAuthorizedRepositories(ctx context.Context, token string, isOAuth bool) ([]AuthRepository, error) {
52-
client, err := g.newClientWithToken(token, isOAuth)
53+
func (g *Gitlab) GetAuthorizedRepositories(ctx context.Context, token string) ([]domainpkg.AuthRepository, error) {
54+
client, err := g.newClientWithToken(token, false)
5355
if err != nil {
5456
return nil, fmt.Errorf("new client: %w", err)
5557
}
5658
return g.listProjects(ctx, client)
5759
}
5860

59-
func (g *Gitlab) listProjects(ctx context.Context, client *gitlab.Client) ([]AuthRepository, error) {
61+
func (g *Gitlab) listProjects(ctx context.Context, client *gitlab.Client) ([]domainpkg.AuthRepository, error) {
6062
opt := &gitlab.ListProjectsOptions{
6163
ListOptions: gitlab.ListOptions{PerPage: 100},
6264
Membership: ptr(true),
@@ -73,13 +75,13 @@ func (g *Gitlab) listProjects(ctx context.Context, client *gitlab.Client) ([]Aut
7375
}
7476
opt.Page = resp.NextPage
7577
}
76-
out := make([]AuthRepository, 0, len(all))
78+
out := make([]domainpkg.AuthRepository, 0, len(all))
7779
for _, p := range all {
7880
cloneURL := p.HTTPURLToRepo
7981
if cloneURL == "" {
8082
cloneURL = p.SSHURLToRepo
8183
}
82-
out = append(out, AuthRepository{
84+
out = append(out, domainpkg.AuthRepository{
8385
FullName: p.PathWithNamespace,
8486
URL: cloneURL,
8587
Description: p.Description,

backend/pkg/git/gitlab/types.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,3 @@ type GetRepoArchiveResp struct {
6666
ContentType string
6767
Reader io.ReadCloser
6868
}
69-
70-
// AuthRepository 授权仓库信息
71-
type AuthRepository struct {
72-
FullName string `json:"full_name"`
73-
URL string `json:"url"`
74-
Description string `json:"description"`
75-
}

0 commit comments

Comments
 (0)