feat: Forge client + RepoService with CRUD and actions
Co-Authored-By: Virgil <virgil@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b1862445a0
commit
abc8840fa4
4 changed files with 200 additions and 1 deletions
43
forge.go
43
forge.go
|
|
@ -3,10 +3,51 @@ package forge
|
|||
// Forge is the top-level client for the Forgejo API.
|
||||
type Forge struct {
|
||||
client *Client
|
||||
|
||||
Repos *RepoService
|
||||
Issues *IssueService
|
||||
Pulls *PullService
|
||||
Orgs *OrgService
|
||||
Users *UserService
|
||||
Teams *TeamService
|
||||
Admin *AdminService
|
||||
Branches *BranchService
|
||||
Releases *ReleaseService
|
||||
Labels *LabelService
|
||||
Webhooks *WebhookService
|
||||
Notifications *NotificationService
|
||||
Packages *PackageService
|
||||
Actions *ActionsService
|
||||
Contents *ContentService
|
||||
Wiki *WikiService
|
||||
Misc *MiscService
|
||||
}
|
||||
|
||||
// NewForge creates a new Forge client.
|
||||
func NewForge(url, token string, opts ...Option) *Forge {
|
||||
c := NewClient(url, token, opts...)
|
||||
return &Forge{client: c}
|
||||
f := &Forge{client: c}
|
||||
f.Repos = newRepoService(c)
|
||||
// Other services initialised in their respective tasks.
|
||||
// Stub them here so tests compile:
|
||||
f.Issues = &IssueService{}
|
||||
f.Pulls = &PullService{}
|
||||
f.Orgs = &OrgService{}
|
||||
f.Users = &UserService{}
|
||||
f.Teams = &TeamService{}
|
||||
f.Admin = &AdminService{}
|
||||
f.Branches = &BranchService{}
|
||||
f.Releases = &ReleaseService{}
|
||||
f.Labels = &LabelService{}
|
||||
f.Webhooks = &WebhookService{}
|
||||
f.Notifications = &NotificationService{}
|
||||
f.Packages = &PackageService{}
|
||||
f.Actions = &ActionsService{}
|
||||
f.Contents = &ContentService{}
|
||||
f.Wiki = &WikiService{}
|
||||
f.Misc = &MiscService{}
|
||||
return f
|
||||
}
|
||||
|
||||
// Client returns the underlying HTTP client.
|
||||
func (f *Forge) Client() *Client { return f.client }
|
||||
|
|
|
|||
74
forge_test.go
Normal file
74
forge_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"forge.lthn.ai/core/go-forge/types"
|
||||
)
|
||||
|
||||
func TestForge_Good_NewForge(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok")
|
||||
if f.Repos == nil {
|
||||
t.Fatal("Repos service is nil")
|
||||
}
|
||||
if f.Issues == nil {
|
||||
t.Fatal("Issues service is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Repository{{Name: "go-forge"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
result, err := f.Repos.List(context.Background(), Params{"org": "core"}, DefaultList)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(result.Items) != 1 || result.Items[0].Name != "go-forge" {
|
||||
t.Errorf("unexpected result: %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(types.Repository{Name: "go-forge", FullName: "core/go-forge"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
repo, err := f.Repos.Get(context.Background(), Params{"owner": "core", "repo": "go-forge"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if repo.Name != "go-forge" {
|
||||
t.Errorf("got name=%q", repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Good_Fork(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
json.NewEncoder(w).Encode(types.Repository{Name: "go-forge", Fork: true})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
repo, err := f.Repos.Fork(context.Background(), "core", "go-forge", "my-org")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !repo.Fork {
|
||||
t.Error("expected fork=true")
|
||||
}
|
||||
}
|
||||
64
repos.go
Normal file
64
repos.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.lthn.ai/core/go-forge/types"
|
||||
)
|
||||
|
||||
// RepoService handles repository operations.
|
||||
type RepoService struct {
|
||||
Resource[types.Repository, types.CreateRepoOption, types.EditRepoOption]
|
||||
}
|
||||
|
||||
func newRepoService(c *Client) *RepoService {
|
||||
return &RepoService{
|
||||
Resource: *NewResource[types.Repository, types.CreateRepoOption, types.EditRepoOption](
|
||||
c, "/api/v1/repos/{owner}/{repo}",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// ListOrgRepos returns all repositories for an organisation.
|
||||
func (s *RepoService) ListOrgRepos(ctx context.Context, org string) ([]types.Repository, error) {
|
||||
return ListAll[types.Repository](ctx, s.client, "/api/v1/orgs/"+org+"/repos", nil)
|
||||
}
|
||||
|
||||
// ListUserRepos returns all repositories for the authenticated user.
|
||||
func (s *RepoService) ListUserRepos(ctx context.Context) ([]types.Repository, error) {
|
||||
return ListAll[types.Repository](ctx, s.client, "/api/v1/user/repos", nil)
|
||||
}
|
||||
|
||||
// Fork forks a repository. If org is non-empty, forks into that organisation.
|
||||
func (s *RepoService) Fork(ctx context.Context, owner, repo, org string) (*types.Repository, error) {
|
||||
body := map[string]string{}
|
||||
if org != "" {
|
||||
body["organization"] = org
|
||||
}
|
||||
var out types.Repository
|
||||
err := s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/forks", body, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// Transfer initiates a repository transfer.
|
||||
func (s *RepoService) Transfer(ctx context.Context, owner, repo string, opts map[string]any) error {
|
||||
return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/transfer", opts, nil)
|
||||
}
|
||||
|
||||
// AcceptTransfer accepts a pending repository transfer.
|
||||
func (s *RepoService) AcceptTransfer(ctx context.Context, owner, repo string) error {
|
||||
return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/transfer/accept", nil, nil)
|
||||
}
|
||||
|
||||
// RejectTransfer rejects a pending repository transfer.
|
||||
func (s *RepoService) RejectTransfer(ctx context.Context, owner, repo string) error {
|
||||
return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/transfer/reject", nil, nil)
|
||||
}
|
||||
|
||||
// MirrorSync triggers a mirror sync.
|
||||
func (s *RepoService) MirrorSync(ctx context.Context, owner, repo string) error {
|
||||
return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/mirror-sync", nil, nil)
|
||||
}
|
||||
20
services_stub.go
Normal file
20
services_stub.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package forge
|
||||
|
||||
// Stub service types — replaced as each service is implemented.
|
||||
|
||||
type IssueService struct{}
|
||||
type PullService struct{}
|
||||
type OrgService struct{}
|
||||
type UserService struct{}
|
||||
type TeamService struct{}
|
||||
type AdminService struct{}
|
||||
type BranchService struct{}
|
||||
type ReleaseService struct{}
|
||||
type LabelService struct{}
|
||||
type WebhookService struct{}
|
||||
type NotificationService struct{}
|
||||
type PackageService struct{}
|
||||
type ActionsService struct{}
|
||||
type ContentService struct{}
|
||||
type WikiService struct{}
|
||||
type MiscService struct{}
|
||||
Loading…
Add table
Reference in a new issue