this repo has no description
1package db
2
3import (
4 "database/sql"
5 "time"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8)
9
10type Issue struct {
11 RepoAt syntax.ATURI
12 OwnerDid string
13 IssueId int
14 IssueAt string
15 Created *time.Time
16 Title string
17 Body string
18 Open bool
19 Metadata *IssueMetadata
20}
21
22type IssueMetadata struct {
23 CommentCount int
24 // labels, assignee etc.
25}
26
27type Comment struct {
28 OwnerDid string
29 RepoAt syntax.ATURI
30 CommentAt syntax.ATURI
31 Issue int
32 CommentId int
33 Body string
34 Created *time.Time
35 Deleted *time.Time
36 Edited *time.Time
37}
38
39func NewIssue(tx *sql.Tx, issue *Issue) error {
40 defer tx.Rollback()
41
42 _, err := tx.Exec(`
43 insert or ignore into repo_issue_seqs (repo_at, next_issue_id)
44 values (?, 1)
45 `, issue.RepoAt)
46 if err != nil {
47 return err
48 }
49
50 var nextId int
51 err = tx.QueryRow(`
52 update repo_issue_seqs
53 set next_issue_id = next_issue_id + 1
54 where repo_at = ?
55 returning next_issue_id - 1
56 `, issue.RepoAt).Scan(&nextId)
57 if err != nil {
58 return err
59 }
60
61 issue.IssueId = nextId
62
63 _, err = tx.Exec(`
64 insert into issues (repo_at, owner_did, issue_id, title, body)
65 values (?, ?, ?, ?, ?)
66 `, issue.RepoAt, issue.OwnerDid, issue.IssueId, issue.Title, issue.Body)
67 if err != nil {
68 return err
69 }
70
71 if err := tx.Commit(); err != nil {
72 return err
73 }
74
75 return nil
76}
77
78func SetIssueAt(e Execer, repoAt syntax.ATURI, issueId int, issueAt string) error {
79 _, err := e.Exec(`update issues set issue_at = ? where repo_at = ? and issue_id = ?`, issueAt, repoAt, issueId)
80 return err
81}
82
83func GetIssueAt(e Execer, repoAt syntax.ATURI, issueId int) (string, error) {
84 var issueAt string
85 err := e.QueryRow(`select issue_at from issues where repo_at = ? and issue_id = ?`, repoAt, issueId).Scan(&issueAt)
86 return issueAt, err
87}
88
89func GetIssueId(e Execer, repoAt syntax.ATURI) (int, error) {
90 var issueId int
91 err := e.QueryRow(`select next_issue_id from repo_issue_seqs where repo_at = ?`, repoAt).Scan(&issueId)
92 return issueId - 1, err
93}
94
95func GetIssueOwnerDid(e Execer, repoAt syntax.ATURI, issueId int) (string, error) {
96 var ownerDid string
97 err := e.QueryRow(`select owner_did from issues where repo_at = ? and issue_id = ?`, repoAt, issueId).Scan(&ownerDid)
98 return ownerDid, err
99}
100
101func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool) ([]Issue, error) {
102 var issues []Issue
103 openValue := 0
104 if isOpen {
105 openValue = 1
106 }
107
108 rows, err := e.Query(
109 `select
110 i.owner_did,
111 i.issue_id,
112 i.created,
113 i.title,
114 i.body,
115 i.open,
116 count(c.id)
117 from
118 issues i
119 left join
120 comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
121 where
122 i.repo_at = ? and i.open = ?
123 group by
124 i.id, i.owner_did, i.issue_id, i.created, i.title, i.body, i.open
125 order by
126 i.created desc`,
127 repoAt, openValue)
128 if err != nil {
129 return nil, err
130 }
131 defer rows.Close()
132
133 for rows.Next() {
134 var issue Issue
135 var createdAt string
136 var metadata IssueMetadata
137 err := rows.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)
138 if err != nil {
139 return nil, err
140 }
141
142 createdTime, err := time.Parse(time.RFC3339, createdAt)
143 if err != nil {
144 return nil, err
145 }
146 issue.Created = &createdTime
147 issue.Metadata = &metadata
148
149 issues = append(issues, issue)
150 }
151
152 if err := rows.Err(); err != nil {
153 return nil, err
154 }
155
156 return issues, nil
157}
158
159func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) {
160 query := `select owner_did, created, title, body, open from issues where repo_at = ? and issue_id = ?`
161 row := e.QueryRow(query, repoAt, issueId)
162
163 var issue Issue
164 var createdAt string
165 err := row.Scan(&issue.OwnerDid, &createdAt, &issue.Title, &issue.Body, &issue.Open)
166 if err != nil {
167 return nil, err
168 }
169
170 createdTime, err := time.Parse(time.RFC3339, createdAt)
171 if err != nil {
172 return nil, err
173 }
174 issue.Created = &createdTime
175
176 return &issue, nil
177}
178
179func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {
180 query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?`
181 row := e.QueryRow(query, repoAt, issueId)
182
183 var issue Issue
184 var createdAt string
185 err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open)
186 if err != nil {
187 return nil, nil, err
188 }
189
190 createdTime, err := time.Parse(time.RFC3339, createdAt)
191 if err != nil {
192 return nil, nil, err
193 }
194 issue.Created = &createdTime
195
196 comments, err := GetComments(e, repoAt, issueId)
197 if err != nil {
198 return nil, nil, err
199 }
200
201 return &issue, comments, nil
202}
203
204func NewComment(e Execer, comment *Comment) error {
205 query := `insert into comments (owner_did, repo_at, comment_at, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)`
206 _, err := e.Exec(
207 query,
208 comment.OwnerDid,
209 comment.RepoAt,
210 comment.CommentAt,
211 comment.Issue,
212 comment.CommentId,
213 comment.Body,
214 )
215 return err
216}
217
218func GetComments(e Execer, repoAt syntax.ATURI, issueId int) ([]Comment, error) {
219 var comments []Comment
220
221 rows, err := e.Query(`select owner_did, issue_id, comment_id, comment_at, body, created from comments where repo_at = ? and issue_id = ? order by created asc`, repoAt, issueId)
222 if err == sql.ErrNoRows {
223 return []Comment{}, nil
224 }
225 if err != nil {
226 return nil, err
227 }
228 defer rows.Close()
229
230 for rows.Next() {
231 var comment Comment
232 var createdAt string
233 err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &comment.CommentAt, &comment.Body, &createdAt)
234 if err != nil {
235 return nil, err
236 }
237
238 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
239 if err != nil {
240 return nil, err
241 }
242 comment.Created = &createdAtTime
243
244 comments = append(comments, comment)
245 }
246
247 if err := rows.Err(); err != nil {
248 return nil, err
249 }
250
251 return comments, nil
252}
253
254func GetComment(e Execer, repoAt syntax.ATURI, issueId, commentId int) (*Comment, error) {
255 query := `
256 select
257 owner_did, body, comment_at, created, deleted, edited
258 from
259 comments where repo_at = ? and issue_id = ? and comment_id = ?
260 `
261 row := e.QueryRow(query, repoAt, issueId, commentId)
262
263 var comment Comment
264 var createdAt string
265 var deletedAt, editedAt sql.NullString
266 err := row.Scan(&comment.OwnerDid, &comment.Body, &comment.CommentAt, &createdAt, &deletedAt, &editedAt)
267 if err != nil {
268 return nil, err
269 }
270
271 createdTime, err := time.Parse(time.RFC3339, createdAt)
272 if err != nil {
273 return nil, err
274 }
275 comment.Created = &createdTime
276
277 if deletedAt.Valid {
278 deletedTime, err := time.Parse(time.RFC3339, deletedAt.String)
279 if err != nil {
280 return nil, err
281 }
282 comment.Deleted = &deletedTime
283 }
284
285 if editedAt.Valid {
286 editedTime, err := time.Parse(time.RFC3339, editedAt.String)
287 if err != nil {
288 return nil, err
289 }
290 comment.Edited = &editedTime
291 }
292
293 comment.RepoAt = repoAt
294 comment.Issue = issueId
295 comment.CommentId = commentId
296
297 return &comment, nil
298}
299
300func EditComment(e Execer, repoAt syntax.ATURI, issueId, commentId int, newBody string) error {
301 _, err := e.Exec(
302 `
303 update comments
304 set body = ?,
305 edited = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
306 where repo_at = ? and issue_id = ? and comment_id = ?
307 `, newBody, repoAt, issueId, commentId)
308 return err
309}
310
311func DeleteComment(e Execer, repoAt syntax.ATURI, issueId, commentId int) error {
312 _, err := e.Exec(
313 `
314 update comments
315 set body = "",
316 deleted = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
317 where repo_at = ? and issue_id = ? and comment_id = ?
318 `, repoAt, issueId, commentId)
319 return err
320}
321
322func CloseIssue(e Execer, repoAt syntax.ATURI, issueId int) error {
323 _, err := e.Exec(`update issues set open = 0 where repo_at = ? and issue_id = ?`, repoAt, issueId)
324 return err
325}
326
327func ReopenIssue(e Execer, repoAt syntax.ATURI, issueId int) error {
328 _, err := e.Exec(`update issues set open = 1 where repo_at = ? and issue_id = ?`, repoAt, issueId)
329 return err
330}
331
332type IssueCount struct {
333 Open int
334 Closed int
335}
336
337func GetIssueCount(e Execer, repoAt syntax.ATURI) (IssueCount, error) {
338 row := e.QueryRow(`
339 select
340 count(case when open = 1 then 1 end) as open_count,
341 count(case when open = 0 then 1 end) as closed_count
342 from issues
343 where repo_at = ?`,
344 repoAt,
345 )
346
347 var count IssueCount
348 if err := row.Scan(&count.Open, &count.Closed); err != nil {
349 return IssueCount{0, 0}, err
350 }
351
352 return count, nil
353}