main / internal/infra/persistence/sqlite/pr_repo.go
4.0 KB · Go Raw
1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7
8 "gitgud/internal/domain"
9)
10
11type PRRepo struct {
12 db *sql.DB
13}
14
15func NewPRRepo(db *sql.DB) *PRRepo {
16 return &PRRepo{db: db}
17}
18
19func (r *PRRepo) Create(ctx context.Context, pr *domain.PullRequest) error {
20 tx, err := r.db.BeginTx(ctx, nil)
21 if err != nil {
22 return err
23 }
24 defer tx.Rollback()
25
26 var next int
27 err = tx.QueryRowContext(ctx,
28 `SELECT COALESCE(MAX(number),0)+1 FROM pull_requests WHERE repo_id=?`, pr.RepoID).Scan(&next)
29 if err != nil {
30 return err
31 }
32 pr.Number = next
33
34 res, err := tx.ExecContext(ctx,
35 `INSERT INTO pull_requests(repo_id,number,author_id,title,body,base_branch,head_branch,state)
36 VALUES(?,?,?,?,?,?,?,?)`,
37 pr.RepoID, pr.Number, pr.AuthorID, pr.Title, pr.Body, pr.BaseBranch, pr.HeadBranch, string(pr.State))
38 if err != nil {
39 return err
40 }
41 pr.ID, _ = res.LastInsertId()
42 return tx.Commit()
43}
44
45func (r *PRRepo) ByNumber(ctx context.Context, repoID int64, number int) (*domain.PullRequest, error) {
46 const q = `SELECT p.id, p.repo_id, p.number, p.author_id, u.username, p.title, p.body, p.base_branch, p.head_branch, p.state, p.created_at
47FROM pull_requests p
48JOIN users u ON u.id = p.author_id
49WHERE p.repo_id = ? AND p.number = ?`
50
51 pr, err := scanPR(r.db.QueryRowContext(ctx, q, repoID, number))
52 if err != nil {
53 if errors.Is(err, sql.ErrNoRows) {
54 return nil, domain.ErrNotFound
55 }
56 return nil, err
57 }
58 return pr, nil
59}
60
61func (r *PRRepo) List(ctx context.Context, repoID int64, state domain.PRState) ([]*domain.PullRequest, error) {
62 q := `SELECT p.id, p.repo_id, p.number, p.author_id, u.username, p.title, p.body, p.base_branch, p.head_branch, p.state, p.created_at
63FROM pull_requests p
64JOIN users u ON u.id = p.author_id
65WHERE p.repo_id = ?`
66 args := []any{repoID}
67 if state != "" {
68 q += ` AND p.state = ?`
69 args = append(args, string(state))
70 }
71 q += ` ORDER BY p.number DESC`
72
73 rows, err := r.db.QueryContext(ctx, q, args...)
74 if err != nil {
75 return nil, err
76 }
77 defer rows.Close()
78
79 var prs []*domain.PullRequest
80 for rows.Next() {
81 pr, err := scanPR(rows)
82 if err != nil {
83 return nil, err
84 }
85 prs = append(prs, pr)
86 }
87 return prs, rows.Err()
88}
89
90func (r *PRRepo) SetState(ctx context.Context, id int64, state domain.PRState) error {
91 _, err := r.db.ExecContext(ctx, `UPDATE pull_requests SET state=? WHERE id=?`, string(state), id)
92 return err
93}
94
95func (r *PRRepo) AddComment(ctx context.Context, c *domain.PRComment) error {
96 res, err := r.db.ExecContext(ctx,
97 `INSERT INTO pr_comments(pr_id,author_id,body) VALUES(?,?,?)`,
98 c.PRID, c.AuthorID, c.Body)
99 if err != nil {
100 return err
101 }
102 c.ID, _ = res.LastInsertId()
103 return nil
104}
105
106func (r *PRRepo) Comments(ctx context.Context, prID int64) ([]*domain.PRComment, error) {
107 const q = `SELECT c.id, c.pr_id, c.author_id, u.username, c.body, c.created_at
108FROM pr_comments c
109JOIN users u ON u.id = c.author_id
110WHERE c.pr_id = ?
111ORDER BY c.created_at, c.id`
112
113 rows, err := r.db.QueryContext(ctx, q, prID)
114 if err != nil {
115 return nil, err
116 }
117 defer rows.Close()
118
119 var comments []*domain.PRComment
120 for rows.Next() {
121 var c domain.PRComment
122 if err := rows.Scan(&c.ID, &c.PRID, &c.AuthorID, &c.AuthorName, &c.Body, &c.CreatedAt); err != nil {
123 return nil, err
124 }
125 comments = append(comments, &c)
126 }
127 return comments, rows.Err()
128}
129
130func (r *PRRepo) CountByState(ctx context.Context, repoID int64) (open, merged, closed int, err error) {
131 const q = `SELECT
132 COALESCE(SUM(CASE WHEN state='open' THEN 1 ELSE 0 END),0),
133 COALESCE(SUM(CASE WHEN state='merged' THEN 1 ELSE 0 END),0),
134 COALESCE(SUM(CASE WHEN state='closed' THEN 1 ELSE 0 END),0)
135FROM pull_requests WHERE repo_id=?`
136 err = r.db.QueryRowContext(ctx, q, repoID).Scan(&open, &merged, &closed)
137 return open, merged, closed, err
138}
139
140type rowScanner interface {
141 Scan(dest ...any) error
142}
143
144func scanPR(row rowScanner) (*domain.PullRequest, error) {
145 var pr domain.PullRequest
146 err := row.Scan(&pr.ID, &pr.RepoID, &pr.Number, &pr.AuthorID, &pr.AuthorName,
147 &pr.Title, &pr.Body, &pr.BaseBranch, &pr.HeadBranch, &pr.State, &pr.CreatedAt)
148 if err != nil {
149 return nil, err
150 }
151 return &pr, nil
152}