forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1package db 2 3import ( 4 "database/sql" 5 "fmt" 6 "time" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 securejoin "github.com/cyphar/filepath-securejoin" 10 "tangled.sh/tangled.sh/core/api/tangled" 11) 12 13type Repo struct { 14 Did string 15 Name string 16 Knot string 17 Rkey string 18 Created time.Time 19 AtUri string 20 Description string 21 Spindle string 22 23 // optionally, populate this when querying for reverse mappings 24 RepoStats *RepoStats 25 26 // optional 27 Source string 28} 29 30func (r Repo) RepoAt() syntax.ATURI { 31 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", r.Did, tangled.RepoNSID, r.Rkey)) 32} 33 34func (r Repo) DidSlashRepo() string { 35 p, _ := securejoin.SecureJoin(r.Did, r.Name) 36 return p 37} 38 39func GetAllRepos(e Execer, limit int) ([]Repo, error) { 40 var repos []Repo 41 42 rows, err := e.Query( 43 `select did, name, knot, rkey, description, created, source 44 from repos 45 order by created desc 46 limit ? 47 `, 48 limit, 49 ) 50 if err != nil { 51 return nil, err 52 } 53 defer rows.Close() 54 55 for rows.Next() { 56 var repo Repo 57 err := scanRepo( 58 rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created, &repo.Source, 59 ) 60 if err != nil { 61 return nil, err 62 } 63 repos = append(repos, repo) 64 } 65 66 if err := rows.Err(); err != nil { 67 return nil, err 68 } 69 70 return repos, nil 71} 72 73func GetAllReposByDid(e Execer, did string) ([]Repo, error) { 74 var repos []Repo 75 76 rows, err := e.Query( 77 `select 78 r.did, 79 r.name, 80 r.knot, 81 r.rkey, 82 r.description, 83 r.created, 84 count(s.id) as star_count, 85 r.source 86 from 87 repos r 88 left join 89 stars s on r.at_uri = s.repo_at 90 where 91 r.did = ? 92 group by 93 r.at_uri 94 order by r.created desc`, 95 did) 96 if err != nil { 97 return nil, err 98 } 99 defer rows.Close() 100 101 for rows.Next() { 102 var repo Repo 103 var repoStats RepoStats 104 var createdAt string 105 var nullableDescription sql.NullString 106 var nullableSource sql.NullString 107 108 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount, &nullableSource) 109 if err != nil { 110 return nil, err 111 } 112 113 if nullableDescription.Valid { 114 repo.Description = nullableDescription.String 115 } 116 117 if nullableSource.Valid { 118 repo.Source = nullableSource.String 119 } 120 121 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 122 if err != nil { 123 repo.Created = time.Now() 124 } else { 125 repo.Created = createdAtTime 126 } 127 128 repo.RepoStats = &repoStats 129 130 repos = append(repos, repo) 131 } 132 133 if err := rows.Err(); err != nil { 134 return nil, err 135 } 136 137 return repos, nil 138} 139 140func GetRepo(e Execer, did, name string) (*Repo, error) { 141 var repo Repo 142 var description, spindle sql.NullString 143 144 row := e.QueryRow(` 145 select did, name, knot, created, at_uri, description, spindle 146 from repos 147 where did = ? and name = ? 148 `, 149 did, 150 name, 151 ) 152 153 var createdAt string 154 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &description, &spindle); err != nil { 155 return nil, err 156 } 157 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) 158 repo.Created = createdAtTime 159 160 if description.Valid { 161 repo.Description = description.String 162 } 163 164 if spindle.Valid { 165 repo.Spindle = spindle.String 166 } 167 168 return &repo, nil 169} 170 171func GetRepoByAtUri(e Execer, atUri string) (*Repo, error) { 172 var repo Repo 173 var nullableDescription sql.NullString 174 175 row := e.QueryRow(`select did, name, knot, created, at_uri, description from repos where at_uri = ?`, atUri) 176 177 var createdAt string 178 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil { 179 return nil, err 180 } 181 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) 182 repo.Created = createdAtTime 183 184 if nullableDescription.Valid { 185 repo.Description = nullableDescription.String 186 } else { 187 repo.Description = "" 188 } 189 190 return &repo, nil 191} 192 193func AddRepo(e Execer, repo *Repo) error { 194 _, err := e.Exec( 195 `insert into repos 196 (did, name, knot, rkey, at_uri, description, source) 197 values (?, ?, ?, ?, ?, ?, ?)`, 198 repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri, repo.Description, repo.Source, 199 ) 200 return err 201} 202 203func RemoveRepo(e Execer, did, name string) error { 204 _, err := e.Exec(`delete from repos where did = ? and name = ?`, did, name) 205 return err 206} 207 208func GetRepoSource(e Execer, repoAt syntax.ATURI) (string, error) { 209 var nullableSource sql.NullString 210 err := e.QueryRow(`select source from repos where at_uri = ?`, repoAt).Scan(&nullableSource) 211 if err != nil { 212 return "", err 213 } 214 return nullableSource.String, nil 215} 216 217func GetForksByDid(e Execer, did string) ([]Repo, error) { 218 var repos []Repo 219 220 rows, err := e.Query( 221 `select did, name, knot, rkey, description, created, at_uri, source 222 from repos 223 where did = ? and source is not null and source != '' 224 order by created desc`, 225 did, 226 ) 227 if err != nil { 228 return nil, err 229 } 230 defer rows.Close() 231 232 for rows.Next() { 233 var repo Repo 234 var createdAt string 235 var nullableDescription sql.NullString 236 var nullableSource sql.NullString 237 238 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource) 239 if err != nil { 240 return nil, err 241 } 242 243 if nullableDescription.Valid { 244 repo.Description = nullableDescription.String 245 } 246 247 if nullableSource.Valid { 248 repo.Source = nullableSource.String 249 } 250 251 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 252 if err != nil { 253 repo.Created = time.Now() 254 } else { 255 repo.Created = createdAtTime 256 } 257 258 repos = append(repos, repo) 259 } 260 261 if err := rows.Err(); err != nil { 262 return nil, err 263 } 264 265 return repos, nil 266} 267 268func GetForkByDid(e Execer, did string, name string) (*Repo, error) { 269 var repo Repo 270 var createdAt string 271 var nullableDescription sql.NullString 272 var nullableSource sql.NullString 273 274 row := e.QueryRow( 275 `select did, name, knot, rkey, description, created, at_uri, source 276 from repos 277 where did = ? and name = ? and source is not null and source != ''`, 278 did, name, 279 ) 280 281 err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource) 282 if err != nil { 283 return nil, err 284 } 285 286 if nullableDescription.Valid { 287 repo.Description = nullableDescription.String 288 } 289 290 if nullableSource.Valid { 291 repo.Source = nullableSource.String 292 } 293 294 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 295 if err != nil { 296 repo.Created = time.Now() 297 } else { 298 repo.Created = createdAtTime 299 } 300 301 return &repo, nil 302} 303 304func AddCollaborator(e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error { 305 _, err := e.Exec( 306 `insert into collaborators (did, repo) 307 values (?, (select id from repos where did = ? and name = ? and knot = ?));`, 308 collaborator, repoOwnerDid, repoName, repoKnot) 309 return err 310} 311 312func UpdateDescription(e Execer, repoAt, newDescription string) error { 313 _, err := e.Exec( 314 `update repos set description = ? where at_uri = ?`, newDescription, repoAt) 315 return err 316} 317 318func UpdateSpindle(e Execer, repoAt, spindle string) error { 319 _, err := e.Exec( 320 `update repos set spindle = ? where at_uri = ?`, spindle, repoAt) 321 return err 322} 323 324func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) { 325 var repos []Repo 326 327 rows, err := e.Query( 328 `select 329 r.did, r.name, r.knot, r.rkey, r.description, r.created, count(s.id) as star_count 330 from 331 repos r 332 join 333 collaborators c on r.id = c.repo 334 left join 335 stars s on r.at_uri = s.repo_at 336 where 337 c.did = ? 338 group by 339 r.id;`, collaborator) 340 if err != nil { 341 return nil, err 342 } 343 defer rows.Close() 344 345 for rows.Next() { 346 var repo Repo 347 var repoStats RepoStats 348 var createdAt string 349 var nullableDescription sql.NullString 350 351 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount) 352 if err != nil { 353 return nil, err 354 } 355 356 if nullableDescription.Valid { 357 repo.Description = nullableDescription.String 358 } else { 359 repo.Description = "" 360 } 361 362 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 363 if err != nil { 364 repo.Created = time.Now() 365 } else { 366 repo.Created = createdAtTime 367 } 368 369 repo.RepoStats = &repoStats 370 371 repos = append(repos, repo) 372 } 373 374 if err := rows.Err(); err != nil { 375 return nil, err 376 } 377 378 return repos, nil 379} 380 381type RepoStats struct { 382 StarCount int 383 IssueCount IssueCount 384 PullCount PullCount 385} 386 387func scanRepo(rows *sql.Rows, did, name, knot, rkey, description *string, created *time.Time, source *string) error { 388 var createdAt string 389 var nullableDescription sql.NullString 390 var nullableSource sql.NullString 391 if err := rows.Scan(did, name, knot, rkey, &nullableDescription, &createdAt, &nullableSource); err != nil { 392 return err 393 } 394 395 if nullableDescription.Valid { 396 *description = nullableDescription.String 397 } else { 398 *description = "" 399 } 400 401 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 402 if err != nil { 403 *created = time.Now() 404 } else { 405 *created = createdAtTime 406 } 407 408 if nullableSource.Valid { 409 *source = nullableSource.String 410 } else { 411 *source = "" 412 } 413 414 return nil 415}