1package db
2
3import (
4 "database/sql"
5 "time"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8)
9
10type Repo struct {
11 Did string
12 Name string
13 Knot string
14 Rkey string
15 Created time.Time
16 AtUri string
17 Description string
18
19 // optionally, populate this when querying for reverse mappings
20 RepoStats *RepoStats
21
22 // optional
23 Source string
24}
25
26func GetAllRepos(e Execer, limit int) ([]Repo, error) {
27 var repos []Repo
28
29 rows, err := e.Query(
30 `select did, name, knot, rkey, description, created, source
31 from repos
32 order by created desc
33 limit ?
34 `,
35 limit,
36 )
37 if err != nil {
38 return nil, err
39 }
40 defer rows.Close()
41
42 for rows.Next() {
43 var repo Repo
44 err := scanRepo(
45 rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created, &repo.Source,
46 )
47 if err != nil {
48 return nil, err
49 }
50 repos = append(repos, repo)
51 }
52
53 if err := rows.Err(); err != nil {
54 return nil, err
55 }
56
57 return repos, nil
58}
59
60func GetAllReposByDid(e Execer, did string) ([]Repo, error) {
61 var repos []Repo
62
63 rows, err := e.Query(
64 `select
65 r.did,
66 r.name,
67 r.knot,
68 r.rkey,
69 r.description,
70 r.created,
71 count(s.id) as star_count,
72 r.source
73 from
74 repos r
75 left join
76 stars s on r.at_uri = s.repo_at
77 where
78 r.did = ?
79 group by
80 r.at_uri`, did)
81 if err != nil {
82 return nil, err
83 }
84 defer rows.Close()
85
86 for rows.Next() {
87 var repo Repo
88 var repoStats RepoStats
89 var createdAt string
90 var nullableDescription sql.NullString
91 var nullableSource sql.NullString
92
93 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount, &nullableSource)
94 if err != nil {
95 return nil, err
96 }
97
98 if nullableDescription.Valid {
99 repo.Description = nullableDescription.String
100 } else {
101 repo.Description = ""
102 }
103
104 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
105 if err != nil {
106 repo.Created = time.Now()
107 } else {
108 repo.Created = createdAtTime
109 }
110
111 repo.RepoStats = &repoStats
112
113 repos = append(repos, repo)
114 }
115
116 if err := rows.Err(); err != nil {
117 return nil, err
118 }
119
120 return repos, nil
121}
122
123func GetRepo(e Execer, did, name string) (*Repo, error) {
124 var repo Repo
125 var nullableDescription sql.NullString
126
127 row := e.QueryRow(`select did, name, knot, created, at_uri, description from repos where did = ? and name = ?`, did, name)
128
129 var createdAt string
130 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil {
131 return nil, err
132 }
133 createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
134 repo.Created = createdAtTime
135
136 if nullableDescription.Valid {
137 repo.Description = nullableDescription.String
138 } else {
139 repo.Description = ""
140 }
141
142 return &repo, nil
143}
144
145func GetRepoByAtUri(e Execer, atUri string) (*Repo, error) {
146 var repo Repo
147 var nullableDescription sql.NullString
148
149 row := e.QueryRow(`select did, name, knot, created, at_uri, description from repos where at_uri = ?`, atUri)
150
151 var createdAt string
152 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil {
153 return nil, err
154 }
155 createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
156 repo.Created = createdAtTime
157
158 if nullableDescription.Valid {
159 repo.Description = nullableDescription.String
160 } else {
161 repo.Description = ""
162 }
163
164 return &repo, nil
165}
166
167func AddRepo(e Execer, repo *Repo) error {
168 _, err := e.Exec(
169 `insert into repos
170 (did, name, knot, rkey, at_uri, description, source)
171 values (?, ?, ?, ?, ?, ?, ?)`,
172 repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri, repo.Description, repo.Source,
173 )
174 return err
175}
176
177func RemoveRepo(e Execer, did, name, rkey string) error {
178 _, err := e.Exec(`delete from repos where did = ? and name = ? and rkey = ?`, did, name, rkey)
179 return err
180}
181
182func GetRepoSource(e Execer, repoAt syntax.ATURI) (string, error) {
183 var nullableSource sql.NullString
184 err := e.QueryRow(`select source from repos where at_uri = ?`, repoAt).Scan(&nullableSource)
185 if err != nil {
186 return "", err
187 }
188 return nullableSource.String, nil
189}
190
191func AddCollaborator(e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error {
192 _, err := e.Exec(
193 `insert into collaborators (did, repo)
194 values (?, (select id from repos where did = ? and name = ? and knot = ?));`,
195 collaborator, repoOwnerDid, repoName, repoKnot)
196 return err
197}
198
199func UpdateDescription(e Execer, repoAt, newDescription string) error {
200 _, err := e.Exec(
201 `update repos set description = ? where at_uri = ?`, newDescription, repoAt)
202 return err
203}
204
205func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) {
206 var repos []Repo
207
208 rows, err := e.Query(
209 `select
210 r.did, r.name, r.knot, r.rkey, r.description, r.created, count(s.id) as star_count
211 from
212 repos r
213 join
214 collaborators c on r.id = c.repo
215 left join
216 stars s on r.at_uri = s.repo_at
217 where
218 c.did = ?
219 group by
220 r.id;`, collaborator)
221 if err != nil {
222 return nil, err
223 }
224 defer rows.Close()
225
226 for rows.Next() {
227 var repo Repo
228 var repoStats RepoStats
229 var createdAt string
230 var nullableDescription sql.NullString
231
232 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount)
233 if err != nil {
234 return nil, err
235 }
236
237 if nullableDescription.Valid {
238 repo.Description = nullableDescription.String
239 } else {
240 repo.Description = ""
241 }
242
243 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
244 if err != nil {
245 repo.Created = time.Now()
246 } else {
247 repo.Created = createdAtTime
248 }
249
250 repo.RepoStats = &repoStats
251
252 repos = append(repos, repo)
253 }
254
255 if err := rows.Err(); err != nil {
256 return nil, err
257 }
258
259 return repos, nil
260}
261
262type RepoStats struct {
263 StarCount int
264 IssueCount IssueCount
265 PullCount PullCount
266}
267
268func scanRepo(rows *sql.Rows, did, name, knot, rkey, description *string, created *time.Time, source *string) error {
269 var createdAt string
270 var nullableDescription sql.NullString
271 var nullableSource sql.NullString
272 if err := rows.Scan(did, name, knot, rkey, &nullableDescription, &createdAt, &nullableSource); err != nil {
273 return err
274 }
275
276 if nullableDescription.Valid {
277 *description = nullableDescription.String
278 } else {
279 *description = ""
280 }
281
282 createdAtTime, err := time.Parse(time.RFC3339, createdAt)
283 if err != nil {
284 *created = time.Now()
285 } else {
286 *created = createdAtTime
287 }
288
289 if nullableSource.Valid {
290 *source = nullableSource.String
291 } else {
292 *source = ""
293 }
294
295 return nil
296}