forked from tangled.org/core
Monorepo for Tangled

Compare changes

Choose any two refs to compare.

+3 -2
appview/db/artifact.go
··· 8 "github.com/go-git/go-git/v5/plumbing" 9 "github.com/ipfs/go-cid" 10 "tangled.org/core/appview/models" 11 ) 12 13 func AddArtifact(e Execer, artifact models.Artifact) error { ··· 37 return err 38 } 39 40 - func GetArtifact(e Execer, filters ...filter) ([]models.Artifact, error) { 41 var artifacts []models.Artifact 42 43 var conditions []string ··· 109 return artifacts, nil 110 } 111 112 - func DeleteArtifact(e Execer, filters ...filter) error { 113 var conditions []string 114 var args []any 115 for _, filter := range filters {
··· 8 "github.com/go-git/go-git/v5/plumbing" 9 "github.com/ipfs/go-cid" 10 "tangled.org/core/appview/models" 11 + "tangled.org/core/orm" 12 ) 13 14 func AddArtifact(e Execer, artifact models.Artifact) error { ··· 38 return err 39 } 40 41 + func GetArtifact(e Execer, filters ...orm.Filter) ([]models.Artifact, error) { 42 var artifacts []models.Artifact 43 44 var conditions []string ··· 110 return artifacts, nil 111 } 112 113 + func DeleteArtifact(e Execer, filters ...orm.Filter) error { 114 var conditions []string 115 var args []any 116 for _, filter := range filters {
+4 -3
appview/db/collaborators.go
··· 6 "time" 7 8 "tangled.org/core/appview/models" 9 ) 10 11 func AddCollaborator(e Execer, c models.Collaborator) error { ··· 16 return err 17 } 18 19 - func DeleteCollaborator(e Execer, filters ...filter) error { 20 var conditions []string 21 var args []any 22 for _, filter := range filters { ··· 58 return nil, nil 59 } 60 61 - return GetRepos(e, 0, FilterIn("at_uri", repoAts)) 62 } 63 64 - func GetCollaborators(e Execer, filters ...filter) ([]models.Collaborator, error) { 65 var collaborators []models.Collaborator 66 var conditions []string 67 var args []any
··· 6 "time" 7 8 "tangled.org/core/appview/models" 9 + "tangled.org/core/orm" 10 ) 11 12 func AddCollaborator(e Execer, c models.Collaborator) error { ··· 17 return err 18 } 19 20 + func DeleteCollaborator(e Execer, filters ...orm.Filter) error { 21 var conditions []string 22 var args []any 23 for _, filter := range filters { ··· 59 return nil, nil 60 } 61 62 + return GetRepos(e, 0, orm.FilterIn("at_uri", repoAts)) 63 } 64 65 + func GetCollaborators(e Execer, filters ...orm.Filter) ([]models.Collaborator, error) { 66 var collaborators []models.Collaborator 67 var conditions []string 68 var args []any
+24 -137
appview/db/db.go
··· 3 import ( 4 "context" 5 "database/sql" 6 - "fmt" 7 "log/slog" 8 - "reflect" 9 "strings" 10 11 _ "github.com/mattn/go-sqlite3" 12 "tangled.org/core/log" 13 ) 14 15 type DB struct { ··· 584 } 585 586 // run migrations 587 - runMigration(conn, logger, "add-description-to-repos", func(tx *sql.Tx) error { 588 tx.Exec(` 589 alter table repos add column description text check (length(description) <= 200); 590 `) 591 return nil 592 }) 593 594 - runMigration(conn, logger, "add-rkey-to-pubkeys", func(tx *sql.Tx) error { 595 // add unconstrained column 596 _, err := tx.Exec(` 597 alter table public_keys ··· 614 return nil 615 }) 616 617 - runMigration(conn, logger, "add-rkey-to-comments", func(tx *sql.Tx) error { 618 _, err := tx.Exec(` 619 alter table comments drop column comment_at; 620 alter table comments add column rkey text; ··· 622 return err 623 }) 624 625 - runMigration(conn, logger, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error { 626 _, err := tx.Exec(` 627 alter table comments add column deleted text; -- timestamp 628 alter table comments add column edited text; -- timestamp ··· 630 return err 631 }) 632 633 - runMigration(conn, logger, "add-source-info-to-pulls-and-submissions", func(tx *sql.Tx) error { 634 _, err := tx.Exec(` 635 alter table pulls add column source_branch text; 636 alter table pulls add column source_repo_at text; ··· 639 return err 640 }) 641 642 - runMigration(conn, logger, "add-source-to-repos", func(tx *sql.Tx) error { 643 _, err := tx.Exec(` 644 alter table repos add column source text; 645 `) ··· 651 // 652 // [0]: https://sqlite.org/pragma.html#pragma_foreign_keys 653 conn.ExecContext(ctx, "pragma foreign_keys = off;") 654 - runMigration(conn, logger, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error { 655 _, err := tx.Exec(` 656 create table pulls_new ( 657 -- identifiers ··· 708 }) 709 conn.ExecContext(ctx, "pragma foreign_keys = on;") 710 711 - runMigration(conn, logger, "add-spindle-to-repos", func(tx *sql.Tx) error { 712 tx.Exec(` 713 alter table repos add column spindle text; 714 `) ··· 718 // drop all knot secrets, add unique constraint to knots 719 // 720 // knots will henceforth use service auth for signed requests 721 - runMigration(conn, logger, "no-more-secrets", func(tx *sql.Tx) error { 722 _, err := tx.Exec(` 723 create table registrations_new ( 724 id integer primary key autoincrement, ··· 741 }) 742 743 // recreate and add rkey + created columns with default constraint 744 - runMigration(conn, logger, "rework-collaborators-table", func(tx *sql.Tx) error { 745 // create new table 746 // - repo_at instead of repo integer 747 // - rkey field ··· 795 return err 796 }) 797 798 - runMigration(conn, logger, "add-rkey-to-issues", func(tx *sql.Tx) error { 799 _, err := tx.Exec(` 800 alter table issues add column rkey text not null default ''; 801 ··· 807 }) 808 809 // repurpose the read-only column to "needs-upgrade" 810 - runMigration(conn, logger, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error { 811 _, err := tx.Exec(` 812 alter table registrations rename column read_only to needs_upgrade; 813 `) ··· 815 }) 816 817 // require all knots to upgrade after the release of total xrpc 818 - runMigration(conn, logger, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error { 819 _, err := tx.Exec(` 820 update registrations set needs_upgrade = 1; 821 `) ··· 823 }) 824 825 // require all knots to upgrade after the release of total xrpc 826 - runMigration(conn, logger, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error { 827 _, err := tx.Exec(` 828 alter table spindles add column needs_upgrade integer not null default 0; 829 `) ··· 841 // 842 // disable foreign-keys for the next migration 843 conn.ExecContext(ctx, "pragma foreign_keys = off;") 844 - runMigration(conn, logger, "remove-issue-at-from-issues", func(tx *sql.Tx) error { 845 _, err := tx.Exec(` 846 create table if not exists issues_new ( 847 -- identifiers ··· 911 // - new columns 912 // * column "reply_to" which can be any other comment 913 // * column "at-uri" which is a generated column 914 - runMigration(conn, logger, "rework-issue-comments", func(tx *sql.Tx) error { 915 _, err := tx.Exec(` 916 create table if not exists issue_comments ( 917 -- identifiers ··· 971 // 972 // disable foreign-keys for the next migration 973 conn.ExecContext(ctx, "pragma foreign_keys = off;") 974 - runMigration(conn, logger, "add-at-uri-to-pulls", func(tx *sql.Tx) error { 975 _, err := tx.Exec(` 976 create table if not exists pulls_new ( 977 -- identifiers ··· 1052 // 1053 // disable foreign-keys for the next migration 1054 conn.ExecContext(ctx, "pragma foreign_keys = off;") 1055 - runMigration(conn, logger, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error { 1056 _, err := tx.Exec(` 1057 create table if not exists pull_submissions_new ( 1058 -- identifiers ··· 1106 1107 // knots may report the combined patch for a comparison, we can store that on the appview side 1108 // (but not on the pds record), because calculating the combined patch requires a git index 1109 - runMigration(conn, logger, "add-combined-column-submissions", func(tx *sql.Tx) error { 1110 _, err := tx.Exec(` 1111 alter table pull_submissions add column combined text; 1112 `) 1113 return err 1114 }) 1115 1116 - runMigration(conn, logger, "add-pronouns-profile", func(tx *sql.Tx) error { 1117 _, err := tx.Exec(` 1118 alter table profile add column pronouns text; 1119 `) 1120 return err 1121 }) 1122 1123 - runMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error { 1124 _, err := tx.Exec(` 1125 alter table repos add column website text; 1126 alter table repos add column topics text; ··· 1128 return err 1129 }) 1130 1131 - runMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error { 1132 _, err := tx.Exec(` 1133 alter table notification_preferences add column user_mentioned integer not null default 1; 1134 `) ··· 1136 }) 1137 1138 // remove the foreign key constraints from stars. 1139 - runMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error { 1140 _, err := tx.Exec(` 1141 create table stars_new ( 1142 id integer primary key autoincrement, ··· 1180 }, nil 1181 } 1182 1183 - type migrationFn = func(*sql.Tx) error 1184 - 1185 - func runMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error { 1186 - logger = logger.With("migration", name) 1187 - 1188 - tx, err := c.BeginTx(context.Background(), nil) 1189 - if err != nil { 1190 - return err 1191 - } 1192 - defer tx.Rollback() 1193 - 1194 - var exists bool 1195 - err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists) 1196 - if err != nil { 1197 - return err 1198 - } 1199 - 1200 - if !exists { 1201 - // run migration 1202 - err = migrationFn(tx) 1203 - if err != nil { 1204 - logger.Error("failed to run migration", "err", err) 1205 - return err 1206 - } 1207 - 1208 - // mark migration as complete 1209 - _, err = tx.Exec("insert into migrations (name) values (?)", name) 1210 - if err != nil { 1211 - logger.Error("failed to mark migration as complete", "err", err) 1212 - return err 1213 - } 1214 - 1215 - // commit the transaction 1216 - if err := tx.Commit(); err != nil { 1217 - return err 1218 - } 1219 - 1220 - logger.Info("migration applied successfully") 1221 - } else { 1222 - logger.Warn("skipped migration, already applied") 1223 - } 1224 - 1225 - return nil 1226 - } 1227 - 1228 func (d *DB) Close() error { 1229 return d.DB.Close() 1230 } 1231 - 1232 - type filter struct { 1233 - key string 1234 - arg any 1235 - cmp string 1236 - } 1237 - 1238 - func newFilter(key, cmp string, arg any) filter { 1239 - return filter{ 1240 - key: key, 1241 - arg: arg, 1242 - cmp: cmp, 1243 - } 1244 - } 1245 - 1246 - func FilterEq(key string, arg any) filter { return newFilter(key, "=", arg) } 1247 - func FilterNotEq(key string, arg any) filter { return newFilter(key, "<>", arg) } 1248 - func FilterGte(key string, arg any) filter { return newFilter(key, ">=", arg) } 1249 - func FilterLte(key string, arg any) filter { return newFilter(key, "<=", arg) } 1250 - func FilterIs(key string, arg any) filter { return newFilter(key, "is", arg) } 1251 - func FilterIsNot(key string, arg any) filter { return newFilter(key, "is not", arg) } 1252 - func FilterIn(key string, arg any) filter { return newFilter(key, "in", arg) } 1253 - func FilterLike(key string, arg any) filter { return newFilter(key, "like", arg) } 1254 - func FilterNotLike(key string, arg any) filter { return newFilter(key, "not like", arg) } 1255 - func FilterContains(key string, arg any) filter { 1256 - return newFilter(key, "like", fmt.Sprintf("%%%v%%", arg)) 1257 - } 1258 - 1259 - func (f filter) Condition() string { 1260 - rv := reflect.ValueOf(f.arg) 1261 - kind := rv.Kind() 1262 - 1263 - // if we have `FilterIn(k, [1, 2, 3])`, compile it down to `k in (?, ?, ?)` 1264 - if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array { 1265 - if rv.Len() == 0 { 1266 - // always false 1267 - return "1 = 0" 1268 - } 1269 - 1270 - placeholders := make([]string, rv.Len()) 1271 - for i := range placeholders { 1272 - placeholders[i] = "?" 1273 - } 1274 - 1275 - return fmt.Sprintf("%s %s (%s)", f.key, f.cmp, strings.Join(placeholders, ", ")) 1276 - } 1277 - 1278 - return fmt.Sprintf("%s %s ?", f.key, f.cmp) 1279 - } 1280 - 1281 - func (f filter) Arg() []any { 1282 - rv := reflect.ValueOf(f.arg) 1283 - kind := rv.Kind() 1284 - if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array { 1285 - if rv.Len() == 0 { 1286 - return nil 1287 - } 1288 - 1289 - out := make([]any, rv.Len()) 1290 - for i := range rv.Len() { 1291 - out[i] = rv.Index(i).Interface() 1292 - } 1293 - return out 1294 - } 1295 - 1296 - return []any{f.arg} 1297 - }
··· 3 import ( 4 "context" 5 "database/sql" 6 "log/slog" 7 "strings" 8 9 _ "github.com/mattn/go-sqlite3" 10 "tangled.org/core/log" 11 + "tangled.org/core/orm" 12 ) 13 14 type DB struct { ··· 583 } 584 585 // run migrations 586 + orm.RunMigration(conn, logger, "add-description-to-repos", func(tx *sql.Tx) error { 587 tx.Exec(` 588 alter table repos add column description text check (length(description) <= 200); 589 `) 590 return nil 591 }) 592 593 + orm.RunMigration(conn, logger, "add-rkey-to-pubkeys", func(tx *sql.Tx) error { 594 // add unconstrained column 595 _, err := tx.Exec(` 596 alter table public_keys ··· 613 return nil 614 }) 615 616 + orm.RunMigration(conn, logger, "add-rkey-to-comments", func(tx *sql.Tx) error { 617 _, err := tx.Exec(` 618 alter table comments drop column comment_at; 619 alter table comments add column rkey text; ··· 621 return err 622 }) 623 624 + orm.RunMigration(conn, logger, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error { 625 _, err := tx.Exec(` 626 alter table comments add column deleted text; -- timestamp 627 alter table comments add column edited text; -- timestamp ··· 629 return err 630 }) 631 632 + orm.RunMigration(conn, logger, "add-source-info-to-pulls-and-submissions", func(tx *sql.Tx) error { 633 _, err := tx.Exec(` 634 alter table pulls add column source_branch text; 635 alter table pulls add column source_repo_at text; ··· 638 return err 639 }) 640 641 + orm.RunMigration(conn, logger, "add-source-to-repos", func(tx *sql.Tx) error { 642 _, err := tx.Exec(` 643 alter table repos add column source text; 644 `) ··· 650 // 651 // [0]: https://sqlite.org/pragma.html#pragma_foreign_keys 652 conn.ExecContext(ctx, "pragma foreign_keys = off;") 653 + orm.RunMigration(conn, logger, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error { 654 _, err := tx.Exec(` 655 create table pulls_new ( 656 -- identifiers ··· 707 }) 708 conn.ExecContext(ctx, "pragma foreign_keys = on;") 709 710 + orm.RunMigration(conn, logger, "add-spindle-to-repos", func(tx *sql.Tx) error { 711 tx.Exec(` 712 alter table repos add column spindle text; 713 `) ··· 717 // drop all knot secrets, add unique constraint to knots 718 // 719 // knots will henceforth use service auth for signed requests 720 + orm.RunMigration(conn, logger, "no-more-secrets", func(tx *sql.Tx) error { 721 _, err := tx.Exec(` 722 create table registrations_new ( 723 id integer primary key autoincrement, ··· 740 }) 741 742 // recreate and add rkey + created columns with default constraint 743 + orm.RunMigration(conn, logger, "rework-collaborators-table", func(tx *sql.Tx) error { 744 // create new table 745 // - repo_at instead of repo integer 746 // - rkey field ··· 794 return err 795 }) 796 797 + orm.RunMigration(conn, logger, "add-rkey-to-issues", func(tx *sql.Tx) error { 798 _, err := tx.Exec(` 799 alter table issues add column rkey text not null default ''; 800 ··· 806 }) 807 808 // repurpose the read-only column to "needs-upgrade" 809 + orm.RunMigration(conn, logger, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error { 810 _, err := tx.Exec(` 811 alter table registrations rename column read_only to needs_upgrade; 812 `) ··· 814 }) 815 816 // require all knots to upgrade after the release of total xrpc 817 + orm.RunMigration(conn, logger, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error { 818 _, err := tx.Exec(` 819 update registrations set needs_upgrade = 1; 820 `) ··· 822 }) 823 824 // require all knots to upgrade after the release of total xrpc 825 + orm.RunMigration(conn, logger, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error { 826 _, err := tx.Exec(` 827 alter table spindles add column needs_upgrade integer not null default 0; 828 `) ··· 840 // 841 // disable foreign-keys for the next migration 842 conn.ExecContext(ctx, "pragma foreign_keys = off;") 843 + orm.RunMigration(conn, logger, "remove-issue-at-from-issues", func(tx *sql.Tx) error { 844 _, err := tx.Exec(` 845 create table if not exists issues_new ( 846 -- identifiers ··· 910 // - new columns 911 // * column "reply_to" which can be any other comment 912 // * column "at-uri" which is a generated column 913 + orm.RunMigration(conn, logger, "rework-issue-comments", func(tx *sql.Tx) error { 914 _, err := tx.Exec(` 915 create table if not exists issue_comments ( 916 -- identifiers ··· 970 // 971 // disable foreign-keys for the next migration 972 conn.ExecContext(ctx, "pragma foreign_keys = off;") 973 + orm.RunMigration(conn, logger, "add-at-uri-to-pulls", func(tx *sql.Tx) error { 974 _, err := tx.Exec(` 975 create table if not exists pulls_new ( 976 -- identifiers ··· 1051 // 1052 // disable foreign-keys for the next migration 1053 conn.ExecContext(ctx, "pragma foreign_keys = off;") 1054 + orm.RunMigration(conn, logger, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error { 1055 _, err := tx.Exec(` 1056 create table if not exists pull_submissions_new ( 1057 -- identifiers ··· 1105 1106 // knots may report the combined patch for a comparison, we can store that on the appview side 1107 // (but not on the pds record), because calculating the combined patch requires a git index 1108 + orm.RunMigration(conn, logger, "add-combined-column-submissions", func(tx *sql.Tx) error { 1109 _, err := tx.Exec(` 1110 alter table pull_submissions add column combined text; 1111 `) 1112 return err 1113 }) 1114 1115 + orm.RunMigration(conn, logger, "add-pronouns-profile", func(tx *sql.Tx) error { 1116 _, err := tx.Exec(` 1117 alter table profile add column pronouns text; 1118 `) 1119 return err 1120 }) 1121 1122 + orm.RunMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error { 1123 _, err := tx.Exec(` 1124 alter table repos add column website text; 1125 alter table repos add column topics text; ··· 1127 return err 1128 }) 1129 1130 + orm.RunMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error { 1131 _, err := tx.Exec(` 1132 alter table notification_preferences add column user_mentioned integer not null default 1; 1133 `) ··· 1135 }) 1136 1137 // remove the foreign key constraints from stars. 1138 + orm.RunMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error { 1139 _, err := tx.Exec(` 1140 create table stars_new ( 1141 id integer primary key autoincrement, ··· 1179 }, nil 1180 } 1181 1182 func (d *DB) Close() error { 1183 return d.DB.Close() 1184 }
+6 -3
appview/db/follow.go
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 ) 11 12 func AddFollow(e Execer, follow *models.Follow) error { ··· 134 return result, nil 135 } 136 137 - func GetFollows(e Execer, limit int, filters ...filter) ([]models.Follow, error) { 138 var follows []models.Follow 139 140 var conditions []string ··· 166 if err != nil { 167 return nil, err 168 } 169 for rows.Next() { 170 var follow models.Follow 171 var followedAt string ··· 191 } 192 193 func GetFollowers(e Execer, did string) ([]models.Follow, error) { 194 - return GetFollows(e, 0, FilterEq("subject_did", did)) 195 } 196 197 func GetFollowing(e Execer, did string) ([]models.Follow, error) { 198 - return GetFollows(e, 0, FilterEq("user_did", did)) 199 } 200 201 func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 11 ) 12 13 func AddFollow(e Execer, follow *models.Follow) error { ··· 135 return result, nil 136 } 137 138 + func GetFollows(e Execer, limit int, filters ...orm.Filter) ([]models.Follow, error) { 139 var follows []models.Follow 140 141 var conditions []string ··· 167 if err != nil { 168 return nil, err 169 } 170 + defer rows.Close() 171 + 172 for rows.Next() { 173 var follow models.Follow 174 var followedAt string ··· 194 } 195 196 func GetFollowers(e Execer, did string) ([]models.Follow, error) { 197 + return GetFollows(e, 0, orm.FilterEq("subject_did", did)) 198 } 199 200 func GetFollowing(e Execer, did string) ([]models.Follow, error) { 201 + return GetFollows(e, 0, orm.FilterEq("user_did", did)) 202 } 203 204 func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
+22 -20
appview/db/issues.go
··· 13 "tangled.org/core/api/tangled" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/pagination" 16 ) 17 18 func PutIssue(tx *sql.Tx, issue *models.Issue) error { ··· 27 28 issues, err := GetIssues( 29 tx, 30 - FilterEq("did", issue.Did), 31 - FilterEq("rkey", issue.Rkey), 32 ) 33 switch { 34 case err != nil: ··· 98 return nil 99 } 100 101 - func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]models.Issue, error) { 102 issueMap := make(map[string]*models.Issue) // at-uri -> issue 103 104 var conditions []string ··· 114 whereClause = " where " + strings.Join(conditions, " and ") 115 } 116 117 - pLower := FilterGte("row_num", page.Offset+1) 118 - pUpper := FilterLte("row_num", page.Offset+page.Limit) 119 120 pageClause := "" 121 if page.Limit > 0 { ··· 205 repoAts = append(repoAts, string(issue.RepoAt)) 206 } 207 208 - repos, err := GetRepos(e, 0, FilterIn("at_uri", repoAts)) 209 if err != nil { 210 return nil, fmt.Errorf("failed to build repo mappings: %w", err) 211 } ··· 228 // collect comments 229 issueAts := slices.Collect(maps.Keys(issueMap)) 230 231 - comments, err := GetIssueComments(e, FilterIn("issue_at", issueAts)) 232 if err != nil { 233 return nil, fmt.Errorf("failed to query comments: %w", err) 234 } ··· 240 } 241 242 // collect allLabels for each issue 243 - allLabels, err := GetLabels(e, FilterIn("subject", issueAts)) 244 if err != nil { 245 return nil, fmt.Errorf("failed to query labels: %w", err) 246 } ··· 251 } 252 253 // collect references for each issue 254 - allReferencs, err := GetReferencesAll(e, FilterIn("from_at", issueAts)) 255 if err != nil { 256 return nil, fmt.Errorf("failed to query reference_links: %w", err) 257 } ··· 277 issues, err := GetIssuesPaginated( 278 e, 279 pagination.Page{}, 280 - FilterEq("repo_at", repoAt), 281 - FilterEq("issue_id", issueId), 282 ) 283 if err != nil { 284 return nil, err ··· 290 return &issues[0], nil 291 } 292 293 - func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) { 294 return GetIssuesPaginated(e, pagination.Page{}, filters...) 295 } 296 ··· 298 func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) { 299 var ids []int64 300 301 - var filters []filter 302 openValue := 0 303 if opts.IsOpen { 304 openValue = 1 305 } 306 - filters = append(filters, FilterEq("open", openValue)) 307 if opts.RepoAt != "" { 308 - filters = append(filters, FilterEq("repo_at", opts.RepoAt)) 309 } 310 311 var conditions []string ··· 397 return id, nil 398 } 399 400 - func DeleteIssueComments(e Execer, filters ...filter) error { 401 var conditions []string 402 var args []any 403 for _, filter := range filters { ··· 416 return err 417 } 418 419 - func GetIssueComments(e Execer, filters ...filter) ([]models.IssueComment, error) { 420 commentMap := make(map[string]*models.IssueComment) 421 422 var conditions []string ··· 451 if err != nil { 452 return nil, err 453 } 454 455 for rows.Next() { 456 var comment models.IssueComment ··· 506 507 // collect references for each comments 508 commentAts := slices.Collect(maps.Keys(commentMap)) 509 - allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts)) 510 if err != nil { 511 return nil, fmt.Errorf("failed to query reference_links: %w", err) 512 } ··· 548 return nil 549 } 550 551 - func CloseIssues(e Execer, filters ...filter) error { 552 var conditions []string 553 var args []any 554 for _, filter := range filters { ··· 566 return err 567 } 568 569 - func ReopenIssues(e Execer, filters ...filter) error { 570 var conditions []string 571 var args []any 572 for _, filter := range filters {
··· 13 "tangled.org/core/api/tangled" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/pagination" 16 + "tangled.org/core/orm" 17 ) 18 19 func PutIssue(tx *sql.Tx, issue *models.Issue) error { ··· 28 29 issues, err := GetIssues( 30 tx, 31 + orm.FilterEq("did", issue.Did), 32 + orm.FilterEq("rkey", issue.Rkey), 33 ) 34 switch { 35 case err != nil: ··· 99 return nil 100 } 101 102 + func GetIssuesPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]models.Issue, error) { 103 issueMap := make(map[string]*models.Issue) // at-uri -> issue 104 105 var conditions []string ··· 115 whereClause = " where " + strings.Join(conditions, " and ") 116 } 117 118 + pLower := orm.FilterGte("row_num", page.Offset+1) 119 + pUpper := orm.FilterLte("row_num", page.Offset+page.Limit) 120 121 pageClause := "" 122 if page.Limit > 0 { ··· 206 repoAts = append(repoAts, string(issue.RepoAt)) 207 } 208 209 + repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", repoAts)) 210 if err != nil { 211 return nil, fmt.Errorf("failed to build repo mappings: %w", err) 212 } ··· 229 // collect comments 230 issueAts := slices.Collect(maps.Keys(issueMap)) 231 232 + comments, err := GetIssueComments(e, orm.FilterIn("issue_at", issueAts)) 233 if err != nil { 234 return nil, fmt.Errorf("failed to query comments: %w", err) 235 } ··· 241 } 242 243 // collect allLabels for each issue 244 + allLabels, err := GetLabels(e, orm.FilterIn("subject", issueAts)) 245 if err != nil { 246 return nil, fmt.Errorf("failed to query labels: %w", err) 247 } ··· 252 } 253 254 // collect references for each issue 255 + allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", issueAts)) 256 if err != nil { 257 return nil, fmt.Errorf("failed to query reference_links: %w", err) 258 } ··· 278 issues, err := GetIssuesPaginated( 279 e, 280 pagination.Page{}, 281 + orm.FilterEq("repo_at", repoAt), 282 + orm.FilterEq("issue_id", issueId), 283 ) 284 if err != nil { 285 return nil, err ··· 291 return &issues[0], nil 292 } 293 294 + func GetIssues(e Execer, filters ...orm.Filter) ([]models.Issue, error) { 295 return GetIssuesPaginated(e, pagination.Page{}, filters...) 296 } 297 ··· 299 func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) { 300 var ids []int64 301 302 + var filters []orm.Filter 303 openValue := 0 304 if opts.IsOpen { 305 openValue = 1 306 } 307 + filters = append(filters, orm.FilterEq("open", openValue)) 308 if opts.RepoAt != "" { 309 + filters = append(filters, orm.FilterEq("repo_at", opts.RepoAt)) 310 } 311 312 var conditions []string ··· 398 return id, nil 399 } 400 401 + func DeleteIssueComments(e Execer, filters ...orm.Filter) error { 402 var conditions []string 403 var args []any 404 for _, filter := range filters { ··· 417 return err 418 } 419 420 + func GetIssueComments(e Execer, filters ...orm.Filter) ([]models.IssueComment, error) { 421 commentMap := make(map[string]*models.IssueComment) 422 423 var conditions []string ··· 452 if err != nil { 453 return nil, err 454 } 455 + defer rows.Close() 456 457 for rows.Next() { 458 var comment models.IssueComment ··· 508 509 // collect references for each comments 510 commentAts := slices.Collect(maps.Keys(commentMap)) 511 + allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts)) 512 if err != nil { 513 return nil, fmt.Errorf("failed to query reference_links: %w", err) 514 } ··· 550 return nil 551 } 552 553 + func CloseIssues(e Execer, filters ...orm.Filter) error { 554 var conditions []string 555 var args []any 556 for _, filter := range filters { ··· 568 return err 569 } 570 571 + func ReopenIssues(e Execer, filters ...orm.Filter) error { 572 var conditions []string 573 var args []any 574 for _, filter := range filters {
+8 -7
appview/db/label.go
··· 10 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 "tangled.org/core/appview/models" 13 ) 14 15 // no updating type for now ··· 59 return id, nil 60 } 61 62 - func DeleteLabelDefinition(e Execer, filters ...filter) error { 63 var conditions []string 64 var args []any 65 for _, filter := range filters { ··· 75 return err 76 } 77 78 - func GetLabelDefinitions(e Execer, filters ...filter) ([]models.LabelDefinition, error) { 79 var labelDefinitions []models.LabelDefinition 80 var conditions []string 81 var args []any ··· 167 } 168 169 // helper to get exactly one label def 170 - func GetLabelDefinition(e Execer, filters ...filter) (*models.LabelDefinition, error) { 171 labels, err := GetLabelDefinitions(e, filters...) 172 if err != nil { 173 return nil, err ··· 227 return id, nil 228 } 229 230 - func GetLabelOps(e Execer, filters ...filter) ([]models.LabelOp, error) { 231 var labelOps []models.LabelOp 232 var conditions []string 233 var args []any ··· 302 } 303 304 // get labels for a given list of subject URIs 305 - func GetLabels(e Execer, filters ...filter) (map[syntax.ATURI]models.LabelState, error) { 306 ops, err := GetLabelOps(e, filters...) 307 if err != nil { 308 return nil, err ··· 322 } 323 labelAts := slices.Collect(maps.Keys(labelAtSet)) 324 325 - actx, err := NewLabelApplicationCtx(e, FilterIn("at_uri", labelAts)) 326 if err != nil { 327 return nil, err 328 } ··· 338 return results, nil 339 } 340 341 - func NewLabelApplicationCtx(e Execer, filters ...filter) (*models.LabelApplicationCtx, error) { 342 labels, err := GetLabelDefinitions(e, filters...) 343 if err != nil { 344 return nil, err
··· 10 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 "tangled.org/core/appview/models" 13 + "tangled.org/core/orm" 14 ) 15 16 // no updating type for now ··· 60 return id, nil 61 } 62 63 + func DeleteLabelDefinition(e Execer, filters ...orm.Filter) error { 64 var conditions []string 65 var args []any 66 for _, filter := range filters { ··· 76 return err 77 } 78 79 + func GetLabelDefinitions(e Execer, filters ...orm.Filter) ([]models.LabelDefinition, error) { 80 var labelDefinitions []models.LabelDefinition 81 var conditions []string 82 var args []any ··· 168 } 169 170 // helper to get exactly one label def 171 + func GetLabelDefinition(e Execer, filters ...orm.Filter) (*models.LabelDefinition, error) { 172 labels, err := GetLabelDefinitions(e, filters...) 173 if err != nil { 174 return nil, err ··· 228 return id, nil 229 } 230 231 + func GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) { 232 var labelOps []models.LabelOp 233 var conditions []string 234 var args []any ··· 303 } 304 305 // get labels for a given list of subject URIs 306 + func GetLabels(e Execer, filters ...orm.Filter) (map[syntax.ATURI]models.LabelState, error) { 307 ops, err := GetLabelOps(e, filters...) 308 if err != nil { 309 return nil, err ··· 323 } 324 labelAts := slices.Collect(maps.Keys(labelAtSet)) 325 326 + actx, err := NewLabelApplicationCtx(e, orm.FilterIn("at_uri", labelAts)) 327 if err != nil { 328 return nil, err 329 } ··· 339 return results, nil 340 } 341 342 + func NewLabelApplicationCtx(e Execer, filters ...orm.Filter) (*models.LabelApplicationCtx, error) { 343 labels, err := GetLabelDefinitions(e, filters...) 344 if err != nil { 345 return nil, err
+6 -5
appview/db/language.go
··· 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "tangled.org/core/appview/models" 10 ) 11 12 - func GetRepoLanguages(e Execer, filters ...filter) ([]models.RepoLanguage, error) { 13 var conditions []string 14 var args []any 15 for _, filter := range filters { ··· 27 whereClause, 28 ) 29 rows, err := e.Query(query, args...) 30 - 31 if err != nil { 32 return nil, fmt.Errorf("failed to execute query: %w ", err) 33 } 34 35 var langs []models.RepoLanguage 36 for rows.Next() { ··· 85 return nil 86 } 87 88 - func DeleteRepoLanguages(e Execer, filters ...filter) error { 89 var conditions []string 90 var args []any 91 for _, filter := range filters { ··· 107 func UpdateRepoLanguages(tx *sql.Tx, repoAt syntax.ATURI, ref string, langs []models.RepoLanguage) error { 108 err := DeleteRepoLanguages( 109 tx, 110 - FilterEq("repo_at", repoAt), 111 - FilterEq("ref", ref), 112 ) 113 if err != nil { 114 return fmt.Errorf("failed to delete existing languages: %w", err)
··· 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 11 ) 12 13 + func GetRepoLanguages(e Execer, filters ...orm.Filter) ([]models.RepoLanguage, error) { 14 var conditions []string 15 var args []any 16 for _, filter := range filters { ··· 28 whereClause, 29 ) 30 rows, err := e.Query(query, args...) 31 if err != nil { 32 return nil, fmt.Errorf("failed to execute query: %w ", err) 33 } 34 + defer rows.Close() 35 36 var langs []models.RepoLanguage 37 for rows.Next() { ··· 86 return nil 87 } 88 89 + func DeleteRepoLanguages(e Execer, filters ...orm.Filter) error { 90 var conditions []string 91 var args []any 92 for _, filter := range filters { ··· 108 func UpdateRepoLanguages(tx *sql.Tx, repoAt syntax.ATURI, ref string, langs []models.RepoLanguage) error { 109 err := DeleteRepoLanguages( 110 tx, 111 + orm.FilterEq("repo_at", repoAt), 112 + orm.FilterEq("ref", ref), 113 ) 114 if err != nil { 115 return fmt.Errorf("failed to delete existing languages: %w", err)
+14 -13
appview/db/notifications.go
··· 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 "tangled.org/core/appview/models" 13 "tangled.org/core/appview/pagination" 14 ) 15 16 func CreateNotification(e Execer, notification *models.Notification) error { ··· 44 } 45 46 // GetNotificationsPaginated retrieves notifications with filters and pagination 47 - func GetNotificationsPaginated(e Execer, page pagination.Page, filters ...filter) ([]*models.Notification, error) { 48 var conditions []string 49 var args []any 50 ··· 113 } 114 115 // GetNotificationsWithEntities retrieves notifications with their related entities 116 - func GetNotificationsWithEntities(e Execer, page pagination.Page, filters ...filter) ([]*models.NotificationWithEntity, error) { 117 var conditions []string 118 var args []any 119 ··· 256 } 257 258 // GetNotifications retrieves notifications with filters 259 - func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) { 260 return GetNotificationsPaginated(e, pagination.FirstPage(), filters...) 261 } 262 263 - func CountNotifications(e Execer, filters ...filter) (int64, error) { 264 var conditions []string 265 var args []any 266 for _, filter := range filters { ··· 285 } 286 287 func MarkNotificationRead(e Execer, notificationID int64, userDID string) error { 288 - idFilter := FilterEq("id", notificationID) 289 - recipientFilter := FilterEq("recipient_did", userDID) 290 291 query := fmt.Sprintf(` 292 UPDATE notifications ··· 314 } 315 316 func MarkAllNotificationsRead(e Execer, userDID string) error { 317 - recipientFilter := FilterEq("recipient_did", userDID) 318 - readFilter := FilterEq("read", 0) 319 320 query := fmt.Sprintf(` 321 UPDATE notifications ··· 334 } 335 336 func DeleteNotification(e Execer, notificationID int64, userDID string) error { 337 - idFilter := FilterEq("id", notificationID) 338 - recipientFilter := FilterEq("recipient_did", userDID) 339 340 query := fmt.Sprintf(` 341 DELETE FROM notifications ··· 362 } 363 364 func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) { 365 - prefs, err := GetNotificationPreferences(e, FilterEq("user_did", userDid)) 366 if err != nil { 367 return nil, err 368 } ··· 375 return p, nil 376 } 377 378 - func GetNotificationPreferences(e Execer, filters ...filter) (map[syntax.DID]*models.NotificationPreferences, error) { 379 prefsMap := make(map[syntax.DID]*models.NotificationPreferences) 380 381 var conditions []string ··· 483 484 func (d *DB) ClearOldNotifications(ctx context.Context, olderThan time.Duration) error { 485 cutoff := time.Now().Add(-olderThan) 486 - createdFilter := FilterLte("created", cutoff) 487 488 query := fmt.Sprintf(` 489 DELETE FROM notifications
··· 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 "tangled.org/core/appview/models" 13 "tangled.org/core/appview/pagination" 14 + "tangled.org/core/orm" 15 ) 16 17 func CreateNotification(e Execer, notification *models.Notification) error { ··· 45 } 46 47 // GetNotificationsPaginated retrieves notifications with filters and pagination 48 + func GetNotificationsPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]*models.Notification, error) { 49 var conditions []string 50 var args []any 51 ··· 114 } 115 116 // GetNotificationsWithEntities retrieves notifications with their related entities 117 + func GetNotificationsWithEntities(e Execer, page pagination.Page, filters ...orm.Filter) ([]*models.NotificationWithEntity, error) { 118 var conditions []string 119 var args []any 120 ··· 257 } 258 259 // GetNotifications retrieves notifications with filters 260 + func GetNotifications(e Execer, filters ...orm.Filter) ([]*models.Notification, error) { 261 return GetNotificationsPaginated(e, pagination.FirstPage(), filters...) 262 } 263 264 + func CountNotifications(e Execer, filters ...orm.Filter) (int64, error) { 265 var conditions []string 266 var args []any 267 for _, filter := range filters { ··· 286 } 287 288 func MarkNotificationRead(e Execer, notificationID int64, userDID string) error { 289 + idFilter := orm.FilterEq("id", notificationID) 290 + recipientFilter := orm.FilterEq("recipient_did", userDID) 291 292 query := fmt.Sprintf(` 293 UPDATE notifications ··· 315 } 316 317 func MarkAllNotificationsRead(e Execer, userDID string) error { 318 + recipientFilter := orm.FilterEq("recipient_did", userDID) 319 + readFilter := orm.FilterEq("read", 0) 320 321 query := fmt.Sprintf(` 322 UPDATE notifications ··· 335 } 336 337 func DeleteNotification(e Execer, notificationID int64, userDID string) error { 338 + idFilter := orm.FilterEq("id", notificationID) 339 + recipientFilter := orm.FilterEq("recipient_did", userDID) 340 341 query := fmt.Sprintf(` 342 DELETE FROM notifications ··· 363 } 364 365 func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) { 366 + prefs, err := GetNotificationPreferences(e, orm.FilterEq("user_did", userDid)) 367 if err != nil { 368 return nil, err 369 } ··· 376 return p, nil 377 } 378 379 + func GetNotificationPreferences(e Execer, filters ...orm.Filter) (map[syntax.DID]*models.NotificationPreferences, error) { 380 prefsMap := make(map[syntax.DID]*models.NotificationPreferences) 381 382 var conditions []string ··· 484 485 func (d *DB) ClearOldNotifications(ctx context.Context, olderThan time.Duration) error { 486 cutoff := time.Now().Add(-olderThan) 487 + createdFilter := orm.FilterLte("created", cutoff) 488 489 query := fmt.Sprintf(` 490 DELETE FROM notifications
+6 -5
appview/db/pipeline.go
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 ) 11 12 - func GetPipelines(e Execer, filters ...filter) ([]models.Pipeline, error) { 13 var pipelines []models.Pipeline 14 15 var conditions []string ··· 168 169 // this is a mega query, but the most useful one: 170 // get N pipelines, for each one get the latest status of its N workflows 171 - func GetPipelineStatuses(e Execer, limit int, filters ...filter) ([]models.Pipeline, error) { 172 var conditions []string 173 var args []any 174 for _, filter := range filters { 175 - filter.key = "p." + filter.key // the table is aliased in the query to `p` 176 conditions = append(conditions, filter.Condition()) 177 args = append(args, filter.Arg()...) 178 } ··· 264 conditions = nil 265 args = nil 266 for _, p := range pipelines { 267 - knotFilter := FilterEq("pipeline_knot", p.Knot) 268 - rkeyFilter := FilterEq("pipeline_rkey", p.Rkey) 269 conditions = append(conditions, fmt.Sprintf("(%s and %s)", knotFilter.Condition(), rkeyFilter.Condition())) 270 args = append(args, p.Knot) 271 args = append(args, p.Rkey)
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 11 ) 12 13 + func GetPipelines(e Execer, filters ...orm.Filter) ([]models.Pipeline, error) { 14 var pipelines []models.Pipeline 15 16 var conditions []string ··· 169 170 // this is a mega query, but the most useful one: 171 // get N pipelines, for each one get the latest status of its N workflows 172 + func GetPipelineStatuses(e Execer, limit int, filters ...orm.Filter) ([]models.Pipeline, error) { 173 var conditions []string 174 var args []any 175 for _, filter := range filters { 176 + filter.Key = "p." + filter.Key // the table is aliased in the query to `p` 177 conditions = append(conditions, filter.Condition()) 178 args = append(args, filter.Arg()...) 179 } ··· 265 conditions = nil 266 args = nil 267 for _, p := range pipelines { 268 + knotFilter := orm.FilterEq("pipeline_knot", p.Knot) 269 + rkeyFilter := orm.FilterEq("pipeline_rkey", p.Rkey) 270 conditions = append(conditions, fmt.Sprintf("(%s and %s)", knotFilter.Condition(), rkeyFilter.Condition())) 271 args = append(args, p.Knot) 272 args = append(args, p.Rkey)
+11 -5
appview/db/profile.go
··· 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 ) 15 16 const TimeframeMonths = 7 ··· 44 45 issues, err := GetIssues( 46 e, 47 - FilterEq("did", forDid), 48 - FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)), 49 ) 50 if err != nil { 51 return nil, fmt.Errorf("error getting issues by owner did: %w", err) ··· 65 *items = append(*items, &issue) 66 } 67 68 - repos, err := GetRepos(e, 0, FilterEq("did", forDid)) 69 if err != nil { 70 return nil, fmt.Errorf("error getting all repos by did: %w", err) 71 } ··· 199 return tx.Commit() 200 } 201 202 - func GetProfiles(e Execer, filters ...filter) (map[string]*models.Profile, error) { 203 var conditions []string 204 var args []any 205 for _, filter := range filters { ··· 229 if err != nil { 230 return nil, err 231 } 232 233 profileMap := make(map[string]*models.Profile) 234 for rows.Next() { ··· 269 if err != nil { 270 return nil, err 271 } 272 idxs := make(map[string]int) 273 for did := range profileMap { 274 idxs[did] = 0 ··· 289 if err != nil { 290 return nil, err 291 } 292 idxs = make(map[string]int) 293 for did := range profileMap { 294 idxs[did] = 0 ··· 441 } 442 443 // ensure all pinned repos are either own repos or collaborating repos 444 - repos, err := GetRepos(e, 0, FilterEq("did", profile.Did)) 445 if err != nil { 446 log.Printf("getting repos for %s: %s", profile.Did, err) 447 }
··· 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 + "tangled.org/core/orm" 15 ) 16 17 const TimeframeMonths = 7 ··· 45 46 issues, err := GetIssues( 47 e, 48 + orm.FilterEq("did", forDid), 49 + orm.FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)), 50 ) 51 if err != nil { 52 return nil, fmt.Errorf("error getting issues by owner did: %w", err) ··· 66 *items = append(*items, &issue) 67 } 68 69 + repos, err := GetRepos(e, 0, orm.FilterEq("did", forDid)) 70 if err != nil { 71 return nil, fmt.Errorf("error getting all repos by did: %w", err) 72 } ··· 200 return tx.Commit() 201 } 202 203 + func GetProfiles(e Execer, filters ...orm.Filter) (map[string]*models.Profile, error) { 204 var conditions []string 205 var args []any 206 for _, filter := range filters { ··· 230 if err != nil { 231 return nil, err 232 } 233 + defer rows.Close() 234 235 profileMap := make(map[string]*models.Profile) 236 for rows.Next() { ··· 271 if err != nil { 272 return nil, err 273 } 274 + defer rows.Close() 275 + 276 idxs := make(map[string]int) 277 for did := range profileMap { 278 idxs[did] = 0 ··· 293 if err != nil { 294 return nil, err 295 } 296 + defer rows.Close() 297 + 298 idxs = make(map[string]int) 299 for did := range profileMap { 300 idxs[did] = 0 ··· 447 } 448 449 // ensure all pinned repos are either own repos or collaborating repos 450 + repos, err := GetRepos(e, 0, orm.FilterEq("did", profile.Did)) 451 if err != nil { 452 log.Printf("getting repos for %s: %s", profile.Did, err) 453 }
+21 -20
appview/db/pulls.go
··· 13 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 "tangled.org/core/appview/models" 16 ) 17 18 func NewPull(tx *sql.Tx, pull *models.Pull) error { ··· 118 return pullId - 1, err 119 } 120 121 - func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, error) { 122 pulls := make(map[syntax.ATURI]*models.Pull) 123 124 var conditions []string ··· 229 for _, p := range pulls { 230 pullAts = append(pullAts, p.AtUri()) 231 } 232 - submissionsMap, err := GetPullSubmissions(e, FilterIn("pull_at", pullAts)) 233 if err != nil { 234 return nil, fmt.Errorf("failed to get submissions: %w", err) 235 } ··· 241 } 242 243 // collect allLabels for each issue 244 - allLabels, err := GetLabels(e, FilterIn("subject", pullAts)) 245 if err != nil { 246 return nil, fmt.Errorf("failed to query labels: %w", err) 247 } ··· 258 sourceAts = append(sourceAts, *p.PullSource.RepoAt) 259 } 260 } 261 - sourceRepos, err := GetRepos(e, 0, FilterIn("at_uri", sourceAts)) 262 if err != nil && !errors.Is(err, sql.ErrNoRows) { 263 return nil, fmt.Errorf("failed to get source repos: %w", err) 264 } ··· 274 } 275 } 276 277 - allReferences, err := GetReferencesAll(e, FilterIn("from_at", pullAts)) 278 if err != nil { 279 return nil, fmt.Errorf("failed to query reference_links: %w", err) 280 } ··· 295 return orderedByPullId, nil 296 } 297 298 - func GetPulls(e Execer, filters ...filter) ([]*models.Pull, error) { 299 return GetPullsWithLimit(e, 0, filters...) 300 } 301 302 func GetPullIDs(e Execer, opts models.PullSearchOptions) ([]int64, error) { 303 var ids []int64 304 305 - var filters []filter 306 - filters = append(filters, FilterEq("state", opts.State)) 307 if opts.RepoAt != "" { 308 - filters = append(filters, FilterEq("repo_at", opts.RepoAt)) 309 } 310 311 var conditions []string ··· 361 } 362 363 func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 364 - pulls, err := GetPullsWithLimit(e, 1, FilterEq("repo_at", repoAt), FilterEq("pull_id", pullId)) 365 if err != nil { 366 return nil, err 367 } ··· 373 } 374 375 // mapping from pull -> pull submissions 376 - func GetPullSubmissions(e Execer, filters ...filter) (map[syntax.ATURI][]*models.PullSubmission, error) { 377 var conditions []string 378 var args []any 379 for _, filter := range filters { ··· 448 449 // Get comments for all submissions using GetPullComments 450 submissionIds := slices.Collect(maps.Keys(submissionMap)) 451 - comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds)) 452 if err != nil { 453 return nil, fmt.Errorf("failed to get pull comments: %w", err) 454 } ··· 474 return m, nil 475 } 476 477 - func GetPullComments(e Execer, filters ...filter) ([]models.PullComment, error) { 478 var conditions []string 479 var args []any 480 for _, filter := range filters { ··· 542 543 // collect references for each comments 544 commentAts := slices.Collect(maps.Keys(commentMap)) 545 - allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts)) 546 if err != nil { 547 return nil, fmt.Errorf("failed to query reference_links: %w", err) 548 } ··· 708 return err 709 } 710 711 - func SetPullParentChangeId(e Execer, parentChangeId string, filters ...filter) error { 712 var conditions []string 713 var args []any 714 ··· 732 733 // Only used when stacking to update contents in the event of a rebase (the interdiff should be empty). 734 // otherwise submissions are immutable 735 - func UpdatePull(e Execer, newPatch, sourceRev string, filters ...filter) error { 736 var conditions []string 737 var args []any 738 ··· 790 func GetStack(e Execer, stackId string) (models.Stack, error) { 791 unorderedPulls, err := GetPulls( 792 e, 793 - FilterEq("stack_id", stackId), 794 - FilterNotEq("state", models.PullDeleted), 795 ) 796 if err != nil { 797 return nil, err ··· 835 func GetAbandonedPulls(e Execer, stackId string) ([]*models.Pull, error) { 836 pulls, err := GetPulls( 837 e, 838 - FilterEq("stack_id", stackId), 839 - FilterEq("state", models.PullDeleted), 840 ) 841 if err != nil { 842 return nil, err
··· 13 14 "github.com/bluesky-social/indigo/atproto/syntax" 15 "tangled.org/core/appview/models" 16 + "tangled.org/core/orm" 17 ) 18 19 func NewPull(tx *sql.Tx, pull *models.Pull) error { ··· 119 return pullId - 1, err 120 } 121 122 + func GetPullsWithLimit(e Execer, limit int, filters ...orm.Filter) ([]*models.Pull, error) { 123 pulls := make(map[syntax.ATURI]*models.Pull) 124 125 var conditions []string ··· 230 for _, p := range pulls { 231 pullAts = append(pullAts, p.AtUri()) 232 } 233 + submissionsMap, err := GetPullSubmissions(e, orm.FilterIn("pull_at", pullAts)) 234 if err != nil { 235 return nil, fmt.Errorf("failed to get submissions: %w", err) 236 } ··· 242 } 243 244 // collect allLabels for each issue 245 + allLabels, err := GetLabels(e, orm.FilterIn("subject", pullAts)) 246 if err != nil { 247 return nil, fmt.Errorf("failed to query labels: %w", err) 248 } ··· 259 sourceAts = append(sourceAts, *p.PullSource.RepoAt) 260 } 261 } 262 + sourceRepos, err := GetRepos(e, 0, orm.FilterIn("at_uri", sourceAts)) 263 if err != nil && !errors.Is(err, sql.ErrNoRows) { 264 return nil, fmt.Errorf("failed to get source repos: %w", err) 265 } ··· 275 } 276 } 277 278 + allReferences, err := GetReferencesAll(e, orm.FilterIn("from_at", pullAts)) 279 if err != nil { 280 return nil, fmt.Errorf("failed to query reference_links: %w", err) 281 } ··· 296 return orderedByPullId, nil 297 } 298 299 + func GetPulls(e Execer, filters ...orm.Filter) ([]*models.Pull, error) { 300 return GetPullsWithLimit(e, 0, filters...) 301 } 302 303 func GetPullIDs(e Execer, opts models.PullSearchOptions) ([]int64, error) { 304 var ids []int64 305 306 + var filters []orm.Filter 307 + filters = append(filters, orm.FilterEq("state", opts.State)) 308 if opts.RepoAt != "" { 309 + filters = append(filters, orm.FilterEq("repo_at", opts.RepoAt)) 310 } 311 312 var conditions []string ··· 362 } 363 364 func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) { 365 + pulls, err := GetPullsWithLimit(e, 1, orm.FilterEq("repo_at", repoAt), orm.FilterEq("pull_id", pullId)) 366 if err != nil { 367 return nil, err 368 } ··· 374 } 375 376 // mapping from pull -> pull submissions 377 + func GetPullSubmissions(e Execer, filters ...orm.Filter) (map[syntax.ATURI][]*models.PullSubmission, error) { 378 var conditions []string 379 var args []any 380 for _, filter := range filters { ··· 449 450 // Get comments for all submissions using GetPullComments 451 submissionIds := slices.Collect(maps.Keys(submissionMap)) 452 + comments, err := GetPullComments(e, orm.FilterIn("submission_id", submissionIds)) 453 if err != nil { 454 return nil, fmt.Errorf("failed to get pull comments: %w", err) 455 } ··· 475 return m, nil 476 } 477 478 + func GetPullComments(e Execer, filters ...orm.Filter) ([]models.PullComment, error) { 479 var conditions []string 480 var args []any 481 for _, filter := range filters { ··· 543 544 // collect references for each comments 545 commentAts := slices.Collect(maps.Keys(commentMap)) 546 + allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts)) 547 if err != nil { 548 return nil, fmt.Errorf("failed to query reference_links: %w", err) 549 } ··· 709 return err 710 } 711 712 + func SetPullParentChangeId(e Execer, parentChangeId string, filters ...orm.Filter) error { 713 var conditions []string 714 var args []any 715 ··· 733 734 // Only used when stacking to update contents in the event of a rebase (the interdiff should be empty). 735 // otherwise submissions are immutable 736 + func UpdatePull(e Execer, newPatch, sourceRev string, filters ...orm.Filter) error { 737 var conditions []string 738 var args []any 739 ··· 791 func GetStack(e Execer, stackId string) (models.Stack, error) { 792 unorderedPulls, err := GetPulls( 793 e, 794 + orm.FilterEq("stack_id", stackId), 795 + orm.FilterNotEq("state", models.PullDeleted), 796 ) 797 if err != nil { 798 return nil, err ··· 836 func GetAbandonedPulls(e Execer, stackId string) ([]*models.Pull, error) { 837 pulls, err := GetPulls( 838 e, 839 + orm.FilterEq("stack_id", stackId), 840 + orm.FilterEq("state", models.PullDeleted), 841 ) 842 if err != nil { 843 return nil, err
+2 -1
appview/db/punchcard.go
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 ) 11 12 // this adds to the existing count ··· 20 return err 21 } 22 23 - func MakePunchcard(e Execer, filters ...filter) (*models.Punchcard, error) { 24 punchcard := &models.Punchcard{} 25 now := time.Now() 26 startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 11 ) 12 13 // this adds to the existing count ··· 21 return err 22 } 23 24 + func MakePunchcard(e Execer, filters ...orm.Filter) (*models.Punchcard, error) { 25 punchcard := &models.Punchcard{} 26 now := time.Now() 27 startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
+4 -3
appview/db/reference.go
··· 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/appview/models" 11 ) 12 13 // ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs. ··· 205 return err 206 } 207 208 - func GetReferencesAll(e Execer, filters ...filter) (map[syntax.ATURI][]syntax.ATURI, error) { 209 var ( 210 conditions []string 211 args []any ··· 347 if len(aturis) == 0 { 348 return nil, nil 349 } 350 - filter := FilterIn("c.at_uri", aturis) 351 rows, err := e.Query( 352 fmt.Sprintf( 353 `select r.did, r.name, i.issue_id, c.id, i.title, i.open ··· 427 if len(aturis) == 0 { 428 return nil, nil 429 } 430 - filter := FilterIn("c.comment_at", aturis) 431 rows, err := e.Query( 432 fmt.Sprintf( 433 `select r.did, r.name, p.pull_id, c.id, p.title, p.state
··· 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/appview/models" 11 + "tangled.org/core/orm" 12 ) 13 14 // ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs. ··· 206 return err 207 } 208 209 + func GetReferencesAll(e Execer, filters ...orm.Filter) (map[syntax.ATURI][]syntax.ATURI, error) { 210 var ( 211 conditions []string 212 args []any ··· 348 if len(aturis) == 0 { 349 return nil, nil 350 } 351 + filter := orm.FilterIn("c.at_uri", aturis) 352 rows, err := e.Query( 353 fmt.Sprintf( 354 `select r.did, r.name, i.issue_id, c.id, i.title, i.open ··· 428 if len(aturis) == 0 { 429 return nil, nil 430 } 431 + filter := orm.FilterIn("c.comment_at", aturis) 432 rows, err := e.Query( 433 fmt.Sprintf( 434 `select r.did, r.name, p.pull_id, c.id, p.title, p.state
+5 -3
appview/db/registration.go
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 ) 11 12 - func GetRegistrations(e Execer, filters ...filter) ([]models.Registration, error) { 13 var registrations []models.Registration 14 15 var conditions []string ··· 37 if err != nil { 38 return nil, err 39 } 40 41 for rows.Next() { 42 var createdAt string ··· 69 return registrations, nil 70 } 71 72 - func MarkRegistered(e Execer, filters ...filter) error { 73 var conditions []string 74 var args []any 75 for _, filter := range filters { ··· 94 return err 95 } 96 97 - func DeleteKnot(e Execer, filters ...filter) error { 98 var conditions []string 99 var args []any 100 for _, filter := range filters {
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 11 ) 12 13 + func GetRegistrations(e Execer, filters ...orm.Filter) ([]models.Registration, error) { 14 var registrations []models.Registration 15 16 var conditions []string ··· 38 if err != nil { 39 return nil, err 40 } 41 + defer rows.Close() 42 43 for rows.Next() { 44 var createdAt string ··· 71 return registrations, nil 72 } 73 74 + func MarkRegistered(e Execer, filters ...orm.Filter) error { 75 var conditions []string 76 var args []any 77 for _, filter := range filters { ··· 96 return err 97 } 98 99 + func DeleteKnot(e Execer, filters ...orm.Filter) error { 100 var conditions []string 101 var args []any 102 for _, filter := range filters {
+17 -6
appview/db/repos.go
··· 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 ) 15 16 - func GetRepos(e Execer, limit int, filters ...filter) ([]models.Repo, error) { 17 repoMap := make(map[syntax.ATURI]*models.Repo) 18 19 var conditions []string ··· 55 limitClause, 56 ) 57 rows, err := e.Query(repoQuery, args...) 58 - 59 if err != nil { 60 return nil, fmt.Errorf("failed to execute repo query: %w ", err) 61 } 62 63 for rows.Next() { 64 var repo models.Repo ··· 127 if err != nil { 128 return nil, fmt.Errorf("failed to execute labels query: %w ", err) 129 } 130 for rows.Next() { 131 var repoat, labelat string 132 if err := rows.Scan(&repoat, &labelat); err != nil { ··· 164 if err != nil { 165 return nil, fmt.Errorf("failed to execute lang query: %w ", err) 166 } 167 for rows.Next() { 168 var repoat, lang string 169 if err := rows.Scan(&repoat, &lang); err != nil { ··· 190 if err != nil { 191 return nil, fmt.Errorf("failed to execute star-count query: %w ", err) 192 } 193 for rows.Next() { 194 var repoat string 195 var count int ··· 219 if err != nil { 220 return nil, fmt.Errorf("failed to execute issue-count query: %w ", err) 221 } 222 for rows.Next() { 223 var repoat string 224 var open, closed int ··· 260 if err != nil { 261 return nil, fmt.Errorf("failed to execute pulls-count query: %w ", err) 262 } 263 for rows.Next() { 264 var repoat string 265 var open, merged, closed, deleted int ··· 294 } 295 296 // helper to get exactly one repo 297 - func GetRepo(e Execer, filters ...filter) (*models.Repo, error) { 298 repos, err := GetRepos(e, 0, filters...) 299 if err != nil { 300 return nil, err ··· 311 return &repos[0], nil 312 } 313 314 - func CountRepos(e Execer, filters ...filter) (int64, error) { 315 var conditions []string 316 var args []any 317 for _, filter := range filters { ··· 542 return err 543 } 544 545 - func UnsubscribeLabel(e Execer, filters ...filter) error { 546 var conditions []string 547 var args []any 548 for _, filter := range filters { ··· 560 return err 561 } 562 563 - func GetRepoLabels(e Execer, filters ...filter) ([]models.RepoLabel, error) { 564 var conditions []string 565 var args []any 566 for _, filter := range filters {
··· 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 + "tangled.org/core/orm" 15 ) 16 17 + func GetRepos(e Execer, limit int, filters ...orm.Filter) ([]models.Repo, error) { 18 repoMap := make(map[syntax.ATURI]*models.Repo) 19 20 var conditions []string ··· 56 limitClause, 57 ) 58 rows, err := e.Query(repoQuery, args...) 59 if err != nil { 60 return nil, fmt.Errorf("failed to execute repo query: %w ", err) 61 } 62 + defer rows.Close() 63 64 for rows.Next() { 65 var repo models.Repo ··· 128 if err != nil { 129 return nil, fmt.Errorf("failed to execute labels query: %w ", err) 130 } 131 + defer rows.Close() 132 + 133 for rows.Next() { 134 var repoat, labelat string 135 if err := rows.Scan(&repoat, &labelat); err != nil { ··· 167 if err != nil { 168 return nil, fmt.Errorf("failed to execute lang query: %w ", err) 169 } 170 + defer rows.Close() 171 + 172 for rows.Next() { 173 var repoat, lang string 174 if err := rows.Scan(&repoat, &lang); err != nil { ··· 195 if err != nil { 196 return nil, fmt.Errorf("failed to execute star-count query: %w ", err) 197 } 198 + defer rows.Close() 199 + 200 for rows.Next() { 201 var repoat string 202 var count int ··· 226 if err != nil { 227 return nil, fmt.Errorf("failed to execute issue-count query: %w ", err) 228 } 229 + defer rows.Close() 230 + 231 for rows.Next() { 232 var repoat string 233 var open, closed int ··· 269 if err != nil { 270 return nil, fmt.Errorf("failed to execute pulls-count query: %w ", err) 271 } 272 + defer rows.Close() 273 + 274 for rows.Next() { 275 var repoat string 276 var open, merged, closed, deleted int ··· 305 } 306 307 // helper to get exactly one repo 308 + func GetRepo(e Execer, filters ...orm.Filter) (*models.Repo, error) { 309 repos, err := GetRepos(e, 0, filters...) 310 if err != nil { 311 return nil, err ··· 322 return &repos[0], nil 323 } 324 325 + func CountRepos(e Execer, filters ...orm.Filter) (int64, error) { 326 var conditions []string 327 var args []any 328 for _, filter := range filters { ··· 553 return err 554 } 555 556 + func UnsubscribeLabel(e Execer, filters ...orm.Filter) error { 557 var conditions []string 558 var args []any 559 for _, filter := range filters { ··· 571 return err 572 } 573 574 + func GetRepoLabels(e Execer, filters ...orm.Filter) ([]models.RepoLabel, error) { 575 var conditions []string 576 var args []any 577 for _, filter := range filters {
+6 -5
appview/db/spindle.go
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 ) 11 12 - func GetSpindles(e Execer, filters ...filter) ([]models.Spindle, error) { 13 var spindles []models.Spindle 14 15 var conditions []string ··· 91 return err 92 } 93 94 - func VerifySpindle(e Execer, filters ...filter) (int64, error) { 95 var conditions []string 96 var args []any 97 for _, filter := range filters { ··· 114 return res.RowsAffected() 115 } 116 117 - func DeleteSpindle(e Execer, filters ...filter) error { 118 var conditions []string 119 var args []any 120 for _, filter := range filters { ··· 144 return err 145 } 146 147 - func RemoveSpindleMember(e Execer, filters ...filter) error { 148 var conditions []string 149 var args []any 150 for _, filter := range filters { ··· 163 return err 164 } 165 166 - func GetSpindleMembers(e Execer, filters ...filter) ([]models.SpindleMember, error) { 167 var members []models.SpindleMember 168 169 var conditions []string
··· 7 "time" 8 9 "tangled.org/core/appview/models" 10 + "tangled.org/core/orm" 11 ) 12 13 + func GetSpindles(e Execer, filters ...orm.Filter) ([]models.Spindle, error) { 14 var spindles []models.Spindle 15 16 var conditions []string ··· 92 return err 93 } 94 95 + func VerifySpindle(e Execer, filters ...orm.Filter) (int64, error) { 96 var conditions []string 97 var args []any 98 for _, filter := range filters { ··· 115 return res.RowsAffected() 116 } 117 118 + func DeleteSpindle(e Execer, filters ...orm.Filter) error { 119 var conditions []string 120 var args []any 121 for _, filter := range filters { ··· 145 return err 146 } 147 148 + func RemoveSpindleMember(e Execer, filters ...orm.Filter) error { 149 var conditions []string 150 var args []any 151 for _, filter := range filters { ··· 164 return err 165 } 166 167 + func GetSpindleMembers(e Execer, filters ...orm.Filter) ([]models.SpindleMember, error) { 168 var members []models.SpindleMember 169 170 var conditions []string
+6 -4
appview/db/star.go
··· 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 ) 15 16 func AddStar(e Execer, star *models.Star) error { ··· 133 134 // GetRepoStars return a list of stars each holding target repository. 135 // If there isn't known repo with starred at-uri, those stars will be ignored. 136 - func GetRepoStars(e Execer, limit int, filters ...filter) ([]models.RepoStar, error) { 137 var conditions []string 138 var args []any 139 for _, filter := range filters { ··· 164 if err != nil { 165 return nil, err 166 } 167 168 starMap := make(map[string][]models.Star) 169 for rows.Next() { ··· 195 return nil, nil 196 } 197 198 - repos, err := GetRepos(e, 0, FilterIn("at_uri", args)) 199 if err != nil { 200 return nil, err 201 } ··· 225 return repoStars, nil 226 } 227 228 - func CountStars(e Execer, filters ...filter) (int64, error) { 229 var conditions []string 230 var args []any 231 for _, filter := range filters { ··· 298 } 299 300 // get full repo data 301 - repos, err := GetRepos(e, 0, FilterIn("at_uri", repoUris)) 302 if err != nil { 303 return nil, err 304 }
··· 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/appview/models" 14 + "tangled.org/core/orm" 15 ) 16 17 func AddStar(e Execer, star *models.Star) error { ··· 134 135 // GetRepoStars return a list of stars each holding target repository. 136 // If there isn't known repo with starred at-uri, those stars will be ignored. 137 + func GetRepoStars(e Execer, limit int, filters ...orm.Filter) ([]models.RepoStar, error) { 138 var conditions []string 139 var args []any 140 for _, filter := range filters { ··· 165 if err != nil { 166 return nil, err 167 } 168 + defer rows.Close() 169 170 starMap := make(map[string][]models.Star) 171 for rows.Next() { ··· 197 return nil, nil 198 } 199 200 + repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", args)) 201 if err != nil { 202 return nil, err 203 } ··· 227 return repoStars, nil 228 } 229 230 + func CountStars(e Execer, filters ...orm.Filter) (int64, error) { 231 var conditions []string 232 var args []any 233 for _, filter := range filters { ··· 300 } 301 302 // get full repo data 303 + repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", repoUris)) 304 if err != nil { 305 return nil, err 306 }
+4 -3
appview/db/strings.go
··· 8 "time" 9 10 "tangled.org/core/appview/models" 11 ) 12 13 func AddString(e Execer, s models.String) error { ··· 44 return err 45 } 46 47 - func GetStrings(e Execer, limit int, filters ...filter) ([]models.String, error) { 48 var all []models.String 49 50 var conditions []string ··· 127 return all, nil 128 } 129 130 - func CountStrings(e Execer, filters ...filter) (int64, error) { 131 var conditions []string 132 var args []any 133 for _, filter := range filters { ··· 151 return count, nil 152 } 153 154 - func DeleteString(e Execer, filters ...filter) error { 155 var conditions []string 156 var args []any 157 for _, filter := range filters {
··· 8 "time" 9 10 "tangled.org/core/appview/models" 11 + "tangled.org/core/orm" 12 ) 13 14 func AddString(e Execer, s models.String) error { ··· 45 return err 46 } 47 48 + func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, error) { 49 var all []models.String 50 51 var conditions []string ··· 128 return all, nil 129 } 130 131 + func CountStrings(e Execer, filters ...orm.Filter) (int64, error) { 132 var conditions []string 133 var args []any 134 for _, filter := range filters { ··· 152 return count, nil 153 } 154 155 + func DeleteString(e Execer, filters ...orm.Filter) error { 156 var conditions []string 157 var args []any 158 for _, filter := range filters {
+9 -8
appview/db/timeline.go
··· 5 6 "github.com/bluesky-social/indigo/atproto/syntax" 7 "tangled.org/core/appview/models" 8 ) 9 10 // TODO: this gathers heterogenous events from different sources and aggregates ··· 84 } 85 86 func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 87 - filters := make([]filter, 0) 88 if userIsFollowing != nil { 89 - filters = append(filters, FilterIn("did", userIsFollowing)) 90 } 91 92 repos, err := GetRepos(e, limit, filters...) ··· 104 105 var origRepos []models.Repo 106 if args != nil { 107 - origRepos, err = GetRepos(e, 0, FilterIn("at_uri", args)) 108 } 109 if err != nil { 110 return nil, err ··· 144 } 145 146 func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 147 - filters := make([]filter, 0) 148 if userIsFollowing != nil { 149 - filters = append(filters, FilterIn("did", userIsFollowing)) 150 } 151 152 stars, err := GetRepoStars(e, limit, filters...) ··· 180 } 181 182 func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 183 - filters := make([]filter, 0) 184 if userIsFollowing != nil { 185 - filters = append(filters, FilterIn("user_did", userIsFollowing)) 186 } 187 188 follows, err := GetFollows(e, limit, filters...) ··· 199 return nil, nil 200 } 201 202 - profiles, err := GetProfiles(e, FilterIn("did", subjects)) 203 if err != nil { 204 return nil, err 205 }
··· 5 6 "github.com/bluesky-social/indigo/atproto/syntax" 7 "tangled.org/core/appview/models" 8 + "tangled.org/core/orm" 9 ) 10 11 // TODO: this gathers heterogenous events from different sources and aggregates ··· 85 } 86 87 func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 88 + filters := make([]orm.Filter, 0) 89 if userIsFollowing != nil { 90 + filters = append(filters, orm.FilterIn("did", userIsFollowing)) 91 } 92 93 repos, err := GetRepos(e, limit, filters...) ··· 105 106 var origRepos []models.Repo 107 if args != nil { 108 + origRepos, err = GetRepos(e, 0, orm.FilterIn("at_uri", args)) 109 } 110 if err != nil { 111 return nil, err ··· 145 } 146 147 func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 148 + filters := make([]orm.Filter, 0) 149 if userIsFollowing != nil { 150 + filters = append(filters, orm.FilterIn("did", userIsFollowing)) 151 } 152 153 stars, err := GetRepoStars(e, limit, filters...) ··· 181 } 182 183 func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { 184 + filters := make([]orm.Filter, 0) 185 if userIsFollowing != nil { 186 + filters = append(filters, orm.FilterIn("user_did", userIsFollowing)) 187 } 188 189 follows, err := GetFollows(e, limit, filters...) ··· 200 return nil, nil 201 } 202 203 + profiles, err := GetProfiles(e, orm.FilterIn("did", subjects)) 204 if err != nil { 205 return nil, err 206 }
+25 -24
appview/ingester.go
··· 21 "tangled.org/core/appview/serververify" 22 "tangled.org/core/appview/validator" 23 "tangled.org/core/idresolver" 24 "tangled.org/core/rbac" 25 ) 26 ··· 253 254 err = db.AddArtifact(i.Db, artifact) 255 case jmodels.CommitOperationDelete: 256 - err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey)) 257 } 258 259 if err != nil { ··· 350 351 err = db.UpsertProfile(tx, &profile) 352 case jmodels.CommitOperationDelete: 353 - err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey)) 354 } 355 356 if err != nil { ··· 424 // get record from db first 425 members, err := db.GetSpindleMembers( 426 ddb, 427 - db.FilterEq("did", did), 428 - db.FilterEq("rkey", rkey), 429 ) 430 if err != nil || len(members) != 1 { 431 return fmt.Errorf("failed to get member: %w, len(members) = %d", err, len(members)) ··· 440 // remove record by rkey && update enforcer 441 if err = db.RemoveSpindleMember( 442 tx, 443 - db.FilterEq("did", did), 444 - db.FilterEq("rkey", rkey), 445 ); err != nil { 446 return fmt.Errorf("failed to remove from db: %w", err) 447 } ··· 523 // get record from db first 524 spindles, err := db.GetSpindles( 525 ddb, 526 - db.FilterEq("owner", did), 527 - db.FilterEq("instance", instance), 528 ) 529 if err != nil || len(spindles) != 1 { 530 return fmt.Errorf("failed to get spindles: %w, len(spindles) = %d", err, len(spindles)) ··· 543 // remove spindle members first 544 err = db.RemoveSpindleMember( 545 tx, 546 - db.FilterEq("owner", did), 547 - db.FilterEq("instance", instance), 548 ) 549 if err != nil { 550 return err ··· 552 553 err = db.DeleteSpindle( 554 tx, 555 - db.FilterEq("owner", did), 556 - db.FilterEq("instance", instance), 557 ) 558 if err != nil { 559 return err ··· 621 case jmodels.CommitOperationDelete: 622 if err := db.DeleteString( 623 ddb, 624 - db.FilterEq("did", did), 625 - db.FilterEq("rkey", rkey), 626 ); err != nil { 627 l.Error("failed to delete", "err", err) 628 return fmt.Errorf("failed to delete string record: %w", err) ··· 740 // get record from db first 741 registrations, err := db.GetRegistrations( 742 ddb, 743 - db.FilterEq("domain", domain), 744 - db.FilterEq("did", did), 745 ) 746 if err != nil { 747 return fmt.Errorf("failed to get registration: %w", err) ··· 762 763 err = db.DeleteKnot( 764 tx, 765 - db.FilterEq("did", did), 766 - db.FilterEq("domain", domain), 767 ) 768 if err != nil { 769 return err ··· 915 case jmodels.CommitOperationDelete: 916 if err := db.DeleteIssueComments( 917 ddb, 918 - db.FilterEq("did", did), 919 - db.FilterEq("rkey", rkey), 920 ); err != nil { 921 return fmt.Errorf("failed to delete issue comment record: %w", err) 922 } ··· 969 case jmodels.CommitOperationDelete: 970 if err := db.DeleteLabelDefinition( 971 ddb, 972 - db.FilterEq("did", did), 973 - db.FilterEq("rkey", rkey), 974 ); err != nil { 975 return fmt.Errorf("failed to delete labeldef record: %w", err) 976 } ··· 1010 var repo *models.Repo 1011 switch collection { 1012 case tangled.RepoIssueNSID: 1013 - i, err := db.GetIssues(ddb, db.FilterEq("at_uri", subject)) 1014 if err != nil || len(i) != 1 { 1015 return fmt.Errorf("failed to find subject: %w || subject count %d", err, len(i)) 1016 } ··· 1019 return fmt.Errorf("unsupport label subject: %s", collection) 1020 } 1021 1022 - actx, err := db.NewLabelApplicationCtx(ddb, db.FilterIn("at_uri", repo.Labels)) 1023 if err != nil { 1024 return fmt.Errorf("failed to build label application ctx: %w", err) 1025 }
··· 21 "tangled.org/core/appview/serververify" 22 "tangled.org/core/appview/validator" 23 "tangled.org/core/idresolver" 24 + "tangled.org/core/orm" 25 "tangled.org/core/rbac" 26 ) 27 ··· 254 255 err = db.AddArtifact(i.Db, artifact) 256 case jmodels.CommitOperationDelete: 257 + err = db.DeleteArtifact(i.Db, orm.FilterEq("did", did), orm.FilterEq("rkey", e.Commit.RKey)) 258 } 259 260 if err != nil { ··· 351 352 err = db.UpsertProfile(tx, &profile) 353 case jmodels.CommitOperationDelete: 354 + err = db.DeleteArtifact(i.Db, orm.FilterEq("did", did), orm.FilterEq("rkey", e.Commit.RKey)) 355 } 356 357 if err != nil { ··· 425 // get record from db first 426 members, err := db.GetSpindleMembers( 427 ddb, 428 + orm.FilterEq("did", did), 429 + orm.FilterEq("rkey", rkey), 430 ) 431 if err != nil || len(members) != 1 { 432 return fmt.Errorf("failed to get member: %w, len(members) = %d", err, len(members)) ··· 441 // remove record by rkey && update enforcer 442 if err = db.RemoveSpindleMember( 443 tx, 444 + orm.FilterEq("did", did), 445 + orm.FilterEq("rkey", rkey), 446 ); err != nil { 447 return fmt.Errorf("failed to remove from db: %w", err) 448 } ··· 524 // get record from db first 525 spindles, err := db.GetSpindles( 526 ddb, 527 + orm.FilterEq("owner", did), 528 + orm.FilterEq("instance", instance), 529 ) 530 if err != nil || len(spindles) != 1 { 531 return fmt.Errorf("failed to get spindles: %w, len(spindles) = %d", err, len(spindles)) ··· 544 // remove spindle members first 545 err = db.RemoveSpindleMember( 546 tx, 547 + orm.FilterEq("owner", did), 548 + orm.FilterEq("instance", instance), 549 ) 550 if err != nil { 551 return err ··· 553 554 err = db.DeleteSpindle( 555 tx, 556 + orm.FilterEq("owner", did), 557 + orm.FilterEq("instance", instance), 558 ) 559 if err != nil { 560 return err ··· 622 case jmodels.CommitOperationDelete: 623 if err := db.DeleteString( 624 ddb, 625 + orm.FilterEq("did", did), 626 + orm.FilterEq("rkey", rkey), 627 ); err != nil { 628 l.Error("failed to delete", "err", err) 629 return fmt.Errorf("failed to delete string record: %w", err) ··· 741 // get record from db first 742 registrations, err := db.GetRegistrations( 743 ddb, 744 + orm.FilterEq("domain", domain), 745 + orm.FilterEq("did", did), 746 ) 747 if err != nil { 748 return fmt.Errorf("failed to get registration: %w", err) ··· 763 764 err = db.DeleteKnot( 765 tx, 766 + orm.FilterEq("did", did), 767 + orm.FilterEq("domain", domain), 768 ) 769 if err != nil { 770 return err ··· 916 case jmodels.CommitOperationDelete: 917 if err := db.DeleteIssueComments( 918 ddb, 919 + orm.FilterEq("did", did), 920 + orm.FilterEq("rkey", rkey), 921 ); err != nil { 922 return fmt.Errorf("failed to delete issue comment record: %w", err) 923 } ··· 970 case jmodels.CommitOperationDelete: 971 if err := db.DeleteLabelDefinition( 972 ddb, 973 + orm.FilterEq("did", did), 974 + orm.FilterEq("rkey", rkey), 975 ); err != nil { 976 return fmt.Errorf("failed to delete labeldef record: %w", err) 977 } ··· 1011 var repo *models.Repo 1012 switch collection { 1013 case tangled.RepoIssueNSID: 1014 + i, err := db.GetIssues(ddb, orm.FilterEq("at_uri", subject)) 1015 if err != nil || len(i) != 1 { 1016 return fmt.Errorf("failed to find subject: %w || subject count %d", err, len(i)) 1017 } ··· 1020 return fmt.Errorf("unsupport label subject: %s", collection) 1021 } 1022 1023 + actx, err := db.NewLabelApplicationCtx(ddb, orm.FilterIn("at_uri", repo.Labels)) 1024 if err != nil { 1025 return fmt.Errorf("failed to build label application ctx: %w", err) 1026 }
+46 -45
appview/issues/issues.go
··· 19 "tangled.org/core/appview/config" 20 "tangled.org/core/appview/db" 21 issues_indexer "tangled.org/core/appview/indexer/issues" 22 "tangled.org/core/appview/models" 23 "tangled.org/core/appview/notify" 24 "tangled.org/core/appview/oauth" 25 "tangled.org/core/appview/pages" 26 "tangled.org/core/appview/pages/repoinfo" 27 "tangled.org/core/appview/pagination" 28 - "tangled.org/core/appview/refresolver" 29 "tangled.org/core/appview/reporesolver" 30 "tangled.org/core/appview/validator" 31 "tangled.org/core/idresolver" 32 "tangled.org/core/rbac" 33 "tangled.org/core/tid" 34 ) 35 36 type Issues struct { 37 - oauth *oauth.OAuth 38 - repoResolver *reporesolver.RepoResolver 39 - enforcer *rbac.Enforcer 40 - pages *pages.Pages 41 - idResolver *idresolver.Resolver 42 - refResolver *refresolver.Resolver 43 - db *db.DB 44 - config *config.Config 45 - notifier notify.Notifier 46 - logger *slog.Logger 47 - validator *validator.Validator 48 - indexer *issues_indexer.Indexer 49 } 50 51 func New( ··· 54 enforcer *rbac.Enforcer, 55 pages *pages.Pages, 56 idResolver *idresolver.Resolver, 57 - refResolver *refresolver.Resolver, 58 db *db.DB, 59 config *config.Config, 60 notifier notify.Notifier, ··· 63 logger *slog.Logger, 64 ) *Issues { 65 return &Issues{ 66 - oauth: oauth, 67 - repoResolver: repoResolver, 68 - enforcer: enforcer, 69 - pages: pages, 70 - idResolver: idResolver, 71 - refResolver: refResolver, 72 - db: db, 73 - config: config, 74 - notifier: notifier, 75 - logger: logger, 76 - validator: validator, 77 - indexer: indexer, 78 } 79 } 80 ··· 113 114 labelDefs, err := db.GetLabelDefinitions( 115 rp.db, 116 - db.FilterIn("at_uri", f.Labels), 117 - db.FilterContains("scope", tangled.RepoIssueNSID), 118 ) 119 if err != nil { 120 l.Error("failed to fetch labels", "err", err) ··· 163 newIssue := issue 164 newIssue.Title = r.FormValue("title") 165 newIssue.Body = r.FormValue("body") 166 - newIssue.Mentions, newIssue.References = rp.refResolver.Resolve(r.Context(), newIssue.Body) 167 168 if err := rp.validator.ValidateIssue(newIssue); err != nil { 169 l.Error("validation error", "err", err) ··· 314 if isIssueOwner || isRepoOwner || isCollaborator { 315 err = db.CloseIssues( 316 rp.db, 317 - db.FilterEq("id", issue.Id), 318 ) 319 if err != nil { 320 l.Error("failed to close issue", "err", err) ··· 361 if isCollaborator || isRepoOwner || isIssueOwner { 362 err := db.ReopenIssues( 363 rp.db, 364 - db.FilterEq("id", issue.Id), 365 ) 366 if err != nil { 367 l.Error("failed to reopen issue", "err", err) ··· 412 replyTo = &replyToUri 413 } 414 415 - mentions, references := rp.refResolver.Resolve(r.Context(), body) 416 417 comment := models.IssueComment{ 418 Did: user.Did, ··· 506 commentId := chi.URLParam(r, "commentId") 507 comments, err := db.GetIssueComments( 508 rp.db, 509 - db.FilterEq("id", commentId), 510 ) 511 if err != nil { 512 l.Error("failed to fetch comment", "id", commentId) ··· 542 commentId := chi.URLParam(r, "commentId") 543 comments, err := db.GetIssueComments( 544 rp.db, 545 - db.FilterEq("id", commentId), 546 ) 547 if err != nil { 548 l.Error("failed to fetch comment", "id", commentId) ··· 584 newComment := comment 585 newComment.Body = newBody 586 newComment.Edited = &now 587 - newComment.Mentions, newComment.References = rp.refResolver.Resolve(r.Context(), newBody) 588 589 record := newComment.AsRecord() 590 ··· 652 commentId := chi.URLParam(r, "commentId") 653 comments, err := db.GetIssueComments( 654 rp.db, 655 - db.FilterEq("id", commentId), 656 ) 657 if err != nil { 658 l.Error("failed to fetch comment", "id", commentId) ··· 688 commentId := chi.URLParam(r, "commentId") 689 comments, err := db.GetIssueComments( 690 rp.db, 691 - db.FilterEq("id", commentId), 692 ) 693 if err != nil { 694 l.Error("failed to fetch comment", "id", commentId) ··· 724 commentId := chi.URLParam(r, "commentId") 725 comments, err := db.GetIssueComments( 726 rp.db, 727 - db.FilterEq("id", commentId), 728 ) 729 if err != nil { 730 l.Error("failed to fetch comment", "id", commentId) ··· 751 752 // optimistic deletion 753 deleted := time.Now() 754 - err = db.DeleteIssueComments(rp.db, db.FilterEq("id", comment.Id)) 755 if err != nil { 756 l.Error("failed to delete comment", "err", err) 757 rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment") ··· 840 841 issues, err = db.GetIssues( 842 rp.db, 843 - db.FilterIn("id", res.Hits), 844 ) 845 if err != nil { 846 l.Error("failed to get issues", "err", err) ··· 856 issues, err = db.GetIssuesPaginated( 857 rp.db, 858 page, 859 - db.FilterEq("repo_at", f.RepoAt()), 860 - db.FilterEq("open", openInt), 861 ) 862 if err != nil { 863 l.Error("failed to get issues", "err", err) ··· 868 869 labelDefs, err := db.GetLabelDefinitions( 870 rp.db, 871 - db.FilterIn("at_uri", f.Labels), 872 - db.FilterContains("scope", tangled.RepoIssueNSID), 873 ) 874 if err != nil { 875 l.Error("failed to fetch labels", "err", err) ··· 912 }) 913 case http.MethodPost: 914 body := r.FormValue("body") 915 - mentions, references := rp.refResolver.Resolve(r.Context(), body) 916 917 issue := &models.Issue{ 918 RepoAt: f.RepoAt(),
··· 19 "tangled.org/core/appview/config" 20 "tangled.org/core/appview/db" 21 issues_indexer "tangled.org/core/appview/indexer/issues" 22 + "tangled.org/core/appview/mentions" 23 "tangled.org/core/appview/models" 24 "tangled.org/core/appview/notify" 25 "tangled.org/core/appview/oauth" 26 "tangled.org/core/appview/pages" 27 "tangled.org/core/appview/pages/repoinfo" 28 "tangled.org/core/appview/pagination" 29 "tangled.org/core/appview/reporesolver" 30 "tangled.org/core/appview/validator" 31 "tangled.org/core/idresolver" 32 + "tangled.org/core/orm" 33 "tangled.org/core/rbac" 34 "tangled.org/core/tid" 35 ) 36 37 type Issues struct { 38 + oauth *oauth.OAuth 39 + repoResolver *reporesolver.RepoResolver 40 + enforcer *rbac.Enforcer 41 + pages *pages.Pages 42 + idResolver *idresolver.Resolver 43 + mentionsResolver *mentions.Resolver 44 + db *db.DB 45 + config *config.Config 46 + notifier notify.Notifier 47 + logger *slog.Logger 48 + validator *validator.Validator 49 + indexer *issues_indexer.Indexer 50 } 51 52 func New( ··· 55 enforcer *rbac.Enforcer, 56 pages *pages.Pages, 57 idResolver *idresolver.Resolver, 58 + mentionsResolver *mentions.Resolver, 59 db *db.DB, 60 config *config.Config, 61 notifier notify.Notifier, ··· 64 logger *slog.Logger, 65 ) *Issues { 66 return &Issues{ 67 + oauth: oauth, 68 + repoResolver: repoResolver, 69 + enforcer: enforcer, 70 + pages: pages, 71 + idResolver: idResolver, 72 + mentionsResolver: mentionsResolver, 73 + db: db, 74 + config: config, 75 + notifier: notifier, 76 + logger: logger, 77 + validator: validator, 78 + indexer: indexer, 79 } 80 } 81 ··· 114 115 labelDefs, err := db.GetLabelDefinitions( 116 rp.db, 117 + orm.FilterIn("at_uri", f.Labels), 118 + orm.FilterContains("scope", tangled.RepoIssueNSID), 119 ) 120 if err != nil { 121 l.Error("failed to fetch labels", "err", err) ··· 164 newIssue := issue 165 newIssue.Title = r.FormValue("title") 166 newIssue.Body = r.FormValue("body") 167 + newIssue.Mentions, newIssue.References = rp.mentionsResolver.Resolve(r.Context(), newIssue.Body) 168 169 if err := rp.validator.ValidateIssue(newIssue); err != nil { 170 l.Error("validation error", "err", err) ··· 315 if isIssueOwner || isRepoOwner || isCollaborator { 316 err = db.CloseIssues( 317 rp.db, 318 + orm.FilterEq("id", issue.Id), 319 ) 320 if err != nil { 321 l.Error("failed to close issue", "err", err) ··· 362 if isCollaborator || isRepoOwner || isIssueOwner { 363 err := db.ReopenIssues( 364 rp.db, 365 + orm.FilterEq("id", issue.Id), 366 ) 367 if err != nil { 368 l.Error("failed to reopen issue", "err", err) ··· 413 replyTo = &replyToUri 414 } 415 416 + mentions, references := rp.mentionsResolver.Resolve(r.Context(), body) 417 418 comment := models.IssueComment{ 419 Did: user.Did, ··· 507 commentId := chi.URLParam(r, "commentId") 508 comments, err := db.GetIssueComments( 509 rp.db, 510 + orm.FilterEq("id", commentId), 511 ) 512 if err != nil { 513 l.Error("failed to fetch comment", "id", commentId) ··· 543 commentId := chi.URLParam(r, "commentId") 544 comments, err := db.GetIssueComments( 545 rp.db, 546 + orm.FilterEq("id", commentId), 547 ) 548 if err != nil { 549 l.Error("failed to fetch comment", "id", commentId) ··· 585 newComment := comment 586 newComment.Body = newBody 587 newComment.Edited = &now 588 + newComment.Mentions, newComment.References = rp.mentionsResolver.Resolve(r.Context(), newBody) 589 590 record := newComment.AsRecord() 591 ··· 653 commentId := chi.URLParam(r, "commentId") 654 comments, err := db.GetIssueComments( 655 rp.db, 656 + orm.FilterEq("id", commentId), 657 ) 658 if err != nil { 659 l.Error("failed to fetch comment", "id", commentId) ··· 689 commentId := chi.URLParam(r, "commentId") 690 comments, err := db.GetIssueComments( 691 rp.db, 692 + orm.FilterEq("id", commentId), 693 ) 694 if err != nil { 695 l.Error("failed to fetch comment", "id", commentId) ··· 725 commentId := chi.URLParam(r, "commentId") 726 comments, err := db.GetIssueComments( 727 rp.db, 728 + orm.FilterEq("id", commentId), 729 ) 730 if err != nil { 731 l.Error("failed to fetch comment", "id", commentId) ··· 752 753 // optimistic deletion 754 deleted := time.Now() 755 + err = db.DeleteIssueComments(rp.db, orm.FilterEq("id", comment.Id)) 756 if err != nil { 757 l.Error("failed to delete comment", "err", err) 758 rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment") ··· 841 842 issues, err = db.GetIssues( 843 rp.db, 844 + orm.FilterIn("id", res.Hits), 845 ) 846 if err != nil { 847 l.Error("failed to get issues", "err", err) ··· 857 issues, err = db.GetIssuesPaginated( 858 rp.db, 859 page, 860 + orm.FilterEq("repo_at", f.RepoAt()), 861 + orm.FilterEq("open", openInt), 862 ) 863 if err != nil { 864 l.Error("failed to get issues", "err", err) ··· 869 870 labelDefs, err := db.GetLabelDefinitions( 871 rp.db, 872 + orm.FilterIn("at_uri", f.Labels), 873 + orm.FilterContains("scope", tangled.RepoIssueNSID), 874 ) 875 if err != nil { 876 l.Error("failed to fetch labels", "err", err) ··· 913 }) 914 case http.MethodPost: 915 body := r.FormValue("body") 916 + mentions, references := rp.mentionsResolver.Resolve(r.Context(), body) 917 918 issue := &models.Issue{ 919 RepoAt: f.RepoAt(),
+19 -18
appview/knots/knots.go
··· 21 "tangled.org/core/appview/xrpcclient" 22 "tangled.org/core/eventconsumer" 23 "tangled.org/core/idresolver" 24 "tangled.org/core/rbac" 25 "tangled.org/core/tid" 26 ··· 72 user := k.OAuth.GetUser(r) 73 registrations, err := db.GetRegistrations( 74 k.Db, 75 - db.FilterEq("did", user.Did), 76 ) 77 if err != nil { 78 k.Logger.Error("failed to fetch knot registrations", "err", err) ··· 102 103 registrations, err := db.GetRegistrations( 104 k.Db, 105 - db.FilterEq("did", user.Did), 106 - db.FilterEq("domain", domain), 107 ) 108 if err != nil { 109 l.Error("failed to get registrations", "err", err) ··· 127 repos, err := db.GetRepos( 128 k.Db, 129 0, 130 - db.FilterEq("knot", domain), 131 ) 132 if err != nil { 133 l.Error("failed to get knot repos", "err", err) ··· 293 // get record from db first 294 registrations, err := db.GetRegistrations( 295 k.Db, 296 - db.FilterEq("did", user.Did), 297 - db.FilterEq("domain", domain), 298 ) 299 if err != nil { 300 l.Error("failed to get registration", "err", err) ··· 321 322 err = db.DeleteKnot( 323 tx, 324 - db.FilterEq("did", user.Did), 325 - db.FilterEq("domain", domain), 326 ) 327 if err != nil { 328 l.Error("failed to delete registration", "err", err) ··· 402 // get record from db first 403 registrations, err := db.GetRegistrations( 404 k.Db, 405 - db.FilterEq("did", user.Did), 406 - db.FilterEq("domain", domain), 407 ) 408 if err != nil { 409 l.Error("failed to get registration", "err", err) ··· 493 // Get updated registration to show 494 registrations, err = db.GetRegistrations( 495 k.Db, 496 - db.FilterEq("did", user.Did), 497 - db.FilterEq("domain", domain), 498 ) 499 if err != nil { 500 l.Error("failed to get registration", "err", err) ··· 529 530 registrations, err := db.GetRegistrations( 531 k.Db, 532 - db.FilterEq("did", user.Did), 533 - db.FilterEq("domain", domain), 534 - db.FilterIsNot("registered", "null"), 535 ) 536 if err != nil { 537 l.Error("failed to get registration", "err", err) ··· 637 638 registrations, err := db.GetRegistrations( 639 k.Db, 640 - db.FilterEq("did", user.Did), 641 - db.FilterEq("domain", domain), 642 - db.FilterIsNot("registered", "null"), 643 ) 644 if err != nil { 645 l.Error("failed to get registration", "err", err)
··· 21 "tangled.org/core/appview/xrpcclient" 22 "tangled.org/core/eventconsumer" 23 "tangled.org/core/idresolver" 24 + "tangled.org/core/orm" 25 "tangled.org/core/rbac" 26 "tangled.org/core/tid" 27 ··· 73 user := k.OAuth.GetUser(r) 74 registrations, err := db.GetRegistrations( 75 k.Db, 76 + orm.FilterEq("did", user.Did), 77 ) 78 if err != nil { 79 k.Logger.Error("failed to fetch knot registrations", "err", err) ··· 103 104 registrations, err := db.GetRegistrations( 105 k.Db, 106 + orm.FilterEq("did", user.Did), 107 + orm.FilterEq("domain", domain), 108 ) 109 if err != nil { 110 l.Error("failed to get registrations", "err", err) ··· 128 repos, err := db.GetRepos( 129 k.Db, 130 0, 131 + orm.FilterEq("knot", domain), 132 ) 133 if err != nil { 134 l.Error("failed to get knot repos", "err", err) ··· 294 // get record from db first 295 registrations, err := db.GetRegistrations( 296 k.Db, 297 + orm.FilterEq("did", user.Did), 298 + orm.FilterEq("domain", domain), 299 ) 300 if err != nil { 301 l.Error("failed to get registration", "err", err) ··· 322 323 err = db.DeleteKnot( 324 tx, 325 + orm.FilterEq("did", user.Did), 326 + orm.FilterEq("domain", domain), 327 ) 328 if err != nil { 329 l.Error("failed to delete registration", "err", err) ··· 403 // get record from db first 404 registrations, err := db.GetRegistrations( 405 k.Db, 406 + orm.FilterEq("did", user.Did), 407 + orm.FilterEq("domain", domain), 408 ) 409 if err != nil { 410 l.Error("failed to get registration", "err", err) ··· 494 // Get updated registration to show 495 registrations, err = db.GetRegistrations( 496 k.Db, 497 + orm.FilterEq("did", user.Did), 498 + orm.FilterEq("domain", domain), 499 ) 500 if err != nil { 501 l.Error("failed to get registration", "err", err) ··· 530 531 registrations, err := db.GetRegistrations( 532 k.Db, 533 + orm.FilterEq("did", user.Did), 534 + orm.FilterEq("domain", domain), 535 + orm.FilterIsNot("registered", "null"), 536 ) 537 if err != nil { 538 l.Error("failed to get registration", "err", err) ··· 638 639 registrations, err := db.GetRegistrations( 640 k.Db, 641 + orm.FilterEq("did", user.Did), 642 + orm.FilterEq("domain", domain), 643 + orm.FilterIsNot("registered", "null"), 644 ) 645 if err != nil { 646 l.Error("failed to get registration", "err", err)
+5 -4
appview/labels/labels.go
··· 16 "tangled.org/core/appview/oauth" 17 "tangled.org/core/appview/pages" 18 "tangled.org/core/appview/validator" 19 "tangled.org/core/rbac" 20 "tangled.org/core/tid" 21 ··· 88 repoAt := r.Form.Get("repo") 89 subjectUri := r.Form.Get("subject") 90 91 - repo, err := db.GetRepo(l.db, db.FilterEq("at_uri", repoAt)) 92 if err != nil { 93 fail("Failed to get repository.", err) 94 return 95 } 96 97 // find all the labels that this repo subscribes to 98 - repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt)) 99 if err != nil { 100 fail("Failed to get labels for this repository.", err) 101 return ··· 106 labelAts = append(labelAts, rl.LabelAt.String()) 107 } 108 109 - actx, err := db.NewLabelApplicationCtx(l.db, db.FilterIn("at_uri", labelAts)) 110 if err != nil { 111 fail("Invalid form data.", err) 112 return 113 } 114 115 // calculate the start state by applying already known labels 116 - existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri)) 117 if err != nil { 118 fail("Invalid form data.", err) 119 return
··· 16 "tangled.org/core/appview/oauth" 17 "tangled.org/core/appview/pages" 18 "tangled.org/core/appview/validator" 19 + "tangled.org/core/orm" 20 "tangled.org/core/rbac" 21 "tangled.org/core/tid" 22 ··· 89 repoAt := r.Form.Get("repo") 90 subjectUri := r.Form.Get("subject") 91 92 + repo, err := db.GetRepo(l.db, orm.FilterEq("at_uri", repoAt)) 93 if err != nil { 94 fail("Failed to get repository.", err) 95 return 96 } 97 98 // find all the labels that this repo subscribes to 99 + repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_at", repoAt)) 100 if err != nil { 101 fail("Failed to get labels for this repository.", err) 102 return ··· 107 labelAts = append(labelAts, rl.LabelAt.String()) 108 } 109 110 + actx, err := db.NewLabelApplicationCtx(l.db, orm.FilterIn("at_uri", labelAts)) 111 if err != nil { 112 fail("Invalid form data.", err) 113 return 114 } 115 116 // calculate the start state by applying already known labels 117 + existingOps, err := db.GetLabelOps(l.db, orm.FilterEq("subject", subjectUri)) 118 if err != nil { 119 fail("Invalid form data.", err) 120 return
+67
appview/mentions/resolver.go
···
··· 1 + package mentions 2 + 3 + import ( 4 + "context" 5 + "log/slog" 6 + 7 + "github.com/bluesky-social/indigo/atproto/syntax" 8 + "tangled.org/core/appview/config" 9 + "tangled.org/core/appview/db" 10 + "tangled.org/core/appview/models" 11 + "tangled.org/core/appview/pages/markup" 12 + "tangled.org/core/idresolver" 13 + ) 14 + 15 + type Resolver struct { 16 + config *config.Config 17 + idResolver *idresolver.Resolver 18 + execer db.Execer 19 + logger *slog.Logger 20 + } 21 + 22 + func New( 23 + config *config.Config, 24 + idResolver *idresolver.Resolver, 25 + execer db.Execer, 26 + logger *slog.Logger, 27 + ) *Resolver { 28 + return &Resolver{ 29 + config, 30 + idResolver, 31 + execer, 32 + logger, 33 + } 34 + } 35 + 36 + func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) { 37 + l := r.logger.With("method", "Resolve") 38 + 39 + rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source) 40 + l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs) 41 + 42 + idents := r.idResolver.ResolveIdents(ctx, rawMentions) 43 + var mentions []syntax.DID 44 + for _, ident := range idents { 45 + if ident != nil && !ident.Handle.IsInvalidHandle() { 46 + mentions = append(mentions, ident.DID) 47 + } 48 + } 49 + l.Debug("found mentions", "mentions", mentions) 50 + 51 + var resolvedRefs []models.ReferenceLink 52 + for _, rawRef := range rawRefs { 53 + ident, err := r.idResolver.ResolveIdent(ctx, rawRef.Handle) 54 + if err != nil || ident == nil || ident.Handle.IsInvalidHandle() { 55 + continue 56 + } 57 + rawRef.Handle = string(ident.DID) 58 + resolvedRefs = append(resolvedRefs, rawRef) 59 + } 60 + aturiRefs, err := db.ValidateReferenceLinks(r.execer, resolvedRefs) 61 + if err != nil { 62 + l.Error("failed running query", "err", err) 63 + } 64 + l.Debug("found references", "refs", aturiRefs) 65 + 66 + return mentions, aturiRefs 67 + }
+3 -2
appview/middleware/middleware.go
··· 18 "tangled.org/core/appview/pagination" 19 "tangled.org/core/appview/reporesolver" 20 "tangled.org/core/idresolver" 21 "tangled.org/core/rbac" 22 ) 23 ··· 217 218 repo, err := db.GetRepo( 219 mw.db, 220 - db.FilterEq("did", id.DID.String()), 221 - db.FilterEq("name", repoName), 222 ) 223 if err != nil { 224 log.Println("failed to resolve repo", "err", err)
··· 18 "tangled.org/core/appview/pagination" 19 "tangled.org/core/appview/reporesolver" 20 "tangled.org/core/idresolver" 21 + "tangled.org/core/orm" 22 "tangled.org/core/rbac" 23 ) 24 ··· 218 219 repo, err := db.GetRepo( 220 mw.db, 221 + orm.FilterEq("did", id.DID.String()), 222 + orm.FilterEq("name", repoName), 223 ) 224 if err != nil { 225 log.Println("failed to resolve repo", "err", err)
+5 -4
appview/notifications/notifications.go
··· 11 "tangled.org/core/appview/oauth" 12 "tangled.org/core/appview/pages" 13 "tangled.org/core/appview/pagination" 14 ) 15 16 type Notifications struct { ··· 53 54 total, err := db.CountNotifications( 55 n.db, 56 - db.FilterEq("recipient_did", user.Did), 57 ) 58 if err != nil { 59 l.Error("failed to get total notifications", "err", err) ··· 64 notifications, err := db.GetNotificationsWithEntities( 65 n.db, 66 page, 67 - db.FilterEq("recipient_did", user.Did), 68 ) 69 if err != nil { 70 l.Error("failed to get notifications", "err", err) ··· 96 97 count, err := db.CountNotifications( 98 n.db, 99 - db.FilterEq("recipient_did", user.Did), 100 - db.FilterEq("read", 0), 101 ) 102 if err != nil { 103 http.Error(w, "Failed to get unread count", http.StatusInternalServerError)
··· 11 "tangled.org/core/appview/oauth" 12 "tangled.org/core/appview/pages" 13 "tangled.org/core/appview/pagination" 14 + "tangled.org/core/orm" 15 ) 16 17 type Notifications struct { ··· 54 55 total, err := db.CountNotifications( 56 n.db, 57 + orm.FilterEq("recipient_did", user.Did), 58 ) 59 if err != nil { 60 l.Error("failed to get total notifications", "err", err) ··· 65 notifications, err := db.GetNotificationsWithEntities( 66 n.db, 67 page, 68 + orm.FilterEq("recipient_did", user.Did), 69 ) 70 if err != nil { 71 l.Error("failed to get notifications", "err", err) ··· 97 98 count, err := db.CountNotifications( 99 n.db, 100 + orm.FilterEq("recipient_did", user.Did), 101 + orm.FilterEq("read", 0), 102 ) 103 if err != nil { 104 http.Error(w, "Failed to get unread count", http.StatusInternalServerError)
+77 -66
appview/notify/db/db.go
··· 3 import ( 4 "context" 5 "log" 6 - "maps" 7 "slices" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" ··· 12 "tangled.org/core/appview/models" 13 "tangled.org/core/appview/notify" 14 "tangled.org/core/idresolver" 15 ) 16 17 const ( 18 - maxMentions = 5 19 ) 20 21 type databaseNotifier struct { ··· 42 return 43 } 44 var err error 45 - repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt))) 46 if err != nil { 47 log.Printf("NewStar: failed to get repos: %v", err) 48 return 49 } 50 51 actorDid := syntax.DID(star.Did) 52 - recipients := []syntax.DID{syntax.DID(repo.Did)} 53 eventType := models.NotificationTypeRepoStarred 54 entityType := "repo" 55 entityId := star.RepoAt.String() ··· 74 } 75 76 func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 77 - 78 - // build the recipients list 79 - // - owner of the repo 80 - // - collaborators in the repo 81 - var recipients []syntax.DID 82 - recipients = append(recipients, syntax.DID(issue.Repo.Did)) 83 - collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt())) 84 if err != nil { 85 log.Printf("failed to fetch collaborators: %v", err) 86 return 87 } 88 for _, c := range collaborators { 89 - recipients = append(recipients, c.SubjectDid) 90 } 91 92 actorDid := syntax.DID(issue.Did) ··· 108 ) 109 n.notifyEvent( 110 actorDid, 111 - mentions, 112 models.NotificationTypeUserMentioned, 113 entityType, 114 entityId, ··· 119 } 120 121 func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { 122 - issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt)) 123 if err != nil { 124 log.Printf("NewIssueComment: failed to get issues: %v", err) 125 return ··· 130 } 131 issue := issues[0] 132 133 - var recipients []syntax.DID 134 - recipients = append(recipients, syntax.DID(issue.Repo.Did)) 135 136 if comment.IsReply() { 137 // if this comment is a reply, then notify everybody in that thread 138 parentAtUri := *comment.ReplyTo 139 - allThreads := issue.CommentList() 140 141 // find the parent thread, and add all DIDs from here to the recipient list 142 - for _, t := range allThreads { 143 if t.Self.AtUri().String() == parentAtUri { 144 - recipients = append(recipients, t.Participants()...) 145 } 146 } 147 } else { 148 // not a reply, notify just the issue author 149 - recipients = append(recipients, syntax.DID(issue.Did)) 150 } 151 152 actorDid := syntax.DID(comment.Did) ··· 168 ) 169 n.notifyEvent( 170 actorDid, 171 - mentions, 172 models.NotificationTypeUserMentioned, 173 entityType, 174 entityId, ··· 184 185 func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 186 actorDid := syntax.DID(follow.UserDid) 187 - recipients := []syntax.DID{syntax.DID(follow.SubjectDid)} 188 eventType := models.NotificationTypeFollowed 189 entityType := "follow" 190 entityId := follow.UserDid ··· 207 } 208 209 func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) { 210 - repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) 211 if err != nil { 212 log.Printf("NewPull: failed to get repos: %v", err) 213 return 214 } 215 - 216 - // build the recipients list 217 - // - owner of the repo 218 - // - collaborators in the repo 219 - var recipients []syntax.DID 220 - recipients = append(recipients, syntax.DID(repo.Did)) 221 - collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt())) 222 if err != nil { 223 log.Printf("failed to fetch collaborators: %v", err) 224 return 225 } 226 for _, c := range collaborators { 227 - recipients = append(recipients, c.SubjectDid) 228 } 229 230 actorDid := syntax.DID(pull.OwnerDid) ··· 258 return 259 } 260 261 - repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt)) 262 if err != nil { 263 log.Printf("NewPullComment: failed to get repos: %v", err) 264 return ··· 267 // build up the recipients list: 268 // - repo owner 269 // - all pull participants 270 - var recipients []syntax.DID 271 - recipients = append(recipients, syntax.DID(repo.Did)) 272 for _, p := range pull.Participants() { 273 - recipients = append(recipients, syntax.DID(p)) 274 } 275 276 actorDid := syntax.DID(comment.OwnerDid) ··· 294 ) 295 n.notifyEvent( 296 actorDid, 297 - mentions, 298 models.NotificationTypeUserMentioned, 299 entityType, 300 entityId, ··· 321 } 322 323 func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { 324 - // build up the recipients list: 325 - // - repo owner 326 - // - repo collaborators 327 - // - all issue participants 328 - var recipients []syntax.DID 329 - recipients = append(recipients, syntax.DID(issue.Repo.Did)) 330 - collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt())) 331 if err != nil { 332 log.Printf("failed to fetch collaborators: %v", err) 333 return 334 } 335 for _, c := range collaborators { 336 - recipients = append(recipients, c.SubjectDid) 337 } 338 for _, p := range issue.Participants() { 339 - recipients = append(recipients, syntax.DID(p)) 340 } 341 342 entityType := "pull" ··· 366 367 func (n *databaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) { 368 // Get repo details 369 - repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) 370 if err != nil { 371 log.Printf("NewPullState: failed to get repos: %v", err) 372 return 373 } 374 375 - // build up the recipients list: 376 - // - repo owner 377 - // - all pull participants 378 - var recipients []syntax.DID 379 - recipients = append(recipients, syntax.DID(repo.Did)) 380 - collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt())) 381 if err != nil { 382 log.Printf("failed to fetch collaborators: %v", err) 383 return 384 } 385 for _, c := range collaborators { 386 - recipients = append(recipients, c.SubjectDid) 387 } 388 for _, p := range pull.Participants() { 389 - recipients = append(recipients, syntax.DID(p)) 390 } 391 392 entityType := "pull" ··· 422 423 func (n *databaseNotifier) notifyEvent( 424 actorDid syntax.DID, 425 - recipients []syntax.DID, 426 eventType models.NotificationType, 427 entityType string, 428 entityId string, ··· 430 issueId *int64, 431 pullId *int64, 432 ) { 433 - if eventType == models.NotificationTypeUserMentioned && len(recipients) > maxMentions { 434 - recipients = recipients[:maxMentions] 435 } 436 - recipientSet := make(map[syntax.DID]struct{}) 437 - for _, did := range recipients { 438 - // everybody except actor themselves 439 - if did != actorDid { 440 - recipientSet[did] = struct{}{} 441 - } 442 - } 443 444 prefMap, err := db.GetNotificationPreferences( 445 n.db, 446 - db.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))), 447 ) 448 if err != nil { 449 // failed to get prefs for users ··· 459 defer tx.Rollback() 460 461 // filter based on preferences 462 - for recipientDid := range recipientSet { 463 prefs, ok := prefMap[recipientDid] 464 if !ok { 465 prefs = models.DefaultNotificationPreferences(recipientDid)
··· 3 import ( 4 "context" 5 "log" 6 "slices" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" ··· 11 "tangled.org/core/appview/models" 12 "tangled.org/core/appview/notify" 13 "tangled.org/core/idresolver" 14 + "tangled.org/core/orm" 15 + "tangled.org/core/sets" 16 ) 17 18 const ( 19 + maxMentions = 8 20 ) 21 22 type databaseNotifier struct { ··· 43 return 44 } 45 var err error 46 + repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(star.RepoAt))) 47 if err != nil { 48 log.Printf("NewStar: failed to get repos: %v", err) 49 return 50 } 51 52 actorDid := syntax.DID(star.Did) 53 + recipients := sets.Singleton(syntax.DID(repo.Did)) 54 eventType := models.NotificationTypeRepoStarred 55 entityType := "repo" 56 entityId := star.RepoAt.String() ··· 75 } 76 77 func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 78 + collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) 79 if err != nil { 80 log.Printf("failed to fetch collaborators: %v", err) 81 return 82 } 83 + 84 + // build the recipients list 85 + // - owner of the repo 86 + // - collaborators in the repo 87 + // - remove users already mentioned 88 + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 89 for _, c := range collaborators { 90 + recipients.Insert(c.SubjectDid) 91 + } 92 + for _, m := range mentions { 93 + recipients.Remove(m) 94 } 95 96 actorDid := syntax.DID(issue.Did) ··· 112 ) 113 n.notifyEvent( 114 actorDid, 115 + sets.Collect(slices.Values(mentions)), 116 models.NotificationTypeUserMentioned, 117 entityType, 118 entityId, ··· 123 } 124 125 func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { 126 + issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.IssueAt)) 127 if err != nil { 128 log.Printf("NewIssueComment: failed to get issues: %v", err) 129 return ··· 134 } 135 issue := issues[0] 136 137 + // built the recipients list: 138 + // - the owner of the repo 139 + // - | if the comment is a reply -> everybody on that thread 140 + // | if the comment is a top level -> just the issue owner 141 + // - remove mentioned users from the recipients list 142 + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 143 144 if comment.IsReply() { 145 // if this comment is a reply, then notify everybody in that thread 146 parentAtUri := *comment.ReplyTo 147 148 // find the parent thread, and add all DIDs from here to the recipient list 149 + for _, t := range issue.CommentList() { 150 if t.Self.AtUri().String() == parentAtUri { 151 + for _, p := range t.Participants() { 152 + recipients.Insert(p) 153 + } 154 } 155 } 156 } else { 157 // not a reply, notify just the issue author 158 + recipients.Insert(syntax.DID(issue.Did)) 159 + } 160 + 161 + for _, m := range mentions { 162 + recipients.Remove(m) 163 } 164 165 actorDid := syntax.DID(comment.Did) ··· 181 ) 182 n.notifyEvent( 183 actorDid, 184 + sets.Collect(slices.Values(mentions)), 185 models.NotificationTypeUserMentioned, 186 entityType, 187 entityId, ··· 197 198 func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 199 actorDid := syntax.DID(follow.UserDid) 200 + recipients := sets.Singleton(syntax.DID(follow.SubjectDid)) 201 eventType := models.NotificationTypeFollowed 202 entityType := "follow" 203 entityId := follow.UserDid ··· 220 } 221 222 func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) { 223 + repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(pull.RepoAt))) 224 if err != nil { 225 log.Printf("NewPull: failed to get repos: %v", err) 226 return 227 } 228 + collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) 229 if err != nil { 230 log.Printf("failed to fetch collaborators: %v", err) 231 return 232 } 233 + 234 + // build the recipients list 235 + // - owner of the repo 236 + // - collaborators in the repo 237 + recipients := sets.Singleton(syntax.DID(repo.Did)) 238 for _, c := range collaborators { 239 + recipients.Insert(c.SubjectDid) 240 } 241 242 actorDid := syntax.DID(pull.OwnerDid) ··· 270 return 271 } 272 273 + repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", comment.RepoAt)) 274 if err != nil { 275 log.Printf("NewPullComment: failed to get repos: %v", err) 276 return ··· 279 // build up the recipients list: 280 // - repo owner 281 // - all pull participants 282 + // - remove those already mentioned 283 + recipients := sets.Singleton(syntax.DID(repo.Did)) 284 for _, p := range pull.Participants() { 285 + recipients.Insert(syntax.DID(p)) 286 + } 287 + for _, m := range mentions { 288 + recipients.Remove(m) 289 } 290 291 actorDid := syntax.DID(comment.OwnerDid) ··· 309 ) 310 n.notifyEvent( 311 actorDid, 312 + sets.Collect(slices.Values(mentions)), 313 models.NotificationTypeUserMentioned, 314 entityType, 315 entityId, ··· 336 } 337 338 func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { 339 + collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt())) 340 if err != nil { 341 log.Printf("failed to fetch collaborators: %v", err) 342 return 343 } 344 + 345 + // build up the recipients list: 346 + // - repo owner 347 + // - repo collaborators 348 + // - all issue participants 349 + recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 350 for _, c := range collaborators { 351 + recipients.Insert(c.SubjectDid) 352 } 353 for _, p := range issue.Participants() { 354 + recipients.Insert(syntax.DID(p)) 355 } 356 357 entityType := "pull" ··· 381 382 func (n *databaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) { 383 // Get repo details 384 + repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(pull.RepoAt))) 385 if err != nil { 386 log.Printf("NewPullState: failed to get repos: %v", err) 387 return 388 } 389 390 + collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt())) 391 if err != nil { 392 log.Printf("failed to fetch collaborators: %v", err) 393 return 394 } 395 + 396 + // build up the recipients list: 397 + // - repo owner 398 + // - all pull participants 399 + recipients := sets.Singleton(syntax.DID(repo.Did)) 400 for _, c := range collaborators { 401 + recipients.Insert(c.SubjectDid) 402 } 403 for _, p := range pull.Participants() { 404 + recipients.Insert(syntax.DID(p)) 405 } 406 407 entityType := "pull" ··· 437 438 func (n *databaseNotifier) notifyEvent( 439 actorDid syntax.DID, 440 + recipients sets.Set[syntax.DID], 441 eventType models.NotificationType, 442 entityType string, 443 entityId string, ··· 445 issueId *int64, 446 pullId *int64, 447 ) { 448 + // if the user is attempting to mention >maxMentions users, this is probably spam, do not mention anybody 449 + if eventType == models.NotificationTypeUserMentioned && recipients.Len() > maxMentions { 450 + return 451 } 452 + 453 + recipients.Remove(actorDid) 454 455 prefMap, err := db.GetNotificationPreferences( 456 n.db, 457 + orm.FilterIn("user_did", slices.Collect(recipients.All())), 458 ) 459 if err != nil { 460 // failed to get prefs for users ··· 470 defer tx.Rollback() 471 472 // filter based on preferences 473 + for recipientDid := range recipients.All() { 474 prefs, ok := prefMap[recipientDid] 475 if !ok { 476 prefs = models.DefaultNotificationPreferences(recipientDid)
-1
appview/notify/merged_notifier.go
··· 39 v.Call(in) 40 }(n) 41 } 42 - wg.Wait() 43 } 44 45 func (m *mergedNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
··· 39 v.Call(in) 40 }(n) 41 } 42 } 43 44 func (m *mergedNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
+3 -2
appview/oauth/handler.go
··· 16 "tangled.org/core/api/tangled" 17 "tangled.org/core/appview/db" 18 "tangled.org/core/consts" 19 "tangled.org/core/tid" 20 ) 21 ··· 97 // and create an sh.tangled.spindle.member record with that 98 spindleMembers, err := db.GetSpindleMembers( 99 o.Db, 100 - db.FilterEq("instance", "spindle.tangled.sh"), 101 - db.FilterEq("subject", did), 102 ) 103 if err != nil { 104 l.Error("failed to get spindle members", "err", err)
··· 16 "tangled.org/core/api/tangled" 17 "tangled.org/core/appview/db" 18 "tangled.org/core/consts" 19 + "tangled.org/core/orm" 20 "tangled.org/core/tid" 21 ) 22 ··· 98 // and create an sh.tangled.spindle.member record with that 99 spindleMembers, err := db.GetSpindleMembers( 100 o.Db, 101 + orm.FilterEq("instance", "spindle.tangled.sh"), 102 + orm.FilterEq("subject", did), 103 ) 104 if err != nil { 105 l.Error("failed to get spindle members", "err", err)
+6 -1
appview/pages/funcmap.go
··· 25 "github.com/dustin/go-humanize" 26 "github.com/go-enry/go-enry/v2" 27 "github.com/yuin/goldmark" 28 "tangled.org/core/appview/filetree" 29 "tangled.org/core/appview/models" 30 "tangled.org/core/appview/pages/markup" ··· 261 }, 262 "description": func(text string) template.HTML { 263 p.rctx.RendererType = markup.RendererTypeDefault 264 - htmlString := p.rctx.RenderMarkdownWith(text, goldmark.New()) 265 sanitized := p.rctx.SanitizeDescription(htmlString) 266 return template.HTML(sanitized) 267 },
··· 25 "github.com/dustin/go-humanize" 26 "github.com/go-enry/go-enry/v2" 27 "github.com/yuin/goldmark" 28 + emoji "github.com/yuin/goldmark-emoji" 29 "tangled.org/core/appview/filetree" 30 "tangled.org/core/appview/models" 31 "tangled.org/core/appview/pages/markup" ··· 262 }, 263 "description": func(text string) template.HTML { 264 p.rctx.RendererType = markup.RendererTypeDefault 265 + htmlString := p.rctx.RenderMarkdownWith(text, goldmark.New( 266 + goldmark.WithExtensions( 267 + emoji.Emoji, 268 + ), 269 + )) 270 sanitized := p.rctx.SanitizeDescription(htmlString) 271 return template.HTML(sanitized) 272 },
+2 -2
appview/pages/markup/markdown.go
··· 12 13 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 14 "github.com/alecthomas/chroma/v2/styles" 15 - treeblood "github.com/wyatt915/goldmark-treeblood" 16 "github.com/yuin/goldmark" 17 highlighting "github.com/yuin/goldmark-highlighting/v2" 18 "github.com/yuin/goldmark/ast" 19 "github.com/yuin/goldmark/extension" ··· 65 extension.NewFootnote( 66 extension.WithFootnoteIDPrefix([]byte("footnote")), 67 ), 68 - treeblood.MathML(), 69 callout.CalloutExtention, 70 textension.AtExt, 71 ), 72 goldmark.WithParserOptions( 73 parser.WithAutoHeadingID(),
··· 12 13 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 14 "github.com/alecthomas/chroma/v2/styles" 15 "github.com/yuin/goldmark" 16 + "github.com/yuin/goldmark-emoji" 17 highlighting "github.com/yuin/goldmark-highlighting/v2" 18 "github.com/yuin/goldmark/ast" 19 "github.com/yuin/goldmark/extension" ··· 65 extension.NewFootnote( 66 extension.WithFootnoteIDPrefix([]byte("footnote")), 67 ), 68 callout.CalloutExtention, 69 textension.AtExt, 70 + emoji.Emoji, 71 ), 72 goldmark.WithParserOptions( 73 parser.WithAutoHeadingID(),
+1 -1
appview/pages/pages.go
··· 640 } 641 642 func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 643 - return p.executePlain("fragments/starBtn", w, params) 644 } 645 646 type RepoIndexParams struct {
··· 640 } 641 642 func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 643 + return p.executePlain("fragments/starBtn-oob", w, params) 644 } 645 646 type RepoIndexParams struct {
+5
appview/pages/templates/fragments/starBtn-oob.html
···
··· 1 + {{ define "fragments/starBtn-oob" }} 2 + <div hx-swap-oob='outerHTML:#starBtn[data-star-subject-at="{{ .SubjectAt }}"]'> 3 + {{ template "fragments/starBtn" . }} 4 + </div> 5 + {{ end }}
+1 -3
appview/pages/templates/fragments/starBtn.html
··· 1 {{ define "fragments/starBtn" }} 2 <button 3 id="starBtn" 4 class="btn disabled:opacity-50 disabled:cursor-not-allowed flex gap-2 items-center group" ··· 10 {{ end }} 11 12 hx-trigger="click" 13 - hx-target="this" 14 - hx-swap="outerHTML" 15 - hx-swap-oob='outerHTML:#starBtn[data-star-subject-at="{{ .SubjectAt }}"]' 16 hx-disabled-elt="#starBtn" 17 > 18 {{ if .IsStarred }}
··· 1 {{ define "fragments/starBtn" }} 2 + {{/* NOTE: this fragment is always replaced with hx-swap-oob */}} 3 <button 4 id="starBtn" 5 class="btn disabled:opacity-50 disabled:cursor-not-allowed flex gap-2 items-center group" ··· 11 {{ end }} 12 13 hx-trigger="click" 14 hx-disabled-elt="#starBtn" 15 > 16 {{ if .IsStarred }}
+1 -1
appview/pages/templates/knots/index.html
··· 105 {{ define "docsButton" }} 106 <a 107 class="btn flex items-center gap-2" 108 - href="https://tangled.org/@tangled.org/core/blob/master/docs/spindle/hosting.md"> 109 {{ i "book" "size-4" }} 110 docs 111 </a>
··· 105 {{ define "docsButton" }} 106 <a 107 class="btn flex items-center gap-2" 108 + href="https://tangled.org/@tangled.org/core/blob/master/docs/knot-hosting.md"> 109 {{ i "book" "size-4" }} 110 docs 111 </a>
+1 -1
appview/pages/templates/repo/empty.html
··· 26 {{ else if (and .LoggedInUser (eq .LoggedInUser.Did .RepoInfo.OwnerDid)) }} 27 {{ $knot := .RepoInfo.Knot }} 28 {{ if eq $knot "knot1.tangled.sh" }} 29 - {{ $knot = "tangled.sh" }} 30 {{ end }} 31 <div class="w-full flex place-content-center"> 32 <div class="py-6 w-fit flex flex-col gap-4">
··· 26 {{ else if (and .LoggedInUser (eq .LoggedInUser.Did .RepoInfo.OwnerDid)) }} 27 {{ $knot := .RepoInfo.Knot }} 28 {{ if eq $knot "knot1.tangled.sh" }} 29 + {{ $knot = "tangled.org" }} 30 {{ end }} 31 <div class="w-full flex place-content-center"> 32 <div class="py-6 w-fit flex flex-col gap-4">
+6 -6
appview/pages/templates/repo/fragments/backlinks.html
··· 14 <div class="flex gap-2 items-center"> 15 {{ if .State.IsClosed }} 16 <span class="text-gray-500 dark:text-gray-400"> 17 - {{ i "ban" "w-4 h-4" }} 18 </span> 19 {{ else if eq .Kind.String "issues" }} 20 <span class="text-green-600 dark:text-green-500"> 21 - {{ i "circle-dot" "w-4 h-4" }} 22 </span> 23 {{ else if .State.IsOpen }} 24 <span class="text-green-600 dark:text-green-500"> 25 - {{ i "git-pull-request" "w-4 h-4" }} 26 </span> 27 {{ else if .State.IsMerged }} 28 <span class="text-purple-600 dark:text-purple-500"> 29 - {{ i "git-merge" "w-4 h-4" }} 30 </span> 31 {{ else }} 32 <span class="text-gray-600 dark:text-gray-300"> 33 - {{ i "git-pull-request-closed" "w-4 h-4" }} 34 </span> 35 {{ end }} 36 - <a href="{{ . }}"><span class="text-gray-500 dark:text-gray-400">#{{ .SubjectId }}</span> {{ .Title }}</a> 37 </div> 38 {{ if not (eq $.RepoInfo.FullName $repoUrl) }} 39 <div>
··· 14 <div class="flex gap-2 items-center"> 15 {{ if .State.IsClosed }} 16 <span class="text-gray-500 dark:text-gray-400"> 17 + {{ i "ban" "size-3" }} 18 </span> 19 {{ else if eq .Kind.String "issues" }} 20 <span class="text-green-600 dark:text-green-500"> 21 + {{ i "circle-dot" "size-3" }} 22 </span> 23 {{ else if .State.IsOpen }} 24 <span class="text-green-600 dark:text-green-500"> 25 + {{ i "git-pull-request" "size-3" }} 26 </span> 27 {{ else if .State.IsMerged }} 28 <span class="text-purple-600 dark:text-purple-500"> 29 + {{ i "git-merge" "size-3" }} 30 </span> 31 {{ else }} 32 <span class="text-gray-600 dark:text-gray-300"> 33 + {{ i "git-pull-request-closed" "size-3" }} 34 </span> 35 {{ end }} 36 + <a href="{{ . }}" class="line-clamp-1 text-sm"><span class="text-gray-500 dark:text-gray-400">#{{ .SubjectId }}</span> {{ .Title }}</a> 37 </div> 38 {{ if not (eq $.RepoInfo.FullName $repoUrl) }} 39 <div>
+1 -1
appview/pages/templates/strings/string.html
··· 17 <span class="select-none">/</span> 18 <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 19 </div> 20 - <div class="flex gap-2 text-base"> 21 {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 22 <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 23 hx-boost="true"
··· 17 <span class="select-none">/</span> 18 <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 19 </div> 20 + <div class="flex gap-2 items-stretch text-base"> 21 {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 22 <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 23 hx-boost="true"
+2 -2
appview/pages/templates/user/fragments/followCard.html
··· 6 <img class="object-cover rounded-full p-2" src="{{ fullAvatar $userIdent }}" alt="{{ $userIdent }}" /> 7 </div> 8 9 - <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full"> 10 <div class="flex-1 min-h-0 justify-around flex flex-col"> 11 <a href="/{{ $userIdent }}"> 12 <span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $userIdent | truncateAt30 }}</span> 13 </a> 14 {{ with .Profile }} 15 - <p class="text-sm pb-2 md:pb-2">{{.Description}}</p> 16 {{ end }} 17 <div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full"> 18 <span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
··· 6 <img class="object-cover rounded-full p-2" src="{{ fullAvatar $userIdent }}" alt="{{ $userIdent }}" /> 7 </div> 8 9 + <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full min-w-0"> 10 <div class="flex-1 min-h-0 justify-around flex flex-col"> 11 <a href="/{{ $userIdent }}"> 12 <span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $userIdent | truncateAt30 }}</span> 13 </a> 14 {{ with .Profile }} 15 + <p class="text-sm pb-2 md:pb-2 break-words">{{.Description}}</p> 16 {{ end }} 17 <div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full"> 18 <span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
+9 -6
appview/pages/templates/user/signup.html
··· 43 page to complete your registration. 44 </span> 45 <div class="w-full mt-4 text-center"> 46 - <div class="cf-turnstile" data-sitekey="{{ .CloudflareSiteKey }}"></div> 47 </div> 48 <button class="btn text-base w-full my-2 mt-6" type="submit" id="signup-button" tabindex="7" > 49 <span>join now</span> 50 </button> 51 </form> 52 - <p class="text-sm text-gray-500"> 53 - Already have an AT Protocol account? <a href="/login" class="underline">Login to Tangled</a>. 54 - </p> 55 - 56 - <p id="signup-msg" class="error w-full"></p> 57 </main> 58 </body> 59 </html>
··· 43 page to complete your registration. 44 </span> 45 <div class="w-full mt-4 text-center"> 46 + <div class="cf-turnstile" data-sitekey="{{ .CloudflareSiteKey }}" data-size="flexible"></div> 47 </div> 48 <button class="btn text-base w-full my-2 mt-6" type="submit" id="signup-button" tabindex="7" > 49 <span>join now</span> 50 </button> 51 + <p class="text-sm text-gray-500"> 52 + Already have an AT Protocol account? <a href="/login" class="underline">Login to Tangled</a>. 53 + </p> 54 + 55 + <p id="signup-msg" class="error w-full"></p> 56 + <p class="text-sm text-gray-500 pt-4"> 57 + By signing up, you agree to our <a href="/terms" class="underline">Terms of Service</a> and <a href="/privacy" class="underline">Privacy Policy</a>. 58 + </p> 59 </form> 60 </main> 61 </body> 62 </html>
+12 -11
appview/pipelines/pipelines.go
··· 16 "tangled.org/core/appview/reporesolver" 17 "tangled.org/core/eventconsumer" 18 "tangled.org/core/idresolver" 19 "tangled.org/core/rbac" 20 spindlemodel "tangled.org/core/spindle/models" 21 ··· 81 ps, err := db.GetPipelineStatuses( 82 p.db, 83 30, 84 - db.FilterEq("repo_owner", f.Did), 85 - db.FilterEq("repo_name", f.Name), 86 - db.FilterEq("knot", f.Knot), 87 ) 88 if err != nil { 89 l.Error("failed to query db", "err", err) ··· 122 ps, err := db.GetPipelineStatuses( 123 p.db, 124 1, 125 - db.FilterEq("repo_owner", f.Did), 126 - db.FilterEq("repo_name", f.Name), 127 - db.FilterEq("knot", f.Knot), 128 - db.FilterEq("id", pipelineId), 129 ) 130 if err != nil { 131 l.Error("failed to query db", "err", err) ··· 189 ps, err := db.GetPipelineStatuses( 190 p.db, 191 1, 192 - db.FilterEq("repo_owner", f.Did), 193 - db.FilterEq("repo_name", f.Name), 194 - db.FilterEq("knot", f.Knot), 195 - db.FilterEq("id", pipelineId), 196 ) 197 if err != nil || len(ps) != 1 { 198 l.Error("pipeline query failed", "err", err, "count", len(ps))
··· 16 "tangled.org/core/appview/reporesolver" 17 "tangled.org/core/eventconsumer" 18 "tangled.org/core/idresolver" 19 + "tangled.org/core/orm" 20 "tangled.org/core/rbac" 21 spindlemodel "tangled.org/core/spindle/models" 22 ··· 82 ps, err := db.GetPipelineStatuses( 83 p.db, 84 30, 85 + orm.FilterEq("repo_owner", f.Did), 86 + orm.FilterEq("repo_name", f.Name), 87 + orm.FilterEq("knot", f.Knot), 88 ) 89 if err != nil { 90 l.Error("failed to query db", "err", err) ··· 123 ps, err := db.GetPipelineStatuses( 124 p.db, 125 1, 126 + orm.FilterEq("repo_owner", f.Did), 127 + orm.FilterEq("repo_name", f.Name), 128 + orm.FilterEq("knot", f.Knot), 129 + orm.FilterEq("id", pipelineId), 130 ) 131 if err != nil { 132 l.Error("failed to query db", "err", err) ··· 190 ps, err := db.GetPipelineStatuses( 191 p.db, 192 1, 193 + orm.FilterEq("repo_owner", f.Did), 194 + orm.FilterEq("repo_name", f.Name), 195 + orm.FilterEq("knot", f.Knot), 196 + orm.FilterEq("id", pipelineId), 197 ) 198 if err != nil || len(ps) != 1 { 199 l.Error("pipeline query failed", "err", err, "count", len(ps))
+2 -1
appview/pulls/opengraph.go
··· 13 "tangled.org/core/appview/db" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/ogcard" 16 "tangled.org/core/patchutil" 17 "tangled.org/core/types" 18 ) ··· 276 } 277 278 // Get comment count from database 279 - comments, err := db.GetPullComments(s.db, db.FilterEq("pull_id", pull.ID)) 280 if err != nil { 281 log.Printf("failed to get pull comments: %v", err) 282 }
··· 13 "tangled.org/core/appview/db" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/ogcard" 16 + "tangled.org/core/orm" 17 "tangled.org/core/patchutil" 18 "tangled.org/core/types" 19 ) ··· 277 } 278 279 // Get comment count from database 280 + comments, err := db.GetPullComments(s.db, orm.FilterEq("pull_id", pull.ID)) 281 if err != nil { 282 log.Printf("failed to get pull comments: %v", err) 283 }
+56 -47
appview/pulls/pulls.go
··· 19 "tangled.org/core/appview/config" 20 "tangled.org/core/appview/db" 21 pulls_indexer "tangled.org/core/appview/indexer/pulls" 22 "tangled.org/core/appview/models" 23 "tangled.org/core/appview/notify" 24 "tangled.org/core/appview/oauth" 25 "tangled.org/core/appview/pages" 26 "tangled.org/core/appview/pages/markup" 27 "tangled.org/core/appview/pages/repoinfo" 28 - "tangled.org/core/appview/refresolver" 29 "tangled.org/core/appview/reporesolver" 30 "tangled.org/core/appview/validator" 31 "tangled.org/core/appview/xrpcclient" 32 "tangled.org/core/idresolver" 33 "tangled.org/core/patchutil" 34 "tangled.org/core/rbac" 35 "tangled.org/core/tid" ··· 44 ) 45 46 type Pulls struct { 47 - oauth *oauth.OAuth 48 - repoResolver *reporesolver.RepoResolver 49 - pages *pages.Pages 50 - idResolver *idresolver.Resolver 51 - refResolver *refresolver.Resolver 52 - db *db.DB 53 - config *config.Config 54 - notifier notify.Notifier 55 - enforcer *rbac.Enforcer 56 - logger *slog.Logger 57 - validator *validator.Validator 58 - indexer *pulls_indexer.Indexer 59 } 60 61 func New( ··· 63 repoResolver *reporesolver.RepoResolver, 64 pages *pages.Pages, 65 resolver *idresolver.Resolver, 66 - refResolver *refresolver.Resolver, 67 db *db.DB, 68 config *config.Config, 69 notifier notify.Notifier, ··· 73 logger *slog.Logger, 74 ) *Pulls { 75 return &Pulls{ 76 - oauth: oauth, 77 - repoResolver: repoResolver, 78 - pages: pages, 79 - idResolver: resolver, 80 - refResolver: refResolver, 81 - db: db, 82 - config: config, 83 - notifier: notifier, 84 - enforcer: enforcer, 85 - logger: logger, 86 - validator: validator, 87 - indexer: indexer, 88 } 89 } 90 ··· 190 ps, err := db.GetPipelineStatuses( 191 s.db, 192 len(shas), 193 - db.FilterEq("repo_owner", f.Did), 194 - db.FilterEq("repo_name", f.Name), 195 - db.FilterEq("knot", f.Knot), 196 - db.FilterIn("sha", shas), 197 ) 198 if err != nil { 199 log.Printf("failed to fetch pipeline statuses: %s", err) ··· 217 218 labelDefs, err := db.GetLabelDefinitions( 219 s.db, 220 - db.FilterIn("at_uri", f.Labels), 221 - db.FilterContains("scope", tangled.RepoPullNSID), 222 ) 223 if err != nil { 224 log.Println("failed to fetch labels", err) ··· 597 598 pulls, err := db.GetPulls( 599 s.db, 600 - db.FilterIn("id", ids), 601 ) 602 if err != nil { 603 log.Println("failed to get pulls", err) ··· 648 ps, err := db.GetPipelineStatuses( 649 s.db, 650 len(shas), 651 - db.FilterEq("repo_owner", f.Did), 652 - db.FilterEq("repo_name", f.Name), 653 - db.FilterEq("knot", f.Knot), 654 - db.FilterIn("sha", shas), 655 ) 656 if err != nil { 657 log.Printf("failed to fetch pipeline statuses: %s", err) ··· 664 665 labelDefs, err := db.GetLabelDefinitions( 666 s.db, 667 - db.FilterIn("at_uri", f.Labels), 668 - db.FilterContains("scope", tangled.RepoPullNSID), 669 ) 670 if err != nil { 671 log.Println("failed to fetch labels", err) ··· 729 return 730 } 731 732 - mentions, references := s.refResolver.Resolve(r.Context(), body) 733 734 // Start a transaction 735 tx, err := s.db.BeginTx(r.Context(), nil) ··· 1205 } 1206 } 1207 1208 - mentions, references := s.refResolver.Resolve(r.Context(), body) 1209 1210 rkey := tid.TID() 1211 initialSubmission := models.PullSubmission{ ··· 1365 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1366 return 1367 } 1368 } 1369 1370 if err = tx.Commit(); err != nil { 1371 log.Println("failed to create pull request", err) 1372 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1373 return 1374 } 1375 1376 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, repo) ··· 1498 // fork repo 1499 repo, err := db.GetRepo( 1500 s.db, 1501 - db.FilterEq("did", forkOwnerDid), 1502 - db.FilterEq("name", forkName), 1503 ) 1504 if err != nil { 1505 log.Println("failed to get repo", "did", forkOwnerDid, "name", forkName, "err", err) ··· 2066 tx, 2067 p.ParentChangeId, 2068 // these should be enough filters to be unique per-stack 2069 - db.FilterEq("repo_at", p.RepoAt.String()), 2070 - db.FilterEq("owner_did", p.OwnerDid), 2071 - db.FilterEq("change_id", p.ChangeId), 2072 ) 2073 2074 if err != nil { ··· 2397 body := fp.Body 2398 rkey := tid.TID() 2399 2400 - mentions, references := s.refResolver.Resolve(ctx, body) 2401 2402 initialSubmission := models.PullSubmission{ 2403 Patch: fp.Raw,
··· 19 "tangled.org/core/appview/config" 20 "tangled.org/core/appview/db" 21 pulls_indexer "tangled.org/core/appview/indexer/pulls" 22 + "tangled.org/core/appview/mentions" 23 "tangled.org/core/appview/models" 24 "tangled.org/core/appview/notify" 25 "tangled.org/core/appview/oauth" 26 "tangled.org/core/appview/pages" 27 "tangled.org/core/appview/pages/markup" 28 "tangled.org/core/appview/pages/repoinfo" 29 "tangled.org/core/appview/reporesolver" 30 "tangled.org/core/appview/validator" 31 "tangled.org/core/appview/xrpcclient" 32 "tangled.org/core/idresolver" 33 + "tangled.org/core/orm" 34 "tangled.org/core/patchutil" 35 "tangled.org/core/rbac" 36 "tangled.org/core/tid" ··· 45 ) 46 47 type Pulls struct { 48 + oauth *oauth.OAuth 49 + repoResolver *reporesolver.RepoResolver 50 + pages *pages.Pages 51 + idResolver *idresolver.Resolver 52 + mentionsResolver *mentions.Resolver 53 + db *db.DB 54 + config *config.Config 55 + notifier notify.Notifier 56 + enforcer *rbac.Enforcer 57 + logger *slog.Logger 58 + validator *validator.Validator 59 + indexer *pulls_indexer.Indexer 60 } 61 62 func New( ··· 64 repoResolver *reporesolver.RepoResolver, 65 pages *pages.Pages, 66 resolver *idresolver.Resolver, 67 + mentionsResolver *mentions.Resolver, 68 db *db.DB, 69 config *config.Config, 70 notifier notify.Notifier, ··· 74 logger *slog.Logger, 75 ) *Pulls { 76 return &Pulls{ 77 + oauth: oauth, 78 + repoResolver: repoResolver, 79 + pages: pages, 80 + idResolver: resolver, 81 + mentionsResolver: mentionsResolver, 82 + db: db, 83 + config: config, 84 + notifier: notifier, 85 + enforcer: enforcer, 86 + logger: logger, 87 + validator: validator, 88 + indexer: indexer, 89 } 90 } 91 ··· 191 ps, err := db.GetPipelineStatuses( 192 s.db, 193 len(shas), 194 + orm.FilterEq("repo_owner", f.Did), 195 + orm.FilterEq("repo_name", f.Name), 196 + orm.FilterEq("knot", f.Knot), 197 + orm.FilterIn("sha", shas), 198 ) 199 if err != nil { 200 log.Printf("failed to fetch pipeline statuses: %s", err) ··· 218 219 labelDefs, err := db.GetLabelDefinitions( 220 s.db, 221 + orm.FilterIn("at_uri", f.Labels), 222 + orm.FilterContains("scope", tangled.RepoPullNSID), 223 ) 224 if err != nil { 225 log.Println("failed to fetch labels", err) ··· 598 599 pulls, err := db.GetPulls( 600 s.db, 601 + orm.FilterIn("id", ids), 602 ) 603 if err != nil { 604 log.Println("failed to get pulls", err) ··· 649 ps, err := db.GetPipelineStatuses( 650 s.db, 651 len(shas), 652 + orm.FilterEq("repo_owner", f.Did), 653 + orm.FilterEq("repo_name", f.Name), 654 + orm.FilterEq("knot", f.Knot), 655 + orm.FilterIn("sha", shas), 656 ) 657 if err != nil { 658 log.Printf("failed to fetch pipeline statuses: %s", err) ··· 665 666 labelDefs, err := db.GetLabelDefinitions( 667 s.db, 668 + orm.FilterIn("at_uri", f.Labels), 669 + orm.FilterContains("scope", tangled.RepoPullNSID), 670 ) 671 if err != nil { 672 log.Println("failed to fetch labels", err) ··· 730 return 731 } 732 733 + mentions, references := s.mentionsResolver.Resolve(r.Context(), body) 734 735 // Start a transaction 736 tx, err := s.db.BeginTx(r.Context(), nil) ··· 1206 } 1207 } 1208 1209 + mentions, references := s.mentionsResolver.Resolve(r.Context(), body) 1210 1211 rkey := tid.TID() 1212 initialSubmission := models.PullSubmission{ ··· 1366 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1367 return 1368 } 1369 + 1370 } 1371 1372 if err = tx.Commit(); err != nil { 1373 log.Println("failed to create pull request", err) 1374 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1375 return 1376 + } 1377 + 1378 + // notify about each pull 1379 + // 1380 + // this is performed after tx.Commit, because it could result in a locked DB otherwise 1381 + for _, p := range stack { 1382 + s.notifier.NewPull(r.Context(), p) 1383 } 1384 1385 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, repo) ··· 1507 // fork repo 1508 repo, err := db.GetRepo( 1509 s.db, 1510 + orm.FilterEq("did", forkOwnerDid), 1511 + orm.FilterEq("name", forkName), 1512 ) 1513 if err != nil { 1514 log.Println("failed to get repo", "did", forkOwnerDid, "name", forkName, "err", err) ··· 2075 tx, 2076 p.ParentChangeId, 2077 // these should be enough filters to be unique per-stack 2078 + orm.FilterEq("repo_at", p.RepoAt.String()), 2079 + orm.FilterEq("owner_did", p.OwnerDid), 2080 + orm.FilterEq("change_id", p.ChangeId), 2081 ) 2082 2083 if err != nil { ··· 2406 body := fp.Body 2407 rkey := tid.TID() 2408 2409 + mentions, references := s.mentionsResolver.Resolve(ctx, body) 2410 2411 initialSubmission := models.PullSubmission{ 2412 Patch: fp.Raw,
-65
appview/refresolver/resolver.go
··· 1 - package refresolver 2 - 3 - import ( 4 - "context" 5 - "log/slog" 6 - 7 - "github.com/bluesky-social/indigo/atproto/syntax" 8 - "tangled.org/core/appview/config" 9 - "tangled.org/core/appview/db" 10 - "tangled.org/core/appview/models" 11 - "tangled.org/core/appview/pages/markup" 12 - "tangled.org/core/idresolver" 13 - ) 14 - 15 - type Resolver struct { 16 - config *config.Config 17 - idResolver *idresolver.Resolver 18 - execer db.Execer 19 - logger *slog.Logger 20 - } 21 - 22 - func New( 23 - config *config.Config, 24 - idResolver *idresolver.Resolver, 25 - execer db.Execer, 26 - logger *slog.Logger, 27 - ) *Resolver { 28 - return &Resolver{ 29 - config, 30 - idResolver, 31 - execer, 32 - logger, 33 - } 34 - } 35 - 36 - func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) { 37 - l := r.logger.With("method", "Resolve") 38 - rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source) 39 - l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs) 40 - idents := r.idResolver.ResolveIdents(ctx, rawMentions) 41 - var mentions []syntax.DID 42 - for _, ident := range idents { 43 - if ident != nil && !ident.Handle.IsInvalidHandle() { 44 - mentions = append(mentions, ident.DID) 45 - } 46 - } 47 - l.Debug("found mentions", "mentions", mentions) 48 - 49 - var resolvedRefs []models.ReferenceLink 50 - for _, rawRef := range rawRefs { 51 - ident, err := r.idResolver.ResolveIdent(ctx, rawRef.Handle) 52 - if err != nil || ident == nil || ident.Handle.IsInvalidHandle() { 53 - continue 54 - } 55 - rawRef.Handle = string(ident.DID) 56 - resolvedRefs = append(resolvedRefs, rawRef) 57 - } 58 - aturiRefs, err := db.ValidateReferenceLinks(r.execer, resolvedRefs) 59 - if err != nil { 60 - l.Error("failed running query", "err", err) 61 - } 62 - l.Debug("found references", "refs", aturiRefs) 63 - 64 - return mentions, aturiRefs 65 - }
···
+10 -9
appview/repo/artifact.go
··· 15 "tangled.org/core/appview/models" 16 "tangled.org/core/appview/pages" 17 "tangled.org/core/appview/xrpcclient" 18 "tangled.org/core/tid" 19 "tangled.org/core/types" 20 ··· 155 156 artifacts, err := db.GetArtifact( 157 rp.db, 158 - db.FilterEq("repo_at", f.RepoAt()), 159 - db.FilterEq("tag", tag.Tag.Hash[:]), 160 - db.FilterEq("name", filename), 161 ) 162 if err != nil { 163 log.Println("failed to get artifacts", err) ··· 234 235 artifacts, err := db.GetArtifact( 236 rp.db, 237 - db.FilterEq("repo_at", f.RepoAt()), 238 - db.FilterEq("tag", tag[:]), 239 - db.FilterEq("name", filename), 240 ) 241 if err != nil { 242 log.Println("failed to get artifacts", err) ··· 276 defer tx.Rollback() 277 278 err = db.DeleteArtifact(tx, 279 - db.FilterEq("repo_at", f.RepoAt()), 280 - db.FilterEq("tag", artifact.Tag[:]), 281 - db.FilterEq("name", filename), 282 ) 283 if err != nil { 284 log.Println("failed to remove artifact record from db", err)
··· 15 "tangled.org/core/appview/models" 16 "tangled.org/core/appview/pages" 17 "tangled.org/core/appview/xrpcclient" 18 + "tangled.org/core/orm" 19 "tangled.org/core/tid" 20 "tangled.org/core/types" 21 ··· 156 157 artifacts, err := db.GetArtifact( 158 rp.db, 159 + orm.FilterEq("repo_at", f.RepoAt()), 160 + orm.FilterEq("tag", tag.Tag.Hash[:]), 161 + orm.FilterEq("name", filename), 162 ) 163 if err != nil { 164 log.Println("failed to get artifacts", err) ··· 235 236 artifacts, err := db.GetArtifact( 237 rp.db, 238 + orm.FilterEq("repo_at", f.RepoAt()), 239 + orm.FilterEq("tag", tag[:]), 240 + orm.FilterEq("name", filename), 241 ) 242 if err != nil { 243 log.Println("failed to get artifacts", err) ··· 277 defer tx.Rollback() 278 279 err = db.DeleteArtifact(tx, 280 + orm.FilterEq("repo_at", f.RepoAt()), 281 + orm.FilterEq("tag", artifact.Tag[:]), 282 + orm.FilterEq("name", filename), 283 ) 284 if err != nil { 285 log.Println("failed to remove artifact record from db", err)
+3 -2
appview/repo/feed.go
··· 11 "tangled.org/core/appview/db" 12 "tangled.org/core/appview/models" 13 "tangled.org/core/appview/pagination" 14 15 "github.com/bluesky-social/indigo/atproto/identity" 16 "github.com/bluesky-social/indigo/atproto/syntax" ··· 20 func (rp *Repo) getRepoFeed(ctx context.Context, repo *models.Repo, ownerSlashRepo string) (*feeds.Feed, error) { 21 const feedLimitPerType = 100 22 23 - pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", repo.RepoAt())) 24 if err != nil { 25 return nil, err 26 } ··· 28 issues, err := db.GetIssuesPaginated( 29 rp.db, 30 pagination.Page{Limit: feedLimitPerType}, 31 - db.FilterEq("repo_at", repo.RepoAt()), 32 ) 33 if err != nil { 34 return nil, err
··· 11 "tangled.org/core/appview/db" 12 "tangled.org/core/appview/models" 13 "tangled.org/core/appview/pagination" 14 + "tangled.org/core/orm" 15 16 "github.com/bluesky-social/indigo/atproto/identity" 17 "github.com/bluesky-social/indigo/atproto/syntax" ··· 21 func (rp *Repo) getRepoFeed(ctx context.Context, repo *models.Repo, ownerSlashRepo string) (*feeds.Feed, error) { 22 const feedLimitPerType = 100 23 24 + pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, orm.FilterEq("repo_at", repo.RepoAt())) 25 if err != nil { 26 return nil, err 27 } ··· 29 issues, err := db.GetIssuesPaginated( 30 rp.db, 31 pagination.Page{Limit: feedLimitPerType}, 32 + orm.FilterEq("repo_at", repo.RepoAt()), 33 ) 34 if err != nil { 35 return nil, err
+3 -2
appview/repo/index.go
··· 23 "tangled.org/core/appview/models" 24 "tangled.org/core/appview/pages" 25 "tangled.org/core/appview/xrpcclient" 26 "tangled.org/core/types" 27 28 "github.com/go-chi/chi/v5" ··· 171 // first attempt to fetch from db 172 langs, err := db.GetRepoLanguages( 173 rp.db, 174 - db.FilterEq("repo_at", repo.RepoAt()), 175 - db.FilterEq("ref", currentRef), 176 ) 177 178 if err != nil || langs == nil {
··· 23 "tangled.org/core/appview/models" 24 "tangled.org/core/appview/pages" 25 "tangled.org/core/appview/xrpcclient" 26 + "tangled.org/core/orm" 27 "tangled.org/core/types" 28 29 "github.com/go-chi/chi/v5" ··· 172 // first attempt to fetch from db 173 langs, err := db.GetRepoLanguages( 174 rp.db, 175 + orm.FilterEq("repo_at", repo.RepoAt()), 176 + orm.FilterEq("ref", currentRef), 177 ) 178 179 if err != nil || langs == nil {
+3 -2
appview/repo/opengraph.go
··· 16 "tangled.org/core/appview/db" 17 "tangled.org/core/appview/models" 18 "tangled.org/core/appview/ogcard" 19 "tangled.org/core/types" 20 ) 21 ··· 338 var languageStats []types.RepoLanguageDetails 339 langs, err := db.GetRepoLanguages( 340 rp.db, 341 - db.FilterEq("repo_at", f.RepoAt()), 342 - db.FilterEq("is_default_ref", 1), 343 ) 344 if err != nil { 345 log.Printf("failed to get language stats from db: %v", err)
··· 16 "tangled.org/core/appview/db" 17 "tangled.org/core/appview/models" 18 "tangled.org/core/appview/ogcard" 19 + "tangled.org/core/orm" 20 "tangled.org/core/types" 21 ) 22 ··· 339 var languageStats []types.RepoLanguageDetails 340 langs, err := db.GetRepoLanguages( 341 rp.db, 342 + orm.FilterEq("repo_at", f.RepoAt()), 343 + orm.FilterEq("is_default_ref", 1), 344 ) 345 if err != nil { 346 log.Printf("failed to get language stats from db: %v", err)
+17 -16
appview/repo/repo.go
··· 24 xrpcclient "tangled.org/core/appview/xrpcclient" 25 "tangled.org/core/eventconsumer" 26 "tangled.org/core/idresolver" 27 "tangled.org/core/rbac" 28 "tangled.org/core/tid" 29 "tangled.org/core/xrpc/serviceauth" ··· 345 // get form values 346 labelId := r.FormValue("label-id") 347 348 - label, err := db.GetLabelDefinition(rp.db, db.FilterEq("id", labelId)) 349 if err != nil { 350 fail("Failed to find label definition.", err) 351 return ··· 409 410 err = db.UnsubscribeLabel( 411 tx, 412 - db.FilterEq("repo_at", f.RepoAt()), 413 - db.FilterEq("label_at", removedAt), 414 ) 415 if err != nil { 416 fail("Failed to unsubscribe label.", err) 417 return 418 } 419 420 - err = db.DeleteLabelDefinition(tx, db.FilterEq("id", label.Id)) 421 if err != nil { 422 fail("Failed to delete label definition.", err) 423 return ··· 456 } 457 458 labelAts := r.Form["label"] 459 - _, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts)) 460 if err != nil { 461 fail("Failed to subscribe to label.", err) 462 return ··· 542 } 543 544 labelAts := r.Form["label"] 545 - _, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts)) 546 if err != nil { 547 fail("Failed to unsubscribe to label.", err) 548 return ··· 582 583 err = db.UnsubscribeLabel( 584 rp.db, 585 - db.FilterEq("repo_at", f.RepoAt()), 586 - db.FilterIn("label_at", labelAts), 587 ) 588 if err != nil { 589 fail("Failed to unsubscribe label.", err) ··· 612 613 labelDefs, err := db.GetLabelDefinitions( 614 rp.db, 615 - db.FilterIn("at_uri", f.Labels), 616 - db.FilterContains("scope", subject.Collection().String()), 617 ) 618 if err != nil { 619 l.Error("failed to fetch label defs", "err", err) ··· 625 defs[l.AtUri().String()] = &l 626 } 627 628 - states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject)) 629 if err != nil { 630 l.Error("failed to build label state", "err", err) 631 return ··· 660 661 labelDefs, err := db.GetLabelDefinitions( 662 rp.db, 663 - db.FilterIn("at_uri", f.Labels), 664 - db.FilterContains("scope", subject.Collection().String()), 665 ) 666 if err != nil { 667 l.Error("failed to fetch labels", "err", err) ··· 673 defs[l.AtUri().String()] = &l 674 } 675 676 - states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject)) 677 if err != nil { 678 l.Error("failed to build label state", "err", err) 679 return ··· 1036 // in the user's account. 1037 existingRepo, err := db.GetRepo( 1038 rp.db, 1039 - db.FilterEq("did", user.Did), 1040 - db.FilterEq("name", forkName), 1041 ) 1042 if err != nil { 1043 if !errors.Is(err, sql.ErrNoRows) {
··· 24 xrpcclient "tangled.org/core/appview/xrpcclient" 25 "tangled.org/core/eventconsumer" 26 "tangled.org/core/idresolver" 27 + "tangled.org/core/orm" 28 "tangled.org/core/rbac" 29 "tangled.org/core/tid" 30 "tangled.org/core/xrpc/serviceauth" ··· 346 // get form values 347 labelId := r.FormValue("label-id") 348 349 + label, err := db.GetLabelDefinition(rp.db, orm.FilterEq("id", labelId)) 350 if err != nil { 351 fail("Failed to find label definition.", err) 352 return ··· 410 411 err = db.UnsubscribeLabel( 412 tx, 413 + orm.FilterEq("repo_at", f.RepoAt()), 414 + orm.FilterEq("label_at", removedAt), 415 ) 416 if err != nil { 417 fail("Failed to unsubscribe label.", err) 418 return 419 } 420 421 + err = db.DeleteLabelDefinition(tx, orm.FilterEq("id", label.Id)) 422 if err != nil { 423 fail("Failed to delete label definition.", err) 424 return ··· 457 } 458 459 labelAts := r.Form["label"] 460 + _, err = db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", labelAts)) 461 if err != nil { 462 fail("Failed to subscribe to label.", err) 463 return ··· 543 } 544 545 labelAts := r.Form["label"] 546 + _, err = db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", labelAts)) 547 if err != nil { 548 fail("Failed to unsubscribe to label.", err) 549 return ··· 583 584 err = db.UnsubscribeLabel( 585 rp.db, 586 + orm.FilterEq("repo_at", f.RepoAt()), 587 + orm.FilterIn("label_at", labelAts), 588 ) 589 if err != nil { 590 fail("Failed to unsubscribe label.", err) ··· 613 614 labelDefs, err := db.GetLabelDefinitions( 615 rp.db, 616 + orm.FilterIn("at_uri", f.Labels), 617 + orm.FilterContains("scope", subject.Collection().String()), 618 ) 619 if err != nil { 620 l.Error("failed to fetch label defs", "err", err) ··· 626 defs[l.AtUri().String()] = &l 627 } 628 629 + states, err := db.GetLabels(rp.db, orm.FilterEq("subject", subject)) 630 if err != nil { 631 l.Error("failed to build label state", "err", err) 632 return ··· 661 662 labelDefs, err := db.GetLabelDefinitions( 663 rp.db, 664 + orm.FilterIn("at_uri", f.Labels), 665 + orm.FilterContains("scope", subject.Collection().String()), 666 ) 667 if err != nil { 668 l.Error("failed to fetch labels", "err", err) ··· 674 defs[l.AtUri().String()] = &l 675 } 676 677 + states, err := db.GetLabels(rp.db, orm.FilterEq("subject", subject)) 678 if err != nil { 679 l.Error("failed to build label state", "err", err) 680 return ··· 1037 // in the user's account. 1038 existingRepo, err := db.GetRepo( 1039 rp.db, 1040 + orm.FilterEq("did", user.Did), 1041 + orm.FilterEq("name", forkName), 1042 ) 1043 if err != nil { 1044 if !errors.Is(err, sql.ErrNoRows) {
+5 -4
appview/repo/repo_util.go
··· 8 9 "tangled.org/core/appview/db" 10 "tangled.org/core/appview/models" 11 "tangled.org/core/types" 12 ) 13 ··· 102 ps, err := db.GetPipelineStatuses( 103 d, 104 len(shas), 105 - db.FilterEq("repo_owner", repo.Did), 106 - db.FilterEq("repo_name", repo.Name), 107 - db.FilterEq("knot", repo.Knot), 108 - db.FilterIn("sha", shas), 109 ) 110 if err != nil { 111 return nil, err
··· 8 9 "tangled.org/core/appview/db" 10 "tangled.org/core/appview/models" 11 + "tangled.org/core/orm" 12 "tangled.org/core/types" 13 ) 14 ··· 103 ps, err := db.GetPipelineStatuses( 104 d, 105 len(shas), 106 + orm.FilterEq("repo_owner", repo.Did), 107 + orm.FilterEq("repo_name", repo.Name), 108 + orm.FilterEq("knot", repo.Knot), 109 + orm.FilterIn("sha", shas), 110 ) 111 if err != nil { 112 return nil, err
+3 -2
appview/repo/settings.go
··· 14 "tangled.org/core/appview/oauth" 15 "tangled.org/core/appview/pages" 16 xrpcclient "tangled.org/core/appview/xrpcclient" 17 "tangled.org/core/types" 18 19 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 210 return 211 } 212 213 - defaultLabels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs)) 214 if err != nil { 215 l.Error("failed to fetch labels", "err", err) 216 rp.pages.Error503(w) 217 return 218 } 219 220 - labels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Labels)) 221 if err != nil { 222 l.Error("failed to fetch labels", "err", err) 223 rp.pages.Error503(w)
··· 14 "tangled.org/core/appview/oauth" 15 "tangled.org/core/appview/pages" 16 xrpcclient "tangled.org/core/appview/xrpcclient" 17 + "tangled.org/core/orm" 18 "tangled.org/core/types" 19 20 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 211 return 212 } 213 214 + defaultLabels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs)) 215 if err != nil { 216 l.Error("failed to fetch labels", "err", err) 217 rp.pages.Error503(w) 218 return 219 } 220 221 + labels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", f.Labels)) 222 if err != nil { 223 l.Error("failed to fetch labels", "err", err) 224 rp.pages.Error503(w)
+2 -1
appview/repo/tags.go
··· 10 "tangled.org/core/appview/models" 11 "tangled.org/core/appview/pages" 12 xrpcclient "tangled.org/core/appview/xrpcclient" 13 "tangled.org/core/types" 14 15 indigoxrpc "github.com/bluesky-social/indigo/xrpc" ··· 44 rp.pages.Error503(w) 45 return 46 } 47 - artifacts, err := db.GetArtifact(rp.db, db.FilterEq("repo_at", f.RepoAt())) 48 if err != nil { 49 l.Error("failed grab artifacts", "err", err) 50 return
··· 10 "tangled.org/core/appview/models" 11 "tangled.org/core/appview/pages" 12 xrpcclient "tangled.org/core/appview/xrpcclient" 13 + "tangled.org/core/orm" 14 "tangled.org/core/types" 15 16 indigoxrpc "github.com/bluesky-social/indigo/xrpc" ··· 45 rp.pages.Error503(w) 46 return 47 } 48 + artifacts, err := db.GetArtifact(rp.db, orm.FilterEq("repo_at", f.RepoAt())) 49 if err != nil { 50 l.Error("failed grab artifacts", "err", err) 51 return
+5 -4
appview/serververify/verify.go
··· 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/appview/db" 11 "tangled.org/core/appview/xrpcclient" 12 "tangled.org/core/rbac" 13 ) 14 ··· 76 // mark this spindle as verified in the db 77 rowId, err := db.VerifySpindle( 78 tx, 79 - db.FilterEq("owner", owner), 80 - db.FilterEq("instance", instance), 81 ) 82 if err != nil { 83 return 0, fmt.Errorf("failed to write to DB: %w", err) ··· 115 // mark as registered 116 err = db.MarkRegistered( 117 tx, 118 - db.FilterEq("did", owner), 119 - db.FilterEq("domain", domain), 120 ) 121 if err != nil { 122 return fmt.Errorf("failed to register domain: %w", err)
··· 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/appview/db" 11 "tangled.org/core/appview/xrpcclient" 12 + "tangled.org/core/orm" 13 "tangled.org/core/rbac" 14 ) 15 ··· 77 // mark this spindle as verified in the db 78 rowId, err := db.VerifySpindle( 79 tx, 80 + orm.FilterEq("owner", owner), 81 + orm.FilterEq("instance", instance), 82 ) 83 if err != nil { 84 return 0, fmt.Errorf("failed to write to DB: %w", err) ··· 116 // mark as registered 117 err = db.MarkRegistered( 118 tx, 119 + orm.FilterEq("did", owner), 120 + orm.FilterEq("domain", domain), 121 ) 122 if err != nil { 123 return fmt.Errorf("failed to register domain: %w", err)
+25 -24
appview/spindles/spindles.go
··· 20 "tangled.org/core/appview/serververify" 21 "tangled.org/core/appview/xrpcclient" 22 "tangled.org/core/idresolver" 23 "tangled.org/core/rbac" 24 "tangled.org/core/tid" 25 ··· 71 user := s.OAuth.GetUser(r) 72 all, err := db.GetSpindles( 73 s.Db, 74 - db.FilterEq("owner", user.Did), 75 ) 76 if err != nil { 77 s.Logger.Error("failed to fetch spindles", "err", err) ··· 101 102 spindles, err := db.GetSpindles( 103 s.Db, 104 - db.FilterEq("instance", instance), 105 - db.FilterEq("owner", user.Did), 106 - db.FilterIsNot("verified", "null"), 107 ) 108 if err != nil || len(spindles) != 1 { 109 l.Error("failed to get spindle", "err", err, "len(spindles)", len(spindles)) ··· 123 repos, err := db.GetRepos( 124 s.Db, 125 0, 126 - db.FilterEq("spindle", instance), 127 ) 128 if err != nil { 129 l.Error("failed to get spindle repos", "err", err) ··· 290 291 spindles, err := db.GetSpindles( 292 s.Db, 293 - db.FilterEq("owner", user.Did), 294 - db.FilterEq("instance", instance), 295 ) 296 if err != nil || len(spindles) != 1 { 297 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 319 // remove spindle members first 320 err = db.RemoveSpindleMember( 321 tx, 322 - db.FilterEq("did", user.Did), 323 - db.FilterEq("instance", instance), 324 ) 325 if err != nil { 326 l.Error("failed to remove spindle members", "err", err) ··· 330 331 err = db.DeleteSpindle( 332 tx, 333 - db.FilterEq("owner", user.Did), 334 - db.FilterEq("instance", instance), 335 ) 336 if err != nil { 337 l.Error("failed to delete spindle", "err", err) ··· 410 411 spindles, err := db.GetSpindles( 412 s.Db, 413 - db.FilterEq("owner", user.Did), 414 - db.FilterEq("instance", instance), 415 ) 416 if err != nil || len(spindles) != 1 { 417 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 453 454 verifiedSpindle, err := db.GetSpindles( 455 s.Db, 456 - db.FilterEq("id", rowId), 457 ) 458 if err != nil || len(verifiedSpindle) != 1 { 459 l.Error("failed get new spindle", "err", err) ··· 486 487 spindles, err := db.GetSpindles( 488 s.Db, 489 - db.FilterEq("owner", user.Did), 490 - db.FilterEq("instance", instance), 491 ) 492 if err != nil || len(spindles) != 1 { 493 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 622 623 spindles, err := db.GetSpindles( 624 s.Db, 625 - db.FilterEq("owner", user.Did), 626 - db.FilterEq("instance", instance), 627 ) 628 if err != nil || len(spindles) != 1 { 629 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 672 // get the record from the DB first: 673 members, err := db.GetSpindleMembers( 674 s.Db, 675 - db.FilterEq("did", user.Did), 676 - db.FilterEq("instance", instance), 677 - db.FilterEq("subject", memberId.DID), 678 ) 679 if err != nil || len(members) != 1 { 680 l.Error("failed to get member", "err", err) ··· 685 // remove from db 686 if err = db.RemoveSpindleMember( 687 tx, 688 - db.FilterEq("did", user.Did), 689 - db.FilterEq("instance", instance), 690 - db.FilterEq("subject", memberId.DID), 691 ); err != nil { 692 l.Error("failed to remove spindle member", "err", err) 693 fail()
··· 20 "tangled.org/core/appview/serververify" 21 "tangled.org/core/appview/xrpcclient" 22 "tangled.org/core/idresolver" 23 + "tangled.org/core/orm" 24 "tangled.org/core/rbac" 25 "tangled.org/core/tid" 26 ··· 72 user := s.OAuth.GetUser(r) 73 all, err := db.GetSpindles( 74 s.Db, 75 + orm.FilterEq("owner", user.Did), 76 ) 77 if err != nil { 78 s.Logger.Error("failed to fetch spindles", "err", err) ··· 102 103 spindles, err := db.GetSpindles( 104 s.Db, 105 + orm.FilterEq("instance", instance), 106 + orm.FilterEq("owner", user.Did), 107 + orm.FilterIsNot("verified", "null"), 108 ) 109 if err != nil || len(spindles) != 1 { 110 l.Error("failed to get spindle", "err", err, "len(spindles)", len(spindles)) ··· 124 repos, err := db.GetRepos( 125 s.Db, 126 0, 127 + orm.FilterEq("spindle", instance), 128 ) 129 if err != nil { 130 l.Error("failed to get spindle repos", "err", err) ··· 291 292 spindles, err := db.GetSpindles( 293 s.Db, 294 + orm.FilterEq("owner", user.Did), 295 + orm.FilterEq("instance", instance), 296 ) 297 if err != nil || len(spindles) != 1 { 298 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 320 // remove spindle members first 321 err = db.RemoveSpindleMember( 322 tx, 323 + orm.FilterEq("did", user.Did), 324 + orm.FilterEq("instance", instance), 325 ) 326 if err != nil { 327 l.Error("failed to remove spindle members", "err", err) ··· 331 332 err = db.DeleteSpindle( 333 tx, 334 + orm.FilterEq("owner", user.Did), 335 + orm.FilterEq("instance", instance), 336 ) 337 if err != nil { 338 l.Error("failed to delete spindle", "err", err) ··· 411 412 spindles, err := db.GetSpindles( 413 s.Db, 414 + orm.FilterEq("owner", user.Did), 415 + orm.FilterEq("instance", instance), 416 ) 417 if err != nil || len(spindles) != 1 { 418 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 454 455 verifiedSpindle, err := db.GetSpindles( 456 s.Db, 457 + orm.FilterEq("id", rowId), 458 ) 459 if err != nil || len(verifiedSpindle) != 1 { 460 l.Error("failed get new spindle", "err", err) ··· 487 488 spindles, err := db.GetSpindles( 489 s.Db, 490 + orm.FilterEq("owner", user.Did), 491 + orm.FilterEq("instance", instance), 492 ) 493 if err != nil || len(spindles) != 1 { 494 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 623 624 spindles, err := db.GetSpindles( 625 s.Db, 626 + orm.FilterEq("owner", user.Did), 627 + orm.FilterEq("instance", instance), 628 ) 629 if err != nil || len(spindles) != 1 { 630 l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles)) ··· 673 // get the record from the DB first: 674 members, err := db.GetSpindleMembers( 675 s.Db, 676 + orm.FilterEq("did", user.Did), 677 + orm.FilterEq("instance", instance), 678 + orm.FilterEq("subject", memberId.DID), 679 ) 680 if err != nil || len(members) != 1 { 681 l.Error("failed to get member", "err", err) ··· 686 // remove from db 687 if err = db.RemoveSpindleMember( 688 tx, 689 + orm.FilterEq("did", user.Did), 690 + orm.FilterEq("instance", instance), 691 + orm.FilterEq("subject", memberId.DID), 692 ); err != nil { 693 l.Error("failed to remove spindle member", "err", err) 694 fail()
+6 -5
appview/state/gfi.go
··· 11 "tangled.org/core/appview/pages" 12 "tangled.org/core/appview/pagination" 13 "tangled.org/core/consts" 14 ) 15 16 func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) { ··· 20 21 goodFirstIssueLabel := s.config.Label.GoodFirstIssue 22 23 - gfiLabelDef, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", goodFirstIssueLabel)) 24 if err != nil { 25 log.Println("failed to get gfi label def", err) 26 s.pages.Error500(w) 27 return 28 } 29 30 - repoLabels, err := db.GetRepoLabels(s.db, db.FilterEq("label_at", goodFirstIssueLabel)) 31 if err != nil { 32 log.Println("failed to get repo labels", err) 33 s.pages.Error503(w) ··· 55 pagination.Page{ 56 Limit: 500, 57 }, 58 - db.FilterIn("repo_at", repoUris), 59 - db.FilterEq("open", 1), 60 ) 61 if err != nil { 62 log.Println("failed to get issues", err) ··· 132 } 133 134 if len(uriList) > 0 { 135 - allLabelDefs, err = db.GetLabelDefinitions(s.db, db.FilterIn("at_uri", uriList)) 136 if err != nil { 137 log.Println("failed to fetch labels", err) 138 }
··· 11 "tangled.org/core/appview/pages" 12 "tangled.org/core/appview/pagination" 13 "tangled.org/core/consts" 14 + "tangled.org/core/orm" 15 ) 16 17 func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) { ··· 21 22 goodFirstIssueLabel := s.config.Label.GoodFirstIssue 23 24 + gfiLabelDef, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", goodFirstIssueLabel)) 25 if err != nil { 26 log.Println("failed to get gfi label def", err) 27 s.pages.Error500(w) 28 return 29 } 30 31 + repoLabels, err := db.GetRepoLabels(s.db, orm.FilterEq("label_at", goodFirstIssueLabel)) 32 if err != nil { 33 log.Println("failed to get repo labels", err) 34 s.pages.Error503(w) ··· 56 pagination.Page{ 57 Limit: 500, 58 }, 59 + orm.FilterIn("repo_at", repoUris), 60 + orm.FilterEq("open", 1), 61 ) 62 if err != nil { 63 log.Println("failed to get issues", err) ··· 133 } 134 135 if len(uriList) > 0 { 136 + allLabelDefs, err = db.GetLabelDefinitions(s.db, orm.FilterIn("at_uri", uriList)) 137 if err != nil { 138 log.Println("failed to fetch labels", err) 139 }
+17
appview/state/git_http.go
··· 25 26 } 27 28 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 29 user, ok := r.Context().Value("resolvedId").(identity.Identity) 30 if !ok {
··· 25 26 } 27 28 + func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { 29 + user, ok := r.Context().Value("resolvedId").(identity.Identity) 30 + if !ok { 31 + http.Error(w, "failed to resolve user", http.StatusInternalServerError) 32 + return 33 + } 34 + repo := r.Context().Value("repo").(*models.Repo) 35 + 36 + scheme := "https" 37 + if s.config.Core.Dev { 38 + scheme = "http" 39 + } 40 + 41 + targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-archive?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 42 + s.proxyRequest(w, r, targetURL) 43 + } 44 + 45 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 46 user, ok := r.Context().Value("resolvedId").(identity.Identity) 47 if !ok {
+6 -5
appview/state/knotstream.go
··· 16 ec "tangled.org/core/eventconsumer" 17 "tangled.org/core/eventconsumer/cursor" 18 "tangled.org/core/log" 19 "tangled.org/core/rbac" 20 "tangled.org/core/workflow" 21 ··· 30 31 knots, err := db.GetRegistrations( 32 d, 33 - db.FilterIsNot("registered", "null"), 34 ) 35 if err != nil { 36 return nil, err ··· 143 repos, err := db.GetRepos( 144 d, 145 0, 146 - db.FilterEq("did", record.RepoDid), 147 - db.FilterEq("name", record.RepoName), 148 ) 149 if err != nil { 150 return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.RepoDid, record.RepoName, err) ··· 209 repos, err := db.GetRepos( 210 d, 211 0, 212 - db.FilterEq("did", record.TriggerMetadata.Repo.Did), 213 - db.FilterEq("name", record.TriggerMetadata.Repo.Repo), 214 ) 215 if err != nil { 216 return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err)
··· 16 ec "tangled.org/core/eventconsumer" 17 "tangled.org/core/eventconsumer/cursor" 18 "tangled.org/core/log" 19 + "tangled.org/core/orm" 20 "tangled.org/core/rbac" 21 "tangled.org/core/workflow" 22 ··· 31 32 knots, err := db.GetRegistrations( 33 d, 34 + orm.FilterIsNot("registered", "null"), 35 ) 36 if err != nil { 37 return nil, err ··· 144 repos, err := db.GetRepos( 145 d, 146 0, 147 + orm.FilterEq("did", record.RepoDid), 148 + orm.FilterEq("name", record.RepoName), 149 ) 150 if err != nil { 151 return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.RepoDid, record.RepoName, err) ··· 210 repos, err := db.GetRepos( 211 d, 212 0, 213 + orm.FilterEq("did", record.TriggerMetadata.Repo.Did), 214 + orm.FilterEq("name", record.TriggerMetadata.Repo.Repo), 215 ) 216 if err != nil { 217 return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err)
+13 -12
appview/state/profile.go
··· 19 "tangled.org/core/appview/db" 20 "tangled.org/core/appview/models" 21 "tangled.org/core/appview/pages" 22 ) 23 24 func (s *State) Profile(w http.ResponseWriter, r *http.Request) { ··· 56 return nil, fmt.Errorf("failed to get profile: %w", err) 57 } 58 59 - repoCount, err := db.CountRepos(s.db, db.FilterEq("did", did)) 60 if err != nil { 61 return nil, fmt.Errorf("failed to get repo count: %w", err) 62 } 63 64 - stringCount, err := db.CountStrings(s.db, db.FilterEq("did", did)) 65 if err != nil { 66 return nil, fmt.Errorf("failed to get string count: %w", err) 67 } 68 69 - starredCount, err := db.CountStars(s.db, db.FilterEq("did", did)) 70 if err != nil { 71 return nil, fmt.Errorf("failed to get starred repo count: %w", err) 72 } ··· 86 startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) 87 punchcard, err := db.MakePunchcard( 88 s.db, 89 - db.FilterEq("did", did), 90 - db.FilterGte("date", startOfYear.Format(time.DateOnly)), 91 - db.FilterLte("date", now.Format(time.DateOnly)), 92 ) 93 if err != nil { 94 return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err) ··· 123 repos, err := db.GetRepos( 124 s.db, 125 0, 126 - db.FilterEq("did", profile.UserDid), 127 ) 128 if err != nil { 129 l.Error("failed to fetch repos", "err", err) ··· 193 repos, err := db.GetRepos( 194 s.db, 195 0, 196 - db.FilterEq("did", profile.UserDid), 197 ) 198 if err != nil { 199 l.Error("failed to get repos", "err", err) ··· 219 } 220 l = l.With("profileDid", profile.UserDid) 221 222 - stars, err := db.GetRepoStars(s.db, 0, db.FilterEq("did", profile.UserDid)) 223 if err != nil { 224 l.Error("failed to get stars", "err", err) 225 s.pages.Error500(w) ··· 248 } 249 l = l.With("profileDid", profile.UserDid) 250 251 - strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid)) 252 if err != nil { 253 l.Error("failed to get strings", "err", err) 254 s.pages.Error500(w) ··· 300 followDids = append(followDids, extractDid(follow)) 301 } 302 303 - profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids)) 304 if err != nil { 305 l.Error("failed to get profiles", "followDids", followDids, "err", err) 306 return &params, err ··· 703 log.Printf("getting profile data for %s: %s", user.Did, err) 704 } 705 706 - repos, err := db.GetRepos(s.db, 0, db.FilterEq("did", user.Did)) 707 if err != nil { 708 log.Printf("getting repos for %s: %s", user.Did, err) 709 }
··· 19 "tangled.org/core/appview/db" 20 "tangled.org/core/appview/models" 21 "tangled.org/core/appview/pages" 22 + "tangled.org/core/orm" 23 ) 24 25 func (s *State) Profile(w http.ResponseWriter, r *http.Request) { ··· 57 return nil, fmt.Errorf("failed to get profile: %w", err) 58 } 59 60 + repoCount, err := db.CountRepos(s.db, orm.FilterEq("did", did)) 61 if err != nil { 62 return nil, fmt.Errorf("failed to get repo count: %w", err) 63 } 64 65 + stringCount, err := db.CountStrings(s.db, orm.FilterEq("did", did)) 66 if err != nil { 67 return nil, fmt.Errorf("failed to get string count: %w", err) 68 } 69 70 + starredCount, err := db.CountStars(s.db, orm.FilterEq("did", did)) 71 if err != nil { 72 return nil, fmt.Errorf("failed to get starred repo count: %w", err) 73 } ··· 87 startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) 88 punchcard, err := db.MakePunchcard( 89 s.db, 90 + orm.FilterEq("did", did), 91 + orm.FilterGte("date", startOfYear.Format(time.DateOnly)), 92 + orm.FilterLte("date", now.Format(time.DateOnly)), 93 ) 94 if err != nil { 95 return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err) ··· 124 repos, err := db.GetRepos( 125 s.db, 126 0, 127 + orm.FilterEq("did", profile.UserDid), 128 ) 129 if err != nil { 130 l.Error("failed to fetch repos", "err", err) ··· 194 repos, err := db.GetRepos( 195 s.db, 196 0, 197 + orm.FilterEq("did", profile.UserDid), 198 ) 199 if err != nil { 200 l.Error("failed to get repos", "err", err) ··· 220 } 221 l = l.With("profileDid", profile.UserDid) 222 223 + stars, err := db.GetRepoStars(s.db, 0, orm.FilterEq("did", profile.UserDid)) 224 if err != nil { 225 l.Error("failed to get stars", "err", err) 226 s.pages.Error500(w) ··· 249 } 250 l = l.With("profileDid", profile.UserDid) 251 252 + strings, err := db.GetStrings(s.db, 0, orm.FilterEq("did", profile.UserDid)) 253 if err != nil { 254 l.Error("failed to get strings", "err", err) 255 s.pages.Error500(w) ··· 301 followDids = append(followDids, extractDid(follow)) 302 } 303 304 + profiles, err := db.GetProfiles(s.db, orm.FilterIn("did", followDids)) 305 if err != nil { 306 l.Error("failed to get profiles", "followDids", followDids, "err", err) 307 return &params, err ··· 704 log.Printf("getting profile data for %s: %s", user.Did, err) 705 } 706 707 + repos, err := db.GetRepos(s.db, 0, orm.FilterEq("did", user.Did)) 708 if err != nil { 709 log.Printf("getting repos for %s: %s", user.Did, err) 710 }
+3 -2
appview/state/router.go
··· 101 102 // These routes get proxied to the knot 103 r.Get("/info/refs", s.InfoRefs) 104 r.Post("/git-upload-pack", s.UploadPack) 105 r.Post("/git-receive-pack", s.ReceivePack) 106 ··· 266 s.enforcer, 267 s.pages, 268 s.idResolver, 269 - s.refResolver, 270 s.db, 271 s.config, 272 s.notifier, ··· 283 s.repoResolver, 284 s.pages, 285 s.idResolver, 286 - s.refResolver, 287 s.db, 288 s.config, 289 s.notifier,
··· 101 102 // These routes get proxied to the knot 103 r.Get("/info/refs", s.InfoRefs) 104 + r.Post("/git-upload-archive", s.UploadArchive) 105 r.Post("/git-upload-pack", s.UploadPack) 106 r.Post("/git-receive-pack", s.ReceivePack) 107 ··· 267 s.enforcer, 268 s.pages, 269 s.idResolver, 270 + s.mentionsResolver, 271 s.db, 272 s.config, 273 s.notifier, ··· 284 s.repoResolver, 285 s.pages, 286 s.idResolver, 287 + s.mentionsResolver, 288 s.db, 289 s.config, 290 s.notifier,
+2 -1
appview/state/spindlestream.go
··· 17 ec "tangled.org/core/eventconsumer" 18 "tangled.org/core/eventconsumer/cursor" 19 "tangled.org/core/log" 20 "tangled.org/core/rbac" 21 spindle "tangled.org/core/spindle/models" 22 ) ··· 27 28 spindles, err := db.GetSpindles( 29 d, 30 - db.FilterIsNot("verified", "null"), 31 ) 32 if err != nil { 33 return nil, err
··· 17 ec "tangled.org/core/eventconsumer" 18 "tangled.org/core/eventconsumer/cursor" 19 "tangled.org/core/log" 20 + "tangled.org/core/orm" 21 "tangled.org/core/rbac" 22 spindle "tangled.org/core/spindle/models" 23 ) ··· 28 29 spindles, err := db.GetSpindles( 30 d, 31 + orm.FilterIsNot("verified", "null"), 32 ) 33 if err != nil { 34 return nil, err
+28 -27
appview/state/state.go
··· 15 "tangled.org/core/appview/config" 16 "tangled.org/core/appview/db" 17 "tangled.org/core/appview/indexer" 18 "tangled.org/core/appview/models" 19 "tangled.org/core/appview/notify" 20 dbnotify "tangled.org/core/appview/notify/db" 21 phnotify "tangled.org/core/appview/notify/posthog" 22 "tangled.org/core/appview/oauth" 23 "tangled.org/core/appview/pages" 24 - "tangled.org/core/appview/refresolver" 25 "tangled.org/core/appview/reporesolver" 26 "tangled.org/core/appview/validator" 27 xrpcclient "tangled.org/core/appview/xrpcclient" ··· 30 "tangled.org/core/jetstream" 31 "tangled.org/core/log" 32 tlog "tangled.org/core/log" 33 "tangled.org/core/rbac" 34 "tangled.org/core/tid" 35 ··· 43 ) 44 45 type State struct { 46 - db *db.DB 47 - notifier notify.Notifier 48 - indexer *indexer.Indexer 49 - oauth *oauth.OAuth 50 - enforcer *rbac.Enforcer 51 - pages *pages.Pages 52 - idResolver *idresolver.Resolver 53 - refResolver *refresolver.Resolver 54 - posthog posthog.Client 55 - jc *jetstream.JetstreamClient 56 - config *config.Config 57 - repoResolver *reporesolver.RepoResolver 58 - knotstream *eventconsumer.Consumer 59 - spindlestream *eventconsumer.Consumer 60 - logger *slog.Logger 61 - validator *validator.Validator 62 } 63 64 func Make(ctx context.Context, config *config.Config) (*State, error) { ··· 100 101 repoResolver := reporesolver.New(config, enforcer, d) 102 103 - refResolver := refresolver.New(config, res, d, log.SubLogger(logger, "refResolver")) 104 105 wrapper := db.DbWrapper{Execer: d} 106 jc, err := jetstream.NewJetstreamClient( ··· 182 enforcer, 183 pages, 184 res, 185 - refResolver, 186 posthog, 187 jc, 188 config, ··· 299 return 300 } 301 302 - gfiLabel, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", s.config.Label.GoodFirstIssue)) 303 if err != nil { 304 // non-fatal 305 } ··· 323 324 regs, err := db.GetRegistrations( 325 s.db, 326 - db.FilterEq("did", user.Did), 327 - db.FilterEq("needs_upgrade", 1), 328 ) 329 if err != nil { 330 l.Error("non-fatal: failed to get registrations", "err", err) ··· 332 333 spindles, err := db.GetSpindles( 334 s.db, 335 - db.FilterEq("owner", user.Did), 336 - db.FilterEq("needs_upgrade", 1), 337 ) 338 if err != nil { 339 l.Error("non-fatal: failed to get spindles", "err", err) ··· 504 // Check for existing repos 505 existingRepo, err := db.GetRepo( 506 s.db, 507 - db.FilterEq("did", user.Did), 508 - db.FilterEq("name", repoName), 509 ) 510 if err == nil && existingRepo != nil { 511 l.Info("repo exists") ··· 665 } 666 667 func BackfillDefaultDefs(e db.Execer, r *idresolver.Resolver, defaults []string) error { 668 - defaultLabels, err := db.GetLabelDefinitions(e, db.FilterIn("at_uri", defaults)) 669 if err != nil { 670 return err 671 }
··· 15 "tangled.org/core/appview/config" 16 "tangled.org/core/appview/db" 17 "tangled.org/core/appview/indexer" 18 + "tangled.org/core/appview/mentions" 19 "tangled.org/core/appview/models" 20 "tangled.org/core/appview/notify" 21 dbnotify "tangled.org/core/appview/notify/db" 22 phnotify "tangled.org/core/appview/notify/posthog" 23 "tangled.org/core/appview/oauth" 24 "tangled.org/core/appview/pages" 25 "tangled.org/core/appview/reporesolver" 26 "tangled.org/core/appview/validator" 27 xrpcclient "tangled.org/core/appview/xrpcclient" ··· 30 "tangled.org/core/jetstream" 31 "tangled.org/core/log" 32 tlog "tangled.org/core/log" 33 + "tangled.org/core/orm" 34 "tangled.org/core/rbac" 35 "tangled.org/core/tid" 36 ··· 44 ) 45 46 type State struct { 47 + db *db.DB 48 + notifier notify.Notifier 49 + indexer *indexer.Indexer 50 + oauth *oauth.OAuth 51 + enforcer *rbac.Enforcer 52 + pages *pages.Pages 53 + idResolver *idresolver.Resolver 54 + mentionsResolver *mentions.Resolver 55 + posthog posthog.Client 56 + jc *jetstream.JetstreamClient 57 + config *config.Config 58 + repoResolver *reporesolver.RepoResolver 59 + knotstream *eventconsumer.Consumer 60 + spindlestream *eventconsumer.Consumer 61 + logger *slog.Logger 62 + validator *validator.Validator 63 } 64 65 func Make(ctx context.Context, config *config.Config) (*State, error) { ··· 101 102 repoResolver := reporesolver.New(config, enforcer, d) 103 104 + mentionsResolver := mentions.New(config, res, d, log.SubLogger(logger, "mentionsResolver")) 105 106 wrapper := db.DbWrapper{Execer: d} 107 jc, err := jetstream.NewJetstreamClient( ··· 183 enforcer, 184 pages, 185 res, 186 + mentionsResolver, 187 posthog, 188 jc, 189 config, ··· 300 return 301 } 302 303 + gfiLabel, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", s.config.Label.GoodFirstIssue)) 304 if err != nil { 305 // non-fatal 306 } ··· 324 325 regs, err := db.GetRegistrations( 326 s.db, 327 + orm.FilterEq("did", user.Did), 328 + orm.FilterEq("needs_upgrade", 1), 329 ) 330 if err != nil { 331 l.Error("non-fatal: failed to get registrations", "err", err) ··· 333 334 spindles, err := db.GetSpindles( 335 s.db, 336 + orm.FilterEq("owner", user.Did), 337 + orm.FilterEq("needs_upgrade", 1), 338 ) 339 if err != nil { 340 l.Error("non-fatal: failed to get spindles", "err", err) ··· 505 // Check for existing repos 506 existingRepo, err := db.GetRepo( 507 s.db, 508 + orm.FilterEq("did", user.Did), 509 + orm.FilterEq("name", repoName), 510 ) 511 if err == nil && existingRepo != nil { 512 l.Info("repo exists") ··· 666 } 667 668 func BackfillDefaultDefs(e db.Execer, r *idresolver.Resolver, defaults []string) error { 669 + defaultLabels, err := db.GetLabelDefinitions(e, orm.FilterIn("at_uri", defaults)) 670 if err != nil { 671 return err 672 }
+7 -6
appview/strings/strings.go
··· 17 "tangled.org/core/appview/pages" 18 "tangled.org/core/appview/pages/markup" 19 "tangled.org/core/idresolver" 20 "tangled.org/core/tid" 21 22 "github.com/bluesky-social/indigo/api/atproto" ··· 108 strings, err := db.GetStrings( 109 s.Db, 110 0, 111 - db.FilterEq("did", id.DID), 112 - db.FilterEq("rkey", rkey), 113 ) 114 if err != nil { 115 l.Error("failed to fetch string", "err", err) ··· 199 all, err := db.GetStrings( 200 s.Db, 201 0, 202 - db.FilterEq("did", id.DID), 203 - db.FilterEq("rkey", rkey), 204 ) 205 if err != nil { 206 l.Error("failed to fetch string", "err", err) ··· 408 409 if err := db.DeleteString( 410 s.Db, 411 - db.FilterEq("did", user.Did), 412 - db.FilterEq("rkey", rkey), 413 ); err != nil { 414 fail("Failed to delete string.", err) 415 return
··· 17 "tangled.org/core/appview/pages" 18 "tangled.org/core/appview/pages/markup" 19 "tangled.org/core/idresolver" 20 + "tangled.org/core/orm" 21 "tangled.org/core/tid" 22 23 "github.com/bluesky-social/indigo/api/atproto" ··· 109 strings, err := db.GetStrings( 110 s.Db, 111 0, 112 + orm.FilterEq("did", id.DID), 113 + orm.FilterEq("rkey", rkey), 114 ) 115 if err != nil { 116 l.Error("failed to fetch string", "err", err) ··· 200 all, err := db.GetStrings( 201 s.Db, 202 0, 203 + orm.FilterEq("did", id.DID), 204 + orm.FilterEq("rkey", rkey), 205 ) 206 if err != nil { 207 l.Error("failed to fetch string", "err", err) ··· 409 410 if err := db.DeleteString( 411 s.Db, 412 + orm.FilterEq("did", user.Did), 413 + orm.FilterEq("rkey", rkey), 414 ); err != nil { 415 fail("Failed to delete string.", err) 416 return
+2 -1
appview/validator/issue.go
··· 6 7 "tangled.org/core/appview/db" 8 "tangled.org/core/appview/models" 9 ) 10 11 func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error { 12 // if comments have parents, only ingest ones that are 1 level deep 13 if comment.ReplyTo != nil { 14 - parents, err := db.GetIssueComments(v.db, db.FilterEq("at_uri", *comment.ReplyTo)) 15 if err != nil { 16 return fmt.Errorf("failed to fetch parent comment: %w", err) 17 }
··· 6 7 "tangled.org/core/appview/db" 8 "tangled.org/core/appview/models" 9 + "tangled.org/core/orm" 10 ) 11 12 func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error { 13 // if comments have parents, only ingest ones that are 1 level deep 14 if comment.ReplyTo != nil { 15 + parents, err := db.GetIssueComments(v.db, orm.FilterEq("at_uri", *comment.ReplyTo)) 16 if err != nil { 17 return fmt.Errorf("failed to fetch parent comment: %w", err) 18 }
+9 -9
flake.lock
··· 35 "systems": "systems" 36 }, 37 "locked": { 38 - "lastModified": 1694529238, 39 - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 40 "owner": "numtide", 41 "repo": "flake-utils", 42 - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 43 "type": "github" 44 }, 45 "original": { ··· 56 ] 57 }, 58 "locked": { 59 - "lastModified": 1754078208, 60 - "narHash": "sha256-YVoIFDCDpYuU3riaDEJ3xiGdPOtsx4sR5eTzHTytPV8=", 61 "owner": "nix-community", 62 "repo": "gomod2nix", 63 - "rev": "7f963246a71626c7fc70b431a315c4388a0c95cf", 64 "type": "github" 65 }, 66 "original": { ··· 150 }, 151 "nixpkgs": { 152 "locked": { 153 - "lastModified": 1751984180, 154 - "narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=", 155 "owner": "nixos", 156 "repo": "nixpkgs", 157 - "rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0", 158 "type": "github" 159 }, 160 "original": {
··· 35 "systems": "systems" 36 }, 37 "locked": { 38 + "lastModified": 1731533236, 39 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 40 "owner": "numtide", 41 "repo": "flake-utils", 42 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 43 "type": "github" 44 }, 45 "original": { ··· 56 ] 57 }, 58 "locked": { 59 + "lastModified": 1763982521, 60 + "narHash": "sha256-ur4QIAHwgFc0vXiaxn5No/FuZicxBr2p0gmT54xZkUQ=", 61 "owner": "nix-community", 62 "repo": "gomod2nix", 63 + "rev": "02e63a239d6eabd595db56852535992c898eba72", 64 "type": "github" 65 }, 66 "original": { ··· 150 }, 151 "nixpkgs": { 152 "locked": { 153 + "lastModified": 1766070988, 154 + "narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=", 155 "owner": "nixos", 156 "repo": "nixpkgs", 157 + "rev": "c6245e83d836d0433170a16eb185cefe0572f8b8", 158 "type": "github" 159 }, 160 "original": {
-2
flake.nix
··· 80 }).buildGoApplication; 81 modules = ./nix/gomod2nix.toml; 82 sqlite-lib = self.callPackage ./nix/pkgs/sqlite-lib.nix { 83 - inherit (pkgs) gcc; 84 inherit sqlite-lib-src; 85 }; 86 lexgen = self.callPackage ./nix/pkgs/lexgen.nix {inherit indigo;}; ··· 156 nativeBuildInputs = [ 157 pkgs.go 158 pkgs.air 159 - pkgs.tilt 160 pkgs.gopls 161 pkgs.httpie 162 pkgs.litecli
··· 80 }).buildGoApplication; 81 modules = ./nix/gomod2nix.toml; 82 sqlite-lib = self.callPackage ./nix/pkgs/sqlite-lib.nix { 83 inherit sqlite-lib-src; 84 }; 85 lexgen = self.callPackage ./nix/pkgs/lexgen.nix {inherit indigo;}; ··· 155 nativeBuildInputs = [ 156 pkgs.go 157 pkgs.air 158 pkgs.gopls 159 pkgs.httpie 160 pkgs.litecli
+3 -4
go.mod
··· 1 module tangled.org/core 2 3 - go 1.24.4 4 5 require ( 6 github.com/Blank-Xu/sql-adapter v1.1.1 ··· 44 github.com/stretchr/testify v1.10.0 45 github.com/urfave/cli/v3 v3.3.3 46 github.com/whyrusleeping/cbor-gen v0.3.1 47 - github.com/wyatt915/goldmark-treeblood v0.0.1 48 github.com/yuin/goldmark v1.7.13 49 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 50 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab 51 golang.org/x/crypto v0.40.0 52 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b 53 golang.org/x/image v0.31.0 54 golang.org/x/net v0.42.0 55 - golang.org/x/sync v0.17.0 56 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 57 gopkg.in/yaml.v3 v3.0.1 58 ) ··· 190 github.com/vmihailenco/go-tinylfu v0.2.2 // indirect 191 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 192 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 193 - github.com/wyatt915/treeblood v0.1.16 // indirect 194 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 195 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 196 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect ··· 205 go.uber.org/atomic v1.11.0 // indirect 206 go.uber.org/multierr v1.11.0 // indirect 207 go.uber.org/zap v1.27.0 // indirect 208 golang.org/x/sys v0.34.0 // indirect 209 golang.org/x/text v0.29.0 // indirect 210 golang.org/x/time v0.12.0 // indirect
··· 1 module tangled.org/core 2 3 + go 1.25.0 4 5 require ( 6 github.com/Blank-Xu/sql-adapter v1.1.1 ··· 44 github.com/stretchr/testify v1.10.0 45 github.com/urfave/cli/v3 v3.3.3 46 github.com/whyrusleeping/cbor-gen v0.3.1 47 github.com/yuin/goldmark v1.7.13 48 + github.com/yuin/goldmark-emoji v1.0.6 49 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 50 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab 51 golang.org/x/crypto v0.40.0 52 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b 53 golang.org/x/image v0.31.0 54 golang.org/x/net v0.42.0 55 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 56 gopkg.in/yaml.v3 v3.0.1 57 ) ··· 189 github.com/vmihailenco/go-tinylfu v0.2.2 // indirect 190 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 191 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 192 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 193 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 194 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect ··· 203 go.uber.org/atomic v1.11.0 // indirect 204 go.uber.org/multierr v1.11.0 // indirect 205 go.uber.org/zap v1.27.0 // indirect 206 + golang.org/x/sync v0.17.0 // indirect 207 golang.org/x/sys v0.34.0 // indirect 208 golang.org/x/text v0.29.0 // indirect 209 golang.org/x/time v0.12.0 // indirect
+2 -4
go.sum
··· 495 github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 496 github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= 497 github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 498 - github.com/wyatt915/goldmark-treeblood v0.0.1 h1:6vLJcjFrHgE4ASu2ga4hqIQmbvQLU37v53jlHZ3pqDs= 499 - github.com/wyatt915/goldmark-treeblood v0.0.1/go.mod h1:SmcJp5EBaV17rroNlgNQFydYwy0+fv85CUr/ZaCz208= 500 - github.com/wyatt915/treeblood v0.1.16 h1:byxNbWZhnPDxdTp7W5kQhCeaY8RBVmojTFz1tEHgg8Y= 501 - github.com/wyatt915/treeblood v0.1.16/go.mod h1:i7+yhhmzdDP17/97pIsOSffw74EK/xk+qJ0029cSXUY= 502 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 503 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 504 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= ··· 509 github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 510 github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= 511 github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 512 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 513 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 514 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A=
··· 495 github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 496 github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= 497 github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 498 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 499 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 500 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= ··· 505 github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 506 github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= 507 github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 508 + github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= 509 + github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= 510 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 511 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 512 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A=
+4 -4
hook/hook.go
··· 48 }, 49 Commands: []*cli.Command{ 50 { 51 - Name: "post-recieve", 52 - Usage: "sends a post-recieve hook to the knot (waits for stdin)", 53 - Action: postRecieve, 54 }, 55 }, 56 } 57 } 58 59 - func postRecieve(ctx context.Context, cmd *cli.Command) error { 60 gitDir := cmd.String("git-dir") 61 userDid := cmd.String("user-did") 62 userHandle := cmd.String("user-handle")
··· 48 }, 49 Commands: []*cli.Command{ 50 { 51 + Name: "post-receive", 52 + Usage: "sends a post-receive hook to the knot (waits for stdin)", 53 + Action: postReceive, 54 }, 55 }, 56 } 57 } 58 59 + func postReceive(ctx context.Context, cmd *cli.Command) error { 60 gitDir := cmd.String("git-dir") 61 userDid := cmd.String("user-did") 62 userHandle := cmd.String("user-handle")
+1 -1
hook/setup.go
··· 138 option_var="GIT_PUSH_OPTION_$i" 139 push_options+=(-push-option "${!option_var}") 140 done 141 - %s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" "${push_options[@]}" post-recieve 142 `, executablePath, config.internalApi) 143 144 return os.WriteFile(hookPath, []byte(hookContent), 0755)
··· 138 option_var="GIT_PUSH_OPTION_$i" 139 push_options+=(-push-option "${!option_var}") 140 done 141 + %s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" "${push_options[@]}" post-receive 142 `, executablePath, config.internalApi) 143 144 return os.WriteFile(hookPath, []byte(hookContent), 0755)
+81
knotserver/db/db.go
···
··· 1 + package db 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "log/slog" 7 + "strings" 8 + 9 + _ "github.com/mattn/go-sqlite3" 10 + "tangled.org/core/log" 11 + ) 12 + 13 + type DB struct { 14 + db *sql.DB 15 + logger *slog.Logger 16 + } 17 + 18 + func Setup(ctx context.Context, dbPath string) (*DB, error) { 19 + // https://github.com/mattn/go-sqlite3#connection-string 20 + opts := []string{ 21 + "_foreign_keys=1", 22 + "_journal_mode=WAL", 23 + "_synchronous=NORMAL", 24 + "_auto_vacuum=incremental", 25 + } 26 + 27 + logger := log.FromContext(ctx) 28 + logger = log.SubLogger(logger, "db") 29 + 30 + db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&")) 31 + if err != nil { 32 + return nil, err 33 + } 34 + 35 + conn, err := db.Conn(ctx) 36 + if err != nil { 37 + return nil, err 38 + } 39 + defer conn.Close() 40 + 41 + _, err = conn.ExecContext(ctx, ` 42 + create table if not exists known_dids ( 43 + did text primary key 44 + ); 45 + 46 + create table if not exists public_keys ( 47 + id integer primary key autoincrement, 48 + did text not null, 49 + key text not null, 50 + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 51 + unique(did, key), 52 + foreign key (did) references known_dids(did) on delete cascade 53 + ); 54 + 55 + create table if not exists _jetstream ( 56 + id integer primary key autoincrement, 57 + last_time_us integer not null 58 + ); 59 + 60 + create table if not exists events ( 61 + rkey text not null, 62 + nsid text not null, 63 + event text not null, -- json 64 + created integer not null default (strftime('%s', 'now')), 65 + primary key (rkey, nsid) 66 + ); 67 + 68 + create table if not exists migrations ( 69 + id integer primary key autoincrement, 70 + name text unique 71 + ); 72 + `) 73 + if err != nil { 74 + return nil, err 75 + } 76 + 77 + return &DB{ 78 + db: db, 79 + logger: logger, 80 + }, nil 81 + }
-64
knotserver/db/init.go
··· 1 - package db 2 - 3 - import ( 4 - "database/sql" 5 - "strings" 6 - 7 - _ "github.com/mattn/go-sqlite3" 8 - ) 9 - 10 - type DB struct { 11 - db *sql.DB 12 - } 13 - 14 - func Setup(dbPath string) (*DB, error) { 15 - // https://github.com/mattn/go-sqlite3#connection-string 16 - opts := []string{ 17 - "_foreign_keys=1", 18 - "_journal_mode=WAL", 19 - "_synchronous=NORMAL", 20 - "_auto_vacuum=incremental", 21 - } 22 - 23 - db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&")) 24 - if err != nil { 25 - return nil, err 26 - } 27 - 28 - // NOTE: If any other migration is added here, you MUST 29 - // copy the pattern in appview: use a single sql.Conn 30 - // for every migration. 31 - 32 - _, err = db.Exec(` 33 - create table if not exists known_dids ( 34 - did text primary key 35 - ); 36 - 37 - create table if not exists public_keys ( 38 - id integer primary key autoincrement, 39 - did text not null, 40 - key text not null, 41 - created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 42 - unique(did, key), 43 - foreign key (did) references known_dids(did) on delete cascade 44 - ); 45 - 46 - create table if not exists _jetstream ( 47 - id integer primary key autoincrement, 48 - last_time_us integer not null 49 - ); 50 - 51 - create table if not exists events ( 52 - rkey text not null, 53 - nsid text not null, 54 - event text not null, -- json 55 - created integer not null default (strftime('%s', 'now')), 56 - primary key (rkey, nsid) 57 - ); 58 - `) 59 - if err != nil { 60 - return nil, err 61 - } 62 - 63 - return &DB{db: db}, nil 64 - }
···
+13 -1
knotserver/git/service/service.go
··· 95 return c.RunService(cmd) 96 } 97 98 func (c *ServiceCommand) UploadPack() error { 99 cmd := exec.Command("git", []string{ 100 - "-c", "uploadpack.allowFilter=true", 101 "upload-pack", 102 "--stateless-rpc", 103 ".",
··· 95 return c.RunService(cmd) 96 } 97 98 + func (c *ServiceCommand) UploadArchive() error { 99 + cmd := exec.Command("git", []string{ 100 + "upload-archive", 101 + ".", 102 + }...) 103 + 104 + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 105 + cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_PROTOCOL=%s", c.GitProtocol)) 106 + cmd.Dir = c.Dir 107 + 108 + return c.RunService(cmd) 109 + } 110 + 111 func (c *ServiceCommand) UploadPack() error { 112 cmd := exec.Command("git", []string{ 113 "upload-pack", 114 "--stateless-rpc", 115 ".",
+47
knotserver/git.go
··· 56 } 57 } 58 59 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 60 did := chi.URLParam(r, "did") 61 name := chi.URLParam(r, "name")
··· 56 } 57 } 58 59 + func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 60 + did := chi.URLParam(r, "did") 61 + name := chi.URLParam(r, "name") 62 + repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 63 + if err != nil { 64 + gitError(w, err.Error(), http.StatusInternalServerError) 65 + h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 66 + return 67 + } 68 + 69 + const expectedContentType = "application/x-git-upload-archive-request" 70 + contentType := r.Header.Get("Content-Type") 71 + if contentType != expectedContentType { 72 + gitError(w, fmt.Sprintf("Expected Content-Type: '%s', but received '%s'.", expectedContentType, contentType), http.StatusUnsupportedMediaType) 73 + } 74 + 75 + var bodyReader io.ReadCloser = r.Body 76 + if r.Header.Get("Content-Encoding") == "gzip" { 77 + gzipReader, err := gzip.NewReader(r.Body) 78 + if err != nil { 79 + gitError(w, err.Error(), http.StatusInternalServerError) 80 + h.l.Error("git: failed to create gzip reader", "handler", "UploadArchive", "error", err) 81 + return 82 + } 83 + defer gzipReader.Close() 84 + bodyReader = gzipReader 85 + } 86 + 87 + w.Header().Set("Content-Type", "application/x-git-upload-archive-result") 88 + 89 + h.l.Info("git: executing git-upload-archive", "handler", "UploadArchive", "repo", repo) 90 + 91 + cmd := service.ServiceCommand{ 92 + GitProtocol: r.Header.Get("Git-Protocol"), 93 + Dir: repo, 94 + Stdout: w, 95 + Stdin: bodyReader, 96 + } 97 + 98 + w.WriteHeader(http.StatusOK) 99 + 100 + if err := cmd.UploadArchive(); err != nil { 101 + h.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err) 102 + return 103 + } 104 + } 105 + 106 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 107 did := chi.URLParam(r, "did") 108 name := chi.URLParam(r, "name")
+1
knotserver/router.go
··· 82 r.Route("/{name}", func(r chi.Router) { 83 // routes for git operations 84 r.Get("/info/refs", h.InfoRefs) 85 r.Post("/git-upload-pack", h.UploadPack) 86 r.Post("/git-receive-pack", h.ReceivePack) 87 })
··· 82 r.Route("/{name}", func(r chi.Router) { 83 // routes for git operations 84 r.Get("/info/refs", h.InfoRefs) 85 + r.Post("/git-upload-archive", h.UploadArchive) 86 r.Post("/git-upload-pack", h.UploadPack) 87 r.Post("/git-receive-pack", h.ReceivePack) 88 })
+1 -1
knotserver/server.go
··· 64 logger.Info("running in dev mode, signature verification is disabled") 65 } 66 67 - db, err := db.Setup(c.Server.DBPath) 68 if err != nil { 69 return fmt.Errorf("failed to load db: %w", err) 70 }
··· 64 logger.Info("running in dev mode, signature verification is disabled") 65 } 66 67 + db, err := db.Setup(ctx, c.Server.DBPath) 68 if err != nil { 69 return fmt.Errorf("failed to load db: %w", err) 70 }
+3 -30
nix/gomod2nix.toml
··· 165 [mod."github.com/davecgh/go-spew"] 166 version = "v1.1.2-0.20180830191138-d8f796af33cc" 167 hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc=" 168 - [mod."github.com/decred/dcrd/dcrec/secp256k1/v4"] 169 - version = "v4.4.0" 170 - hash = "sha256-qrhEIwhDll3cxoVpMbm1NQ9/HTI42S7ms8Buzlo5HCg=" 171 [mod."github.com/dgraph-io/ristretto"] 172 version = "v0.2.0" 173 hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw=" ··· 373 [mod."github.com/klauspost/cpuid/v2"] 374 version = "v2.3.0" 375 hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc=" 376 - [mod."github.com/lestrrat-go/blackmagic"] 377 - version = "v1.0.4" 378 - hash = "sha256-HmWOpwoPDNMwLdOi7onNn3Sb+ZsAa3Ai3gVBbXmQ0e8=" 379 - [mod."github.com/lestrrat-go/httpcc"] 380 - version = "v1.0.1" 381 - hash = "sha256-SMRSwJpqDIs/xL0l2e8vP0W65qtCHX2wigcOeqPJmos=" 382 - [mod."github.com/lestrrat-go/httprc"] 383 - version = "v1.0.6" 384 - hash = "sha256-mfZzePEhrmyyu/avEBd2MsDXyto8dq5+fyu5lA8GUWM=" 385 - [mod."github.com/lestrrat-go/iter"] 386 - version = "v1.0.2" 387 - hash = "sha256-30tErRf7Qu/NOAt1YURXY/XJSA6sCr6hYQfO8QqHrtw=" 388 - [mod."github.com/lestrrat-go/jwx/v2"] 389 - version = "v2.1.6" 390 - hash = "sha256-0LszXRZIba+X8AOrs3T4uanAUafBdlVB8/MpUNEFpbc=" 391 - [mod."github.com/lestrrat-go/option"] 392 - version = "v1.0.1" 393 - hash = "sha256-jVcIYYVsxElIS/l2akEw32vdEPR8+anR6oeT1FoYULI=" 394 [mod."github.com/lucasb-eyer/go-colorful"] 395 version = "v1.2.0" 396 hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE=" ··· 511 [mod."github.com/ryanuber/go-glob"] 512 version = "v1.0.0" 513 hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY=" 514 - [mod."github.com/segmentio/asm"] 515 - version = "v1.2.0" 516 - hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs=" 517 [mod."github.com/sergi/go-diff"] 518 version = "v1.1.0" 519 hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY=" ··· 548 [mod."github.com/whyrusleeping/cbor-gen"] 549 version = "v0.3.1" 550 hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc=" 551 - [mod."github.com/wyatt915/goldmark-treeblood"] 552 - version = "v0.0.1" 553 - hash = "sha256-hAVFaktO02MiiqZFffr8ZlvFEfwxw4Y84OZ2t7e5G7g=" 554 - [mod."github.com/wyatt915/treeblood"] 555 - version = "v0.1.16" 556 - hash = "sha256-T68sa+iVx0qY7dDjXEAJvRWQEGXYIpUsf9tcWwO1tIw=" 557 [mod."github.com/xo/terminfo"] 558 version = "v0.0.0-20220910002029-abceb7e1c41e" 559 hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU=" 560 [mod."github.com/yuin/goldmark"] 561 version = "v1.7.13" 562 hash = "sha256-vBCxZrPYPc8x/nvAAv3Au59dCCyfS80Vw3/a9EXK7TE=" 563 [mod."github.com/yuin/goldmark-highlighting/v2"] 564 version = "v2.0.0-20230729083705-37449abec8cc" 565 hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg="
··· 165 [mod."github.com/davecgh/go-spew"] 166 version = "v1.1.2-0.20180830191138-d8f796af33cc" 167 hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc=" 168 [mod."github.com/dgraph-io/ristretto"] 169 version = "v0.2.0" 170 hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw=" ··· 370 [mod."github.com/klauspost/cpuid/v2"] 371 version = "v2.3.0" 372 hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc=" 373 [mod."github.com/lucasb-eyer/go-colorful"] 374 version = "v1.2.0" 375 hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE=" ··· 490 [mod."github.com/ryanuber/go-glob"] 491 version = "v1.0.0" 492 hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY=" 493 [mod."github.com/sergi/go-diff"] 494 version = "v1.1.0" 495 hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY=" ··· 524 [mod."github.com/whyrusleeping/cbor-gen"] 525 version = "v0.3.1" 526 hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc=" 527 [mod."github.com/xo/terminfo"] 528 version = "v0.0.0-20220910002029-abceb7e1c41e" 529 hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU=" 530 [mod."github.com/yuin/goldmark"] 531 version = "v1.7.13" 532 hash = "sha256-vBCxZrPYPc8x/nvAAv3Au59dCCyfS80Vw3/a9EXK7TE=" 533 + [mod."github.com/yuin/goldmark-emoji"] 534 + version = "v1.0.6" 535 + hash = "sha256-+d6bZzOPE+JSFsZbQNZMCWE+n3jgcQnkPETVk47mxSY=" 536 [mod."github.com/yuin/goldmark-highlighting/v2"] 537 version = "v2.0.0-20230729083705-37449abec8cc" 538 hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg="
+7 -5
nix/pkgs/sqlite-lib.nix
··· 1 { 2 - gcc, 3 stdenv, 4 sqlite-lib-src, 5 }: 6 stdenv.mkDerivation { 7 name = "sqlite-lib"; 8 src = sqlite-lib-src; 9 - nativeBuildInputs = [gcc]; 10 buildPhase = '' 11 - gcc -c sqlite3.c 12 - ar rcs libsqlite3.a sqlite3.o 13 - ranlib libsqlite3.a 14 mkdir -p $out/include $out/lib 15 cp *.h $out/include 16 cp libsqlite3.a $out/lib
··· 1 { 2 stdenv, 3 sqlite-lib-src, 4 }: 5 stdenv.mkDerivation { 6 name = "sqlite-lib"; 7 src = sqlite-lib-src; 8 + 9 buildPhase = '' 10 + $CC -c sqlite3.c 11 + $AR rcs libsqlite3.a sqlite3.o 12 + $RANLIB libsqlite3.a 13 + ''; 14 + 15 + installPhase = '' 16 mkdir -p $out/include $out/lib 17 cp *.h $out/include 18 cp libsqlite3.a $out/lib
+122
orm/orm.go
···
··· 1 + package orm 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + "log/slog" 8 + "reflect" 9 + "strings" 10 + ) 11 + 12 + type migrationFn = func(*sql.Tx) error 13 + 14 + func RunMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error { 15 + logger = logger.With("migration", name) 16 + 17 + tx, err := c.BeginTx(context.Background(), nil) 18 + if err != nil { 19 + return err 20 + } 21 + defer tx.Rollback() 22 + 23 + var exists bool 24 + err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists) 25 + if err != nil { 26 + return err 27 + } 28 + 29 + if !exists { 30 + // run migration 31 + err = migrationFn(tx) 32 + if err != nil { 33 + logger.Error("failed to run migration", "err", err) 34 + return err 35 + } 36 + 37 + // mark migration as complete 38 + _, err = tx.Exec("insert into migrations (name) values (?)", name) 39 + if err != nil { 40 + logger.Error("failed to mark migration as complete", "err", err) 41 + return err 42 + } 43 + 44 + // commit the transaction 45 + if err := tx.Commit(); err != nil { 46 + return err 47 + } 48 + 49 + logger.Info("migration applied successfully") 50 + } else { 51 + logger.Warn("skipped migration, already applied") 52 + } 53 + 54 + return nil 55 + } 56 + 57 + type Filter struct { 58 + Key string 59 + arg any 60 + Cmp string 61 + } 62 + 63 + func newFilter(key, cmp string, arg any) Filter { 64 + return Filter{ 65 + Key: key, 66 + arg: arg, 67 + Cmp: cmp, 68 + } 69 + } 70 + 71 + func FilterEq(key string, arg any) Filter { return newFilter(key, "=", arg) } 72 + func FilterNotEq(key string, arg any) Filter { return newFilter(key, "<>", arg) } 73 + func FilterGte(key string, arg any) Filter { return newFilter(key, ">=", arg) } 74 + func FilterLte(key string, arg any) Filter { return newFilter(key, "<=", arg) } 75 + func FilterIs(key string, arg any) Filter { return newFilter(key, "is", arg) } 76 + func FilterIsNot(key string, arg any) Filter { return newFilter(key, "is not", arg) } 77 + func FilterIn(key string, arg any) Filter { return newFilter(key, "in", arg) } 78 + func FilterLike(key string, arg any) Filter { return newFilter(key, "like", arg) } 79 + func FilterNotLike(key string, arg any) Filter { return newFilter(key, "not like", arg) } 80 + func FilterContains(key string, arg any) Filter { 81 + return newFilter(key, "like", fmt.Sprintf("%%%v%%", arg)) 82 + } 83 + 84 + func (f Filter) Condition() string { 85 + rv := reflect.ValueOf(f.arg) 86 + kind := rv.Kind() 87 + 88 + // if we have `FilterIn(k, [1, 2, 3])`, compile it down to `k in (?, ?, ?)` 89 + if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array { 90 + if rv.Len() == 0 { 91 + // always false 92 + return "1 = 0" 93 + } 94 + 95 + placeholders := make([]string, rv.Len()) 96 + for i := range placeholders { 97 + placeholders[i] = "?" 98 + } 99 + 100 + return fmt.Sprintf("%s %s (%s)", f.Key, f.Cmp, strings.Join(placeholders, ", ")) 101 + } 102 + 103 + return fmt.Sprintf("%s %s ?", f.Key, f.Cmp) 104 + } 105 + 106 + func (f Filter) Arg() []any { 107 + rv := reflect.ValueOf(f.arg) 108 + kind := rv.Kind() 109 + if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array { 110 + if rv.Len() == 0 { 111 + return nil 112 + } 113 + 114 + out := make([]any, rv.Len()) 115 + for i := range rv.Len() { 116 + out[i] = rv.Index(i).Interface() 117 + } 118 + return out 119 + } 120 + 121 + return []any{f.arg} 122 + }
+31
sets/gen.go
···
··· 1 + package sets 2 + 3 + import ( 4 + "math/rand" 5 + "reflect" 6 + "testing/quick" 7 + ) 8 + 9 + func (_ Set[T]) Generate(rand *rand.Rand, size int) reflect.Value { 10 + s := New[T]() 11 + 12 + var zero T 13 + itemType := reflect.TypeOf(zero) 14 + 15 + for { 16 + if s.Len() >= size { 17 + break 18 + } 19 + 20 + item, ok := quick.Value(itemType, rand) 21 + if !ok { 22 + continue 23 + } 24 + 25 + if val, ok := item.Interface().(T); ok { 26 + s.Insert(val) 27 + } 28 + } 29 + 30 + return reflect.ValueOf(s) 31 + }
+35
sets/readme.txt
···
··· 1 + sets 2 + ---- 3 + set datastructure for go with generics and iterators. the 4 + api is supposed to mimic rust's std::collections::HashSet api. 5 + 6 + s1 := sets.Collect(slices.Values([]int{1, 2, 3, 4})) 7 + s2 := sets.Collect(slices.Values([]int{1, 2, 3, 4, 5, 6})) 8 + 9 + union := sets.Collect(s1.Union(s2)) 10 + intersect := sets.Collect(s1.Intersection(s2)) 11 + diff := sets.Collect(s1.Difference(s2)) 12 + symdiff := sets.Collect(s1.SymmetricDifference(s2)) 13 + 14 + s1.Len() // 4 15 + s1.Contains(1) // true 16 + s1.IsEmpty() // false 17 + s1.IsSubset(s2) // true 18 + s1.IsSuperset(s2) // false 19 + s1.IsDisjoint(s2) // false 20 + 21 + if exists := s1.Insert(1); exists { 22 + // already existed in set 23 + } 24 + 25 + if existed := s1.Remove(1); existed { 26 + // existed in set, now removed 27 + } 28 + 29 + 30 + testing 31 + ------- 32 + includes property-based tests using the wonderful 33 + testing/quick module! 34 + 35 + go test -v
+174
sets/set.go
···
··· 1 + package sets 2 + 3 + import ( 4 + "iter" 5 + "maps" 6 + ) 7 + 8 + type Set[T comparable] struct { 9 + data map[T]struct{} 10 + } 11 + 12 + func New[T comparable]() Set[T] { 13 + return Set[T]{ 14 + data: make(map[T]struct{}), 15 + } 16 + } 17 + 18 + func (s *Set[T]) Insert(item T) bool { 19 + _, exists := s.data[item] 20 + s.data[item] = struct{}{} 21 + return !exists 22 + } 23 + 24 + func Singleton[T comparable](item T) Set[T] { 25 + n := New[T]() 26 + _ = n.Insert(item) 27 + return n 28 + } 29 + 30 + func (s *Set[T]) Remove(item T) bool { 31 + _, exists := s.data[item] 32 + if exists { 33 + delete(s.data, item) 34 + } 35 + return exists 36 + } 37 + 38 + func (s Set[T]) Contains(item T) bool { 39 + _, exists := s.data[item] 40 + return exists 41 + } 42 + 43 + func (s Set[T]) Len() int { 44 + return len(s.data) 45 + } 46 + 47 + func (s Set[T]) IsEmpty() bool { 48 + return len(s.data) == 0 49 + } 50 + 51 + func (s *Set[T]) Clear() { 52 + s.data = make(map[T]struct{}) 53 + } 54 + 55 + func (s Set[T]) All() iter.Seq[T] { 56 + return func(yield func(T) bool) { 57 + for item := range s.data { 58 + if !yield(item) { 59 + return 60 + } 61 + } 62 + } 63 + } 64 + 65 + func (s Set[T]) Clone() Set[T] { 66 + return Set[T]{ 67 + data: maps.Clone(s.data), 68 + } 69 + } 70 + 71 + func (s Set[T]) Union(other Set[T]) iter.Seq[T] { 72 + if s.Len() >= other.Len() { 73 + return chain(s.All(), other.Difference(s)) 74 + } else { 75 + return chain(other.All(), s.Difference(other)) 76 + } 77 + } 78 + 79 + func chain[T any](seqs ...iter.Seq[T]) iter.Seq[T] { 80 + return func(yield func(T) bool) { 81 + for _, seq := range seqs { 82 + for item := range seq { 83 + if !yield(item) { 84 + return 85 + } 86 + } 87 + } 88 + } 89 + } 90 + 91 + func (s Set[T]) Intersection(other Set[T]) iter.Seq[T] { 92 + return func(yield func(T) bool) { 93 + for item := range s.data { 94 + if other.Contains(item) { 95 + if !yield(item) { 96 + return 97 + } 98 + } 99 + } 100 + } 101 + } 102 + 103 + func (s Set[T]) Difference(other Set[T]) iter.Seq[T] { 104 + return func(yield func(T) bool) { 105 + for item := range s.data { 106 + if !other.Contains(item) { 107 + if !yield(item) { 108 + return 109 + } 110 + } 111 + } 112 + } 113 + } 114 + 115 + func (s Set[T]) SymmetricDifference(other Set[T]) iter.Seq[T] { 116 + return func(yield func(T) bool) { 117 + for item := range s.data { 118 + if !other.Contains(item) { 119 + if !yield(item) { 120 + return 121 + } 122 + } 123 + } 124 + for item := range other.data { 125 + if !s.Contains(item) { 126 + if !yield(item) { 127 + return 128 + } 129 + } 130 + } 131 + } 132 + } 133 + 134 + func (s Set[T]) IsSubset(other Set[T]) bool { 135 + for item := range s.data { 136 + if !other.Contains(item) { 137 + return false 138 + } 139 + } 140 + return true 141 + } 142 + 143 + func (s Set[T]) IsSuperset(other Set[T]) bool { 144 + return other.IsSubset(s) 145 + } 146 + 147 + func (s Set[T]) IsDisjoint(other Set[T]) bool { 148 + for item := range s.data { 149 + if other.Contains(item) { 150 + return false 151 + } 152 + } 153 + return true 154 + } 155 + 156 + func (s Set[T]) Equal(other Set[T]) bool { 157 + if s.Len() != other.Len() { 158 + return false 159 + } 160 + for item := range s.data { 161 + if !other.Contains(item) { 162 + return false 163 + } 164 + } 165 + return true 166 + } 167 + 168 + func Collect[T comparable](seq iter.Seq[T]) Set[T] { 169 + result := New[T]() 170 + for item := range seq { 171 + result.Insert(item) 172 + } 173 + return result 174 + }
+411
sets/set_test.go
···
··· 1 + package sets 2 + 3 + import ( 4 + "slices" 5 + "testing" 6 + "testing/quick" 7 + ) 8 + 9 + func TestNew(t *testing.T) { 10 + s := New[int]() 11 + if s.Len() != 0 { 12 + t.Errorf("New set should be empty, got length %d", s.Len()) 13 + } 14 + if !s.IsEmpty() { 15 + t.Error("New set should be empty") 16 + } 17 + } 18 + 19 + func TestFromSlice(t *testing.T) { 20 + s := Collect(slices.Values([]int{1, 2, 3, 2, 1})) 21 + if s.Len() != 3 { 22 + t.Errorf("Expected length 3, got %d", s.Len()) 23 + } 24 + if !s.Contains(1) || !s.Contains(2) || !s.Contains(3) { 25 + t.Error("Set should contain all unique elements from slice") 26 + } 27 + } 28 + 29 + func TestInsert(t *testing.T) { 30 + s := New[string]() 31 + 32 + if !s.Insert("hello") { 33 + t.Error("First insert should return true") 34 + } 35 + if s.Insert("hello") { 36 + t.Error("Duplicate insert should return false") 37 + } 38 + if s.Len() != 1 { 39 + t.Errorf("Expected length 1, got %d", s.Len()) 40 + } 41 + } 42 + 43 + func TestRemove(t *testing.T) { 44 + s := Collect(slices.Values([]int{1, 2, 3})) 45 + 46 + if !s.Remove(2) { 47 + t.Error("Remove existing element should return true") 48 + } 49 + if s.Remove(2) { 50 + t.Error("Remove non-existing element should return false") 51 + } 52 + if s.Contains(2) { 53 + t.Error("Element should be removed") 54 + } 55 + if s.Len() != 2 { 56 + t.Errorf("Expected length 2, got %d", s.Len()) 57 + } 58 + } 59 + 60 + func TestContains(t *testing.T) { 61 + s := Collect(slices.Values([]int{1, 2, 3})) 62 + 63 + if !s.Contains(1) { 64 + t.Error("Should contain 1") 65 + } 66 + if s.Contains(4) { 67 + t.Error("Should not contain 4") 68 + } 69 + } 70 + 71 + func TestClear(t *testing.T) { 72 + s := Collect(slices.Values([]int{1, 2, 3})) 73 + s.Clear() 74 + 75 + if !s.IsEmpty() { 76 + t.Error("Set should be empty after clear") 77 + } 78 + if s.Len() != 0 { 79 + t.Errorf("Expected length 0, got %d", s.Len()) 80 + } 81 + } 82 + 83 + func TestIterator(t *testing.T) { 84 + s := Collect(slices.Values([]int{1, 2, 3})) 85 + var items []int 86 + 87 + for item := range s.All() { 88 + items = append(items, item) 89 + } 90 + 91 + slices.Sort(items) 92 + expected := []int{1, 2, 3} 93 + if !slices.Equal(items, expected) { 94 + t.Errorf("Expected %v, got %v", expected, items) 95 + } 96 + } 97 + 98 + func TestClone(t *testing.T) { 99 + s1 := Collect(slices.Values([]int{1, 2, 3})) 100 + s2 := s1.Clone() 101 + 102 + if !s1.Equal(s2) { 103 + t.Error("Cloned set should be equal to original") 104 + } 105 + 106 + s2.Insert(4) 107 + if s1.Contains(4) { 108 + t.Error("Modifying clone should not affect original") 109 + } 110 + } 111 + 112 + func TestUnion(t *testing.T) { 113 + s1 := Collect(slices.Values([]int{1, 2})) 114 + s2 := Collect(slices.Values([]int{2, 3})) 115 + 116 + result := Collect(s1.Union(s2)) 117 + expected := Collect(slices.Values([]int{1, 2, 3})) 118 + 119 + if !result.Equal(expected) { 120 + t.Errorf("Expected %v, got %v", expected, result) 121 + } 122 + } 123 + 124 + func TestIntersection(t *testing.T) { 125 + s1 := Collect(slices.Values([]int{1, 2, 3})) 126 + s2 := Collect(slices.Values([]int{2, 3, 4})) 127 + 128 + expected := Collect(slices.Values([]int{2, 3})) 129 + result := Collect(s1.Intersection(s2)) 130 + 131 + if !result.Equal(expected) { 132 + t.Errorf("Expected %v, got %v", expected, result) 133 + } 134 + } 135 + 136 + func TestDifference(t *testing.T) { 137 + s1 := Collect(slices.Values([]int{1, 2, 3})) 138 + s2 := Collect(slices.Values([]int{2, 3, 4})) 139 + 140 + expected := Collect(slices.Values([]int{1})) 141 + result := Collect(s1.Difference(s2)) 142 + 143 + if !result.Equal(expected) { 144 + t.Errorf("Expected %v, got %v", expected, result) 145 + } 146 + } 147 + 148 + func TestSymmetricDifference(t *testing.T) { 149 + s1 := Collect(slices.Values([]int{1, 2, 3})) 150 + s2 := Collect(slices.Values([]int{2, 3, 4})) 151 + 152 + expected := Collect(slices.Values([]int{1, 4})) 153 + result := Collect(s1.SymmetricDifference(s2)) 154 + 155 + if !result.Equal(expected) { 156 + t.Errorf("Expected %v, got %v", expected, result) 157 + } 158 + } 159 + 160 + func TestSymmetricDifferenceCommutativeProperty(t *testing.T) { 161 + s1 := Collect(slices.Values([]int{1, 2, 3})) 162 + s2 := Collect(slices.Values([]int{2, 3, 4})) 163 + 164 + result1 := Collect(s1.SymmetricDifference(s2)) 165 + result2 := Collect(s2.SymmetricDifference(s1)) 166 + 167 + if !result1.Equal(result2) { 168 + t.Errorf("Expected %v, got %v", result1, result2) 169 + } 170 + } 171 + 172 + func TestIsSubset(t *testing.T) { 173 + s1 := Collect(slices.Values([]int{1, 2})) 174 + s2 := Collect(slices.Values([]int{1, 2, 3})) 175 + 176 + if !s1.IsSubset(s2) { 177 + t.Error("s1 should be subset of s2") 178 + } 179 + if s2.IsSubset(s1) { 180 + t.Error("s2 should not be subset of s1") 181 + } 182 + } 183 + 184 + func TestIsSuperset(t *testing.T) { 185 + s1 := Collect(slices.Values([]int{1, 2, 3})) 186 + s2 := Collect(slices.Values([]int{1, 2})) 187 + 188 + if !s1.IsSuperset(s2) { 189 + t.Error("s1 should be superset of s2") 190 + } 191 + if s2.IsSuperset(s1) { 192 + t.Error("s2 should not be superset of s1") 193 + } 194 + } 195 + 196 + func TestIsDisjoint(t *testing.T) { 197 + s1 := Collect(slices.Values([]int{1, 2})) 198 + s2 := Collect(slices.Values([]int{3, 4})) 199 + s3 := Collect(slices.Values([]int{2, 3})) 200 + 201 + if !s1.IsDisjoint(s2) { 202 + t.Error("s1 and s2 should be disjoint") 203 + } 204 + if s1.IsDisjoint(s3) { 205 + t.Error("s1 and s3 should not be disjoint") 206 + } 207 + } 208 + 209 + func TestEqual(t *testing.T) { 210 + s1 := Collect(slices.Values([]int{1, 2, 3})) 211 + s2 := Collect(slices.Values([]int{3, 2, 1})) 212 + s3 := Collect(slices.Values([]int{1, 2})) 213 + 214 + if !s1.Equal(s2) { 215 + t.Error("s1 and s2 should be equal") 216 + } 217 + if s1.Equal(s3) { 218 + t.Error("s1 and s3 should not be equal") 219 + } 220 + } 221 + 222 + func TestCollect(t *testing.T) { 223 + s1 := Collect(slices.Values([]int{1, 2})) 224 + s2 := Collect(slices.Values([]int{2, 3})) 225 + 226 + unionSet := Collect(s1.Union(s2)) 227 + if unionSet.Len() != 3 { 228 + t.Errorf("Expected union set length 3, got %d", unionSet.Len()) 229 + } 230 + if !unionSet.Contains(1) || !unionSet.Contains(2) || !unionSet.Contains(3) { 231 + t.Error("Union set should contain 1, 2, and 3") 232 + } 233 + 234 + diffSet := Collect(s1.Difference(s2)) 235 + if diffSet.Len() != 1 { 236 + t.Errorf("Expected difference set length 1, got %d", diffSet.Len()) 237 + } 238 + if !diffSet.Contains(1) { 239 + t.Error("Difference set should contain 1") 240 + } 241 + } 242 + 243 + func TestPropertySingleonLen(t *testing.T) { 244 + f := func(item int) bool { 245 + single := Singleton(item) 246 + return single.Len() == 1 247 + } 248 + 249 + if err := quick.Check(f, nil); err != nil { 250 + t.Error(err) 251 + } 252 + } 253 + 254 + func TestPropertyInsertIdempotent(t *testing.T) { 255 + f := func(s Set[int], item int) bool { 256 + clone := s.Clone() 257 + 258 + clone.Insert(item) 259 + firstLen := clone.Len() 260 + 261 + clone.Insert(item) 262 + secondLen := clone.Len() 263 + 264 + return firstLen == secondLen 265 + } 266 + 267 + if err := quick.Check(f, nil); err != nil { 268 + t.Error(err) 269 + } 270 + } 271 + 272 + func TestPropertyUnionCommutative(t *testing.T) { 273 + f := func(s1 Set[int], s2 Set[int]) bool { 274 + union1 := Collect(s1.Union(s2)) 275 + union2 := Collect(s2.Union(s1)) 276 + return union1.Equal(union2) 277 + } 278 + 279 + if err := quick.Check(f, nil); err != nil { 280 + t.Error(err) 281 + } 282 + } 283 + 284 + func TestPropertyIntersectionCommutative(t *testing.T) { 285 + f := func(s1 Set[int], s2 Set[int]) bool { 286 + inter1 := Collect(s1.Intersection(s2)) 287 + inter2 := Collect(s2.Intersection(s1)) 288 + return inter1.Equal(inter2) 289 + } 290 + 291 + if err := quick.Check(f, nil); err != nil { 292 + t.Error(err) 293 + } 294 + } 295 + 296 + func TestPropertyCloneEquals(t *testing.T) { 297 + f := func(s Set[int]) bool { 298 + clone := s.Clone() 299 + return s.Equal(clone) 300 + } 301 + 302 + if err := quick.Check(f, nil); err != nil { 303 + t.Error(err) 304 + } 305 + } 306 + 307 + func TestPropertyIntersectionIsSubset(t *testing.T) { 308 + f := func(s1 Set[int], s2 Set[int]) bool { 309 + inter := Collect(s1.Intersection(s2)) 310 + return inter.IsSubset(s1) && inter.IsSubset(s2) 311 + } 312 + 313 + if err := quick.Check(f, nil); err != nil { 314 + t.Error(err) 315 + } 316 + } 317 + 318 + func TestPropertyUnionIsSuperset(t *testing.T) { 319 + f := func(s1 Set[int], s2 Set[int]) bool { 320 + union := Collect(s1.Union(s2)) 321 + return union.IsSuperset(s1) && union.IsSuperset(s2) 322 + } 323 + 324 + if err := quick.Check(f, nil); err != nil { 325 + t.Error(err) 326 + } 327 + } 328 + 329 + func TestPropertyDifferenceDisjoint(t *testing.T) { 330 + f := func(s1 Set[int], s2 Set[int]) bool { 331 + diff := Collect(s1.Difference(s2)) 332 + return diff.IsDisjoint(s2) 333 + } 334 + 335 + if err := quick.Check(f, nil); err != nil { 336 + t.Error(err) 337 + } 338 + } 339 + 340 + func TestPropertySymmetricDifferenceCommutative(t *testing.T) { 341 + f := func(s1 Set[int], s2 Set[int]) bool { 342 + symDiff1 := Collect(s1.SymmetricDifference(s2)) 343 + symDiff2 := Collect(s2.SymmetricDifference(s1)) 344 + return symDiff1.Equal(symDiff2) 345 + } 346 + 347 + if err := quick.Check(f, nil); err != nil { 348 + t.Error(err) 349 + } 350 + } 351 + 352 + func TestPropertyRemoveWorks(t *testing.T) { 353 + f := func(s Set[int], item int) bool { 354 + clone := s.Clone() 355 + clone.Insert(item) 356 + clone.Remove(item) 357 + return !clone.Contains(item) 358 + } 359 + 360 + if err := quick.Check(f, nil); err != nil { 361 + t.Error(err) 362 + } 363 + } 364 + 365 + func TestPropertyClearEmpty(t *testing.T) { 366 + f := func(s Set[int]) bool { 367 + s.Clear() 368 + return s.IsEmpty() && s.Len() == 0 369 + } 370 + 371 + if err := quick.Check(f, nil); err != nil { 372 + t.Error(err) 373 + } 374 + } 375 + 376 + func TestPropertyIsSubsetReflexive(t *testing.T) { 377 + f := func(s Set[int]) bool { 378 + return s.IsSubset(s) 379 + } 380 + 381 + if err := quick.Check(f, nil); err != nil { 382 + t.Error(err) 383 + } 384 + } 385 + 386 + func TestPropertyDeMorganUnion(t *testing.T) { 387 + f := func(s1 Set[int], s2 Set[int], universe Set[int]) bool { 388 + // create a universe that contains both sets 389 + u := universe.Clone() 390 + for item := range s1.All() { 391 + u.Insert(item) 392 + } 393 + for item := range s2.All() { 394 + u.Insert(item) 395 + } 396 + 397 + // (A u B)' = A' n B' 398 + union := Collect(s1.Union(s2)) 399 + complementUnion := Collect(u.Difference(union)) 400 + 401 + complementS1 := Collect(u.Difference(s1)) 402 + complementS2 := Collect(u.Difference(s2)) 403 + intersectionComplements := Collect(complementS1.Intersection(complementS2)) 404 + 405 + return complementUnion.Equal(intersectionComplements) 406 + } 407 + 408 + if err := quick.Check(f, nil); err != nil { 409 + t.Error(err) 410 + } 411 + }
+1
spindle/db/repos.go
··· 16 if err != nil { 17 return nil, err 18 } 19 20 var knots []string 21 for rows.Next() {
··· 16 if err != nil { 17 return nil, err 18 } 19 + defer rows.Close() 20 21 var knots []string 22 for rows.Next() {
+22 -21
spindle/engine/engine.go
··· 3 import ( 4 "context" 5 "errors" 6 - "fmt" 7 "log/slog" 8 9 securejoin "github.com/cyphar/filepath-securejoin" 10 - "golang.org/x/sync/errgroup" 11 "tangled.org/core/notifier" 12 "tangled.org/core/spindle/config" 13 "tangled.org/core/spindle/db" ··· 31 } 32 } 33 34 - eg, ctx := errgroup.WithContext(ctx) 35 for eng, wfs := range pipeline.Workflows { 36 workflowTimeout := eng.WorkflowTimeout() 37 l.Info("using workflow timeout", "timeout", workflowTimeout) 38 39 for _, w := range wfs { 40 - eg.Go(func() error { 41 wid := models.WorkflowId{ 42 PipelineId: pipelineId, 43 Name: w.Name, ··· 45 46 err := db.StatusRunning(wid, n) 47 if err != nil { 48 - return err 49 } 50 51 err = eng.SetupWorkflow(ctx, wid, &w) ··· 61 62 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 63 if dbErr != nil { 64 - return dbErr 65 } 66 - return err 67 } 68 defer eng.DestroyWorkflow(ctx, wid) 69 70 - wfLogger, err := models.NewWorkflowLogger(cfg.Server.LogDir, wid) 71 if err != nil { 72 l.Warn("failed to setup step logger; logs will not be persisted", "error", err) 73 wfLogger = nil ··· 99 if errors.Is(err, ErrTimedOut) { 100 dbErr := db.StatusTimeout(wid, n) 101 if dbErr != nil { 102 - return dbErr 103 } 104 } else { 105 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 106 if dbErr != nil { 107 - return dbErr 108 } 109 } 110 - 111 - return fmt.Errorf("starting steps image: %w", err) 112 } 113 } 114 115 err = db.StatusSuccess(wid, n) 116 if err != nil { 117 - return err 118 } 119 - 120 - return nil 121 - }) 122 } 123 } 124 125 - if err := eg.Wait(); err != nil { 126 - l.Error("failed to run one or more workflows", "err", err) 127 - } else { 128 - l.Info("successfully ran full pipeline") 129 - } 130 }
··· 3 import ( 4 "context" 5 "errors" 6 "log/slog" 7 + "sync" 8 9 securejoin "github.com/cyphar/filepath-securejoin" 10 "tangled.org/core/notifier" 11 "tangled.org/core/spindle/config" 12 "tangled.org/core/spindle/db" ··· 30 } 31 } 32 33 + var wg sync.WaitGroup 34 for eng, wfs := range pipeline.Workflows { 35 workflowTimeout := eng.WorkflowTimeout() 36 l.Info("using workflow timeout", "timeout", workflowTimeout) 37 38 for _, w := range wfs { 39 + wg.Add(1) 40 + go func() { 41 + defer wg.Done() 42 + 43 wid := models.WorkflowId{ 44 PipelineId: pipelineId, 45 Name: w.Name, ··· 47 48 err := db.StatusRunning(wid, n) 49 if err != nil { 50 + l.Error("failed to set workflow status to running", "wid", wid, "err", err) 51 + return 52 } 53 54 err = eng.SetupWorkflow(ctx, wid, &w) ··· 64 65 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 66 if dbErr != nil { 67 + l.Error("failed to set workflow status to failed", "wid", wid, "err", dbErr) 68 } 69 + return 70 } 71 defer eng.DestroyWorkflow(ctx, wid) 72 73 + secretValues := make([]string, len(allSecrets)) 74 + for i, s := range allSecrets { 75 + secretValues[i] = s.Value 76 + } 77 + wfLogger, err := models.NewWorkflowLogger(cfg.Server.LogDir, wid, secretValues) 78 if err != nil { 79 l.Warn("failed to setup step logger; logs will not be persisted", "error", err) 80 wfLogger = nil ··· 106 if errors.Is(err, ErrTimedOut) { 107 dbErr := db.StatusTimeout(wid, n) 108 if dbErr != nil { 109 + l.Error("failed to set workflow status to timeout", "wid", wid, "err", dbErr) 110 } 111 } else { 112 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 113 if dbErr != nil { 114 + l.Error("failed to set workflow status to failed", "wid", wid, "err", dbErr) 115 } 116 } 117 + return 118 } 119 } 120 121 err = db.StatusSuccess(wid, n) 122 if err != nil { 123 + l.Error("failed to set workflow status to success", "wid", wid, "err", err) 124 } 125 + }() 126 } 127 } 128 129 + wg.Wait() 130 + l.Info("all workflows completed") 131 }
+5 -3
spindle/engines/nixery/engine.go
··· 294 workflowEnvs.AddEnv(s.Key, s.Value) 295 } 296 297 - step := w.Steps[idx].(Step) 298 299 select { 300 case <-ctx.Done(): ··· 303 } 304 305 envs := append(EnvVars(nil), workflowEnvs...) 306 - for k, v := range step.environment { 307 - envs.AddEnv(k, v) 308 } 309 envs.AddEnv("HOME", homeDir) 310
··· 294 workflowEnvs.AddEnv(s.Key, s.Value) 295 } 296 297 + step := w.Steps[idx] 298 299 select { 300 case <-ctx.Done(): ··· 303 } 304 305 envs := append(EnvVars(nil), workflowEnvs...) 306 + if nixStep, ok := step.(Step); ok { 307 + for k, v := range nixStep.environment { 308 + envs.AddEnv(k, v) 309 + } 310 } 311 envs.AddEnv("HOME", homeDir) 312
+6 -1
spindle/models/logger.go
··· 12 type WorkflowLogger struct { 13 file *os.File 14 encoder *json.Encoder 15 } 16 17 - func NewWorkflowLogger(baseDir string, wid WorkflowId) (*WorkflowLogger, error) { 18 path := LogFilePath(baseDir, wid) 19 20 file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) ··· 25 return &WorkflowLogger{ 26 file: file, 27 encoder: json.NewEncoder(file), 28 }, nil 29 } 30 ··· 62 63 func (w *dataWriter) Write(p []byte) (int, error) { 64 line := strings.TrimRight(string(p), "\r\n") 65 entry := NewDataLogLine(w.idx, line, w.stream) 66 if err := w.logger.encoder.Encode(entry); err != nil { 67 return 0, err
··· 12 type WorkflowLogger struct { 13 file *os.File 14 encoder *json.Encoder 15 + mask *SecretMask 16 } 17 18 + func NewWorkflowLogger(baseDir string, wid WorkflowId, secretValues []string) (*WorkflowLogger, error) { 19 path := LogFilePath(baseDir, wid) 20 21 file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) ··· 26 return &WorkflowLogger{ 27 file: file, 28 encoder: json.NewEncoder(file), 29 + mask: NewSecretMask(secretValues), 30 }, nil 31 } 32 ··· 64 65 func (w *dataWriter) Write(p []byte) (int, error) { 66 line := strings.TrimRight(string(p), "\r\n") 67 + if w.logger.mask != nil { 68 + line = w.logger.mask.Mask(line) 69 + } 70 entry := NewDataLogLine(w.idx, line, w.stream) 71 if err := w.logger.encoder.Encode(entry); err != nil { 72 return 0, err
+51
spindle/models/secret_mask.go
···
··· 1 + package models 2 + 3 + import ( 4 + "encoding/base64" 5 + "strings" 6 + ) 7 + 8 + // SecretMask replaces secret values in strings with "***". 9 + type SecretMask struct { 10 + replacer *strings.Replacer 11 + } 12 + 13 + // NewSecretMask creates a mask for the given secret values. 14 + // Also registers base64-encoded variants of each secret. 15 + func NewSecretMask(values []string) *SecretMask { 16 + var pairs []string 17 + 18 + for _, value := range values { 19 + if value == "" { 20 + continue 21 + } 22 + 23 + pairs = append(pairs, value, "***") 24 + 25 + b64 := base64.StdEncoding.EncodeToString([]byte(value)) 26 + if b64 != value { 27 + pairs = append(pairs, b64, "***") 28 + } 29 + 30 + b64NoPad := strings.TrimRight(b64, "=") 31 + if b64NoPad != b64 && b64NoPad != value { 32 + pairs = append(pairs, b64NoPad, "***") 33 + } 34 + } 35 + 36 + if len(pairs) == 0 { 37 + return nil 38 + } 39 + 40 + return &SecretMask{ 41 + replacer: strings.NewReplacer(pairs...), 42 + } 43 + } 44 + 45 + // Mask replaces all registered secret values with "***". 46 + func (m *SecretMask) Mask(input string) string { 47 + if m == nil || m.replacer == nil { 48 + return input 49 + } 50 + return m.replacer.Replace(input) 51 + }
+135
spindle/models/secret_mask_test.go
···
··· 1 + package models 2 + 3 + import ( 4 + "encoding/base64" 5 + "testing" 6 + ) 7 + 8 + func TestSecretMask_BasicMasking(t *testing.T) { 9 + mask := NewSecretMask([]string{"mysecret123"}) 10 + 11 + input := "The password is mysecret123 in this log" 12 + expected := "The password is *** in this log" 13 + 14 + result := mask.Mask(input) 15 + if result != expected { 16 + t.Errorf("expected %q, got %q", expected, result) 17 + } 18 + } 19 + 20 + func TestSecretMask_Base64Encoded(t *testing.T) { 21 + secret := "mysecret123" 22 + mask := NewSecretMask([]string{secret}) 23 + 24 + b64 := base64.StdEncoding.EncodeToString([]byte(secret)) 25 + input := "Encoded: " + b64 26 + expected := "Encoded: ***" 27 + 28 + result := mask.Mask(input) 29 + if result != expected { 30 + t.Errorf("expected %q, got %q", expected, result) 31 + } 32 + } 33 + 34 + func TestSecretMask_Base64NoPadding(t *testing.T) { 35 + // "test" encodes to "dGVzdA==" with padding 36 + secret := "test" 37 + mask := NewSecretMask([]string{secret}) 38 + 39 + b64NoPad := "dGVzdA" // base64 without padding 40 + input := "Token: " + b64NoPad 41 + expected := "Token: ***" 42 + 43 + result := mask.Mask(input) 44 + if result != expected { 45 + t.Errorf("expected %q, got %q", expected, result) 46 + } 47 + } 48 + 49 + func TestSecretMask_MultipleSecrets(t *testing.T) { 50 + mask := NewSecretMask([]string{"password1", "apikey123"}) 51 + 52 + input := "Using password1 and apikey123 for auth" 53 + expected := "Using *** and *** for auth" 54 + 55 + result := mask.Mask(input) 56 + if result != expected { 57 + t.Errorf("expected %q, got %q", expected, result) 58 + } 59 + } 60 + 61 + func TestSecretMask_MultipleOccurrences(t *testing.T) { 62 + mask := NewSecretMask([]string{"secret"}) 63 + 64 + input := "secret appears twice: secret" 65 + expected := "*** appears twice: ***" 66 + 67 + result := mask.Mask(input) 68 + if result != expected { 69 + t.Errorf("expected %q, got %q", expected, result) 70 + } 71 + } 72 + 73 + func TestSecretMask_ShortValues(t *testing.T) { 74 + mask := NewSecretMask([]string{"abc", "xy", ""}) 75 + 76 + if mask == nil { 77 + t.Fatal("expected non-nil mask") 78 + } 79 + 80 + input := "abc xy test" 81 + expected := "*** *** test" 82 + result := mask.Mask(input) 83 + if result != expected { 84 + t.Errorf("expected %q, got %q", expected, result) 85 + } 86 + } 87 + 88 + func TestSecretMask_NilMask(t *testing.T) { 89 + var mask *SecretMask 90 + 91 + input := "some input text" 92 + result := mask.Mask(input) 93 + if result != input { 94 + t.Errorf("expected %q, got %q", input, result) 95 + } 96 + } 97 + 98 + func TestSecretMask_EmptyInput(t *testing.T) { 99 + mask := NewSecretMask([]string{"secret"}) 100 + 101 + result := mask.Mask("") 102 + if result != "" { 103 + t.Errorf("expected empty string, got %q", result) 104 + } 105 + } 106 + 107 + func TestSecretMask_NoMatch(t *testing.T) { 108 + mask := NewSecretMask([]string{"secretvalue"}) 109 + 110 + input := "nothing to mask here" 111 + result := mask.Mask(input) 112 + if result != input { 113 + t.Errorf("expected %q, got %q", input, result) 114 + } 115 + } 116 + 117 + func TestSecretMask_EmptySecretsList(t *testing.T) { 118 + mask := NewSecretMask([]string{}) 119 + 120 + if mask != nil { 121 + t.Error("expected nil mask for empty secrets list") 122 + } 123 + } 124 + 125 + func TestSecretMask_EmptySecretsFiltered(t *testing.T) { 126 + mask := NewSecretMask([]string{"ab", "validpassword", "", "xyz"}) 127 + 128 + input := "Using validpassword here" 129 + expected := "Using *** here" 130 + 131 + result := mask.Mask(input) 132 + if result != expected { 133 + t.Errorf("expected %q, got %q", expected, result) 134 + } 135 + }
+6 -1
types/commit.go
··· 174 175 func (commit Commit) CoAuthors() []object.Signature { 176 var coAuthors []object.Signature 177 - 178 matches := coAuthorRegex.FindAllStringSubmatch(commit.Message, -1) 179 180 for _, match := range matches { 181 if len(match) >= 3 { 182 name := strings.TrimSpace(match[1]) 183 email := strings.TrimSpace(match[2]) 184 185 coAuthors = append(coAuthors, object.Signature{ 186 Name: name,
··· 174 175 func (commit Commit) CoAuthors() []object.Signature { 176 var coAuthors []object.Signature 177 + seen := make(map[string]bool) 178 matches := coAuthorRegex.FindAllStringSubmatch(commit.Message, -1) 179 180 for _, match := range matches { 181 if len(match) >= 3 { 182 name := strings.TrimSpace(match[1]) 183 email := strings.TrimSpace(match[2]) 184 + 185 + if seen[email] { 186 + continue 187 + } 188 + seen[email] = true 189 190 coAuthors = append(coAuthors, object.Signature{ 191 Name: name,