main / internal/infra/persistence/sqlite/issue_repo.go
3.7 KB · Go Raw
1package sqlite
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7
8 "gitgud/internal/domain"
9)
10
11type IssueRepo struct {
12 db *sql.DB
13}
14
15func NewIssueRepo(db *sql.DB) *IssueRepo {
16 return &IssueRepo{db: db}
17}
18
19func (r *IssueRepo) Create(ctx context.Context, i *domain.Issue) 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 issues WHERE repo_id=?`, i.RepoID).Scan(&next)
29 if err != nil {
30 return err
31 }
32 i.Number = next
33
34 res, err := tx.ExecContext(ctx,
35 `INSERT INTO issues(repo_id,number,author_id,title,body,state) VALUES(?,?,?,?,?,?)`,
36 i.RepoID, i.Number, i.AuthorID, i.Title, i.Body, string(i.State))
37 if err != nil {
38 return err
39 }
40 i.ID, _ = res.LastInsertId()
41 return tx.Commit()
42}
43
44func (r *IssueRepo) ByNumber(ctx context.Context, repoID int64, number int) (*domain.Issue, error) {
45 const q = `SELECT i.id, i.repo_id, i.number, i.author_id, u.username, i.title, i.body, i.state, i.created_at
46FROM issues i
47JOIN users u ON u.id = i.author_id
48WHERE i.repo_id = ? AND i.number = ?`
49
50 var i domain.Issue
51 err := r.db.QueryRowContext(ctx, q, repoID, number).Scan(
52 &i.ID, &i.RepoID, &i.Number, &i.AuthorID, &i.AuthorName, &i.Title, &i.Body, &i.State, &i.CreatedAt)
53 if err != nil {
54 if errors.Is(err, sql.ErrNoRows) {
55 return nil, domain.ErrNotFound
56 }
57 return nil, err
58 }
59 return &i, nil
60}
61
62func (r *IssueRepo) List(ctx context.Context, repoID int64, state domain.IssueState) ([]*domain.Issue, error) {
63 q := `SELECT i.id, i.repo_id, i.number, i.author_id, u.username, i.title, i.body, i.state, i.created_at
64FROM issues i
65JOIN users u ON u.id = i.author_id
66WHERE i.repo_id = ?`
67 args := []any{repoID}
68 if state != "" {
69 q += ` AND i.state = ?`
70 args = append(args, string(state))
71 }
72 q += ` ORDER BY i.number DESC`
73
74 rows, err := r.db.QueryContext(ctx, q, args...)
75 if err != nil {
76 return nil, err
77 }
78 defer rows.Close()
79
80 var issues []*domain.Issue
81 for rows.Next() {
82 var i domain.Issue
83 if err := rows.Scan(&i.ID, &i.RepoID, &i.Number, &i.AuthorID, &i.AuthorName,
84 &i.Title, &i.Body, &i.State, &i.CreatedAt); err != nil {
85 return nil, err
86 }
87 issues = append(issues, &i)
88 }
89 return issues, rows.Err()
90}
91
92func (r *IssueRepo) SetState(ctx context.Context, id int64, state domain.IssueState) error {
93 _, err := r.db.ExecContext(ctx, `UPDATE issues SET state=? WHERE id=?`, string(state), id)
94 return err
95}
96
97func (r *IssueRepo) AddComment(ctx context.Context, c *domain.IssueComment) error {
98 res, err := r.db.ExecContext(ctx,
99 `INSERT INTO issue_comments(issue_id,author_id,body) VALUES(?,?,?)`,
100 c.IssueID, c.AuthorID, c.Body)
101 if err != nil {
102 return err
103 }
104 c.ID, _ = res.LastInsertId()
105 return nil
106}
107
108func (r *IssueRepo) Comments(ctx context.Context, issueID int64) ([]*domain.IssueComment, error) {
109 const q = `SELECT c.id, c.issue_id, c.author_id, u.username, c.body, c.created_at
110FROM issue_comments c
111JOIN users u ON u.id = c.author_id
112WHERE c.issue_id = ?
113ORDER BY c.created_at, c.id`
114
115 rows, err := r.db.QueryContext(ctx, q, issueID)
116 if err != nil {
117 return nil, err
118 }
119 defer rows.Close()
120
121 var comments []*domain.IssueComment
122 for rows.Next() {
123 var c domain.IssueComment
124 if err := rows.Scan(&c.ID, &c.IssueID, &c.AuthorID, &c.AuthorName, &c.Body, &c.CreatedAt); err != nil {
125 return nil, err
126 }
127 comments = append(comments, &c)
128 }
129 return comments, rows.Err()
130}
131
132func (r *IssueRepo) CountByState(ctx context.Context, repoID int64) (open, closed int, err error) {
133 const q = `SELECT
134 COALESCE(SUM(CASE WHEN state='open' THEN 1 ELSE 0 END),0),
135 COALESCE(SUM(CASE WHEN state='closed' THEN 1 ELSE 0 END),0)
136FROM issues WHERE repo_id=?`
137 err = r.db.QueryRowContext(ctx, q, repoID).Scan(&open, &closed)
138 return open, closed, err
139}