1package db
2
3import (
4 "database/sql"
5 "time"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8)
9
10type PullState int
11
12const (
13 PullClosed PullState = iota
14 PullOpen
15 PullMerged
16)
17
18func (p PullState) String() string {
19 switch p {
20 case PullOpen:
21 return "open"
22 case PullMerged:
23 return "merged"
24 case PullClosed:
25 return "closed"
26 default:
27 return "closed"
28 }
29}
30
31func (p PullState) IsOpen() bool {
32 return p == PullOpen
33}
34func (p PullState) IsMerged() bool {
35 return p == PullMerged
36}
37func (p PullState) IsClosed() bool {
38 return p == PullClosed
39}
40
41type Pull struct {
42 ID int
43 OwnerDid string
44 RepoAt syntax.ATURI
45 PullAt syntax.ATURI
46 TargetBranch string
47 Patch string
48 PullId int
49 Title string
50 Body string
51 State PullState
52 Created time.Time
53 Rkey string
54}
55
56type PullComment struct {
57 ID int
58 OwnerDid string
59 PullId int
60 RepoAt string
61 CommentId int
62 CommentAt string
63 Body string
64 Created time.Time
65}
66
67func NewPull(tx *sql.Tx, pull *Pull) error {
68 defer tx.Rollback()
69
70 _, err := tx.Exec(`
71 insert or ignore into repo_pull_seqs (repo_at, next_pull_id)
72 values (?, 1)
73 `, pull.RepoAt)
74 if err != nil {
75 return err
76 }
77
78 var nextId int
79 err = tx.QueryRow(`
80 update repo_pull_seqs
81 set next_pull_id = next_pull_id + 1
82 where repo_at = ?
83 returning next_pull_id - 1
84 `, pull.RepoAt).Scan(&nextId)
85 if err != nil {
86 return err
87 }
88
89 pull.PullId = nextId
90 pull.State = PullOpen
91
92 _, err = tx.Exec(`
93 insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, patch, rkey, state)
94 values (?, ?, ?, ?, ?, ?, ?, ?, ?)
95 `, pull.RepoAt, pull.OwnerDid, pull.PullId, pull.Title, pull.TargetBranch, pull.Body, pull.Patch, pull.Rkey, pull.State)
96 if err != nil {
97 return err
98 }
99
100 if err := tx.Commit(); err != nil {
101 return err
102 }
103
104 return nil
105}
106
107func SetPullAt(e Execer, repoAt syntax.ATURI, pullId int, pullAt string) error {
108 _, err := e.Exec(`update pulls set pull_at = ? where repo_at = ? and pull_id = ?`, pullAt, repoAt, pullId)
109 return err
110}
111
112func GetPullAt(e Execer, repoAt syntax.ATURI, pullId int) (string, error) {
113 var pullAt string
114 err := e.QueryRow(`select pull_at from pulls where repo_at = ? and pull_id = ?`, repoAt, pullId).Scan(&pullAt)
115 return pullAt, err
116}
117
118func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) {
119 var pullId int
120 err := e.QueryRow(`select next_pull_id from repo_pull_seqs where repo_at = ?`, repoAt).Scan(&pullId)
121 return pullId - 1, err
122}
123
124func GetPulls(e Execer, repoAt syntax.ATURI, state PullState) ([]Pull, error) {
125 var pulls []Pull
126
127 rows, err := e.Query(`
128 select
129 owner_did,
130 pull_id,
131 created,
132 title,
133 state,
134 target_branch,
135 pull_at,
136 body,
137 patch,
138 rkey
139 from
140 pulls
141 where
142 repo_at = ? and state = ?
143 order by
144 created desc`, repoAt, state)
145 if err != nil {
146 return nil, err
147 }
148 defer rows.Close()
149
150 for rows.Next() {
151 var pull Pull
152 var createdAt string
153 err := rows.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.State, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
154 if err != nil {
155 return nil, err
156 }
157
158 createdTime, err := time.Parse(time.RFC3339, createdAt)
159 if err != nil {
160 return nil, err
161 }
162 pull.Created = createdTime
163
164 pulls = append(pulls, pull)
165 }
166
167 if err := rows.Err(); err != nil {
168 return nil, err
169 }
170
171 return pulls, nil
172}
173
174func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) {
175 query := `select owner_did, created, title, state, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?`
176 row := e.QueryRow(query, repoAt, pullId)
177
178 var pull Pull
179 var createdAt string
180 err := row.Scan(&pull.OwnerDid, &createdAt, &pull.Title, &pull.State, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
181 if err != nil {
182 return nil, err
183 }
184
185 createdTime, err := time.Parse(time.RFC3339, createdAt)
186 if err != nil {
187 return nil, err
188 }
189 pull.Created = createdTime
190
191 return &pull, nil
192}
193
194func GetPullWithComments(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, []PullComment, error) {
195 query := `select owner_did, pull_id, created, title, state, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?`
196 row := e.QueryRow(query, repoAt, pullId)
197
198 var pull Pull
199 var createdAt string
200 err := row.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.State, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
201 if err != nil {
202 return nil, nil, err
203 }
204
205 createdTime, err := time.Parse(time.RFC3339, createdAt)
206 if err != nil {
207 return nil, nil, err
208 }
209 pull.Created = createdTime
210
211 comments, err := GetPullComments(e, repoAt, pullId)
212 if err != nil {
213 return nil, nil, err
214 }
215
216 return &pull, comments, nil
217}
218
219func NewPullComment(e Execer, comment *PullComment) error {
220 query := `insert into pull_comments (owner_did, repo_at, comment_at, pull_id, comment_id, body) values (?, ?, ?, ?, ?, ?)`
221 _, err := e.Exec(
222 query,
223 comment.OwnerDid,
224 comment.RepoAt,
225 comment.CommentAt,
226 comment.PullId,
227 comment.CommentId,
228 comment.Body,
229 )
230 return err
231}
232
233func GetPullComments(e Execer, repoAt syntax.ATURI, pullId int) ([]PullComment, error) {
234 var comments []PullComment
235
236 rows, err := e.Query(`select owner_did, pull_id, comment_id, comment_at, body, created from pull_comments where repo_at = ? and pull_id = ? order by created asc`, repoAt, pullId)
237 if err == sql.ErrNoRows {
238 return []PullComment{}, nil
239 }
240 if err != nil {
241 return nil, err
242 }
243 defer rows.Close()
244
245 for rows.Next() {
246 var comment PullComment
247 var createdAt string
248 err := rows.Scan(&comment.OwnerDid, &comment.PullId, &comment.CommentId, &comment.CommentAt, &comment.Body, &createdAt)
249 if err != nil {
250 return nil, err
251 }
252
253 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
254 if err != nil {
255 return nil, err
256 }
257 comment.Created = createdAtTime
258
259 comments = append(comments, comment)
260 }
261
262 if err := rows.Err(); err != nil {
263 return nil, err
264 }
265
266 return comments, nil
267}
268
269func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState PullState) error {
270 _, err := e.Exec(`update pulls set state = ? where repo_at = ? and pull_id = ?`, pullState, repoAt, pullId)
271 return err
272}
273
274func ClosePull(e Execer, repoAt syntax.ATURI, pullId int) error {
275 err := SetPullState(e, repoAt, pullId, PullClosed)
276 return err
277}
278
279func ReopenPull(e Execer, repoAt syntax.ATURI, pullId int) error {
280 err := SetPullState(e, repoAt, pullId, PullOpen)
281 return err
282}
283
284func MergePull(e Execer, repoAt syntax.ATURI, pullId int) error {
285 err := SetPullState(e, repoAt, pullId, PullMerged)
286 return err
287}
288
289type PullCount struct {
290 Open int
291 Merged int
292 Closed int
293}
294
295func GetPullCount(e Execer, repoAt syntax.ATURI) (PullCount, error) {
296 row := e.QueryRow(`
297 select
298 count(case when state = ? then 1 end) as open_count,
299 count(case when state = ? then 1 end) as merged_count,
300 count(case when state = ? then 1 end) as closed_count
301 from pulls
302 where repo_at = ?`,
303 PullOpen,
304 PullMerged,
305 PullClosed,
306 repoAt,
307 )
308
309 var count PullCount
310 if err := row.Scan(&count.Open, &count.Merged, &count.Closed); err != nil {
311 return PullCount{0, 0, 0}, err
312 }
313
314 return count, nil
315}
316
317func EditPatch(e Execer, repoAt syntax.ATURI, pullId int, patch string) error {
318 _, err := e.Exec(`update pulls set patch = ? where repo_at = ? and pull_id = ?`, patch, repoAt, pullId)
319 return err
320}