ikurotime / gitgud

public
main / internal/app/pull_service.go
3.6 KB · Go Raw
  1package app
  2
  3import (
  4	"context"
  5	"fmt"
  6	"slices"
  7	"strings"
  8
  9	"gitgud/internal/domain"
 10)
 11
 12type PullService struct {
 13	prs    domain.PullRequestRepository
 14	reader domain.GitReader
 15	git    domain.GitService
 16}
 17
 18func NewPullService(prs domain.PullRequestRepository, reader domain.GitReader, git domain.GitService) *PullService {
 19	return &PullService{prs: prs, reader: reader, git: git}
 20}
 21
 22func (s *PullService) Open(ctx context.Context, repo *domain.Repository, author *domain.User, title, body, base, head string) (*domain.PullRequest, error) {
 23	if author == nil {
 24		return nil, domain.ErrUnauthorized
 25	}
 26	title = strings.TrimSpace(title)
 27	base = strings.TrimSpace(base)
 28	head = strings.TrimSpace(head)
 29
 30	if title == "" {
 31		return nil, fmt.Errorf("title is required: %w", domain.ErrValidation)
 32	}
 33	if base == "" || head == "" || base == head {
 34		return nil, fmt.Errorf("base and head must be two different branches: %w", domain.ErrValidation)
 35	}
 36
 37	branches, err := s.reader.Branches(ctx, repo.OwnerName, repo.Name)
 38	if err != nil {
 39		return nil, err
 40	}
 41	if !slices.Contains(branches, base) || !slices.Contains(branches, head) {
 42		return nil, fmt.Errorf("base and head must be existing branches: %w", domain.ErrValidation)
 43	}
 44
 45	pr := &domain.PullRequest{
 46		RepoID:     repo.ID,
 47		AuthorID:   author.ID,
 48		Title:      title,
 49		Body:       strings.TrimSpace(body),
 50		BaseBranch: base,
 51		HeadBranch: head,
 52		State:      domain.PROpen,
 53	}
 54	if err := s.prs.Create(ctx, pr); err != nil {
 55		return nil, err
 56	}
 57	return pr, nil
 58}
 59
 60func (s *PullService) Compare(ctx context.Context, repo *domain.Repository, base, head string) (*domain.Comparison, error) {
 61	return s.reader.Compare(ctx, repo.OwnerName, repo.Name, base, head)
 62}
 63
 64func (s *PullService) Comment(ctx context.Context, pr *domain.PullRequest, author *domain.User, body string) error {
 65	if author == nil {
 66		return domain.ErrUnauthorized
 67	}
 68	body = strings.TrimSpace(body)
 69	if body == "" {
 70		return fmt.Errorf("comment is required: %w", domain.ErrValidation)
 71	}
 72	return s.prs.AddComment(ctx, &domain.PRComment{
 73		PRID:     pr.ID,
 74		AuthorID: author.ID,
 75		Body:     body,
 76	})
 77}
 78
 79func (s *PullService) Merge(ctx context.Context, repo *domain.Repository, pr *domain.PullRequest, actor *domain.User) error {
 80	if !CanMergePR(repo, actor) {
 81		return domain.ErrPermission
 82	}
 83	if pr.State != domain.PROpen {
 84		return fmt.Errorf("pull request is not open: %w", domain.ErrValidation)
 85	}
 86
 87	msg := fmt.Sprintf("Merge pull request #%d from %s", pr.Number, pr.HeadBranch)
 88	if err := s.git.Merge(ctx, repo.OwnerName, repo.Name, pr.BaseBranch, pr.HeadBranch, msg, actor.Username, actor.Email); err != nil {
 89		return err
 90	}
 91	return s.prs.SetState(ctx, pr.ID, domain.PRMerged)
 92}
 93
 94func (s *PullService) Close(ctx context.Context, repo *domain.Repository, pr *domain.PullRequest, actor *domain.User) error {
 95	if !(actor != nil && (actor.ID == pr.AuthorID || actor.ID == repo.OwnerID)) {
 96		return domain.ErrPermission
 97	}
 98	return s.prs.SetState(ctx, pr.ID, domain.PRClosed)
 99}
100
101func (s *PullService) List(ctx context.Context, repoID int64, state domain.PRState) ([]*domain.PullRequest, error) {
102	return s.prs.List(ctx, repoID, state)
103}
104
105func (s *PullService) Get(ctx context.Context, repoID int64, number int) (*domain.PullRequest, error) {
106	return s.prs.ByNumber(ctx, repoID, number)
107}
108
109func (s *PullService) Comments(ctx context.Context, prID int64) ([]*domain.PRComment, error) {
110	return s.prs.Comments(ctx, prID)
111}
112
113func (s *PullService) Counts(ctx context.Context, repoID int64) (open, merged, closed int, err error) {
114	return s.prs.CountByState(ctx, repoID)
115}
116
117func CanMergePR(repo *domain.Repository, actor *domain.User) bool {
118	return actor != nil && actor.ID == repo.OwnerID
119}