Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271package db
import ( "database/sql" "errors" "fmt" "time"
"tangled.org/core/appview/models")
var ( ErrDomainTaken = errors.New("domain is already claimed by another user") ErrDomainCooldown = errors.New("domain is in a 30-day cooldown period after being released") ErrAlreadyClaimed = errors.New("you already have an active domain claim; release it before claiming a new one"))
func scanClaim(row *sql.Row) (*models.DomainClaim, error) { var claim models.DomainClaim var deletedStr sql.NullString
if err := row.Scan(&claim.ID, &claim.Did, &claim.Domain, &deletedStr); err != nil { return nil, err }
if deletedStr.Valid { t, err := time.Parse(time.RFC3339, deletedStr.String) if err != nil { return nil, fmt.Errorf("parsing deleted timestamp: %w", err) } claim.Deleted = &t }
return &claim, nil}
func GetDomainClaimByDomain(e Execer, domain string) (*models.DomainClaim, error) { row := e.QueryRow(` select id, did, domain, deleted from domain_claims where domain = ? `, domain) return scanClaim(row)}
func GetActiveDomainClaimForDid(e Execer, did string) (*models.DomainClaim, error) { row := e.QueryRow(` select id, did, domain, deleted from domain_claims where did = ? and deleted is null `, did) return scanClaim(row)}
func GetDomainClaimForDid(e Execer, did string) (*models.DomainClaim, error) { row := e.QueryRow(` select id, did, domain, deleted from domain_claims where did = ? `, did) return scanClaim(row)}
func ClaimDomain(e Execer, did, domain string) error { const cooldown = 30 * 24 * time.Hour
domainRow, err := GetDomainClaimByDomain(e, domain) if err != nil && !errors.Is(err, sql.ErrNoRows) { return fmt.Errorf("looking up domain: %w", err) }
if domainRow != nil { if domainRow.Did == did { if domainRow.Deleted == nil { return nil } if time.Since(*domainRow.Deleted) < cooldown { return ErrDomainCooldown } _, err = e.Exec(` update domain_claims set deleted = null where did = ? and domain = ? `, did, domain) return err }
if domainRow.Deleted == nil { return ErrDomainTaken } if time.Since(*domainRow.Deleted) < cooldown { return ErrDomainCooldown }
if _, err = e.Exec(`delete from domain_claims where domain = ?`, domain); err != nil { return fmt.Errorf("clearing expired domain row: %w", err) } }
didRow, err := GetDomainClaimForDid(e, did) if err != nil && !errors.Is(err, sql.ErrNoRows) { return fmt.Errorf("looking up DID claim: %w", err) }
if didRow == nil { _, err = e.Exec(` insert into domain_claims (did, domain) values (?, ?) `, did, domain) return err }
if didRow.Deleted == nil { return ErrAlreadyClaimed }
_, err = e.Exec(` update domain_claims set domain = ?, deleted = null where did = ? `, domain, did) return err}
func ReleaseDomain(e Execer, did, domain string) error { result, err := e.Exec(` update domain_claims set deleted = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') where did = ? and domain = ? and deleted is null `, did, domain) if err != nil { return err }
n, err := result.RowsAffected() if err != nil { return err } if n == 0 { return errors.New("domain not found or not actively claimed by this account") } return nil}
// GetRepoSiteConfig returns the site configuration for a repo, or nil if not configured.func GetRepoSiteConfig(e Execer, repoDid string) (*models.RepoSite, error) { row := e.QueryRow(` select id, repo_did, branch, dir, is_index, created, updated from repo_sites where repo_did = ? `, repoDid)
var s models.RepoSite var isIndex int var createdStr, updatedStr string
err := row.Scan(&s.ID, &s.RepoDid, &s.Branch, &s.Dir, &isIndex, &createdStr, &updatedStr) if errors.Is(err, sql.ErrNoRows) { return nil, nil } if err != nil { return nil, err }
s.IsIndex = isIndex != 0
s.Created, err = time.Parse(time.RFC3339, createdStr) if err != nil { return nil, fmt.Errorf("parsing created timestamp: %w", err) }
s.Updated, err = time.Parse(time.RFC3339, updatedStr) if err != nil { return nil, fmt.Errorf("parsing updated timestamp: %w", err) }
return &s, nil}
// SetRepoSiteConfig inserts or replaces the site configuration for a repo.func SetRepoSiteConfig(e Execer, repoDid, branch, dir string, isIndex bool) error { isIndexInt := 0 if isIndex { isIndexInt = 1 }
_, err := e.Exec(` insert into repo_sites (repo_did, branch, dir, is_index, updated) values (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) on conflict(repo_did) do update set branch = excluded.branch, dir = excluded.dir, is_index = excluded.is_index, updated = excluded.updated `, repoDid, branch, dir, isIndexInt) return err}
// DeleteRepoSiteConfig removes the site configuration for a repo.func DeleteRepoSiteConfig(e Execer, repoDid string) error { _, err := e.Exec(`delete from repo_sites where repo_did = ?`, repoDid) return err}
// GetRepoSiteConfigsForDid returns all site configurations for repos owned by a DID.// RepoRkey is populated on each returned RepoSite.func GetRepoSiteConfigsForDid(e Execer, did string) ([]*models.RepoSite, error) { rows, err := e.Query(` select rs.id, rs.repo_did, r.rkey, rs.branch, rs.dir, rs.is_index, rs.created, rs.updated from repo_sites rs join repos r on r.repo_did = rs.repo_did where r.did = ? `, did) if err != nil { return nil, err } defer rows.Close()
var sites []*models.RepoSite for rows.Next() { var s models.RepoSite var isIndex int var createdStr, updatedStr string if err := rows.Scan(&s.ID, &s.RepoDid, &s.RepoRkey, &s.Branch, &s.Dir, &isIndex, &createdStr, &updatedStr); err != nil { return nil, err } s.IsIndex = isIndex != 0 s.Created, err = time.Parse(time.RFC3339, createdStr) if err != nil { return nil, fmt.Errorf("parsing created timestamp: %w", err) } s.Updated, err = time.Parse(time.RFC3339, updatedStr) if err != nil { return nil, fmt.Errorf("parsing updated timestamp: %w", err) } sites = append(sites, &s) } return sites, rows.Err()}
// DeleteRepoSiteConfigsForDid removes all site configurations for repos owned by a DID.func DeleteRepoSiteConfigsForDid(e Execer, did string) error { _, err := e.Exec(` delete from repo_sites where repo_did in ( select repo_did from repos where did = ? ) `, did) return err}
// GetIndexRepoDidForDid returns the repo_did of the repo that currently holds// is_index=1 for the given DID, excluding excludeRepoDid (the current repo).// Returns "", nil if no other repo is the index site.func GetIndexRepoDidForDid(e Execer, did, excludeRepoDid string) (string, error) { row := e.QueryRow(` select rs.repo_did from repo_sites rs join repos r on r.repo_did = rs.repo_did where r.did = ? and rs.is_index = 1 and rs.repo_did != ? limit 1 `, did, excludeRepoDid)
var repoDid string err := row.Scan(&repoDid) if errors.Is(err, sql.ErrNoRows) { return "", nil } if err != nil { return "", err } return repoDid, nil}