package db import ( "database/sql" "errors" "github.com/bluesky-social/indigo/atproto/syntax" ) type Repo struct { Knot string Owner syntax.DID Rkey syntax.RecordKey RepoDid syntax.DID CreatedAt string } func (d *DB) UpsertRepo(repo Repo) error { var createdAt sql.NullString if repo.CreatedAt != "" { createdAt = sql.NullString{String: repo.CreatedAt, Valid: true} } _, err := d.Exec( `insert or replace into repos (repo_did, knot, owner, rkey, created_at) values (?, ?, ?, ?, ?)`, repo.RepoDid, repo.Knot, repo.Owner, repo.Rkey, createdAt, ) return err } func (d *DB) RepoOwners() ([]syntax.DID, error) { repos, err := d.AllRepos() if err != nil { return nil, err } seen := make(map[syntax.DID]struct{}, len(repos)) dids := make([]syntax.DID, 0, len(repos)) for _, r := range repos { if r.Owner == "" { continue } if _, ok := seen[r.Owner]; ok { continue } seen[r.Owner] = struct{}{} dids = append(dids, r.Owner) } return dids, nil } func (d *DB) Knots() ([]string, error) { rows, err := d.Query(`select distinct knot from repos`) if err != nil { return nil, err } defer rows.Close() var knots []string for rows.Next() { var knot string if err := rows.Scan(&knot); err != nil { return nil, err } knots = append(knots, knot) } if err := rows.Err(); err != nil { return nil, err } return knots, nil } func scanRepo(row interface{ Scan(...any) error }) (*Repo, error) { var knot, owner, rkey, repoDid string if err := row.Scan(&knot, &owner, &rkey, &repoDid); err != nil { return nil, err } return &Repo{ Knot: knot, Owner: syntax.DID(owner), Rkey: syntax.RecordKey(rkey), RepoDid: syntax.DID(repoDid), }, nil } func (d *DB) GetRepoByDid(repoDid syntax.DID) (*Repo, error) { return scanRepo(d.QueryRow( `select knot, owner, rkey, repo_did from repos where repo_did = ?`, repoDid.String(), )) } func (d *DB) AllRepos() ([]Repo, error) { rows, err := d.Query(`select knot, owner, rkey, repo_did from repos`) if err != nil { return nil, err } defer rows.Close() var repos []Repo for rows.Next() { r, err := scanRepo(rows) if err != nil { return nil, err } repos = append(repos, *r) } if err := rows.Err(); err != nil { return nil, err } return repos, nil } func (d *DB) DeleteRepo(repoDid syntax.DID) error { _, err := d.Exec(`delete from repos where repo_did = ?`, repoDid) return err } // DeleteRepoByOwnerRkey deletes a repo by (owner,rkey) pair and returns deleted repos DID. func (d *DB) DeleteRepoByOwnerRkey(owner syntax.DID, rkey syntax.RecordKey) (syntax.DID, error) { var repoDid string err := d.QueryRow( `delete from repos where owner = ? and rkey = ? returning repo_did`, owner.String(), rkey.String(), ).Scan(&repoDid) if errors.Is(err, sql.ErrNoRows) { return "", nil } if err != nil { return "", err } return syntax.DID(repoDid), nil }