diff --git a/forge.go b/forge.go index 5cfe323..df80533 100644 --- a/forge.go +++ b/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 } diff --git a/forge_test.go b/forge_test.go new file mode 100644 index 0000000..1d3304e --- /dev/null +++ b/forge_test.go @@ -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") + } +} diff --git a/repos.go b/repos.go new file mode 100644 index 0000000..f8610e7 --- /dev/null +++ b/repos.go @@ -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) +} diff --git a/services_stub.go b/services_stub.go new file mode 100644 index 0000000..5cbaaf5 --- /dev/null +++ b/services_stub.go @@ -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{}