Monorepo for Tangled tangled.org
858
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/db,models: add repo_did columns and update model structs #272

open opened by oyster.cafe targeting master from lt/repo-rename-by-rkey
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mjm6w2jguo22
+637 -101
Diff #8
+414 -14
appview/db/db.go
··· 3 3 import ( 4 4 "context" 5 5 "database/sql" 6 + "fmt" 6 7 "log/slog" 7 8 "strings" 8 9 ··· 116 117 unique(repo_at, issue_id), 117 118 foreign key (repo_at) references repos(at_uri) on delete cascade 118 119 ); 119 - create table if not exists comments ( 120 - id integer primary key autoincrement, 121 - owner_did text not null, 122 - issue_id integer not null, 123 - repo_at text not null, 124 - comment_id integer not null, 125 - comment_at text not null, 126 - body text not null, 127 - created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 128 - unique(issue_id, comment_id), 129 - foreign key (repo_at, issue_id) references issues(repo_at, issue_id) on delete cascade 130 - ); 131 120 create table if not exists pulls ( 132 121 -- identifiers 133 122 id integer primary key autoincrement, ··· 693 682 create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read); 694 683 create index if not exists idx_references_from_at on reference_links(from_at); 695 684 create index if not exists idx_references_to_at on reference_links(to_at); 696 - create index if not exists idx_webhooks_repo_at on webhooks(repo_at); 697 685 create index if not exists idx_webhook_deliveries_webhook_id on webhook_deliveries(webhook_id); 698 - create index if not exists idx_site_deploys_repo_at on site_deploys(repo_at); 699 686 create index if not exists idx_newsletter_prefs_user_did on newsletter_preferences(user_did); 700 687 `) 701 688 if err != nil { ··· 1519 1506 }) 1520 1507 conn.ExecContext(ctx, "pragma foreign_keys = on;") 1521 1508 1509 + orm.RunMigration(conn, logger, "add-repo-renames", func(tx *sql.Tx) error { 1510 + res, err := tx.Exec(` 1511 + update repos 1512 + set name = name || '-renamed-' || id || '-' || lower(hex(randomblob(4))) 1513 + where id in ( 1514 + select id from ( 1515 + select id, row_number() over ( 1516 + partition by did, knot, name 1517 + order by created desc, id desc 1518 + ) as rn 1519 + from repos 1520 + ) where rn > 1 1521 + ); 1522 + `) 1523 + if err != nil { 1524 + return err 1525 + } 1526 + if n, _ := res.RowsAffected(); n > 0 { 1527 + logger.Warn("suffixed legacy duplicate repo names before adding unique index", "rows", n) 1528 + } 1529 + 1530 + var remaining int 1531 + if err := tx.QueryRow(` 1532 + select count(*) from ( 1533 + select 1 from repos group by did, knot, name having count(*) > 1 1534 + ) 1535 + `).Scan(&remaining); err != nil { 1536 + return fmt.Errorf("checking for residual duplicate (did, knot, name) groups: %w", err) 1537 + } 1538 + if remaining > 0 { 1539 + return fmt.Errorf("add-repo-renames: %d duplicate (did, knot, name) groups remain after suffix pass; manual cleanup required before unique index can be created", remaining) 1540 + } 1541 + 1542 + _, err = tx.Exec(` 1543 + create table if not exists repo_renames ( 1544 + owner_did text not null, 1545 + old_rkey text not null, 1546 + repo_did text not null, 1547 + renamed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1548 + primary key (owner_did, old_rkey) 1549 + ); 1550 + create unique index if not exists idx_repos_owner_knot_name 1551 + on repos(did, knot, name); 1552 + `) 1553 + return err 1554 + }) 1555 + 1556 + orm.RunMigration(conn, logger, "repos-canonical-rkey-uniqueness", func(tx *sql.Tx) error { 1557 + _, err := tx.Exec(` 1558 + drop index if exists idx_repos_owner_knot_name; 1559 + create unique index if not exists idx_repos_did_rkey 1560 + on repos(did, rkey); 1561 + `) 1562 + return err 1563 + }) 1564 + 1565 + orm.RunMigration(conn, logger, "repo-did-references", func(tx *sql.Tx) error { 1566 + tables := []struct{ table, oldCol, newCol string }{ 1567 + {"issues", "repo_at", "repo_did"}, 1568 + {"pulls", "repo_at", "repo_did"}, 1569 + {"pull_comments", "repo_at", "repo_did"}, 1570 + {"stars", "subject_at", "subject_did"}, 1571 + {"artifacts", "repo_at", "repo_did"}, 1572 + {"webhooks", "repo_at", "repo_did"}, 1573 + {"repo_sites", "repo_at", "repo_did"}, 1574 + {"site_deploys", "repo_at", "repo_did"}, 1575 + {"collaborators", "repo_at", "repo_did"}, 1576 + {"repo_issue_seqs", "repo_at", "repo_did"}, 1577 + {"repo_pull_seqs", "repo_at", "repo_did"}, 1578 + {"repo_languages", "repo_at", "repo_did"}, 1579 + {"repo_labels", "repo_at", "repo_did"}, 1580 + } 1581 + 1582 + stmts := "" 1583 + for _, t := range tables { 1584 + stmts += fmt.Sprintf( 1585 + `ALTER TABLE %s ADD COLUMN %s TEXT; 1586 + UPDATE %s SET %s = (SELECT repos.repo_did FROM repos WHERE repos.at_uri = %s.%s); 1587 + CREATE INDEX IF NOT EXISTS idx_%s_%s ON %s(%s); 1588 + `, t.table, t.newCol, t.table, t.newCol, t.table, t.oldCol, t.table, t.newCol, t.table, t.newCol) 1589 + } 1590 + 1591 + stmts += `ALTER TABLE pulls ADD COLUMN source_repo_did TEXT; 1592 + UPDATE pulls SET source_repo_did = (SELECT repos.repo_did FROM repos WHERE repos.at_uri = pulls.source_repo_at); 1593 + 1594 + UPDATE profile_pinned_repositories SET pin = ( 1595 + SELECT repos.repo_did FROM repos WHERE repos.at_uri = profile_pinned_repositories.pin 1596 + ) WHERE pin LIKE 'at://%' 1597 + AND EXISTS (SELECT 1 FROM repos WHERE repos.at_uri = profile_pinned_repositories.pin AND repos.repo_did IS NOT NULL AND repos.repo_did != ''); 1598 + ` 1599 + 1600 + _, err := tx.Exec(stmts) 1601 + return err 1602 + }) 1603 + 1604 + orm.RunMigration(conn, logger, "backfill-pds-rewrites-star-issue-pull-collab", func(tx *sql.Tx) error { 1605 + type source struct { 1606 + userDidCol string 1607 + table string 1608 + nsid string 1609 + fkCol string 1610 + } 1611 + sources := []source{ 1612 + {"did", "stars", "sh.tangled.feed.star", "subject_at"}, 1613 + {"did", "issues", "sh.tangled.repo.issue", "repo_at"}, 1614 + {"owner_did", "pulls", "sh.tangled.repo.pull", "repo_at"}, 1615 + {"did", "collaborators", "sh.tangled.repo.collaborator", "repo_at"}, 1616 + } 1617 + 1618 + for _, src := range sources { 1619 + _, err := tx.Exec(fmt.Sprintf(` 1620 + INSERT INTO pds_migration (name, did, collection, rkey, status) 1621 + SELECT 'add-repo-did', t.%s, '%s', t.rkey, 'pending' 1622 + FROM %s t 1623 + JOIN repos r ON r.at_uri = t.%s 1624 + WHERE r.repo_did IS NOT NULL AND r.repo_did != '' 1625 + ON CONFLICT(name, did, collection, rkey) DO NOTHING 1626 + `, src.userDidCol, src.nsid, src.table, src.fkCol)) 1627 + if err != nil { 1628 + return fmt.Errorf("backfill pds rewrites for %s: %w", src.table, err) 1629 + } 1630 + } 1631 + 1632 + return nil 1633 + }) 1634 + 1635 + orm.RunMigration(conn, logger, "backfill-pds-rewrites-profiles", func(tx *sql.Tx) error { 1636 + _, err := tx.Exec(` 1637 + INSERT INTO pds_migration (name, did, collection, rkey, status) 1638 + SELECT DISTINCT 'add-repo-did', pp.did, 'sh.tangled.actor.profile', 'self', 'pending' 1639 + FROM profile_pinned_repositories pp 1640 + JOIN repos r ON r.at_uri = pp.pin 1641 + WHERE pp.pin LIKE 'at://%' 1642 + AND r.repo_did IS NOT NULL AND r.repo_did != '' 1643 + ON CONFLICT(name, did, collection, rkey) DO NOTHING 1644 + `) 1645 + if err != nil { 1646 + return fmt.Errorf("backfill pds rewrites for profiles: %w", err) 1647 + } 1648 + return nil 1649 + }) 1650 + 1651 + conn.ExecContext(ctx, "pragma foreign_keys = off;") 1652 + orm.RunMigration(conn, logger, "drop-old-at-uri-columns", func(tx *sql.Tx) error { 1653 + _, err := tx.Exec(` 1654 + CREATE TABLE repos_new ( 1655 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1656 + did TEXT NOT NULL, 1657 + name TEXT NOT NULL, 1658 + knot TEXT NOT NULL, 1659 + rkey TEXT NOT NULL, 1660 + at_uri TEXT NOT NULL UNIQUE, 1661 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1662 + description TEXT CHECK (length(description) <= 200), 1663 + source TEXT, 1664 + spindle TEXT, 1665 + website TEXT, 1666 + topics TEXT, 1667 + repo_did TEXT, 1668 + UNIQUE(did, rkey) 1669 + ); 1670 + INSERT INTO repos_new (id, did, name, knot, rkey, at_uri, created, description, source, spindle, website, topics, repo_did) 1671 + SELECT id, did, name, knot, rkey, at_uri, created, description, source, spindle, website, topics, repo_did 1672 + FROM repos; 1673 + DROP TABLE repos; 1674 + ALTER TABLE repos_new RENAME TO repos; 1675 + CREATE UNIQUE INDEX idx_repos_repo_did ON repos(repo_did); 1676 + CREATE UNIQUE INDEX idx_repos_did_rkey ON repos(did, rkey); 1677 + 1678 + CREATE TABLE issues_new ( 1679 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1680 + did TEXT NOT NULL, 1681 + rkey TEXT NOT NULL, 1682 + at_uri TEXT GENERATED ALWAYS AS ('at://' || did || '/' || 'sh.tangled.repo.issue' || '/' || rkey) STORED, 1683 + repo_did TEXT NOT NULL, 1684 + issue_id INTEGER NOT NULL, 1685 + title TEXT NOT NULL, 1686 + body TEXT NOT NULL, 1687 + open INTEGER NOT NULL DEFAULT 1, 1688 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1689 + edited TEXT, 1690 + deleted TEXT, 1691 + UNIQUE(did, rkey), 1692 + UNIQUE(repo_did, issue_id), 1693 + UNIQUE(at_uri), 1694 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1695 + ); 1696 + INSERT INTO issues_new (id, did, rkey, repo_did, issue_id, title, body, open, created, edited, deleted) 1697 + SELECT id, did, rkey, repo_did, issue_id, title, body, open, created, edited, deleted 1698 + FROM issues WHERE repo_did IS NOT NULL AND repo_did != ''; 1699 + DROP TABLE issues; 1700 + ALTER TABLE issues_new RENAME TO issues; 1701 + CREATE INDEX idx_issues_repo_did ON issues(repo_did); 1702 + 1703 + CREATE TABLE pulls_new ( 1704 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1705 + pull_id INTEGER NOT NULL, 1706 + at_uri TEXT GENERATED ALWAYS AS ('at://' || owner_did || '/' || 'sh.tangled.repo.pull' || '/' || rkey) STORED, 1707 + repo_did TEXT NOT NULL, 1708 + owner_did TEXT NOT NULL, 1709 + rkey TEXT NOT NULL, 1710 + title TEXT NOT NULL, 1711 + body TEXT NOT NULL, 1712 + target_branch TEXT NOT NULL, 1713 + state INTEGER NOT NULL DEFAULT 0 CHECK (state IN (0, 1, 2, 3)), 1714 + source_branch TEXT, 1715 + source_repo_did TEXT, 1716 + change_id TEXT, 1717 + dependent_on TEXT, 1718 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1719 + UNIQUE(repo_did, pull_id), 1720 + UNIQUE(at_uri), 1721 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1722 + ); 1723 + INSERT INTO pulls_new (id, pull_id, repo_did, owner_did, rkey, title, body, target_branch, state, source_branch, source_repo_did, change_id, dependent_on, created) 1724 + SELECT id, pull_id, repo_did, owner_did, rkey, title, body, target_branch, state, source_branch, source_repo_did, change_id, dependent_on, created 1725 + FROM pulls WHERE repo_did IS NOT NULL AND repo_did != ''; 1726 + DROP TABLE pulls; 1727 + ALTER TABLE pulls_new RENAME TO pulls; 1728 + CREATE INDEX idx_pulls_repo_did ON pulls(repo_did); 1729 + CREATE INDEX idx_pulls_source_repo_did ON pulls(source_repo_did); 1730 + 1731 + CREATE TABLE pull_comments_new ( 1732 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1733 + pull_id INTEGER NOT NULL, 1734 + submission_id INTEGER NOT NULL, 1735 + repo_did TEXT NOT NULL, 1736 + owner_did TEXT NOT NULL, 1737 + comment_at TEXT NOT NULL, 1738 + body TEXT NOT NULL, 1739 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1740 + FOREIGN KEY (repo_did, pull_id) REFERENCES pulls(repo_did, pull_id) ON DELETE CASCADE, 1741 + FOREIGN KEY (submission_id) REFERENCES pull_submissions(id) ON DELETE CASCADE 1742 + ); 1743 + INSERT INTO pull_comments_new (id, pull_id, submission_id, repo_did, owner_did, comment_at, body, created) 1744 + SELECT id, pull_id, submission_id, repo_did, owner_did, comment_at, body, created 1745 + FROM pull_comments WHERE repo_did IS NOT NULL AND repo_did != ''; 1746 + DROP TABLE pull_comments; 1747 + ALTER TABLE pull_comments_new RENAME TO pull_comments; 1748 + CREATE INDEX idx_pull_comments_repo_did ON pull_comments(repo_did); 1749 + 1750 + CREATE TABLE stars_new ( 1751 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1752 + did TEXT NOT NULL, 1753 + rkey TEXT NOT NULL, 1754 + subject_type TEXT NOT NULL CHECK (subject_type IN ('repo', 'string')), 1755 + subject TEXT NOT NULL, 1756 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1757 + UNIQUE(did, rkey), 1758 + UNIQUE(did, subject) 1759 + ); 1760 + INSERT INTO stars_new (id, did, rkey, subject_type, subject, created) 1761 + SELECT id, did, rkey, 'repo', subject_did, created 1762 + FROM stars 1763 + WHERE subject_did IS NOT NULL AND subject_did != ''; 1764 + INSERT OR IGNORE INTO stars_new (id, did, rkey, subject_type, subject, created) 1765 + SELECT id, did, rkey, 'string', subject_at, created 1766 + FROM stars 1767 + WHERE (subject_did IS NULL OR subject_did = '') 1768 + AND subject_at LIKE 'at://%/sh.tangled.string/%'; 1769 + DROP TABLE stars; 1770 + ALTER TABLE stars_new RENAME TO stars; 1771 + CREATE INDEX idx_stars_subject ON stars(subject); 1772 + CREATE INDEX idx_stars_subject_type ON stars(subject_type); 1773 + CREATE INDEX idx_stars_created ON stars(created); 1774 + 1775 + CREATE TABLE collaborators_new ( 1776 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1777 + did TEXT NOT NULL, 1778 + rkey TEXT, 1779 + subject_did TEXT NOT NULL, 1780 + repo_did TEXT NOT NULL, 1781 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1782 + UNIQUE(did, rkey), 1783 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1784 + ); 1785 + INSERT INTO collaborators_new (id, did, rkey, subject_did, repo_did, created) 1786 + SELECT id, did, NULLIF(rkey, ''), subject_did, repo_did, created 1787 + FROM collaborators WHERE repo_did IS NOT NULL AND repo_did != ''; 1788 + DROP TABLE collaborators; 1789 + ALTER TABLE collaborators_new RENAME TO collaborators; 1790 + CREATE INDEX idx_collaborators_repo_did ON collaborators(repo_did); 1791 + 1792 + CREATE TABLE artifacts_new ( 1793 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1794 + did TEXT NOT NULL, 1795 + rkey TEXT NOT NULL, 1796 + repo_did TEXT NOT NULL, 1797 + tag BINARY(20) NOT NULL, 1798 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1799 + blob_cid TEXT NOT NULL, 1800 + name TEXT NOT NULL, 1801 + size INTEGER NOT NULL DEFAULT 0, 1802 + mimetype TEXT NOT NULL DEFAULT '*/*', 1803 + UNIQUE(did, rkey), 1804 + UNIQUE(repo_did, tag, name), 1805 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1806 + ); 1807 + INSERT INTO artifacts_new (id, did, rkey, repo_did, tag, created, blob_cid, name, size, mimetype) 1808 + SELECT id, did, rkey, repo_did, tag, created, blob_cid, name, size, mimetype 1809 + FROM artifacts WHERE repo_did IS NOT NULL AND repo_did != ''; 1810 + DROP TABLE artifacts; 1811 + ALTER TABLE artifacts_new RENAME TO artifacts; 1812 + CREATE INDEX idx_artifacts_repo_did ON artifacts(repo_did); 1813 + 1814 + CREATE TABLE webhooks_new ( 1815 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1816 + repo_did TEXT NOT NULL, 1817 + url TEXT NOT NULL, 1818 + secret TEXT, 1819 + active INTEGER NOT NULL DEFAULT 1, 1820 + events TEXT NOT NULL, 1821 + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1822 + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1823 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1824 + ); 1825 + INSERT INTO webhooks_new (id, repo_did, url, secret, active, events, created_at, updated_at) 1826 + SELECT id, repo_did, url, secret, active, events, created_at, updated_at 1827 + FROM webhooks WHERE repo_did IS NOT NULL AND repo_did != ''; 1828 + DROP TABLE webhooks; 1829 + ALTER TABLE webhooks_new RENAME TO webhooks; 1830 + CREATE INDEX idx_webhooks_repo_did ON webhooks(repo_did); 1831 + 1832 + CREATE TABLE repo_sites_new ( 1833 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1834 + repo_did TEXT NOT NULL UNIQUE, 1835 + branch TEXT NOT NULL, 1836 + dir TEXT NOT NULL DEFAULT '/', 1837 + is_index INTEGER NOT NULL DEFAULT 0, 1838 + created TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1839 + updated TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1840 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1841 + ); 1842 + INSERT INTO repo_sites_new (id, repo_did, branch, dir, is_index, created, updated) 1843 + SELECT id, repo_did, branch, dir, is_index, created, updated 1844 + FROM repo_sites WHERE repo_did IS NOT NULL AND repo_did != ''; 1845 + DROP TABLE repo_sites; 1846 + ALTER TABLE repo_sites_new RENAME TO repo_sites; 1847 + 1848 + CREATE TABLE site_deploys_new ( 1849 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1850 + repo_did TEXT NOT NULL, 1851 + branch TEXT NOT NULL, 1852 + dir TEXT NOT NULL DEFAULT '/', 1853 + commit_sha TEXT NOT NULL DEFAULT '', 1854 + status TEXT NOT NULL CHECK (status IN ('success', 'failure')), 1855 + trigger TEXT NOT NULL CHECK (trigger IN ('config_change', 'push')), 1856 + error TEXT NOT NULL DEFAULT '', 1857 + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1858 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1859 + ); 1860 + INSERT INTO site_deploys_new (id, repo_did, branch, dir, commit_sha, status, trigger, error, created_at) 1861 + SELECT id, repo_did, branch, dir, commit_sha, status, trigger, error, created_at 1862 + FROM site_deploys WHERE repo_did IS NOT NULL AND repo_did != ''; 1863 + DROP TABLE site_deploys; 1864 + ALTER TABLE site_deploys_new RENAME TO site_deploys; 1865 + CREATE INDEX idx_site_deploys_repo_did ON site_deploys(repo_did); 1866 + 1867 + CREATE TABLE repo_issue_seqs_new ( 1868 + repo_did TEXT PRIMARY KEY, 1869 + next_issue_id INTEGER NOT NULL DEFAULT 1, 1870 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1871 + ); 1872 + INSERT INTO repo_issue_seqs_new (repo_did, next_issue_id) 1873 + SELECT repo_did, next_issue_id 1874 + FROM repo_issue_seqs WHERE repo_did IS NOT NULL AND repo_did != ''; 1875 + DROP TABLE repo_issue_seqs; 1876 + ALTER TABLE repo_issue_seqs_new RENAME TO repo_issue_seqs; 1877 + 1878 + CREATE TABLE repo_pull_seqs_new ( 1879 + repo_did TEXT PRIMARY KEY, 1880 + next_pull_id INTEGER NOT NULL DEFAULT 1, 1881 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1882 + ); 1883 + INSERT INTO repo_pull_seqs_new (repo_did, next_pull_id) 1884 + SELECT repo_did, next_pull_id 1885 + FROM repo_pull_seqs WHERE repo_did IS NOT NULL AND repo_did != ''; 1886 + DROP TABLE repo_pull_seqs; 1887 + ALTER TABLE repo_pull_seqs_new RENAME TO repo_pull_seqs; 1888 + 1889 + CREATE TABLE repo_languages_new ( 1890 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1891 + repo_did TEXT NOT NULL, 1892 + ref TEXT NOT NULL, 1893 + is_default_ref INTEGER NOT NULL DEFAULT 0, 1894 + language TEXT NOT NULL, 1895 + bytes INTEGER NOT NULL CHECK (bytes >= 0), 1896 + UNIQUE(repo_did, ref, language), 1897 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1898 + ); 1899 + INSERT INTO repo_languages_new (id, repo_did, ref, is_default_ref, language, bytes) 1900 + SELECT id, repo_did, ref, is_default_ref, language, bytes 1901 + FROM repo_languages WHERE repo_did IS NOT NULL AND repo_did != ''; 1902 + DROP TABLE repo_languages; 1903 + ALTER TABLE repo_languages_new RENAME TO repo_languages; 1904 + 1905 + CREATE TABLE repo_labels_new ( 1906 + id INTEGER PRIMARY KEY AUTOINCREMENT, 1907 + repo_did TEXT NOT NULL, 1908 + label_at TEXT NOT NULL, 1909 + UNIQUE(repo_did, label_at), 1910 + FOREIGN KEY (repo_did) REFERENCES repos(repo_did) ON DELETE CASCADE 1911 + ); 1912 + INSERT INTO repo_labels_new (id, repo_did, label_at) 1913 + SELECT id, repo_did, label_at 1914 + FROM repo_labels WHERE repo_did IS NOT NULL AND repo_did != ''; 1915 + DROP TABLE repo_labels; 1916 + ALTER TABLE repo_labels_new RENAME TO repo_labels; 1917 + `) 1918 + return err 1919 + }) 1920 + conn.ExecContext(ctx, "pragma foreign_keys = on;") 1921 + 1522 1922 return &DB{ 1523 1923 db, 1524 1924 logger,
+1 -1
appview/models/artifact.go
··· 15 15 Did string 16 16 Rkey string 17 17 18 - RepoAt syntax.ATURI 18 + RepoDid syntax.DID 19 19 Tag plumbing.Hash 20 20 CreatedAt time.Time 21 21
+1 -1
appview/models/collaborator.go
··· 14 14 15 15 // content 16 16 SubjectDid syntax.DID 17 - RepoAt syntax.ATURI 17 + RepoDid syntax.DID 18 18 19 19 // meta 20 20 Created time.Time
+3 -12
appview/models/issue.go
··· 13 13 Id int64 14 14 Did string 15 15 Rkey string 16 - RepoAt syntax.ATURI 16 + RepoDid syntax.DID 17 17 IssueId int 18 18 Created time.Time 19 19 Edited *time.Time ··· 44 44 for i, uri := range i.References { 45 45 references[i] = string(uri) 46 46 } 47 - repoAtStr := i.RepoAt.String() 48 47 rec := tangled.RepoIssue{ 49 - Repo: &repoAtStr, 48 + Repo: string(i.RepoDid), 50 49 Title: i.Title, 51 50 Body: &i.Body, 52 51 Mentions: mentions, 53 52 References: references, 54 53 CreatedAt: i.Created.Format(time.RFC3339), 55 54 } 56 - if i.Repo != nil && i.Repo.RepoDid != "" { 57 - rec.RepoDid = &i.Repo.RepoDid 58 - } 59 55 return rec 60 56 } 61 57 ··· 166 162 body = *record.Body 167 163 } 168 164 169 - var repoAt syntax.ATURI 170 - if record.Repo != nil { 171 - repoAt = syntax.ATURI(*record.Repo) 172 - } 173 - 174 165 return Issue{ 175 - RepoAt: repoAt, 166 + RepoDid: syntax.DID(record.Repo), 176 167 Did: did, 177 168 Rkey: rkey, 178 169 Created: created,
+2 -4
appview/models/language.go
··· 1 1 package models 2 2 3 - import ( 4 - "github.com/bluesky-social/indigo/atproto/syntax" 5 - ) 3 + import "github.com/bluesky-social/indigo/atproto/syntax" 6 4 7 5 type RepoLanguage struct { 8 6 Id int64 9 - RepoAt syntax.ATURI 7 + RepoDid syntax.DID 10 8 Ref string 11 9 IsDefaultRef bool 12 10 Language string
+23 -48
appview/models/pull.go
··· 61 61 PullId int 62 62 63 63 // at ids 64 - RepoAt syntax.ATURI 64 + RepoDid syntax.DID 65 65 OwnerDid string 66 66 Rkey string 67 67 ··· 97 97 references[i] = string(uri) 98 98 } 99 99 100 - var targetRepoAt, targetRepoDid *string 101 - targetRepoAt = new(string) 102 - *targetRepoAt = p.RepoAt.String() 103 - if p.Repo != nil && p.Repo.RepoDid != "" { 104 - targetRepoDid = new(string) 105 - *targetRepoDid = p.Repo.RepoDid 106 - } 107 - 108 100 rounds := make([]*tangled.RepoPull_Round, len(p.Submissions)) 109 101 for i, submission := range p.Submissions { 110 102 rounds[i] = submission.AsRecord() ··· 123 115 References: references, 124 116 CreatedAt: p.Created.Format(time.RFC3339), 125 117 Target: &tangled.RepoPull_Target{ 126 - Repo: targetRepoAt, 127 - RepoDid: targetRepoDid, 128 - Branch: p.TargetBranch, 118 + Repo: string(p.RepoDid), 119 + Branch: p.TargetBranch, 129 120 }, 130 121 Rounds: rounds, 131 122 Source: p.PullSource.AsRecord(), ··· 151 142 } 152 143 } 153 144 154 - var targetRepoAt syntax.ATURI 145 + var targetRepoDid syntax.DID 155 146 var targetBranch string 156 147 if record.Target != nil { 157 - if record.Target.Repo != nil { 158 - uri, err := syntax.ParseATURI(*record.Target.Repo) 159 - if err != nil { 160 - return nil, fmt.Errorf("invalid target.repo aturi: %w", err) 161 - } 162 - targetRepoAt = uri 148 + did, err := syntax.ParseDID(record.Target.Repo) 149 + if err != nil { 150 + return nil, fmt.Errorf("invalid target.repo did: %w", err) 163 151 } 152 + targetRepoDid = did 164 153 targetBranch = record.Target.Branch 165 154 } 166 155 ··· 171 160 } 172 161 173 162 if record.Source.Repo != nil { 174 - uri, err := syntax.ParseATURI(*record.Source.Repo) 163 + did, err := syntax.ParseDID(*record.Source.Repo) 175 164 if err != nil { 176 - return nil, fmt.Errorf("invalid source.repo aturi: %w", err) 177 - } 178 - pullSource.RepoAt = &uri 179 - } 180 - if record.Source.RepoDid != nil { 181 - did, err := syntax.ParseDID(*record.Source.RepoDid) 182 - if err != nil { 183 - return nil, fmt.Errorf("invalid source.repoDid did: %w", err) 165 + return nil, fmt.Errorf("invalid source.repo did: %w", err) 184 166 } 185 167 pullSource.RepoDid = &did 186 168 } ··· 209 191 } 210 192 211 193 return &Pull{ 212 - RepoAt: targetRepoAt, 194 + RepoDid: targetRepoDid, 213 195 OwnerDid: did, 214 196 Rkey: rkey, 215 197 Title: record.Title, ··· 260 242 261 243 type PullSource struct { 262 244 Branch string 263 - RepoAt *syntax.ATURI 264 245 RepoDid *syntax.DID 265 246 266 247 // optionally populate this for reverse mappings ··· 271 252 if s == nil { 272 253 return nil 273 254 } 274 - var repoAt, repoDid *string 275 - if s.RepoAt != nil { 276 - repoAt = new(string) 277 - *repoAt = s.RepoAt.String() 278 - } 255 + var repo *string 279 256 if s.RepoDid != nil { 280 - repoDid = new(string) 281 - *repoDid = s.RepoDid.String() 257 + r := s.RepoDid.String() 258 + repo = &r 282 259 } 283 260 return &tangled.RepoPull_Source{ 284 - Branch: s.Branch, 285 - Repo: repoAt, 286 - RepoDid: repoDid, 261 + Branch: s.Branch, 262 + Repo: repo, 287 263 } 288 264 } 289 265 ··· 313 289 SubmissionId int 314 290 315 291 // at ids 316 - RepoAt string 292 + RepoDid string 317 293 OwnerDid string 318 294 CommentAt string 319 295 ··· 366 342 367 343 func (p *Pull) IsBranchBased() bool { 368 344 if p.PullSource != nil { 369 - if p.PullSource.RepoAt != nil { 370 - return p.PullSource.RepoAt == &p.RepoAt 371 - } else { 372 - // no repo specified 373 - return true 345 + if p.PullSource.RepoDid != nil { 346 + return *p.PullSource.RepoDid == p.RepoDid 374 347 } 348 + // no repo specified 349 + return true 375 350 } 376 351 return false 377 352 } 378 353 379 354 func (p *Pull) IsForkBased() bool { 380 355 if p.PullSource != nil { 381 - if p.PullSource.RepoAt != nil { 356 + if p.PullSource.RepoDid != nil { 382 357 // make sure repos are different 383 - return p.PullSource.RepoAt != &p.RepoAt 358 + return *p.PullSource.RepoDid != p.RepoDid 384 359 } 385 360 } 386 361 return false
+54 -3
appview/models/repo.go
··· 57 57 58 58 return tangled.Repo{ 59 59 Knot: r.Knot, 60 - Name: r.Name, 60 + Name: r.cosmeticName(), 61 61 Description: description, 62 62 Website: website, 63 63 Topics: r.Topics, ··· 69 69 } 70 70 } 71 71 72 + func (r *Repo) cosmeticName() *string { 73 + if r.Name == "" || r.Name == r.Rkey { 74 + return nil 75 + } 76 + return &r.Name 77 + } 78 + 72 79 func (r Repo) RepoAt() syntax.ATURI { 73 80 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", r.Did, tangled.RepoNSID, r.Rkey)) 74 81 } ··· 77 84 if r.RepoDid != "" { 78 85 return r.RepoDid 79 86 } 80 - p, _ := securejoin.SecureJoin(r.Did, r.Name) 87 + p, _ := securejoin.SecureJoin(r.Did, r.Rkey) 81 88 return p 82 89 } 83 90 ··· 113 120 114 121 type RepoLabel struct { 115 122 Id int64 116 - RepoAt syntax.ATURI 123 + RepoDid syntax.DID 117 124 LabelAt syntax.ATURI 118 125 } 119 126 127 + var reservedRepoNames = map[string]struct{}{ 128 + "self": {}, 129 + } 130 + 131 + func ValidateRepoName(name string) error { 132 + if len(name) == 0 { 133 + return fmt.Errorf("Repository name cannot be empty") 134 + } 135 + if len(name) > 100 { 136 + return fmt.Errorf("Repository name must be 100 characters or fewer") 137 + } 138 + 139 + if strings.Contains(name, "/") || strings.Contains(name, "\\") { 140 + return fmt.Errorf("Repository name contains invalid path characters") 141 + } 142 + 143 + if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { 144 + return fmt.Errorf("Repository name contains invalid path sequence") 145 + } 146 + 147 + for _, char := range name { 148 + if !((char >= 'a' && char <= 'z') || 149 + (char >= 'A' && char <= 'Z') || 150 + (char >= '0' && char <= '9') || 151 + char == '-' || char == '_' || char == '.') { 152 + return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores") 153 + } 154 + } 155 + 156 + if strings.Contains(name, "..") { 157 + return fmt.Errorf("Repository name cannot contain sequential dots") 158 + } 159 + 160 + if _, reserved := reservedRepoNames[strings.ToLower(name)]; reserved { 161 + return fmt.Errorf("Repository name %q is reserved", name) 162 + } 163 + 164 + return nil 165 + } 166 + 167 + func StripGitExt(name string) string { 168 + return strings.TrimSuffix(name, ".git") 169 + } 170 + 120 171 type RepoGroup struct { 121 172 Repo *Repo 122 173 Issues []Issue
+86
appview/models/repo_test.go
··· 1 + package models 2 + 3 + import ( 4 + "strings" 5 + "testing" 6 + ) 7 + 8 + func TestValidateRepoName_ValidRkeys(t *testing.T) { 9 + valid := []string{ 10 + "myrepo", 11 + "MyRepo", 12 + "my-repo", 13 + "my_repo", 14 + "my.repo", 15 + "a", 16 + "repo123", 17 + strings.Repeat("a", 100), 18 + } 19 + for _, name := range valid { 20 + if err := ValidateRepoName(name); err != nil { 21 + t.Errorf("ValidateRepoName(%q) = %v, want nil", name, err) 22 + } 23 + } 24 + } 25 + 26 + func TestValidateRepoName_InvalidRkeys(t *testing.T) { 27 + cases := []struct { 28 + input string 29 + substr string 30 + }{ 31 + {"", "empty"}, 32 + {strings.Repeat("a", 101), "100 characters"}, 33 + {"has space", "alphanumeric"}, 34 + {"has/slash", "invalid path"}, 35 + {"has\\backslash", "invalid path"}, 36 + {".dotprefix", "invalid path"}, 37 + {"dotsuffix.", "invalid path"}, 38 + {"two..dots", "sequential dots"}, 39 + {"../traversal", "invalid path"}, 40 + {"self", "reserved"}, 41 + {"SELF", "reserved"}, 42 + } 43 + for _, tc := range cases { 44 + err := ValidateRepoName(tc.input) 45 + if err == nil { 46 + t.Errorf("ValidateRepoName(%q) = nil, want error containing %q", tc.input, tc.substr) 47 + continue 48 + } 49 + if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.substr)) { 50 + t.Errorf("ValidateRepoName(%q) = %q, want substring %q", tc.input, err.Error(), tc.substr) 51 + } 52 + } 53 + } 54 + 55 + func TestStripGitExt(t *testing.T) { 56 + cases := []struct{ in, want string }{ 57 + {"repo.git", "repo"}, 58 + {"repo", "repo"}, 59 + {"repo.git.git", "repo.git"}, 60 + {".git", ""}, 61 + } 62 + for _, tc := range cases { 63 + if got := StripGitExt(tc.in); got != tc.want { 64 + t.Errorf("StripGitExt(%q) = %q, want %q", tc.in, got, tc.want) 65 + } 66 + } 67 + } 68 + 69 + func TestCosmeticName_NilWhenMatchesRkey(t *testing.T) { 70 + r := Repo{Name: "myrepo", Rkey: "myrepo"} 71 + rec := r.AsRecord() 72 + if rec.Name != nil { 73 + t.Errorf("cosmeticName should be nil when Name == Rkey, got %q", *rec.Name) 74 + } 75 + } 76 + 77 + func TestCosmeticName_PresentWhenDiffers(t *testing.T) { 78 + r := Repo{Name: "MyRepo", Rkey: "myrepo", Knot: "k"} 79 + rec := r.AsRecord() 80 + if rec.Name == nil { 81 + t.Fatal("cosmeticName should be non-nil when Name != Rkey") 82 + } 83 + if *rec.Name != "MyRepo" { 84 + t.Errorf("cosmeticName = %q, want %q", *rec.Name, "MyRepo") 85 + } 86 + }
+2 -2
appview/models/search.go
··· 5 5 type IssueSearchOptions struct { 6 6 Keywords []string 7 7 Phrases []string 8 - RepoAt string 8 + RepoDid string 9 9 IsOpen *bool 10 10 AuthorDid string 11 11 Labels []string ··· 31 31 type PullSearchOptions struct { 32 32 Keywords []string 33 33 Phrases []string 34 - RepoAt string 34 + RepoDid string 35 35 State *PullState 36 36 AuthorDid string 37 37 Labels []string
+4 -4
appview/models/search_test.go
··· 16 16 want: false, 17 17 }, 18 18 { 19 - name: "non-filter fields only (RepoAt, IsOpen, Page) return false", 20 - opts: IssueSearchOptions{RepoAt: "at://did:plc:abc/repo"}, 19 + name: "non-filter fields only (RepoDid, IsOpen, Page) return false", 20 + opts: IssueSearchOptions{RepoDid: "did:plc:abc"}, 21 21 want: false, 22 22 }, 23 23 { ··· 93 93 want: false, 94 94 }, 95 95 { 96 - name: "non-filter fields only (RepoAt, State, Page) return false", 97 - opts: PullSearchOptions{RepoAt: "at://did:plc:abc/repo"}, 96 + name: "non-filter fields only (RepoDid, State, Page) return false", 97 + opts: PullSearchOptions{RepoDid: "did:plc:abc"}, 98 98 want: false, 99 99 }, 100 100 {
+6 -2
appview/models/site_deploy.go
··· 1 1 package models 2 2 3 - import "time" 3 + import ( 4 + "time" 5 + 6 + "github.com/bluesky-social/indigo/atproto/syntax" 7 + ) 4 8 5 9 type SiteDeployStatus string 6 10 ··· 29 33 30 34 type SiteDeploy struct { 31 35 Id int64 32 - RepoAt string 36 + RepoDid syntax.DID 33 37 Branch string 34 38 Dir string 35 39 CommitSHA string
+7 -3
appview/models/sites.go
··· 1 1 package models 2 2 3 - import "time" 3 + import ( 4 + "time" 5 + 6 + "github.com/bluesky-social/indigo/atproto/syntax" 7 + ) 4 8 5 9 type DomainClaim struct { 6 10 ID int64 ··· 11 15 12 16 type RepoSite struct { 13 17 ID int64 14 - RepoAt string 15 - RepoName string // populated when joined with repos table 18 + RepoDid syntax.DID 19 + RepoRkey string // populated when joined with repos table 16 20 Branch string 17 21 Dir string 18 22 IsIndex bool
+11 -5
appview/models/star.go
··· 2 2 3 3 import ( 4 4 "time" 5 + ) 6 + 7 + type StarSubjectType string 5 8 6 - "github.com/bluesky-social/indigo/atproto/syntax" 9 + const ( 10 + StarSubjectRepo StarSubjectType = "repo" 11 + StarSubjectString StarSubjectType = "string" 7 12 ) 8 13 9 14 type Star struct { 10 - Did string 11 - RepoAt syntax.ATURI 12 - Created time.Time 13 - Rkey string 15 + Did string 16 + SubjectType StarSubjectType 17 + Subject string 18 + Created time.Time 19 + Rkey string 14 20 } 15 21 16 22 // RepoStar is used for reverse mapping to repos
+11 -2
appview/models/webhook.go
··· 10 10 type WebhookEvent string 11 11 12 12 const ( 13 - WebhookEventPush WebhookEvent = "push" 13 + WebhookEventPush WebhookEvent = "push" 14 + WebhookEventRepoRenamed WebhookEvent = "repository:renamed" 14 15 ) 15 16 16 17 type Webhook struct { 17 18 Id int64 18 - RepoAt syntax.ATURI 19 + RepoDid syntax.DID 19 20 Url string 20 21 Secret string 21 22 Active bool ··· 72 73 type WebhookUser struct { 73 74 Did string `json:"did"` 74 75 } 76 + 77 + // WebhookRenamePayload represents the payload for a repository:renamed event 78 + type WebhookRenamePayload struct { 79 + OldName string `json:"old_name"` 80 + NewName string `json:"new_name"` 81 + Repository WebhookRepository `json:"repository"` 82 + Sender WebhookUser `json:"sender"` 83 + }
+12
orm/orm.go
··· 3 3 import ( 4 4 "context" 5 5 "database/sql" 6 + "errors" 6 7 "fmt" 7 8 "log/slog" 8 9 "reflect" 9 10 "strings" 11 + 12 + "github.com/mattn/go-sqlite3" 10 13 ) 11 14 15 + func IsUniqueViolation(err error) bool { 16 + var sqlErr sqlite3.Error 17 + if !errors.As(err, &sqlErr) { 18 + return false 19 + } 20 + return sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique || 21 + sqlErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey 22 + } 23 + 12 24 type migrationFn = func(*sql.Tx) error 13 25 14 26 func RunMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error {

History

9 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
appview/db,models: add repo_did columns and update model structs
merge conflicts detected
expand
  • api/tangled/cbor_gen.go:866
  • api/tangled/feedstar.go:5
  • api/tangled/gitrefUpdate.go:29
  • api/tangled/repocollaborator.go:19
  • api/tangled/repoissue.go:22
  • api/tangled/repopull.go:39
  • api/tangled/tangledrepo.go:24
  • cmd/cborgen/cborgen.go:17
  • knotserver/xrpc/merge.go:118
  • lexicons/feed/star.json:10
  • lexicons/git/refUpdate.json:11
  • lexicons/issue/issue.json:9
  • lexicons/pulls/pull.json:65
  • lexicons/repo/collaborator.json:11
  • lexicons/repo/repo.json:6
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments
1 commit
expand
appview/db,models: add repo_did columns and update model structs
expand 0 comments