-39
api/tangled/gitkeepCommit.go
-39
api/tangled/gitkeepCommit.go
···
1
-
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
-
3
-
package tangled
4
-
5
-
// schema: sh.tangled.git.keepCommit
6
-
7
-
import (
8
-
"context"
9
-
10
-
"github.com/bluesky-social/indigo/lex/util"
11
-
)
12
-
13
-
const (
14
-
GitKeepCommitNSID = "sh.tangled.git.keepCommit"
15
-
)
16
-
17
-
// GitKeepCommit_Input is the input argument to a sh.tangled.git.keepCommit call.
18
-
type GitKeepCommit_Input struct {
19
-
// ref: ref to keep
20
-
Ref string `json:"ref" cborgen:"ref"`
21
-
// repo: AT-URI of the repository
22
-
Repo string `json:"repo" cborgen:"repo"`
23
-
}
24
-
25
-
// GitKeepCommit_Output is the output of a sh.tangled.git.keepCommit call.
26
-
type GitKeepCommit_Output struct {
27
-
// commitId: Keeped commit hash
28
-
CommitId string `json:"commitId" cborgen:"commitId"`
29
-
}
30
-
31
-
// GitKeepCommit calls the XRPC method "sh.tangled.git.keepCommit".
32
-
func GitKeepCommit(ctx context.Context, c util.LexClient, input *GitKeepCommit_Input) (*GitKeepCommit_Output, error) {
33
-
var out GitKeepCommit_Output
34
-
if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.git.keepCommit", nil, input, &out); err != nil {
35
-
return nil, err
36
-
}
37
-
38
-
return &out, nil
39
-
}
+3
-2
appview/db/artifact.go
+3
-2
appview/db/artifact.go
···
8
8
"github.com/go-git/go-git/v5/plumbing"
9
9
"github.com/ipfs/go-cid"
10
10
"tangled.org/core/appview/models"
11
+
"tangled.org/core/orm"
11
12
)
12
13
13
14
func AddArtifact(e Execer, artifact models.Artifact) error {
···
37
38
return err
38
39
}
39
40
40
-
func GetArtifact(e Execer, filters ...filter) ([]models.Artifact, error) {
41
+
func GetArtifact(e Execer, filters ...orm.Filter) ([]models.Artifact, error) {
41
42
var artifacts []models.Artifact
42
43
43
44
var conditions []string
···
109
110
return artifacts, nil
110
111
}
111
112
112
-
func DeleteArtifact(e Execer, filters ...filter) error {
113
+
func DeleteArtifact(e Execer, filters ...orm.Filter) error {
113
114
var conditions []string
114
115
var args []any
115
116
for _, filter := range filters {
+4
-3
appview/db/collaborators.go
+4
-3
appview/db/collaborators.go
···
6
6
"time"
7
7
8
8
"tangled.org/core/appview/models"
9
+
"tangled.org/core/orm"
9
10
)
10
11
11
12
func AddCollaborator(e Execer, c models.Collaborator) error {
···
16
17
return err
17
18
}
18
19
19
-
func DeleteCollaborator(e Execer, filters ...filter) error {
20
+
func DeleteCollaborator(e Execer, filters ...orm.Filter) error {
20
21
var conditions []string
21
22
var args []any
22
23
for _, filter := range filters {
···
58
59
return nil, nil
59
60
}
60
61
61
-
return GetRepos(e, 0, FilterIn("at_uri", repoAts))
62
+
return GetRepos(e, 0, orm.FilterIn("at_uri", repoAts))
62
63
}
63
64
64
-
func GetCollaborators(e Execer, filters ...filter) ([]models.Collaborator, error) {
65
+
func GetCollaborators(e Execer, filters ...orm.Filter) ([]models.Collaborator, error) {
65
66
var collaborators []models.Collaborator
66
67
var conditions []string
67
68
var args []any
+24
-137
appview/db/db.go
+24
-137
appview/db/db.go
···
3
3
import (
4
4
"context"
5
5
"database/sql"
6
-
"fmt"
7
6
"log/slog"
8
-
"reflect"
9
7
"strings"
10
8
11
9
_ "github.com/mattn/go-sqlite3"
12
10
"tangled.org/core/log"
11
+
"tangled.org/core/orm"
13
12
)
14
13
15
14
type DB struct {
···
584
583
}
585
584
586
585
// run migrations
587
-
runMigration(conn, logger, "add-description-to-repos", func(tx *sql.Tx) error {
586
+
orm.RunMigration(conn, logger, "add-description-to-repos", func(tx *sql.Tx) error {
588
587
tx.Exec(`
589
588
alter table repos add column description text check (length(description) <= 200);
590
589
`)
591
590
return nil
592
591
})
593
592
594
-
runMigration(conn, logger, "add-rkey-to-pubkeys", func(tx *sql.Tx) error {
593
+
orm.RunMigration(conn, logger, "add-rkey-to-pubkeys", func(tx *sql.Tx) error {
595
594
// add unconstrained column
596
595
_, err := tx.Exec(`
597
596
alter table public_keys
···
614
613
return nil
615
614
})
616
615
617
-
runMigration(conn, logger, "add-rkey-to-comments", func(tx *sql.Tx) error {
616
+
orm.RunMigration(conn, logger, "add-rkey-to-comments", func(tx *sql.Tx) error {
618
617
_, err := tx.Exec(`
619
618
alter table comments drop column comment_at;
620
619
alter table comments add column rkey text;
···
622
621
return err
623
622
})
624
623
625
-
runMigration(conn, logger, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
624
+
orm.RunMigration(conn, logger, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
626
625
_, err := tx.Exec(`
627
626
alter table comments add column deleted text; -- timestamp
628
627
alter table comments add column edited text; -- timestamp
···
630
629
return err
631
630
})
632
631
633
-
runMigration(conn, logger, "add-source-info-to-pulls-and-submissions", func(tx *sql.Tx) error {
632
+
orm.RunMigration(conn, logger, "add-source-info-to-pulls-and-submissions", func(tx *sql.Tx) error {
634
633
_, err := tx.Exec(`
635
634
alter table pulls add column source_branch text;
636
635
alter table pulls add column source_repo_at text;
···
639
638
return err
640
639
})
641
640
642
-
runMigration(conn, logger, "add-source-to-repos", func(tx *sql.Tx) error {
641
+
orm.RunMigration(conn, logger, "add-source-to-repos", func(tx *sql.Tx) error {
643
642
_, err := tx.Exec(`
644
643
alter table repos add column source text;
645
644
`)
···
651
650
//
652
651
// [0]: https://sqlite.org/pragma.html#pragma_foreign_keys
653
652
conn.ExecContext(ctx, "pragma foreign_keys = off;")
654
-
runMigration(conn, logger, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error {
653
+
orm.RunMigration(conn, logger, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error {
655
654
_, err := tx.Exec(`
656
655
create table pulls_new (
657
656
-- identifiers
···
708
707
})
709
708
conn.ExecContext(ctx, "pragma foreign_keys = on;")
710
709
711
-
runMigration(conn, logger, "add-spindle-to-repos", func(tx *sql.Tx) error {
710
+
orm.RunMigration(conn, logger, "add-spindle-to-repos", func(tx *sql.Tx) error {
712
711
tx.Exec(`
713
712
alter table repos add column spindle text;
714
713
`)
···
718
717
// drop all knot secrets, add unique constraint to knots
719
718
//
720
719
// knots will henceforth use service auth for signed requests
721
-
runMigration(conn, logger, "no-more-secrets", func(tx *sql.Tx) error {
720
+
orm.RunMigration(conn, logger, "no-more-secrets", func(tx *sql.Tx) error {
722
721
_, err := tx.Exec(`
723
722
create table registrations_new (
724
723
id integer primary key autoincrement,
···
741
740
})
742
741
743
742
// recreate and add rkey + created columns with default constraint
744
-
runMigration(conn, logger, "rework-collaborators-table", func(tx *sql.Tx) error {
743
+
orm.RunMigration(conn, logger, "rework-collaborators-table", func(tx *sql.Tx) error {
745
744
// create new table
746
745
// - repo_at instead of repo integer
747
746
// - rkey field
···
795
794
return err
796
795
})
797
796
798
-
runMigration(conn, logger, "add-rkey-to-issues", func(tx *sql.Tx) error {
797
+
orm.RunMigration(conn, logger, "add-rkey-to-issues", func(tx *sql.Tx) error {
799
798
_, err := tx.Exec(`
800
799
alter table issues add column rkey text not null default '';
801
800
···
807
806
})
808
807
809
808
// repurpose the read-only column to "needs-upgrade"
810
-
runMigration(conn, logger, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {
809
+
orm.RunMigration(conn, logger, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {
811
810
_, err := tx.Exec(`
812
811
alter table registrations rename column read_only to needs_upgrade;
813
812
`)
···
815
814
})
816
815
817
816
// 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 {
817
+
orm.RunMigration(conn, logger, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error {
819
818
_, err := tx.Exec(`
820
819
update registrations set needs_upgrade = 1;
821
820
`)
···
823
822
})
824
823
825
824
// 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 {
825
+
orm.RunMigration(conn, logger, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error {
827
826
_, err := tx.Exec(`
828
827
alter table spindles add column needs_upgrade integer not null default 0;
829
828
`)
···
841
840
//
842
841
// disable foreign-keys for the next migration
843
842
conn.ExecContext(ctx, "pragma foreign_keys = off;")
844
-
runMigration(conn, logger, "remove-issue-at-from-issues", func(tx *sql.Tx) error {
843
+
orm.RunMigration(conn, logger, "remove-issue-at-from-issues", func(tx *sql.Tx) error {
845
844
_, err := tx.Exec(`
846
845
create table if not exists issues_new (
847
846
-- identifiers
···
911
910
// - new columns
912
911
// * column "reply_to" which can be any other comment
913
912
// * column "at-uri" which is a generated column
914
-
runMigration(conn, logger, "rework-issue-comments", func(tx *sql.Tx) error {
913
+
orm.RunMigration(conn, logger, "rework-issue-comments", func(tx *sql.Tx) error {
915
914
_, err := tx.Exec(`
916
915
create table if not exists issue_comments (
917
916
-- identifiers
···
971
970
//
972
971
// disable foreign-keys for the next migration
973
972
conn.ExecContext(ctx, "pragma foreign_keys = off;")
974
-
runMigration(conn, logger, "add-at-uri-to-pulls", func(tx *sql.Tx) error {
973
+
orm.RunMigration(conn, logger, "add-at-uri-to-pulls", func(tx *sql.Tx) error {
975
974
_, err := tx.Exec(`
976
975
create table if not exists pulls_new (
977
976
-- identifiers
···
1052
1051
//
1053
1052
// disable foreign-keys for the next migration
1054
1053
conn.ExecContext(ctx, "pragma foreign_keys = off;")
1055
-
runMigration(conn, logger, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error {
1054
+
orm.RunMigration(conn, logger, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error {
1056
1055
_, err := tx.Exec(`
1057
1056
create table if not exists pull_submissions_new (
1058
1057
-- identifiers
···
1106
1105
1107
1106
// knots may report the combined patch for a comparison, we can store that on the appview side
1108
1107
// (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 {
1108
+
orm.RunMigration(conn, logger, "add-combined-column-submissions", func(tx *sql.Tx) error {
1110
1109
_, err := tx.Exec(`
1111
1110
alter table pull_submissions add column combined text;
1112
1111
`)
1113
1112
return err
1114
1113
})
1115
1114
1116
-
runMigration(conn, logger, "add-pronouns-profile", func(tx *sql.Tx) error {
1115
+
orm.RunMigration(conn, logger, "add-pronouns-profile", func(tx *sql.Tx) error {
1117
1116
_, err := tx.Exec(`
1118
1117
alter table profile add column pronouns text;
1119
1118
`)
1120
1119
return err
1121
1120
})
1122
1121
1123
-
runMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error {
1122
+
orm.RunMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error {
1124
1123
_, err := tx.Exec(`
1125
1124
alter table repos add column website text;
1126
1125
alter table repos add column topics text;
···
1128
1127
return err
1129
1128
})
1130
1129
1131
-
runMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error {
1130
+
orm.RunMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error {
1132
1131
_, err := tx.Exec(`
1133
1132
alter table notification_preferences add column user_mentioned integer not null default 1;
1134
1133
`)
···
1136
1135
})
1137
1136
1138
1137
// remove the foreign key constraints from stars.
1139
-
runMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error {
1138
+
orm.RunMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error {
1140
1139
_, err := tx.Exec(`
1141
1140
create table stars_new (
1142
1141
id integer primary key autoincrement,
···
1180
1179
}, nil
1181
1180
}
1182
1181
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
1182
func (d *DB) Close() error {
1229
1183
return d.DB.Close()
1230
1184
}
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
-
}
+4
-3
appview/db/follow.go
+4
-3
appview/db/follow.go
···
7
7
"time"
8
8
9
9
"tangled.org/core/appview/models"
10
+
"tangled.org/core/orm"
10
11
)
11
12
12
13
func AddFollow(e Execer, follow *models.Follow) error {
···
134
135
return result, nil
135
136
}
136
137
137
-
func GetFollows(e Execer, limit int, filters ...filter) ([]models.Follow, error) {
138
+
func GetFollows(e Execer, limit int, filters ...orm.Filter) ([]models.Follow, error) {
138
139
var follows []models.Follow
139
140
140
141
var conditions []string
···
191
192
}
192
193
193
194
func GetFollowers(e Execer, did string) ([]models.Follow, error) {
194
-
return GetFollows(e, 0, FilterEq("subject_did", did))
195
+
return GetFollows(e, 0, orm.FilterEq("subject_did", did))
195
196
}
196
197
197
198
func GetFollowing(e Execer, did string) ([]models.Follow, error) {
198
-
return GetFollows(e, 0, FilterEq("user_did", did))
199
+
return GetFollows(e, 0, orm.FilterEq("user_did", did))
199
200
}
200
201
201
202
func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
+21
-20
appview/db/issues.go
+21
-20
appview/db/issues.go
···
13
13
"tangled.org/core/api/tangled"
14
14
"tangled.org/core/appview/models"
15
15
"tangled.org/core/appview/pagination"
16
+
"tangled.org/core/orm"
16
17
)
17
18
18
19
func PutIssue(tx *sql.Tx, issue *models.Issue) error {
···
27
28
28
29
issues, err := GetIssues(
29
30
tx,
30
-
FilterEq("did", issue.Did),
31
-
FilterEq("rkey", issue.Rkey),
31
+
orm.FilterEq("did", issue.Did),
32
+
orm.FilterEq("rkey", issue.Rkey),
32
33
)
33
34
switch {
34
35
case err != nil:
···
98
99
return nil
99
100
}
100
101
101
-
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]models.Issue, error) {
102
+
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]models.Issue, error) {
102
103
issueMap := make(map[string]*models.Issue) // at-uri -> issue
103
104
104
105
var conditions []string
···
114
115
whereClause = " where " + strings.Join(conditions, " and ")
115
116
}
116
117
117
-
pLower := FilterGte("row_num", page.Offset+1)
118
-
pUpper := FilterLte("row_num", page.Offset+page.Limit)
118
+
pLower := orm.FilterGte("row_num", page.Offset+1)
119
+
pUpper := orm.FilterLte("row_num", page.Offset+page.Limit)
119
120
120
121
pageClause := ""
121
122
if page.Limit > 0 {
···
205
206
repoAts = append(repoAts, string(issue.RepoAt))
206
207
}
207
208
208
-
repos, err := GetRepos(e, 0, FilterIn("at_uri", repoAts))
209
+
repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", repoAts))
209
210
if err != nil {
210
211
return nil, fmt.Errorf("failed to build repo mappings: %w", err)
211
212
}
···
228
229
// collect comments
229
230
issueAts := slices.Collect(maps.Keys(issueMap))
230
231
231
-
comments, err := GetIssueComments(e, FilterIn("issue_at", issueAts))
232
+
comments, err := GetIssueComments(e, orm.FilterIn("issue_at", issueAts))
232
233
if err != nil {
233
234
return nil, fmt.Errorf("failed to query comments: %w", err)
234
235
}
···
240
241
}
241
242
242
243
// collect allLabels for each issue
243
-
allLabels, err := GetLabels(e, FilterIn("subject", issueAts))
244
+
allLabels, err := GetLabels(e, orm.FilterIn("subject", issueAts))
244
245
if err != nil {
245
246
return nil, fmt.Errorf("failed to query labels: %w", err)
246
247
}
···
251
252
}
252
253
253
254
// collect references for each issue
254
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", issueAts))
255
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", issueAts))
255
256
if err != nil {
256
257
return nil, fmt.Errorf("failed to query reference_links: %w", err)
257
258
}
···
277
278
issues, err := GetIssuesPaginated(
278
279
e,
279
280
pagination.Page{},
280
-
FilterEq("repo_at", repoAt),
281
-
FilterEq("issue_id", issueId),
281
+
orm.FilterEq("repo_at", repoAt),
282
+
orm.FilterEq("issue_id", issueId),
282
283
)
283
284
if err != nil {
284
285
return nil, err
···
290
291
return &issues[0], nil
291
292
}
292
293
293
-
func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) {
294
+
func GetIssues(e Execer, filters ...orm.Filter) ([]models.Issue, error) {
294
295
return GetIssuesPaginated(e, pagination.Page{}, filters...)
295
296
}
296
297
···
298
299
func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) {
299
300
var ids []int64
300
301
301
-
var filters []filter
302
+
var filters []orm.Filter
302
303
openValue := 0
303
304
if opts.IsOpen {
304
305
openValue = 1
305
306
}
306
-
filters = append(filters, FilterEq("open", openValue))
307
+
filters = append(filters, orm.FilterEq("open", openValue))
307
308
if opts.RepoAt != "" {
308
-
filters = append(filters, FilterEq("repo_at", opts.RepoAt))
309
+
filters = append(filters, orm.FilterEq("repo_at", opts.RepoAt))
309
310
}
310
311
311
312
var conditions []string
···
397
398
return id, nil
398
399
}
399
400
400
-
func DeleteIssueComments(e Execer, filters ...filter) error {
401
+
func DeleteIssueComments(e Execer, filters ...orm.Filter) error {
401
402
var conditions []string
402
403
var args []any
403
404
for _, filter := range filters {
···
416
417
return err
417
418
}
418
419
419
-
func GetIssueComments(e Execer, filters ...filter) ([]models.IssueComment, error) {
420
+
func GetIssueComments(e Execer, filters ...orm.Filter) ([]models.IssueComment, error) {
420
421
commentMap := make(map[string]*models.IssueComment)
421
422
422
423
var conditions []string
···
506
507
507
508
// collect references for each comments
508
509
commentAts := slices.Collect(maps.Keys(commentMap))
509
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
510
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
510
511
if err != nil {
511
512
return nil, fmt.Errorf("failed to query reference_links: %w", err)
512
513
}
···
548
549
return nil
549
550
}
550
551
551
-
func CloseIssues(e Execer, filters ...filter) error {
552
+
func CloseIssues(e Execer, filters ...orm.Filter) error {
552
553
var conditions []string
553
554
var args []any
554
555
for _, filter := range filters {
···
566
567
return err
567
568
}
568
569
569
-
func ReopenIssues(e Execer, filters ...filter) error {
570
+
func ReopenIssues(e Execer, filters ...orm.Filter) error {
570
571
var conditions []string
571
572
var args []any
572
573
for _, filter := range filters {
+8
-7
appview/db/label.go
+8
-7
appview/db/label.go
···
10
10
11
11
"github.com/bluesky-social/indigo/atproto/syntax"
12
12
"tangled.org/core/appview/models"
13
+
"tangled.org/core/orm"
13
14
)
14
15
15
16
// no updating type for now
···
59
60
return id, nil
60
61
}
61
62
62
-
func DeleteLabelDefinition(e Execer, filters ...filter) error {
63
+
func DeleteLabelDefinition(e Execer, filters ...orm.Filter) error {
63
64
var conditions []string
64
65
var args []any
65
66
for _, filter := range filters {
···
75
76
return err
76
77
}
77
78
78
-
func GetLabelDefinitions(e Execer, filters ...filter) ([]models.LabelDefinition, error) {
79
+
func GetLabelDefinitions(e Execer, filters ...orm.Filter) ([]models.LabelDefinition, error) {
79
80
var labelDefinitions []models.LabelDefinition
80
81
var conditions []string
81
82
var args []any
···
167
168
}
168
169
169
170
// helper to get exactly one label def
170
-
func GetLabelDefinition(e Execer, filters ...filter) (*models.LabelDefinition, error) {
171
+
func GetLabelDefinition(e Execer, filters ...orm.Filter) (*models.LabelDefinition, error) {
171
172
labels, err := GetLabelDefinitions(e, filters...)
172
173
if err != nil {
173
174
return nil, err
···
227
228
return id, nil
228
229
}
229
230
230
-
func GetLabelOps(e Execer, filters ...filter) ([]models.LabelOp, error) {
231
+
func GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) {
231
232
var labelOps []models.LabelOp
232
233
var conditions []string
233
234
var args []any
···
302
303
}
303
304
304
305
// get labels for a given list of subject URIs
305
-
func GetLabels(e Execer, filters ...filter) (map[syntax.ATURI]models.LabelState, error) {
306
+
func GetLabels(e Execer, filters ...orm.Filter) (map[syntax.ATURI]models.LabelState, error) {
306
307
ops, err := GetLabelOps(e, filters...)
307
308
if err != nil {
308
309
return nil, err
···
322
323
}
323
324
labelAts := slices.Collect(maps.Keys(labelAtSet))
324
325
325
-
actx, err := NewLabelApplicationCtx(e, FilterIn("at_uri", labelAts))
326
+
actx, err := NewLabelApplicationCtx(e, orm.FilterIn("at_uri", labelAts))
326
327
if err != nil {
327
328
return nil, err
328
329
}
···
338
339
return results, nil
339
340
}
340
341
341
-
func NewLabelApplicationCtx(e Execer, filters ...filter) (*models.LabelApplicationCtx, error) {
342
+
func NewLabelApplicationCtx(e Execer, filters ...orm.Filter) (*models.LabelApplicationCtx, error) {
342
343
labels, err := GetLabelDefinitions(e, filters...)
343
344
if err != nil {
344
345
return nil, err
+5
-4
appview/db/language.go
+5
-4
appview/db/language.go
···
7
7
8
8
"github.com/bluesky-social/indigo/atproto/syntax"
9
9
"tangled.org/core/appview/models"
10
+
"tangled.org/core/orm"
10
11
)
11
12
12
-
func GetRepoLanguages(e Execer, filters ...filter) ([]models.RepoLanguage, error) {
13
+
func GetRepoLanguages(e Execer, filters ...orm.Filter) ([]models.RepoLanguage, error) {
13
14
var conditions []string
14
15
var args []any
15
16
for _, filter := range filters {
···
85
86
return nil
86
87
}
87
88
88
-
func DeleteRepoLanguages(e Execer, filters ...filter) error {
89
+
func DeleteRepoLanguages(e Execer, filters ...orm.Filter) error {
89
90
var conditions []string
90
91
var args []any
91
92
for _, filter := range filters {
···
107
108
func UpdateRepoLanguages(tx *sql.Tx, repoAt syntax.ATURI, ref string, langs []models.RepoLanguage) error {
108
109
err := DeleteRepoLanguages(
109
110
tx,
110
-
FilterEq("repo_at", repoAt),
111
-
FilterEq("ref", ref),
111
+
orm.FilterEq("repo_at", repoAt),
112
+
orm.FilterEq("ref", ref),
112
113
)
113
114
if err != nil {
114
115
return fmt.Errorf("failed to delete existing languages: %w", err)
+14
-13
appview/db/notifications.go
+14
-13
appview/db/notifications.go
···
11
11
"github.com/bluesky-social/indigo/atproto/syntax"
12
12
"tangled.org/core/appview/models"
13
13
"tangled.org/core/appview/pagination"
14
+
"tangled.org/core/orm"
14
15
)
15
16
16
17
func CreateNotification(e Execer, notification *models.Notification) error {
···
44
45
}
45
46
46
47
// GetNotificationsPaginated retrieves notifications with filters and pagination
47
-
func GetNotificationsPaginated(e Execer, page pagination.Page, filters ...filter) ([]*models.Notification, error) {
48
+
func GetNotificationsPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]*models.Notification, error) {
48
49
var conditions []string
49
50
var args []any
50
51
···
113
114
}
114
115
115
116
// GetNotificationsWithEntities retrieves notifications with their related entities
116
-
func GetNotificationsWithEntities(e Execer, page pagination.Page, filters ...filter) ([]*models.NotificationWithEntity, error) {
117
+
func GetNotificationsWithEntities(e Execer, page pagination.Page, filters ...orm.Filter) ([]*models.NotificationWithEntity, error) {
117
118
var conditions []string
118
119
var args []any
119
120
···
256
257
}
257
258
258
259
// GetNotifications retrieves notifications with filters
259
-
func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) {
260
+
func GetNotifications(e Execer, filters ...orm.Filter) ([]*models.Notification, error) {
260
261
return GetNotificationsPaginated(e, pagination.FirstPage(), filters...)
261
262
}
262
263
263
-
func CountNotifications(e Execer, filters ...filter) (int64, error) {
264
+
func CountNotifications(e Execer, filters ...orm.Filter) (int64, error) {
264
265
var conditions []string
265
266
var args []any
266
267
for _, filter := range filters {
···
285
286
}
286
287
287
288
func MarkNotificationRead(e Execer, notificationID int64, userDID string) error {
288
-
idFilter := FilterEq("id", notificationID)
289
-
recipientFilter := FilterEq("recipient_did", userDID)
289
+
idFilter := orm.FilterEq("id", notificationID)
290
+
recipientFilter := orm.FilterEq("recipient_did", userDID)
290
291
291
292
query := fmt.Sprintf(`
292
293
UPDATE notifications
···
314
315
}
315
316
316
317
func MarkAllNotificationsRead(e Execer, userDID string) error {
317
-
recipientFilter := FilterEq("recipient_did", userDID)
318
-
readFilter := FilterEq("read", 0)
318
+
recipientFilter := orm.FilterEq("recipient_did", userDID)
319
+
readFilter := orm.FilterEq("read", 0)
319
320
320
321
query := fmt.Sprintf(`
321
322
UPDATE notifications
···
334
335
}
335
336
336
337
func DeleteNotification(e Execer, notificationID int64, userDID string) error {
337
-
idFilter := FilterEq("id", notificationID)
338
-
recipientFilter := FilterEq("recipient_did", userDID)
338
+
idFilter := orm.FilterEq("id", notificationID)
339
+
recipientFilter := orm.FilterEq("recipient_did", userDID)
339
340
340
341
query := fmt.Sprintf(`
341
342
DELETE FROM notifications
···
362
363
}
363
364
364
365
func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) {
365
-
prefs, err := GetNotificationPreferences(e, FilterEq("user_did", userDid))
366
+
prefs, err := GetNotificationPreferences(e, orm.FilterEq("user_did", userDid))
366
367
if err != nil {
367
368
return nil, err
368
369
}
···
375
376
return p, nil
376
377
}
377
378
378
-
func GetNotificationPreferences(e Execer, filters ...filter) (map[syntax.DID]*models.NotificationPreferences, error) {
379
+
func GetNotificationPreferences(e Execer, filters ...orm.Filter) (map[syntax.DID]*models.NotificationPreferences, error) {
379
380
prefsMap := make(map[syntax.DID]*models.NotificationPreferences)
380
381
381
382
var conditions []string
···
483
484
484
485
func (d *DB) ClearOldNotifications(ctx context.Context, olderThan time.Duration) error {
485
486
cutoff := time.Now().Add(-olderThan)
486
-
createdFilter := FilterLte("created", cutoff)
487
+
createdFilter := orm.FilterLte("created", cutoff)
487
488
488
489
query := fmt.Sprintf(`
489
490
DELETE FROM notifications
+6
-5
appview/db/pipeline.go
+6
-5
appview/db/pipeline.go
···
7
7
"time"
8
8
9
9
"tangled.org/core/appview/models"
10
+
"tangled.org/core/orm"
10
11
)
11
12
12
-
func GetPipelines(e Execer, filters ...filter) ([]models.Pipeline, error) {
13
+
func GetPipelines(e Execer, filters ...orm.Filter) ([]models.Pipeline, error) {
13
14
var pipelines []models.Pipeline
14
15
15
16
var conditions []string
···
168
169
169
170
// this is a mega query, but the most useful one:
170
171
// 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
+
func GetPipelineStatuses(e Execer, limit int, filters ...orm.Filter) ([]models.Pipeline, error) {
172
173
var conditions []string
173
174
var args []any
174
175
for _, filter := range filters {
175
-
filter.key = "p." + filter.key // the table is aliased in the query to `p`
176
+
filter.Key = "p." + filter.Key // the table is aliased in the query to `p`
176
177
conditions = append(conditions, filter.Condition())
177
178
args = append(args, filter.Arg()...)
178
179
}
···
264
265
conditions = nil
265
266
args = nil
266
267
for _, p := range pipelines {
267
-
knotFilter := FilterEq("pipeline_knot", p.Knot)
268
-
rkeyFilter := FilterEq("pipeline_rkey", p.Rkey)
268
+
knotFilter := orm.FilterEq("pipeline_knot", p.Knot)
269
+
rkeyFilter := orm.FilterEq("pipeline_rkey", p.Rkey)
269
270
conditions = append(conditions, fmt.Sprintf("(%s and %s)", knotFilter.Condition(), rkeyFilter.Condition()))
270
271
args = append(args, p.Knot)
271
272
args = append(args, p.Rkey)
+6
-5
appview/db/profile.go
+6
-5
appview/db/profile.go
···
11
11
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
13
"tangled.org/core/appview/models"
14
+
"tangled.org/core/orm"
14
15
)
15
16
16
17
const TimeframeMonths = 7
···
44
45
45
46
issues, err := GetIssues(
46
47
e,
47
-
FilterEq("did", forDid),
48
-
FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)),
48
+
orm.FilterEq("did", forDid),
49
+
orm.FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)),
49
50
)
50
51
if err != nil {
51
52
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
···
65
66
*items = append(*items, &issue)
66
67
}
67
68
68
-
repos, err := GetRepos(e, 0, FilterEq("did", forDid))
69
+
repos, err := GetRepos(e, 0, orm.FilterEq("did", forDid))
69
70
if err != nil {
70
71
return nil, fmt.Errorf("error getting all repos by did: %w", err)
71
72
}
···
199
200
return tx.Commit()
200
201
}
201
202
202
-
func GetProfiles(e Execer, filters ...filter) (map[string]*models.Profile, error) {
203
+
func GetProfiles(e Execer, filters ...orm.Filter) (map[string]*models.Profile, error) {
203
204
var conditions []string
204
205
var args []any
205
206
for _, filter := range filters {
···
441
442
}
442
443
443
444
// ensure all pinned repos are either own repos or collaborating repos
444
-
repos, err := GetRepos(e, 0, FilterEq("did", profile.Did))
445
+
repos, err := GetRepos(e, 0, orm.FilterEq("did", profile.Did))
445
446
if err != nil {
446
447
log.Printf("getting repos for %s: %s", profile.Did, err)
447
448
}
+21
-20
appview/db/pulls.go
+21
-20
appview/db/pulls.go
···
13
13
14
14
"github.com/bluesky-social/indigo/atproto/syntax"
15
15
"tangled.org/core/appview/models"
16
+
"tangled.org/core/orm"
16
17
)
17
18
18
19
func NewPull(tx *sql.Tx, pull *models.Pull) error {
···
118
119
return pullId - 1, err
119
120
}
120
121
121
-
func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, error) {
122
+
func GetPullsWithLimit(e Execer, limit int, filters ...orm.Filter) ([]*models.Pull, error) {
122
123
pulls := make(map[syntax.ATURI]*models.Pull)
123
124
124
125
var conditions []string
···
229
230
for _, p := range pulls {
230
231
pullAts = append(pullAts, p.AtUri())
231
232
}
232
-
submissionsMap, err := GetPullSubmissions(e, FilterIn("pull_at", pullAts))
233
+
submissionsMap, err := GetPullSubmissions(e, orm.FilterIn("pull_at", pullAts))
233
234
if err != nil {
234
235
return nil, fmt.Errorf("failed to get submissions: %w", err)
235
236
}
···
241
242
}
242
243
243
244
// collect allLabels for each issue
244
-
allLabels, err := GetLabels(e, FilterIn("subject", pullAts))
245
+
allLabels, err := GetLabels(e, orm.FilterIn("subject", pullAts))
245
246
if err != nil {
246
247
return nil, fmt.Errorf("failed to query labels: %w", err)
247
248
}
···
258
259
sourceAts = append(sourceAts, *p.PullSource.RepoAt)
259
260
}
260
261
}
261
-
sourceRepos, err := GetRepos(e, 0, FilterIn("at_uri", sourceAts))
262
+
sourceRepos, err := GetRepos(e, 0, orm.FilterIn("at_uri", sourceAts))
262
263
if err != nil && !errors.Is(err, sql.ErrNoRows) {
263
264
return nil, fmt.Errorf("failed to get source repos: %w", err)
264
265
}
···
274
275
}
275
276
}
276
277
277
-
allReferences, err := GetReferencesAll(e, FilterIn("from_at", pullAts))
278
+
allReferences, err := GetReferencesAll(e, orm.FilterIn("from_at", pullAts))
278
279
if err != nil {
279
280
return nil, fmt.Errorf("failed to query reference_links: %w", err)
280
281
}
···
295
296
return orderedByPullId, nil
296
297
}
297
298
298
-
func GetPulls(e Execer, filters ...filter) ([]*models.Pull, error) {
299
+
func GetPulls(e Execer, filters ...orm.Filter) ([]*models.Pull, error) {
299
300
return GetPullsWithLimit(e, 0, filters...)
300
301
}
301
302
302
303
func GetPullIDs(e Execer, opts models.PullSearchOptions) ([]int64, error) {
303
304
var ids []int64
304
305
305
-
var filters []filter
306
-
filters = append(filters, FilterEq("state", opts.State))
306
+
var filters []orm.Filter
307
+
filters = append(filters, orm.FilterEq("state", opts.State))
307
308
if opts.RepoAt != "" {
308
-
filters = append(filters, FilterEq("repo_at", opts.RepoAt))
309
+
filters = append(filters, orm.FilterEq("repo_at", opts.RepoAt))
309
310
}
310
311
311
312
var conditions []string
···
361
362
}
362
363
363
364
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
+
pulls, err := GetPullsWithLimit(e, 1, orm.FilterEq("repo_at", repoAt), orm.FilterEq("pull_id", pullId))
365
366
if err != nil {
366
367
return nil, err
367
368
}
···
373
374
}
374
375
375
376
// mapping from pull -> pull submissions
376
-
func GetPullSubmissions(e Execer, filters ...filter) (map[syntax.ATURI][]*models.PullSubmission, error) {
377
+
func GetPullSubmissions(e Execer, filters ...orm.Filter) (map[syntax.ATURI][]*models.PullSubmission, error) {
377
378
var conditions []string
378
379
var args []any
379
380
for _, filter := range filters {
···
448
449
449
450
// Get comments for all submissions using GetPullComments
450
451
submissionIds := slices.Collect(maps.Keys(submissionMap))
451
-
comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds))
452
+
comments, err := GetPullComments(e, orm.FilterIn("submission_id", submissionIds))
452
453
if err != nil {
453
454
return nil, fmt.Errorf("failed to get pull comments: %w", err)
454
455
}
···
474
475
return m, nil
475
476
}
476
477
477
-
func GetPullComments(e Execer, filters ...filter) ([]models.PullComment, error) {
478
+
func GetPullComments(e Execer, filters ...orm.Filter) ([]models.PullComment, error) {
478
479
var conditions []string
479
480
var args []any
480
481
for _, filter := range filters {
···
542
543
543
544
// collect references for each comments
544
545
commentAts := slices.Collect(maps.Keys(commentMap))
545
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
546
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
546
547
if err != nil {
547
548
return nil, fmt.Errorf("failed to query reference_links: %w", err)
548
549
}
···
708
709
return err
709
710
}
710
711
711
-
func SetPullParentChangeId(e Execer, parentChangeId string, filters ...filter) error {
712
+
func SetPullParentChangeId(e Execer, parentChangeId string, filters ...orm.Filter) error {
712
713
var conditions []string
713
714
var args []any
714
715
···
732
733
733
734
// Only used when stacking to update contents in the event of a rebase (the interdiff should be empty).
734
735
// otherwise submissions are immutable
735
-
func UpdatePull(e Execer, newPatch, sourceRev string, filters ...filter) error {
736
+
func UpdatePull(e Execer, newPatch, sourceRev string, filters ...orm.Filter) error {
736
737
var conditions []string
737
738
var args []any
738
739
···
790
791
func GetStack(e Execer, stackId string) (models.Stack, error) {
791
792
unorderedPulls, err := GetPulls(
792
793
e,
793
-
FilterEq("stack_id", stackId),
794
-
FilterNotEq("state", models.PullDeleted),
794
+
orm.FilterEq("stack_id", stackId),
795
+
orm.FilterNotEq("state", models.PullDeleted),
795
796
)
796
797
if err != nil {
797
798
return nil, err
···
835
836
func GetAbandonedPulls(e Execer, stackId string) ([]*models.Pull, error) {
836
837
pulls, err := GetPulls(
837
838
e,
838
-
FilterEq("stack_id", stackId),
839
-
FilterEq("state", models.PullDeleted),
839
+
orm.FilterEq("stack_id", stackId),
840
+
orm.FilterEq("state", models.PullDeleted),
840
841
)
841
842
if err != nil {
842
843
return nil, err
+2
-1
appview/db/punchcard.go
+2
-1
appview/db/punchcard.go
···
7
7
"time"
8
8
9
9
"tangled.org/core/appview/models"
10
+
"tangled.org/core/orm"
10
11
)
11
12
12
13
// this adds to the existing count
···
20
21
return err
21
22
}
22
23
23
-
func MakePunchcard(e Execer, filters ...filter) (*models.Punchcard, error) {
24
+
func MakePunchcard(e Execer, filters ...orm.Filter) (*models.Punchcard, error) {
24
25
punchcard := &models.Punchcard{}
25
26
now := time.Now()
26
27
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
+4
-3
appview/db/reference.go
+4
-3
appview/db/reference.go
···
8
8
"github.com/bluesky-social/indigo/atproto/syntax"
9
9
"tangled.org/core/api/tangled"
10
10
"tangled.org/core/appview/models"
11
+
"tangled.org/core/orm"
11
12
)
12
13
13
14
// ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs.
···
205
206
return err
206
207
}
207
208
208
-
func GetReferencesAll(e Execer, filters ...filter) (map[syntax.ATURI][]syntax.ATURI, error) {
209
+
func GetReferencesAll(e Execer, filters ...orm.Filter) (map[syntax.ATURI][]syntax.ATURI, error) {
209
210
var (
210
211
conditions []string
211
212
args []any
···
347
348
if len(aturis) == 0 {
348
349
return nil, nil
349
350
}
350
-
filter := FilterIn("c.at_uri", aturis)
351
+
filter := orm.FilterIn("c.at_uri", aturis)
351
352
rows, err := e.Query(
352
353
fmt.Sprintf(
353
354
`select r.did, r.name, i.issue_id, c.id, i.title, i.open
···
427
428
if len(aturis) == 0 {
428
429
return nil, nil
429
430
}
430
-
filter := FilterIn("c.comment_at", aturis)
431
+
filter := orm.FilterIn("c.comment_at", aturis)
431
432
rows, err := e.Query(
432
433
fmt.Sprintf(
433
434
`select r.did, r.name, p.pull_id, c.id, p.title, p.state
+4
-3
appview/db/registration.go
+4
-3
appview/db/registration.go
···
7
7
"time"
8
8
9
9
"tangled.org/core/appview/models"
10
+
"tangled.org/core/orm"
10
11
)
11
12
12
-
func GetRegistrations(e Execer, filters ...filter) ([]models.Registration, error) {
13
+
func GetRegistrations(e Execer, filters ...orm.Filter) ([]models.Registration, error) {
13
14
var registrations []models.Registration
14
15
15
16
var conditions []string
···
69
70
return registrations, nil
70
71
}
71
72
72
-
func MarkRegistered(e Execer, filters ...filter) error {
73
+
func MarkRegistered(e Execer, filters ...orm.Filter) error {
73
74
var conditions []string
74
75
var args []any
75
76
for _, filter := range filters {
···
94
95
return err
95
96
}
96
97
97
-
func DeleteKnot(e Execer, filters ...filter) error {
98
+
func DeleteKnot(e Execer, filters ...orm.Filter) error {
98
99
var conditions []string
99
100
var args []any
100
101
for _, filter := range filters {
+6
-5
appview/db/repos.go
+6
-5
appview/db/repos.go
···
11
11
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
13
"tangled.org/core/appview/models"
14
+
"tangled.org/core/orm"
14
15
)
15
16
16
-
func GetRepos(e Execer, limit int, filters ...filter) ([]models.Repo, error) {
17
+
func GetRepos(e Execer, limit int, filters ...orm.Filter) ([]models.Repo, error) {
17
18
repoMap := make(map[syntax.ATURI]*models.Repo)
18
19
19
20
var conditions []string
···
294
295
}
295
296
296
297
// helper to get exactly one repo
297
-
func GetRepo(e Execer, filters ...filter) (*models.Repo, error) {
298
+
func GetRepo(e Execer, filters ...orm.Filter) (*models.Repo, error) {
298
299
repos, err := GetRepos(e, 0, filters...)
299
300
if err != nil {
300
301
return nil, err
···
311
312
return &repos[0], nil
312
313
}
313
314
314
-
func CountRepos(e Execer, filters ...filter) (int64, error) {
315
+
func CountRepos(e Execer, filters ...orm.Filter) (int64, error) {
315
316
var conditions []string
316
317
var args []any
317
318
for _, filter := range filters {
···
542
543
return err
543
544
}
544
545
545
-
func UnsubscribeLabel(e Execer, filters ...filter) error {
546
+
func UnsubscribeLabel(e Execer, filters ...orm.Filter) error {
546
547
var conditions []string
547
548
var args []any
548
549
for _, filter := range filters {
···
560
561
return err
561
562
}
562
563
563
-
func GetRepoLabels(e Execer, filters ...filter) ([]models.RepoLabel, error) {
564
+
func GetRepoLabels(e Execer, filters ...orm.Filter) ([]models.RepoLabel, error) {
564
565
var conditions []string
565
566
var args []any
566
567
for _, filter := range filters {
+6
-5
appview/db/spindle.go
+6
-5
appview/db/spindle.go
···
7
7
"time"
8
8
9
9
"tangled.org/core/appview/models"
10
+
"tangled.org/core/orm"
10
11
)
11
12
12
-
func GetSpindles(e Execer, filters ...filter) ([]models.Spindle, error) {
13
+
func GetSpindles(e Execer, filters ...orm.Filter) ([]models.Spindle, error) {
13
14
var spindles []models.Spindle
14
15
15
16
var conditions []string
···
91
92
return err
92
93
}
93
94
94
-
func VerifySpindle(e Execer, filters ...filter) (int64, error) {
95
+
func VerifySpindle(e Execer, filters ...orm.Filter) (int64, error) {
95
96
var conditions []string
96
97
var args []any
97
98
for _, filter := range filters {
···
114
115
return res.RowsAffected()
115
116
}
116
117
117
-
func DeleteSpindle(e Execer, filters ...filter) error {
118
+
func DeleteSpindle(e Execer, filters ...orm.Filter) error {
118
119
var conditions []string
119
120
var args []any
120
121
for _, filter := range filters {
···
144
145
return err
145
146
}
146
147
147
-
func RemoveSpindleMember(e Execer, filters ...filter) error {
148
+
func RemoveSpindleMember(e Execer, filters ...orm.Filter) error {
148
149
var conditions []string
149
150
var args []any
150
151
for _, filter := range filters {
···
163
164
return err
164
165
}
165
166
166
-
func GetSpindleMembers(e Execer, filters ...filter) ([]models.SpindleMember, error) {
167
+
func GetSpindleMembers(e Execer, filters ...orm.Filter) ([]models.SpindleMember, error) {
167
168
var members []models.SpindleMember
168
169
169
170
var conditions []string
+5
-4
appview/db/star.go
+5
-4
appview/db/star.go
···
11
11
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
13
"tangled.org/core/appview/models"
14
+
"tangled.org/core/orm"
14
15
)
15
16
16
17
func AddStar(e Execer, star *models.Star) error {
···
133
134
134
135
// GetRepoStars return a list of stars each holding target repository.
135
136
// 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
+
func GetRepoStars(e Execer, limit int, filters ...orm.Filter) ([]models.RepoStar, error) {
137
138
var conditions []string
138
139
var args []any
139
140
for _, filter := range filters {
···
195
196
return nil, nil
196
197
}
197
198
198
-
repos, err := GetRepos(e, 0, FilterIn("at_uri", args))
199
+
repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", args))
199
200
if err != nil {
200
201
return nil, err
201
202
}
···
225
226
return repoStars, nil
226
227
}
227
228
228
-
func CountStars(e Execer, filters ...filter) (int64, error) {
229
+
func CountStars(e Execer, filters ...orm.Filter) (int64, error) {
229
230
var conditions []string
230
231
var args []any
231
232
for _, filter := range filters {
···
298
299
}
299
300
300
301
// get full repo data
301
-
repos, err := GetRepos(e, 0, FilterIn("at_uri", repoUris))
302
+
repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", repoUris))
302
303
if err != nil {
303
304
return nil, err
304
305
}
+4
-3
appview/db/strings.go
+4
-3
appview/db/strings.go
···
8
8
"time"
9
9
10
10
"tangled.org/core/appview/models"
11
+
"tangled.org/core/orm"
11
12
)
12
13
13
14
func AddString(e Execer, s models.String) error {
···
44
45
return err
45
46
}
46
47
47
-
func GetStrings(e Execer, limit int, filters ...filter) ([]models.String, error) {
48
+
func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, error) {
48
49
var all []models.String
49
50
50
51
var conditions []string
···
127
128
return all, nil
128
129
}
129
130
130
-
func CountStrings(e Execer, filters ...filter) (int64, error) {
131
+
func CountStrings(e Execer, filters ...orm.Filter) (int64, error) {
131
132
var conditions []string
132
133
var args []any
133
134
for _, filter := range filters {
···
151
152
return count, nil
152
153
}
153
154
154
-
func DeleteString(e Execer, filters ...filter) error {
155
+
func DeleteString(e Execer, filters ...orm.Filter) error {
155
156
var conditions []string
156
157
var args []any
157
158
for _, filter := range filters {
+9
-8
appview/db/timeline.go
+9
-8
appview/db/timeline.go
···
5
5
6
6
"github.com/bluesky-social/indigo/atproto/syntax"
7
7
"tangled.org/core/appview/models"
8
+
"tangled.org/core/orm"
8
9
)
9
10
10
11
// TODO: this gathers heterogenous events from different sources and aggregates
···
84
85
}
85
86
86
87
func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
87
-
filters := make([]filter, 0)
88
+
filters := make([]orm.Filter, 0)
88
89
if userIsFollowing != nil {
89
-
filters = append(filters, FilterIn("did", userIsFollowing))
90
+
filters = append(filters, orm.FilterIn("did", userIsFollowing))
90
91
}
91
92
92
93
repos, err := GetRepos(e, limit, filters...)
···
104
105
105
106
var origRepos []models.Repo
106
107
if args != nil {
107
-
origRepos, err = GetRepos(e, 0, FilterIn("at_uri", args))
108
+
origRepos, err = GetRepos(e, 0, orm.FilterIn("at_uri", args))
108
109
}
109
110
if err != nil {
110
111
return nil, err
···
144
145
}
145
146
146
147
func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
147
-
filters := make([]filter, 0)
148
+
filters := make([]orm.Filter, 0)
148
149
if userIsFollowing != nil {
149
-
filters = append(filters, FilterIn("did", userIsFollowing))
150
+
filters = append(filters, orm.FilterIn("did", userIsFollowing))
150
151
}
151
152
152
153
stars, err := GetRepoStars(e, limit, filters...)
···
180
181
}
181
182
182
183
func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
183
-
filters := make([]filter, 0)
184
+
filters := make([]orm.Filter, 0)
184
185
if userIsFollowing != nil {
185
-
filters = append(filters, FilterIn("user_did", userIsFollowing))
186
+
filters = append(filters, orm.FilterIn("user_did", userIsFollowing))
186
187
}
187
188
188
189
follows, err := GetFollows(e, limit, filters...)
···
199
200
return nil, nil
200
201
}
201
202
202
-
profiles, err := GetProfiles(e, FilterIn("did", subjects))
203
+
profiles, err := GetProfiles(e, orm.FilterIn("did", subjects))
203
204
if err != nil {
204
205
return nil, err
205
206
}
+25
-24
appview/ingester.go
+25
-24
appview/ingester.go
···
21
21
"tangled.org/core/appview/serververify"
22
22
"tangled.org/core/appview/validator"
23
23
"tangled.org/core/idresolver"
24
+
"tangled.org/core/orm"
24
25
"tangled.org/core/rbac"
25
26
)
26
27
···
253
254
254
255
err = db.AddArtifact(i.Db, artifact)
255
256
case jmodels.CommitOperationDelete:
256
-
err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
257
+
err = db.DeleteArtifact(i.Db, orm.FilterEq("did", did), orm.FilterEq("rkey", e.Commit.RKey))
257
258
}
258
259
259
260
if err != nil {
···
350
351
351
352
err = db.UpsertProfile(tx, &profile)
352
353
case jmodels.CommitOperationDelete:
353
-
err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
354
+
err = db.DeleteArtifact(i.Db, orm.FilterEq("did", did), orm.FilterEq("rkey", e.Commit.RKey))
354
355
}
355
356
356
357
if err != nil {
···
424
425
// get record from db first
425
426
members, err := db.GetSpindleMembers(
426
427
ddb,
427
-
db.FilterEq("did", did),
428
-
db.FilterEq("rkey", rkey),
428
+
orm.FilterEq("did", did),
429
+
orm.FilterEq("rkey", rkey),
429
430
)
430
431
if err != nil || len(members) != 1 {
431
432
return fmt.Errorf("failed to get member: %w, len(members) = %d", err, len(members))
···
440
441
// remove record by rkey && update enforcer
441
442
if err = db.RemoveSpindleMember(
442
443
tx,
443
-
db.FilterEq("did", did),
444
-
db.FilterEq("rkey", rkey),
444
+
orm.FilterEq("did", did),
445
+
orm.FilterEq("rkey", rkey),
445
446
); err != nil {
446
447
return fmt.Errorf("failed to remove from db: %w", err)
447
448
}
···
523
524
// get record from db first
524
525
spindles, err := db.GetSpindles(
525
526
ddb,
526
-
db.FilterEq("owner", did),
527
-
db.FilterEq("instance", instance),
527
+
orm.FilterEq("owner", did),
528
+
orm.FilterEq("instance", instance),
528
529
)
529
530
if err != nil || len(spindles) != 1 {
530
531
return fmt.Errorf("failed to get spindles: %w, len(spindles) = %d", err, len(spindles))
···
543
544
// remove spindle members first
544
545
err = db.RemoveSpindleMember(
545
546
tx,
546
-
db.FilterEq("owner", did),
547
-
db.FilterEq("instance", instance),
547
+
orm.FilterEq("owner", did),
548
+
orm.FilterEq("instance", instance),
548
549
)
549
550
if err != nil {
550
551
return err
···
552
553
553
554
err = db.DeleteSpindle(
554
555
tx,
555
-
db.FilterEq("owner", did),
556
-
db.FilterEq("instance", instance),
556
+
orm.FilterEq("owner", did),
557
+
orm.FilterEq("instance", instance),
557
558
)
558
559
if err != nil {
559
560
return err
···
621
622
case jmodels.CommitOperationDelete:
622
623
if err := db.DeleteString(
623
624
ddb,
624
-
db.FilterEq("did", did),
625
-
db.FilterEq("rkey", rkey),
625
+
orm.FilterEq("did", did),
626
+
orm.FilterEq("rkey", rkey),
626
627
); err != nil {
627
628
l.Error("failed to delete", "err", err)
628
629
return fmt.Errorf("failed to delete string record: %w", err)
···
740
741
// get record from db first
741
742
registrations, err := db.GetRegistrations(
742
743
ddb,
743
-
db.FilterEq("domain", domain),
744
-
db.FilterEq("did", did),
744
+
orm.FilterEq("domain", domain),
745
+
orm.FilterEq("did", did),
745
746
)
746
747
if err != nil {
747
748
return fmt.Errorf("failed to get registration: %w", err)
···
762
763
763
764
err = db.DeleteKnot(
764
765
tx,
765
-
db.FilterEq("did", did),
766
-
db.FilterEq("domain", domain),
766
+
orm.FilterEq("did", did),
767
+
orm.FilterEq("domain", domain),
767
768
)
768
769
if err != nil {
769
770
return err
···
915
916
case jmodels.CommitOperationDelete:
916
917
if err := db.DeleteIssueComments(
917
918
ddb,
918
-
db.FilterEq("did", did),
919
-
db.FilterEq("rkey", rkey),
919
+
orm.FilterEq("did", did),
920
+
orm.FilterEq("rkey", rkey),
920
921
); err != nil {
921
922
return fmt.Errorf("failed to delete issue comment record: %w", err)
922
923
}
···
969
970
case jmodels.CommitOperationDelete:
970
971
if err := db.DeleteLabelDefinition(
971
972
ddb,
972
-
db.FilterEq("did", did),
973
-
db.FilterEq("rkey", rkey),
973
+
orm.FilterEq("did", did),
974
+
orm.FilterEq("rkey", rkey),
974
975
); err != nil {
975
976
return fmt.Errorf("failed to delete labeldef record: %w", err)
976
977
}
···
1010
1011
var repo *models.Repo
1011
1012
switch collection {
1012
1013
case tangled.RepoIssueNSID:
1013
-
i, err := db.GetIssues(ddb, db.FilterEq("at_uri", subject))
1014
+
i, err := db.GetIssues(ddb, orm.FilterEq("at_uri", subject))
1014
1015
if err != nil || len(i) != 1 {
1015
1016
return fmt.Errorf("failed to find subject: %w || subject count %d", err, len(i))
1016
1017
}
···
1019
1020
return fmt.Errorf("unsupport label subject: %s", collection)
1020
1021
}
1021
1022
1022
-
actx, err := db.NewLabelApplicationCtx(ddb, db.FilterIn("at_uri", repo.Labels))
1023
+
actx, err := db.NewLabelApplicationCtx(ddb, orm.FilterIn("at_uri", repo.Labels))
1023
1024
if err != nil {
1024
1025
return fmt.Errorf("failed to build label application ctx: %w", err)
1025
1026
}
+46
-45
appview/issues/issues.go
+46
-45
appview/issues/issues.go
···
19
19
"tangled.org/core/appview/config"
20
20
"tangled.org/core/appview/db"
21
21
issues_indexer "tangled.org/core/appview/indexer/issues"
22
+
"tangled.org/core/appview/mentions"
22
23
"tangled.org/core/appview/models"
23
24
"tangled.org/core/appview/notify"
24
25
"tangled.org/core/appview/oauth"
25
26
"tangled.org/core/appview/pages"
26
27
"tangled.org/core/appview/pages/repoinfo"
27
28
"tangled.org/core/appview/pagination"
28
-
"tangled.org/core/appview/refresolver"
29
29
"tangled.org/core/appview/reporesolver"
30
30
"tangled.org/core/appview/validator"
31
31
"tangled.org/core/idresolver"
32
+
"tangled.org/core/orm"
32
33
"tangled.org/core/rbac"
33
34
"tangled.org/core/tid"
34
35
)
35
36
36
37
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
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
49
50
}
50
51
51
52
func New(
···
54
55
enforcer *rbac.Enforcer,
55
56
pages *pages.Pages,
56
57
idResolver *idresolver.Resolver,
57
-
refResolver *refresolver.Resolver,
58
+
mentionsResolver *mentions.Resolver,
58
59
db *db.DB,
59
60
config *config.Config,
60
61
notifier notify.Notifier,
···
63
64
logger *slog.Logger,
64
65
) *Issues {
65
66
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,
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,
78
79
}
79
80
}
80
81
···
113
114
114
115
labelDefs, err := db.GetLabelDefinitions(
115
116
rp.db,
116
-
db.FilterIn("at_uri", f.Labels),
117
-
db.FilterContains("scope", tangled.RepoIssueNSID),
117
+
orm.FilterIn("at_uri", f.Labels),
118
+
orm.FilterContains("scope", tangled.RepoIssueNSID),
118
119
)
119
120
if err != nil {
120
121
l.Error("failed to fetch labels", "err", err)
···
163
164
newIssue := issue
164
165
newIssue.Title = r.FormValue("title")
165
166
newIssue.Body = r.FormValue("body")
166
-
newIssue.Mentions, newIssue.References = rp.refResolver.Resolve(r.Context(), newIssue.Body)
167
+
newIssue.Mentions, newIssue.References = rp.mentionsResolver.Resolve(r.Context(), newIssue.Body)
167
168
168
169
if err := rp.validator.ValidateIssue(newIssue); err != nil {
169
170
l.Error("validation error", "err", err)
···
314
315
if isIssueOwner || isRepoOwner || isCollaborator {
315
316
err = db.CloseIssues(
316
317
rp.db,
317
-
db.FilterEq("id", issue.Id),
318
+
orm.FilterEq("id", issue.Id),
318
319
)
319
320
if err != nil {
320
321
l.Error("failed to close issue", "err", err)
···
361
362
if isCollaborator || isRepoOwner || isIssueOwner {
362
363
err := db.ReopenIssues(
363
364
rp.db,
364
-
db.FilterEq("id", issue.Id),
365
+
orm.FilterEq("id", issue.Id),
365
366
)
366
367
if err != nil {
367
368
l.Error("failed to reopen issue", "err", err)
···
412
413
replyTo = &replyToUri
413
414
}
414
415
415
-
mentions, references := rp.refResolver.Resolve(r.Context(), body)
416
+
mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
416
417
417
418
comment := models.IssueComment{
418
419
Did: user.Did,
···
506
507
commentId := chi.URLParam(r, "commentId")
507
508
comments, err := db.GetIssueComments(
508
509
rp.db,
509
-
db.FilterEq("id", commentId),
510
+
orm.FilterEq("id", commentId),
510
511
)
511
512
if err != nil {
512
513
l.Error("failed to fetch comment", "id", commentId)
···
542
543
commentId := chi.URLParam(r, "commentId")
543
544
comments, err := db.GetIssueComments(
544
545
rp.db,
545
-
db.FilterEq("id", commentId),
546
+
orm.FilterEq("id", commentId),
546
547
)
547
548
if err != nil {
548
549
l.Error("failed to fetch comment", "id", commentId)
···
584
585
newComment := comment
585
586
newComment.Body = newBody
586
587
newComment.Edited = &now
587
-
newComment.Mentions, newComment.References = rp.refResolver.Resolve(r.Context(), newBody)
588
+
newComment.Mentions, newComment.References = rp.mentionsResolver.Resolve(r.Context(), newBody)
588
589
589
590
record := newComment.AsRecord()
590
591
···
652
653
commentId := chi.URLParam(r, "commentId")
653
654
comments, err := db.GetIssueComments(
654
655
rp.db,
655
-
db.FilterEq("id", commentId),
656
+
orm.FilterEq("id", commentId),
656
657
)
657
658
if err != nil {
658
659
l.Error("failed to fetch comment", "id", commentId)
···
688
689
commentId := chi.URLParam(r, "commentId")
689
690
comments, err := db.GetIssueComments(
690
691
rp.db,
691
-
db.FilterEq("id", commentId),
692
+
orm.FilterEq("id", commentId),
692
693
)
693
694
if err != nil {
694
695
l.Error("failed to fetch comment", "id", commentId)
···
724
725
commentId := chi.URLParam(r, "commentId")
725
726
comments, err := db.GetIssueComments(
726
727
rp.db,
727
-
db.FilterEq("id", commentId),
728
+
orm.FilterEq("id", commentId),
728
729
)
729
730
if err != nil {
730
731
l.Error("failed to fetch comment", "id", commentId)
···
751
752
752
753
// optimistic deletion
753
754
deleted := time.Now()
754
-
err = db.DeleteIssueComments(rp.db, db.FilterEq("id", comment.Id))
755
+
err = db.DeleteIssueComments(rp.db, orm.FilterEq("id", comment.Id))
755
756
if err != nil {
756
757
l.Error("failed to delete comment", "err", err)
757
758
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
···
840
841
841
842
issues, err = db.GetIssues(
842
843
rp.db,
843
-
db.FilterIn("id", res.Hits),
844
+
orm.FilterIn("id", res.Hits),
844
845
)
845
846
if err != nil {
846
847
l.Error("failed to get issues", "err", err)
···
856
857
issues, err = db.GetIssuesPaginated(
857
858
rp.db,
858
859
page,
859
-
db.FilterEq("repo_at", f.RepoAt()),
860
-
db.FilterEq("open", openInt),
860
+
orm.FilterEq("repo_at", f.RepoAt()),
861
+
orm.FilterEq("open", openInt),
861
862
)
862
863
if err != nil {
863
864
l.Error("failed to get issues", "err", err)
···
868
869
869
870
labelDefs, err := db.GetLabelDefinitions(
870
871
rp.db,
871
-
db.FilterIn("at_uri", f.Labels),
872
-
db.FilterContains("scope", tangled.RepoIssueNSID),
872
+
orm.FilterIn("at_uri", f.Labels),
873
+
orm.FilterContains("scope", tangled.RepoIssueNSID),
873
874
)
874
875
if err != nil {
875
876
l.Error("failed to fetch labels", "err", err)
···
912
913
})
913
914
case http.MethodPost:
914
915
body := r.FormValue("body")
915
-
mentions, references := rp.refResolver.Resolve(r.Context(), body)
916
+
mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
916
917
917
918
issue := &models.Issue{
918
919
RepoAt: f.RepoAt(),
+19
-18
appview/knots/knots.go
+19
-18
appview/knots/knots.go
···
21
21
"tangled.org/core/appview/xrpcclient"
22
22
"tangled.org/core/eventconsumer"
23
23
"tangled.org/core/idresolver"
24
+
"tangled.org/core/orm"
24
25
"tangled.org/core/rbac"
25
26
"tangled.org/core/tid"
26
27
···
72
73
user := k.OAuth.GetUser(r)
73
74
registrations, err := db.GetRegistrations(
74
75
k.Db,
75
-
db.FilterEq("did", user.Did),
76
+
orm.FilterEq("did", user.Did),
76
77
)
77
78
if err != nil {
78
79
k.Logger.Error("failed to fetch knot registrations", "err", err)
···
102
103
103
104
registrations, err := db.GetRegistrations(
104
105
k.Db,
105
-
db.FilterEq("did", user.Did),
106
-
db.FilterEq("domain", domain),
106
+
orm.FilterEq("did", user.Did),
107
+
orm.FilterEq("domain", domain),
107
108
)
108
109
if err != nil {
109
110
l.Error("failed to get registrations", "err", err)
···
127
128
repos, err := db.GetRepos(
128
129
k.Db,
129
130
0,
130
-
db.FilterEq("knot", domain),
131
+
orm.FilterEq("knot", domain),
131
132
)
132
133
if err != nil {
133
134
l.Error("failed to get knot repos", "err", err)
···
293
294
// get record from db first
294
295
registrations, err := db.GetRegistrations(
295
296
k.Db,
296
-
db.FilterEq("did", user.Did),
297
-
db.FilterEq("domain", domain),
297
+
orm.FilterEq("did", user.Did),
298
+
orm.FilterEq("domain", domain),
298
299
)
299
300
if err != nil {
300
301
l.Error("failed to get registration", "err", err)
···
321
322
322
323
err = db.DeleteKnot(
323
324
tx,
324
-
db.FilterEq("did", user.Did),
325
-
db.FilterEq("domain", domain),
325
+
orm.FilterEq("did", user.Did),
326
+
orm.FilterEq("domain", domain),
326
327
)
327
328
if err != nil {
328
329
l.Error("failed to delete registration", "err", err)
···
402
403
// get record from db first
403
404
registrations, err := db.GetRegistrations(
404
405
k.Db,
405
-
db.FilterEq("did", user.Did),
406
-
db.FilterEq("domain", domain),
406
+
orm.FilterEq("did", user.Did),
407
+
orm.FilterEq("domain", domain),
407
408
)
408
409
if err != nil {
409
410
l.Error("failed to get registration", "err", err)
···
493
494
// Get updated registration to show
494
495
registrations, err = db.GetRegistrations(
495
496
k.Db,
496
-
db.FilterEq("did", user.Did),
497
-
db.FilterEq("domain", domain),
497
+
orm.FilterEq("did", user.Did),
498
+
orm.FilterEq("domain", domain),
498
499
)
499
500
if err != nil {
500
501
l.Error("failed to get registration", "err", err)
···
529
530
530
531
registrations, err := db.GetRegistrations(
531
532
k.Db,
532
-
db.FilterEq("did", user.Did),
533
-
db.FilterEq("domain", domain),
534
-
db.FilterIsNot("registered", "null"),
533
+
orm.FilterEq("did", user.Did),
534
+
orm.FilterEq("domain", domain),
535
+
orm.FilterIsNot("registered", "null"),
535
536
)
536
537
if err != nil {
537
538
l.Error("failed to get registration", "err", err)
···
637
638
638
639
registrations, err := db.GetRegistrations(
639
640
k.Db,
640
-
db.FilterEq("did", user.Did),
641
-
db.FilterEq("domain", domain),
642
-
db.FilterIsNot("registered", "null"),
641
+
orm.FilterEq("did", user.Did),
642
+
orm.FilterEq("domain", domain),
643
+
orm.FilterIsNot("registered", "null"),
643
644
)
644
645
if err != nil {
645
646
l.Error("failed to get registration", "err", err)
+5
-4
appview/labels/labels.go
+5
-4
appview/labels/labels.go
···
16
16
"tangled.org/core/appview/oauth"
17
17
"tangled.org/core/appview/pages"
18
18
"tangled.org/core/appview/validator"
19
+
"tangled.org/core/orm"
19
20
"tangled.org/core/rbac"
20
21
"tangled.org/core/tid"
21
22
···
88
89
repoAt := r.Form.Get("repo")
89
90
subjectUri := r.Form.Get("subject")
90
91
91
-
repo, err := db.GetRepo(l.db, db.FilterEq("at_uri", repoAt))
92
+
repo, err := db.GetRepo(l.db, orm.FilterEq("at_uri", repoAt))
92
93
if err != nil {
93
94
fail("Failed to get repository.", err)
94
95
return
95
96
}
96
97
97
98
// find all the labels that this repo subscribes to
98
-
repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt))
99
+
repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_at", repoAt))
99
100
if err != nil {
100
101
fail("Failed to get labels for this repository.", err)
101
102
return
···
106
107
labelAts = append(labelAts, rl.LabelAt.String())
107
108
}
108
109
109
-
actx, err := db.NewLabelApplicationCtx(l.db, db.FilterIn("at_uri", labelAts))
110
+
actx, err := db.NewLabelApplicationCtx(l.db, orm.FilterIn("at_uri", labelAts))
110
111
if err != nil {
111
112
fail("Invalid form data.", err)
112
113
return
113
114
}
114
115
115
116
// calculate the start state by applying already known labels
116
-
existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))
117
+
existingOps, err := db.GetLabelOps(l.db, orm.FilterEq("subject", subjectUri))
117
118
if err != nil {
118
119
fail("Invalid form data.", err)
119
120
return
+67
appview/mentions/resolver.go
+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
+3
-2
appview/middleware/middleware.go
···
18
18
"tangled.org/core/appview/pagination"
19
19
"tangled.org/core/appview/reporesolver"
20
20
"tangled.org/core/idresolver"
21
+
"tangled.org/core/orm"
21
22
"tangled.org/core/rbac"
22
23
)
23
24
···
217
218
218
219
repo, err := db.GetRepo(
219
220
mw.db,
220
-
db.FilterEq("did", id.DID.String()),
221
-
db.FilterEq("name", repoName),
221
+
orm.FilterEq("did", id.DID.String()),
222
+
orm.FilterEq("name", repoName),
222
223
)
223
224
if err != nil {
224
225
log.Println("failed to resolve repo", "err", err)
+5
-4
appview/notifications/notifications.go
+5
-4
appview/notifications/notifications.go
···
11
11
"tangled.org/core/appview/oauth"
12
12
"tangled.org/core/appview/pages"
13
13
"tangled.org/core/appview/pagination"
14
+
"tangled.org/core/orm"
14
15
)
15
16
16
17
type Notifications struct {
···
53
54
54
55
total, err := db.CountNotifications(
55
56
n.db,
56
-
db.FilterEq("recipient_did", user.Did),
57
+
orm.FilterEq("recipient_did", user.Did),
57
58
)
58
59
if err != nil {
59
60
l.Error("failed to get total notifications", "err", err)
···
64
65
notifications, err := db.GetNotificationsWithEntities(
65
66
n.db,
66
67
page,
67
-
db.FilterEq("recipient_did", user.Did),
68
+
orm.FilterEq("recipient_did", user.Did),
68
69
)
69
70
if err != nil {
70
71
l.Error("failed to get notifications", "err", err)
···
96
97
97
98
count, err := db.CountNotifications(
98
99
n.db,
99
-
db.FilterEq("recipient_did", user.Did),
100
-
db.FilterEq("read", 0),
100
+
orm.FilterEq("recipient_did", user.Did),
101
+
orm.FilterEq("read", 0),
101
102
)
102
103
if err != nil {
103
104
http.Error(w, "Failed to get unread count", http.StatusInternalServerError)
+11
-10
appview/notify/db/db.go
+11
-10
appview/notify/db/db.go
···
12
12
"tangled.org/core/appview/models"
13
13
"tangled.org/core/appview/notify"
14
14
"tangled.org/core/idresolver"
15
+
"tangled.org/core/orm"
15
16
)
16
17
17
18
const (
···
42
43
return
43
44
}
44
45
var err error
45
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt)))
46
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(star.RepoAt)))
46
47
if err != nil {
47
48
log.Printf("NewStar: failed to get repos: %v", err)
48
49
return
···
80
81
// - collaborators in the repo
81
82
var recipients []syntax.DID
82
83
recipients = append(recipients, syntax.DID(issue.Repo.Did))
83
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
84
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt()))
84
85
if err != nil {
85
86
log.Printf("failed to fetch collaborators: %v", err)
86
87
return
···
119
120
}
120
121
121
122
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
+
issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.IssueAt))
123
124
if err != nil {
124
125
log.Printf("NewIssueComment: failed to get issues: %v", err)
125
126
return
···
207
208
}
208
209
209
210
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
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(pull.RepoAt)))
211
212
if err != nil {
212
213
log.Printf("NewPull: failed to get repos: %v", err)
213
214
return
···
218
219
// - collaborators in the repo
219
220
var recipients []syntax.DID
220
221
recipients = append(recipients, syntax.DID(repo.Did))
221
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
222
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt()))
222
223
if err != nil {
223
224
log.Printf("failed to fetch collaborators: %v", err)
224
225
return
···
258
259
return
259
260
}
260
261
261
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
262
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", comment.RepoAt))
262
263
if err != nil {
263
264
log.Printf("NewPullComment: failed to get repos: %v", err)
264
265
return
···
327
328
// - all issue participants
328
329
var recipients []syntax.DID
329
330
recipients = append(recipients, syntax.DID(issue.Repo.Did))
330
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
331
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt()))
331
332
if err != nil {
332
333
log.Printf("failed to fetch collaborators: %v", err)
333
334
return
···
366
367
367
368
func (n *databaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {
368
369
// Get repo details
369
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
370
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(pull.RepoAt)))
370
371
if err != nil {
371
372
log.Printf("NewPullState: failed to get repos: %v", err)
372
373
return
···
377
378
// - all pull participants
378
379
var recipients []syntax.DID
379
380
recipients = append(recipients, syntax.DID(repo.Did))
380
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
381
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt()))
381
382
if err != nil {
382
383
log.Printf("failed to fetch collaborators: %v", err)
383
384
return
···
443
444
444
445
prefMap, err := db.GetNotificationPreferences(
445
446
n.db,
446
-
db.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))),
447
+
orm.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))),
447
448
)
448
449
if err != nil {
449
450
// failed to get prefs for users
+3
-2
appview/oauth/handler.go
+3
-2
appview/oauth/handler.go
···
16
16
"tangled.org/core/api/tangled"
17
17
"tangled.org/core/appview/db"
18
18
"tangled.org/core/consts"
19
+
"tangled.org/core/orm"
19
20
"tangled.org/core/tid"
20
21
)
21
22
···
97
98
// and create an sh.tangled.spindle.member record with that
98
99
spindleMembers, err := db.GetSpindleMembers(
99
100
o.Db,
100
-
db.FilterEq("instance", "spindle.tangled.sh"),
101
-
db.FilterEq("subject", did),
101
+
orm.FilterEq("instance", "spindle.tangled.sh"),
102
+
orm.FilterEq("subject", did),
102
103
)
103
104
if err != nil {
104
105
l.Error("failed to get spindle members", "err", err)
+12
-11
appview/pipelines/pipelines.go
+12
-11
appview/pipelines/pipelines.go
···
16
16
"tangled.org/core/appview/reporesolver"
17
17
"tangled.org/core/eventconsumer"
18
18
"tangled.org/core/idresolver"
19
+
"tangled.org/core/orm"
19
20
"tangled.org/core/rbac"
20
21
spindlemodel "tangled.org/core/spindle/models"
21
22
···
81
82
ps, err := db.GetPipelineStatuses(
82
83
p.db,
83
84
30,
84
-
db.FilterEq("repo_owner", f.Did),
85
-
db.FilterEq("repo_name", f.Name),
86
-
db.FilterEq("knot", f.Knot),
85
+
orm.FilterEq("repo_owner", f.Did),
86
+
orm.FilterEq("repo_name", f.Name),
87
+
orm.FilterEq("knot", f.Knot),
87
88
)
88
89
if err != nil {
89
90
l.Error("failed to query db", "err", err)
···
122
123
ps, err := db.GetPipelineStatuses(
123
124
p.db,
124
125
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),
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),
129
130
)
130
131
if err != nil {
131
132
l.Error("failed to query db", "err", err)
···
189
190
ps, err := db.GetPipelineStatuses(
190
191
p.db,
191
192
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),
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),
196
197
)
197
198
if err != nil || len(ps) != 1 {
198
199
l.Error("pipeline query failed", "err", err, "count", len(ps))
+2
-1
appview/pulls/opengraph.go
+2
-1
appview/pulls/opengraph.go
···
13
13
"tangled.org/core/appview/db"
14
14
"tangled.org/core/appview/models"
15
15
"tangled.org/core/appview/ogcard"
16
+
"tangled.org/core/orm"
16
17
"tangled.org/core/patchutil"
17
18
"tangled.org/core/types"
18
19
)
···
276
277
}
277
278
278
279
// Get comment count from database
279
-
comments, err := db.GetPullComments(s.db, db.FilterEq("pull_id", pull.ID))
280
+
comments, err := db.GetPullComments(s.db, orm.FilterEq("pull_id", pull.ID))
280
281
if err != nil {
281
282
log.Printf("failed to get pull comments: %v", err)
282
283
}
+48
-47
appview/pulls/pulls.go
+48
-47
appview/pulls/pulls.go
···
19
19
"tangled.org/core/appview/config"
20
20
"tangled.org/core/appview/db"
21
21
pulls_indexer "tangled.org/core/appview/indexer/pulls"
22
+
"tangled.org/core/appview/mentions"
22
23
"tangled.org/core/appview/models"
23
24
"tangled.org/core/appview/notify"
24
25
"tangled.org/core/appview/oauth"
25
26
"tangled.org/core/appview/pages"
26
27
"tangled.org/core/appview/pages/markup"
27
28
"tangled.org/core/appview/pages/repoinfo"
28
-
"tangled.org/core/appview/refresolver"
29
29
"tangled.org/core/appview/reporesolver"
30
30
"tangled.org/core/appview/validator"
31
31
"tangled.org/core/appview/xrpcclient"
32
32
"tangled.org/core/idresolver"
33
+
"tangled.org/core/orm"
33
34
"tangled.org/core/patchutil"
34
35
"tangled.org/core/rbac"
35
36
"tangled.org/core/tid"
···
44
45
)
45
46
46
47
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
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
59
60
}
60
61
61
62
func New(
···
63
64
repoResolver *reporesolver.RepoResolver,
64
65
pages *pages.Pages,
65
66
resolver *idresolver.Resolver,
66
-
refResolver *refresolver.Resolver,
67
+
mentionsResolver *mentions.Resolver,
67
68
db *db.DB,
68
69
config *config.Config,
69
70
notifier notify.Notifier,
···
73
74
logger *slog.Logger,
74
75
) *Pulls {
75
76
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,
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,
88
89
}
89
90
}
90
91
···
190
191
ps, err := db.GetPipelineStatuses(
191
192
s.db,
192
193
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),
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),
197
198
)
198
199
if err != nil {
199
200
log.Printf("failed to fetch pipeline statuses: %s", err)
···
217
218
218
219
labelDefs, err := db.GetLabelDefinitions(
219
220
s.db,
220
-
db.FilterIn("at_uri", f.Labels),
221
-
db.FilterContains("scope", tangled.RepoPullNSID),
221
+
orm.FilterIn("at_uri", f.Labels),
222
+
orm.FilterContains("scope", tangled.RepoPullNSID),
222
223
)
223
224
if err != nil {
224
225
log.Println("failed to fetch labels", err)
···
597
598
598
599
pulls, err := db.GetPulls(
599
600
s.db,
600
-
db.FilterIn("id", ids),
601
+
orm.FilterIn("id", ids),
601
602
)
602
603
if err != nil {
603
604
log.Println("failed to get pulls", err)
···
648
649
ps, err := db.GetPipelineStatuses(
649
650
s.db,
650
651
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),
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),
655
656
)
656
657
if err != nil {
657
658
log.Printf("failed to fetch pipeline statuses: %s", err)
···
664
665
665
666
labelDefs, err := db.GetLabelDefinitions(
666
667
s.db,
667
-
db.FilterIn("at_uri", f.Labels),
668
-
db.FilterContains("scope", tangled.RepoPullNSID),
668
+
orm.FilterIn("at_uri", f.Labels),
669
+
orm.FilterContains("scope", tangled.RepoPullNSID),
669
670
)
670
671
if err != nil {
671
672
log.Println("failed to fetch labels", err)
···
729
730
return
730
731
}
731
732
732
-
mentions, references := s.refResolver.Resolve(r.Context(), body)
733
+
mentions, references := s.mentionsResolver.Resolve(r.Context(), body)
733
734
734
735
// Start a transaction
735
736
tx, err := s.db.BeginTx(r.Context(), nil)
···
1205
1206
}
1206
1207
}
1207
1208
1208
-
mentions, references := s.refResolver.Resolve(r.Context(), body)
1209
+
mentions, references := s.mentionsResolver.Resolve(r.Context(), body)
1209
1210
1210
1211
rkey := tid.TID()
1211
1212
initialSubmission := models.PullSubmission{
···
1498
1499
// fork repo
1499
1500
repo, err := db.GetRepo(
1500
1501
s.db,
1501
-
db.FilterEq("did", forkOwnerDid),
1502
-
db.FilterEq("name", forkName),
1502
+
orm.FilterEq("did", forkOwnerDid),
1503
+
orm.FilterEq("name", forkName),
1503
1504
)
1504
1505
if err != nil {
1505
1506
log.Println("failed to get repo", "did", forkOwnerDid, "name", forkName, "err", err)
···
2066
2067
tx,
2067
2068
p.ParentChangeId,
2068
2069
// 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),
2070
+
orm.FilterEq("repo_at", p.RepoAt.String()),
2071
+
orm.FilterEq("owner_did", p.OwnerDid),
2072
+
orm.FilterEq("change_id", p.ChangeId),
2072
2073
)
2073
2074
2074
2075
if err != nil {
···
2397
2398
body := fp.Body
2398
2399
rkey := tid.TID()
2399
2400
2400
-
mentions, references := s.refResolver.Resolve(ctx, body)
2401
+
mentions, references := s.mentionsResolver.Resolve(ctx, body)
2401
2402
2402
2403
initialSubmission := models.PullSubmission{
2403
2404
Patch: fp.Raw,
-65
appview/refresolver/resolver.go
-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
+10
-9
appview/repo/artifact.go
···
15
15
"tangled.org/core/appview/models"
16
16
"tangled.org/core/appview/pages"
17
17
"tangled.org/core/appview/xrpcclient"
18
+
"tangled.org/core/orm"
18
19
"tangled.org/core/tid"
19
20
"tangled.org/core/types"
20
21
···
155
156
156
157
artifacts, err := db.GetArtifact(
157
158
rp.db,
158
-
db.FilterEq("repo_at", f.RepoAt()),
159
-
db.FilterEq("tag", tag.Tag.Hash[:]),
160
-
db.FilterEq("name", filename),
159
+
orm.FilterEq("repo_at", f.RepoAt()),
160
+
orm.FilterEq("tag", tag.Tag.Hash[:]),
161
+
orm.FilterEq("name", filename),
161
162
)
162
163
if err != nil {
163
164
log.Println("failed to get artifacts", err)
···
234
235
235
236
artifacts, err := db.GetArtifact(
236
237
rp.db,
237
-
db.FilterEq("repo_at", f.RepoAt()),
238
-
db.FilterEq("tag", tag[:]),
239
-
db.FilterEq("name", filename),
238
+
orm.FilterEq("repo_at", f.RepoAt()),
239
+
orm.FilterEq("tag", tag[:]),
240
+
orm.FilterEq("name", filename),
240
241
)
241
242
if err != nil {
242
243
log.Println("failed to get artifacts", err)
···
276
277
defer tx.Rollback()
277
278
278
279
err = db.DeleteArtifact(tx,
279
-
db.FilterEq("repo_at", f.RepoAt()),
280
-
db.FilterEq("tag", artifact.Tag[:]),
281
-
db.FilterEq("name", filename),
280
+
orm.FilterEq("repo_at", f.RepoAt()),
281
+
orm.FilterEq("tag", artifact.Tag[:]),
282
+
orm.FilterEq("name", filename),
282
283
)
283
284
if err != nil {
284
285
log.Println("failed to remove artifact record from db", err)
+3
-2
appview/repo/feed.go
+3
-2
appview/repo/feed.go
···
11
11
"tangled.org/core/appview/db"
12
12
"tangled.org/core/appview/models"
13
13
"tangled.org/core/appview/pagination"
14
+
"tangled.org/core/orm"
14
15
15
16
"github.com/bluesky-social/indigo/atproto/identity"
16
17
"github.com/bluesky-social/indigo/atproto/syntax"
···
20
21
func (rp *Repo) getRepoFeed(ctx context.Context, repo *models.Repo, ownerSlashRepo string) (*feeds.Feed, error) {
21
22
const feedLimitPerType = 100
22
23
23
-
pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", repo.RepoAt()))
24
+
pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, orm.FilterEq("repo_at", repo.RepoAt()))
24
25
if err != nil {
25
26
return nil, err
26
27
}
···
28
29
issues, err := db.GetIssuesPaginated(
29
30
rp.db,
30
31
pagination.Page{Limit: feedLimitPerType},
31
-
db.FilterEq("repo_at", repo.RepoAt()),
32
+
orm.FilterEq("repo_at", repo.RepoAt()),
32
33
)
33
34
if err != nil {
34
35
return nil, err
+3
-2
appview/repo/index.go
+3
-2
appview/repo/index.go
···
23
23
"tangled.org/core/appview/models"
24
24
"tangled.org/core/appview/pages"
25
25
"tangled.org/core/appview/xrpcclient"
26
+
"tangled.org/core/orm"
26
27
"tangled.org/core/types"
27
28
28
29
"github.com/go-chi/chi/v5"
···
171
172
// first attempt to fetch from db
172
173
langs, err := db.GetRepoLanguages(
173
174
rp.db,
174
-
db.FilterEq("repo_at", repo.RepoAt()),
175
-
db.FilterEq("ref", currentRef),
175
+
orm.FilterEq("repo_at", repo.RepoAt()),
176
+
orm.FilterEq("ref", currentRef),
176
177
)
177
178
178
179
if err != nil || langs == nil {
+3
-2
appview/repo/opengraph.go
+3
-2
appview/repo/opengraph.go
···
16
16
"tangled.org/core/appview/db"
17
17
"tangled.org/core/appview/models"
18
18
"tangled.org/core/appview/ogcard"
19
+
"tangled.org/core/orm"
19
20
"tangled.org/core/types"
20
21
)
21
22
···
338
339
var languageStats []types.RepoLanguageDetails
339
340
langs, err := db.GetRepoLanguages(
340
341
rp.db,
341
-
db.FilterEq("repo_at", f.RepoAt()),
342
-
db.FilterEq("is_default_ref", 1),
342
+
orm.FilterEq("repo_at", f.RepoAt()),
343
+
orm.FilterEq("is_default_ref", 1),
343
344
)
344
345
if err != nil {
345
346
log.Printf("failed to get language stats from db: %v", err)
+17
-16
appview/repo/repo.go
+17
-16
appview/repo/repo.go
···
24
24
xrpcclient "tangled.org/core/appview/xrpcclient"
25
25
"tangled.org/core/eventconsumer"
26
26
"tangled.org/core/idresolver"
27
+
"tangled.org/core/orm"
27
28
"tangled.org/core/rbac"
28
29
"tangled.org/core/tid"
29
30
"tangled.org/core/xrpc/serviceauth"
···
345
346
// get form values
346
347
labelId := r.FormValue("label-id")
347
348
348
-
label, err := db.GetLabelDefinition(rp.db, db.FilterEq("id", labelId))
349
+
label, err := db.GetLabelDefinition(rp.db, orm.FilterEq("id", labelId))
349
350
if err != nil {
350
351
fail("Failed to find label definition.", err)
351
352
return
···
409
410
410
411
err = db.UnsubscribeLabel(
411
412
tx,
412
-
db.FilterEq("repo_at", f.RepoAt()),
413
-
db.FilterEq("label_at", removedAt),
413
+
orm.FilterEq("repo_at", f.RepoAt()),
414
+
orm.FilterEq("label_at", removedAt),
414
415
)
415
416
if err != nil {
416
417
fail("Failed to unsubscribe label.", err)
417
418
return
418
419
}
419
420
420
-
err = db.DeleteLabelDefinition(tx, db.FilterEq("id", label.Id))
421
+
err = db.DeleteLabelDefinition(tx, orm.FilterEq("id", label.Id))
421
422
if err != nil {
422
423
fail("Failed to delete label definition.", err)
423
424
return
···
456
457
}
457
458
458
459
labelAts := r.Form["label"]
459
-
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
460
+
_, err = db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", labelAts))
460
461
if err != nil {
461
462
fail("Failed to subscribe to label.", err)
462
463
return
···
542
543
}
543
544
544
545
labelAts := r.Form["label"]
545
-
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
546
+
_, err = db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", labelAts))
546
547
if err != nil {
547
548
fail("Failed to unsubscribe to label.", err)
548
549
return
···
582
583
583
584
err = db.UnsubscribeLabel(
584
585
rp.db,
585
-
db.FilterEq("repo_at", f.RepoAt()),
586
-
db.FilterIn("label_at", labelAts),
586
+
orm.FilterEq("repo_at", f.RepoAt()),
587
+
orm.FilterIn("label_at", labelAts),
587
588
)
588
589
if err != nil {
589
590
fail("Failed to unsubscribe label.", err)
···
612
613
613
614
labelDefs, err := db.GetLabelDefinitions(
614
615
rp.db,
615
-
db.FilterIn("at_uri", f.Labels),
616
-
db.FilterContains("scope", subject.Collection().String()),
616
+
orm.FilterIn("at_uri", f.Labels),
617
+
orm.FilterContains("scope", subject.Collection().String()),
617
618
)
618
619
if err != nil {
619
620
l.Error("failed to fetch label defs", "err", err)
···
625
626
defs[l.AtUri().String()] = &l
626
627
}
627
628
628
-
states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject))
629
+
states, err := db.GetLabels(rp.db, orm.FilterEq("subject", subject))
629
630
if err != nil {
630
631
l.Error("failed to build label state", "err", err)
631
632
return
···
660
661
661
662
labelDefs, err := db.GetLabelDefinitions(
662
663
rp.db,
663
-
db.FilterIn("at_uri", f.Labels),
664
-
db.FilterContains("scope", subject.Collection().String()),
664
+
orm.FilterIn("at_uri", f.Labels),
665
+
orm.FilterContains("scope", subject.Collection().String()),
665
666
)
666
667
if err != nil {
667
668
l.Error("failed to fetch labels", "err", err)
···
673
674
defs[l.AtUri().String()] = &l
674
675
}
675
676
676
-
states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject))
677
+
states, err := db.GetLabels(rp.db, orm.FilterEq("subject", subject))
677
678
if err != nil {
678
679
l.Error("failed to build label state", "err", err)
679
680
return
···
1036
1037
// in the user's account.
1037
1038
existingRepo, err := db.GetRepo(
1038
1039
rp.db,
1039
-
db.FilterEq("did", user.Did),
1040
-
db.FilterEq("name", forkName),
1040
+
orm.FilterEq("did", user.Did),
1041
+
orm.FilterEq("name", forkName),
1041
1042
)
1042
1043
if err != nil {
1043
1044
if !errors.Is(err, sql.ErrNoRows) {
+5
-4
appview/repo/repo_util.go
+5
-4
appview/repo/repo_util.go
···
8
8
9
9
"tangled.org/core/appview/db"
10
10
"tangled.org/core/appview/models"
11
+
"tangled.org/core/orm"
11
12
"tangled.org/core/types"
12
13
)
13
14
···
102
103
ps, err := db.GetPipelineStatuses(
103
104
d,
104
105
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),
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),
109
110
)
110
111
if err != nil {
111
112
return nil, err
+3
-2
appview/repo/settings.go
+3
-2
appview/repo/settings.go
···
14
14
"tangled.org/core/appview/oauth"
15
15
"tangled.org/core/appview/pages"
16
16
xrpcclient "tangled.org/core/appview/xrpcclient"
17
+
"tangled.org/core/orm"
17
18
"tangled.org/core/types"
18
19
19
20
comatproto "github.com/bluesky-social/indigo/api/atproto"
···
210
211
return
211
212
}
212
213
213
-
defaultLabels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs))
214
+
defaultLabels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs))
214
215
if err != nil {
215
216
l.Error("failed to fetch labels", "err", err)
216
217
rp.pages.Error503(w)
217
218
return
218
219
}
219
220
220
-
labels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Labels))
221
+
labels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", f.Labels))
221
222
if err != nil {
222
223
l.Error("failed to fetch labels", "err", err)
223
224
rp.pages.Error503(w)
+5
-4
appview/serververify/verify.go
+5
-4
appview/serververify/verify.go
···
9
9
"tangled.org/core/api/tangled"
10
10
"tangled.org/core/appview/db"
11
11
"tangled.org/core/appview/xrpcclient"
12
+
"tangled.org/core/orm"
12
13
"tangled.org/core/rbac"
13
14
)
14
15
···
76
77
// mark this spindle as verified in the db
77
78
rowId, err := db.VerifySpindle(
78
79
tx,
79
-
db.FilterEq("owner", owner),
80
-
db.FilterEq("instance", instance),
80
+
orm.FilterEq("owner", owner),
81
+
orm.FilterEq("instance", instance),
81
82
)
82
83
if err != nil {
83
84
return 0, fmt.Errorf("failed to write to DB: %w", err)
···
115
116
// mark as registered
116
117
err = db.MarkRegistered(
117
118
tx,
118
-
db.FilterEq("did", owner),
119
-
db.FilterEq("domain", domain),
119
+
orm.FilterEq("did", owner),
120
+
orm.FilterEq("domain", domain),
120
121
)
121
122
if err != nil {
122
123
return fmt.Errorf("failed to register domain: %w", err)
+25
-24
appview/spindles/spindles.go
+25
-24
appview/spindles/spindles.go
···
20
20
"tangled.org/core/appview/serververify"
21
21
"tangled.org/core/appview/xrpcclient"
22
22
"tangled.org/core/idresolver"
23
+
"tangled.org/core/orm"
23
24
"tangled.org/core/rbac"
24
25
"tangled.org/core/tid"
25
26
···
71
72
user := s.OAuth.GetUser(r)
72
73
all, err := db.GetSpindles(
73
74
s.Db,
74
-
db.FilterEq("owner", user.Did),
75
+
orm.FilterEq("owner", user.Did),
75
76
)
76
77
if err != nil {
77
78
s.Logger.Error("failed to fetch spindles", "err", err)
···
101
102
102
103
spindles, err := db.GetSpindles(
103
104
s.Db,
104
-
db.FilterEq("instance", instance),
105
-
db.FilterEq("owner", user.Did),
106
-
db.FilterIsNot("verified", "null"),
105
+
orm.FilterEq("instance", instance),
106
+
orm.FilterEq("owner", user.Did),
107
+
orm.FilterIsNot("verified", "null"),
107
108
)
108
109
if err != nil || len(spindles) != 1 {
109
110
l.Error("failed to get spindle", "err", err, "len(spindles)", len(spindles))
···
123
124
repos, err := db.GetRepos(
124
125
s.Db,
125
126
0,
126
-
db.FilterEq("spindle", instance),
127
+
orm.FilterEq("spindle", instance),
127
128
)
128
129
if err != nil {
129
130
l.Error("failed to get spindle repos", "err", err)
···
290
291
291
292
spindles, err := db.GetSpindles(
292
293
s.Db,
293
-
db.FilterEq("owner", user.Did),
294
-
db.FilterEq("instance", instance),
294
+
orm.FilterEq("owner", user.Did),
295
+
orm.FilterEq("instance", instance),
295
296
)
296
297
if err != nil || len(spindles) != 1 {
297
298
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
319
320
// remove spindle members first
320
321
err = db.RemoveSpindleMember(
321
322
tx,
322
-
db.FilterEq("did", user.Did),
323
-
db.FilterEq("instance", instance),
323
+
orm.FilterEq("did", user.Did),
324
+
orm.FilterEq("instance", instance),
324
325
)
325
326
if err != nil {
326
327
l.Error("failed to remove spindle members", "err", err)
···
330
331
331
332
err = db.DeleteSpindle(
332
333
tx,
333
-
db.FilterEq("owner", user.Did),
334
-
db.FilterEq("instance", instance),
334
+
orm.FilterEq("owner", user.Did),
335
+
orm.FilterEq("instance", instance),
335
336
)
336
337
if err != nil {
337
338
l.Error("failed to delete spindle", "err", err)
···
410
411
411
412
spindles, err := db.GetSpindles(
412
413
s.Db,
413
-
db.FilterEq("owner", user.Did),
414
-
db.FilterEq("instance", instance),
414
+
orm.FilterEq("owner", user.Did),
415
+
orm.FilterEq("instance", instance),
415
416
)
416
417
if err != nil || len(spindles) != 1 {
417
418
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
453
454
454
455
verifiedSpindle, err := db.GetSpindles(
455
456
s.Db,
456
-
db.FilterEq("id", rowId),
457
+
orm.FilterEq("id", rowId),
457
458
)
458
459
if err != nil || len(verifiedSpindle) != 1 {
459
460
l.Error("failed get new spindle", "err", err)
···
486
487
487
488
spindles, err := db.GetSpindles(
488
489
s.Db,
489
-
db.FilterEq("owner", user.Did),
490
-
db.FilterEq("instance", instance),
490
+
orm.FilterEq("owner", user.Did),
491
+
orm.FilterEq("instance", instance),
491
492
)
492
493
if err != nil || len(spindles) != 1 {
493
494
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
622
623
623
624
spindles, err := db.GetSpindles(
624
625
s.Db,
625
-
db.FilterEq("owner", user.Did),
626
-
db.FilterEq("instance", instance),
626
+
orm.FilterEq("owner", user.Did),
627
+
orm.FilterEq("instance", instance),
627
628
)
628
629
if err != nil || len(spindles) != 1 {
629
630
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
672
673
// get the record from the DB first:
673
674
members, err := db.GetSpindleMembers(
674
675
s.Db,
675
-
db.FilterEq("did", user.Did),
676
-
db.FilterEq("instance", instance),
677
-
db.FilterEq("subject", memberId.DID),
676
+
orm.FilterEq("did", user.Did),
677
+
orm.FilterEq("instance", instance),
678
+
orm.FilterEq("subject", memberId.DID),
678
679
)
679
680
if err != nil || len(members) != 1 {
680
681
l.Error("failed to get member", "err", err)
···
685
686
// remove from db
686
687
if err = db.RemoveSpindleMember(
687
688
tx,
688
-
db.FilterEq("did", user.Did),
689
-
db.FilterEq("instance", instance),
690
-
db.FilterEq("subject", memberId.DID),
689
+
orm.FilterEq("did", user.Did),
690
+
orm.FilterEq("instance", instance),
691
+
orm.FilterEq("subject", memberId.DID),
691
692
); err != nil {
692
693
l.Error("failed to remove spindle member", "err", err)
693
694
fail()
+6
-5
appview/state/gfi.go
+6
-5
appview/state/gfi.go
···
11
11
"tangled.org/core/appview/pages"
12
12
"tangled.org/core/appview/pagination"
13
13
"tangled.org/core/consts"
14
+
"tangled.org/core/orm"
14
15
)
15
16
16
17
func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) {
···
20
21
21
22
goodFirstIssueLabel := s.config.Label.GoodFirstIssue
22
23
23
-
gfiLabelDef, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", goodFirstIssueLabel))
24
+
gfiLabelDef, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", goodFirstIssueLabel))
24
25
if err != nil {
25
26
log.Println("failed to get gfi label def", err)
26
27
s.pages.Error500(w)
27
28
return
28
29
}
29
30
30
-
repoLabels, err := db.GetRepoLabels(s.db, db.FilterEq("label_at", goodFirstIssueLabel))
31
+
repoLabels, err := db.GetRepoLabels(s.db, orm.FilterEq("label_at", goodFirstIssueLabel))
31
32
if err != nil {
32
33
log.Println("failed to get repo labels", err)
33
34
s.pages.Error503(w)
···
55
56
pagination.Page{
56
57
Limit: 500,
57
58
},
58
-
db.FilterIn("repo_at", repoUris),
59
-
db.FilterEq("open", 1),
59
+
orm.FilterIn("repo_at", repoUris),
60
+
orm.FilterEq("open", 1),
60
61
)
61
62
if err != nil {
62
63
log.Println("failed to get issues", err)
···
132
133
}
133
134
134
135
if len(uriList) > 0 {
135
-
allLabelDefs, err = db.GetLabelDefinitions(s.db, db.FilterIn("at_uri", uriList))
136
+
allLabelDefs, err = db.GetLabelDefinitions(s.db, orm.FilterIn("at_uri", uriList))
136
137
if err != nil {
137
138
log.Println("failed to fetch labels", err)
138
139
}
+6
-5
appview/state/knotstream.go
+6
-5
appview/state/knotstream.go
···
16
16
ec "tangled.org/core/eventconsumer"
17
17
"tangled.org/core/eventconsumer/cursor"
18
18
"tangled.org/core/log"
19
+
"tangled.org/core/orm"
19
20
"tangled.org/core/rbac"
20
21
"tangled.org/core/workflow"
21
22
···
30
31
31
32
knots, err := db.GetRegistrations(
32
33
d,
33
-
db.FilterIsNot("registered", "null"),
34
+
orm.FilterIsNot("registered", "null"),
34
35
)
35
36
if err != nil {
36
37
return nil, err
···
143
144
repos, err := db.GetRepos(
144
145
d,
145
146
0,
146
-
db.FilterEq("did", record.RepoDid),
147
-
db.FilterEq("name", record.RepoName),
147
+
orm.FilterEq("did", record.RepoDid),
148
+
orm.FilterEq("name", record.RepoName),
148
149
)
149
150
if err != nil {
150
151
return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.RepoDid, record.RepoName, err)
···
209
210
repos, err := db.GetRepos(
210
211
d,
211
212
0,
212
-
db.FilterEq("did", record.TriggerMetadata.Repo.Did),
213
-
db.FilterEq("name", record.TriggerMetadata.Repo.Repo),
213
+
orm.FilterEq("did", record.TriggerMetadata.Repo.Did),
214
+
orm.FilterEq("name", record.TriggerMetadata.Repo.Repo),
214
215
)
215
216
if err != nil {
216
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
+13
-12
appview/state/profile.go
···
19
19
"tangled.org/core/appview/db"
20
20
"tangled.org/core/appview/models"
21
21
"tangled.org/core/appview/pages"
22
+
"tangled.org/core/orm"
22
23
)
23
24
24
25
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
···
56
57
return nil, fmt.Errorf("failed to get profile: %w", err)
57
58
}
58
59
59
-
repoCount, err := db.CountRepos(s.db, db.FilterEq("did", did))
60
+
repoCount, err := db.CountRepos(s.db, orm.FilterEq("did", did))
60
61
if err != nil {
61
62
return nil, fmt.Errorf("failed to get repo count: %w", err)
62
63
}
63
64
64
-
stringCount, err := db.CountStrings(s.db, db.FilterEq("did", did))
65
+
stringCount, err := db.CountStrings(s.db, orm.FilterEq("did", did))
65
66
if err != nil {
66
67
return nil, fmt.Errorf("failed to get string count: %w", err)
67
68
}
68
69
69
-
starredCount, err := db.CountStars(s.db, db.FilterEq("did", did))
70
+
starredCount, err := db.CountStars(s.db, orm.FilterEq("did", did))
70
71
if err != nil {
71
72
return nil, fmt.Errorf("failed to get starred repo count: %w", err)
72
73
}
···
86
87
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
87
88
punchcard, err := db.MakePunchcard(
88
89
s.db,
89
-
db.FilterEq("did", did),
90
-
db.FilterGte("date", startOfYear.Format(time.DateOnly)),
91
-
db.FilterLte("date", now.Format(time.DateOnly)),
90
+
orm.FilterEq("did", did),
91
+
orm.FilterGte("date", startOfYear.Format(time.DateOnly)),
92
+
orm.FilterLte("date", now.Format(time.DateOnly)),
92
93
)
93
94
if err != nil {
94
95
return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err)
···
123
124
repos, err := db.GetRepos(
124
125
s.db,
125
126
0,
126
-
db.FilterEq("did", profile.UserDid),
127
+
orm.FilterEq("did", profile.UserDid),
127
128
)
128
129
if err != nil {
129
130
l.Error("failed to fetch repos", "err", err)
···
193
194
repos, err := db.GetRepos(
194
195
s.db,
195
196
0,
196
-
db.FilterEq("did", profile.UserDid),
197
+
orm.FilterEq("did", profile.UserDid),
197
198
)
198
199
if err != nil {
199
200
l.Error("failed to get repos", "err", err)
···
219
220
}
220
221
l = l.With("profileDid", profile.UserDid)
221
222
222
-
stars, err := db.GetRepoStars(s.db, 0, db.FilterEq("did", profile.UserDid))
223
+
stars, err := db.GetRepoStars(s.db, 0, orm.FilterEq("did", profile.UserDid))
223
224
if err != nil {
224
225
l.Error("failed to get stars", "err", err)
225
226
s.pages.Error500(w)
···
248
249
}
249
250
l = l.With("profileDid", profile.UserDid)
250
251
251
-
strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid))
252
+
strings, err := db.GetStrings(s.db, 0, orm.FilterEq("did", profile.UserDid))
252
253
if err != nil {
253
254
l.Error("failed to get strings", "err", err)
254
255
s.pages.Error500(w)
···
300
301
followDids = append(followDids, extractDid(follow))
301
302
}
302
303
303
-
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
304
+
profiles, err := db.GetProfiles(s.db, orm.FilterIn("did", followDids))
304
305
if err != nil {
305
306
l.Error("failed to get profiles", "followDids", followDids, "err", err)
306
307
return ¶ms, err
···
703
704
log.Printf("getting profile data for %s: %s", user.Did, err)
704
705
}
705
706
706
-
repos, err := db.GetRepos(s.db, 0, db.FilterEq("did", user.Did))
707
+
repos, err := db.GetRepos(s.db, 0, orm.FilterEq("did", user.Did))
707
708
if err != nil {
708
709
log.Printf("getting repos for %s: %s", user.Did, err)
709
710
}
+2
-2
appview/state/router.go
+2
-2
appview/state/router.go
···
266
266
s.enforcer,
267
267
s.pages,
268
268
s.idResolver,
269
-
s.refResolver,
269
+
s.mentionsResolver,
270
270
s.db,
271
271
s.config,
272
272
s.notifier,
···
283
283
s.repoResolver,
284
284
s.pages,
285
285
s.idResolver,
286
-
s.refResolver,
286
+
s.mentionsResolver,
287
287
s.db,
288
288
s.config,
289
289
s.notifier,
+2
-1
appview/state/spindlestream.go
+2
-1
appview/state/spindlestream.go
···
17
17
ec "tangled.org/core/eventconsumer"
18
18
"tangled.org/core/eventconsumer/cursor"
19
19
"tangled.org/core/log"
20
+
"tangled.org/core/orm"
20
21
"tangled.org/core/rbac"
21
22
spindle "tangled.org/core/spindle/models"
22
23
)
···
27
28
28
29
spindles, err := db.GetSpindles(
29
30
d,
30
-
db.FilterIsNot("verified", "null"),
31
+
orm.FilterIsNot("verified", "null"),
31
32
)
32
33
if err != nil {
33
34
return nil, err
+28
-27
appview/state/state.go
+28
-27
appview/state/state.go
···
15
15
"tangled.org/core/appview/config"
16
16
"tangled.org/core/appview/db"
17
17
"tangled.org/core/appview/indexer"
18
+
"tangled.org/core/appview/mentions"
18
19
"tangled.org/core/appview/models"
19
20
"tangled.org/core/appview/notify"
20
21
dbnotify "tangled.org/core/appview/notify/db"
21
22
phnotify "tangled.org/core/appview/notify/posthog"
22
23
"tangled.org/core/appview/oauth"
23
24
"tangled.org/core/appview/pages"
24
-
"tangled.org/core/appview/refresolver"
25
25
"tangled.org/core/appview/reporesolver"
26
26
"tangled.org/core/appview/validator"
27
27
xrpcclient "tangled.org/core/appview/xrpcclient"
···
30
30
"tangled.org/core/jetstream"
31
31
"tangled.org/core/log"
32
32
tlog "tangled.org/core/log"
33
+
"tangled.org/core/orm"
33
34
"tangled.org/core/rbac"
34
35
"tangled.org/core/tid"
35
36
···
43
44
)
44
45
45
46
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
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
62
63
}
63
64
64
65
func Make(ctx context.Context, config *config.Config) (*State, error) {
···
100
101
101
102
repoResolver := reporesolver.New(config, enforcer, d)
102
103
103
-
refResolver := refresolver.New(config, res, d, log.SubLogger(logger, "refResolver"))
104
+
mentionsResolver := mentions.New(config, res, d, log.SubLogger(logger, "mentionsResolver"))
104
105
105
106
wrapper := db.DbWrapper{Execer: d}
106
107
jc, err := jetstream.NewJetstreamClient(
···
182
183
enforcer,
183
184
pages,
184
185
res,
185
-
refResolver,
186
+
mentionsResolver,
186
187
posthog,
187
188
jc,
188
189
config,
···
299
300
return
300
301
}
301
302
302
-
gfiLabel, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", s.config.Label.GoodFirstIssue))
303
+
gfiLabel, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", s.config.Label.GoodFirstIssue))
303
304
if err != nil {
304
305
// non-fatal
305
306
}
···
323
324
324
325
regs, err := db.GetRegistrations(
325
326
s.db,
326
-
db.FilterEq("did", user.Did),
327
-
db.FilterEq("needs_upgrade", 1),
327
+
orm.FilterEq("did", user.Did),
328
+
orm.FilterEq("needs_upgrade", 1),
328
329
)
329
330
if err != nil {
330
331
l.Error("non-fatal: failed to get registrations", "err", err)
···
332
333
333
334
spindles, err := db.GetSpindles(
334
335
s.db,
335
-
db.FilterEq("owner", user.Did),
336
-
db.FilterEq("needs_upgrade", 1),
336
+
orm.FilterEq("owner", user.Did),
337
+
orm.FilterEq("needs_upgrade", 1),
337
338
)
338
339
if err != nil {
339
340
l.Error("non-fatal: failed to get spindles", "err", err)
···
504
505
// Check for existing repos
505
506
existingRepo, err := db.GetRepo(
506
507
s.db,
507
-
db.FilterEq("did", user.Did),
508
-
db.FilterEq("name", repoName),
508
+
orm.FilterEq("did", user.Did),
509
+
orm.FilterEq("name", repoName),
509
510
)
510
511
if err == nil && existingRepo != nil {
511
512
l.Info("repo exists")
···
665
666
}
666
667
667
668
func BackfillDefaultDefs(e db.Execer, r *idresolver.Resolver, defaults []string) error {
668
-
defaultLabels, err := db.GetLabelDefinitions(e, db.FilterIn("at_uri", defaults))
669
+
defaultLabels, err := db.GetLabelDefinitions(e, orm.FilterIn("at_uri", defaults))
669
670
if err != nil {
670
671
return err
671
672
}
+7
-6
appview/strings/strings.go
+7
-6
appview/strings/strings.go
···
17
17
"tangled.org/core/appview/pages"
18
18
"tangled.org/core/appview/pages/markup"
19
19
"tangled.org/core/idresolver"
20
+
"tangled.org/core/orm"
20
21
"tangled.org/core/tid"
21
22
22
23
"github.com/bluesky-social/indigo/api/atproto"
···
108
109
strings, err := db.GetStrings(
109
110
s.Db,
110
111
0,
111
-
db.FilterEq("did", id.DID),
112
-
db.FilterEq("rkey", rkey),
112
+
orm.FilterEq("did", id.DID),
113
+
orm.FilterEq("rkey", rkey),
113
114
)
114
115
if err != nil {
115
116
l.Error("failed to fetch string", "err", err)
···
199
200
all, err := db.GetStrings(
200
201
s.Db,
201
202
0,
202
-
db.FilterEq("did", id.DID),
203
-
db.FilterEq("rkey", rkey),
203
+
orm.FilterEq("did", id.DID),
204
+
orm.FilterEq("rkey", rkey),
204
205
)
205
206
if err != nil {
206
207
l.Error("failed to fetch string", "err", err)
···
408
409
409
410
if err := db.DeleteString(
410
411
s.Db,
411
-
db.FilterEq("did", user.Did),
412
-
db.FilterEq("rkey", rkey),
412
+
orm.FilterEq("did", user.Did),
413
+
orm.FilterEq("rkey", rkey),
413
414
); err != nil {
414
415
fail("Failed to delete string.", err)
415
416
return
+2
-1
appview/validator/issue.go
+2
-1
appview/validator/issue.go
···
6
6
7
7
"tangled.org/core/appview/db"
8
8
"tangled.org/core/appview/models"
9
+
"tangled.org/core/orm"
9
10
)
10
11
11
12
func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error {
12
13
// if comments have parents, only ingest ones that are 1 level deep
13
14
if comment.ReplyTo != nil {
14
-
parents, err := db.GetIssueComments(v.db, db.FilterEq("at_uri", *comment.ReplyTo))
15
+
parents, err := db.GetIssueComments(v.db, orm.FilterEq("at_uri", *comment.ReplyTo))
15
16
if err != nil {
16
17
return fmt.Errorf("failed to fetch parent comment: %w", err)
17
18
}
+6
-6
cmd/knot/main.go
+6
-6
cmd/knot/main.go
···
6
6
"os"
7
7
8
8
"github.com/urfave/cli/v3"
9
-
"tangled.org/core/knot2/guard"
10
-
"tangled.org/core/knot2/hook"
11
-
"tangled.org/core/knot2/keys"
12
-
"tangled.org/core/knot2/server"
9
+
"tangled.org/core/guard"
10
+
"tangled.org/core/hook"
11
+
"tangled.org/core/keyfetch"
12
+
"tangled.org/core/knotserver"
13
13
tlog "tangled.org/core/log"
14
14
)
15
15
···
19
19
Usage: "knot administration and operation tool",
20
20
Commands: []*cli.Command{
21
21
guard.Command(),
22
-
server.Command(),
23
-
keys.Command(),
22
+
knotserver.Command(),
23
+
keyfetch.Command(),
24
24
hook.Command(),
25
25
},
26
26
}
-1
go.mod
-1
go.mod
···
18
18
github.com/cloudflare/cloudflare-go v0.115.0
19
19
github.com/cyphar/filepath-securejoin v0.4.1
20
20
github.com/dgraph-io/ristretto v0.2.0
21
-
github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038
22
21
github.com/docker/docker v28.2.2+incompatible
23
22
github.com/dustin/go-humanize v1.0.1
24
23
github.com/gliderlabs/ssh v0.3.8
-2
go.sum
-2
go.sum
···
131
131
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
132
132
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
133
133
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
134
-
github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038 h1:AGh+Vn9fXhf9eo8erG1CK4+LACduPo64P1OICQLDv88=
135
-
github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038/go.mod h1:ddIXqTTSXWtj5kMsHAPj8SvbIx2GZdAkBFgFa6e6+CM=
136
134
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
137
135
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
138
136
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-101
knot2/config/config.go
-101
knot2/config/config.go
···
1
-
package config
2
-
3
-
import (
4
-
"context"
5
-
"fmt"
6
-
"net"
7
-
"os"
8
-
"path"
9
-
10
-
"github.com/bluesky-social/indigo/atproto/syntax"
11
-
"github.com/sethvargo/go-envconfig"
12
-
"gopkg.in/yaml.v3"
13
-
)
14
-
15
-
type Config struct {
16
-
Dev bool `yaml:"dev"`
17
-
HostName string `yaml:"hostname"`
18
-
OwnerDid syntax.DID `yaml:"owner_did"`
19
-
ListenHost string `yaml:"listen_host"`
20
-
ListenPort string `yaml:"listen_port"`
21
-
DataDir string `yaml:"data_dir"`
22
-
RepoDir string `yaml:"repo_dir"`
23
-
PlcUrl string `yaml:"plc_url"`
24
-
JetstreamEndpoint string `yaml:"jetstream_endpoint"`
25
-
AppviewEndpoint string `yaml:"appview_endpoint"`
26
-
GitUserName string `yaml:"git_user_name"`
27
-
GitUserEmail string `yaml:"git_user_email"`
28
-
OAuth OAuthConfig
29
-
}
30
-
31
-
type OAuthConfig struct {
32
-
CookieSecret string `env:"KNOT2_COOKIE_SECRET, default=00000000000000000000000000000000"`
33
-
ClientSecret string `env:"KNOT2_OAUTH_CLIENT_SECRET"`
34
-
ClientKid string `env:"KNOT2_OAUTH_CLIENT_KID"`
35
-
}
36
-
37
-
func (c *Config) Uri() string {
38
-
// TODO: make port configurable
39
-
if c.Dev {
40
-
return "http://127.0.0.1:6444"
41
-
}
42
-
return "https://" + c.HostName
43
-
}
44
-
45
-
func (c *Config) ListenAddr() string {
46
-
return net.JoinHostPort(c.ListenHost, c.ListenPort)
47
-
}
48
-
49
-
func (c *Config) DbPath() string {
50
-
return path.Join(c.DataDir, "knot.db")
51
-
}
52
-
53
-
func (c *Config) GitMotdFilePath() string {
54
-
return path.Join(c.DataDir, "motd")
55
-
}
56
-
57
-
func (c *Config) Validate() error {
58
-
if c.HostName == "" {
59
-
return fmt.Errorf("knot hostname cannot be empty")
60
-
}
61
-
if c.OwnerDid == "" {
62
-
return fmt.Errorf("knot owner did cannot be empty")
63
-
}
64
-
return nil
65
-
}
66
-
67
-
func Load(ctx context.Context, path string) (Config, error) {
68
-
// NOTE: yaml.v3 package doesn't support "default" struct tag
69
-
cfg := Config{
70
-
Dev: true,
71
-
ListenHost: "0.0.0.0",
72
-
ListenPort: "5555",
73
-
DataDir: "/home/git",
74
-
RepoDir: "/home/git",
75
-
PlcUrl: "https://plc.directory",
76
-
JetstreamEndpoint: "wss://jetstream1.us-west.bsky.network/subscribe",
77
-
AppviewEndpoint: "https://tangled.org",
78
-
GitUserName: "Tangled",
79
-
GitUserEmail: "noreply@tangled.org",
80
-
}
81
-
// load config from env vars
82
-
err := envconfig.Process(ctx, &cfg.OAuth)
83
-
if err != nil {
84
-
return cfg, err
85
-
}
86
-
87
-
// load config from toml config file
88
-
bytes, err := os.ReadFile(path)
89
-
if err != nil {
90
-
return cfg, err
91
-
}
92
-
if err := yaml.Unmarshal(bytes, &cfg); err != nil {
93
-
return cfg, err
94
-
}
95
-
96
-
// validate the config
97
-
if err = cfg.Validate(); err != nil {
98
-
return cfg, err
99
-
}
100
-
return cfg, nil
101
-
}
-52
knot2/db/db.go
-52
knot2/db/db.go
···
1
-
package db
2
-
3
-
import (
4
-
"database/sql"
5
-
"strings"
6
-
7
-
_ "github.com/mattn/go-sqlite3"
8
-
)
9
-
10
-
func New(dbPath string) (*sql.DB, error) {
11
-
// https://github.com/mattn/go-sqlite3#connection-string
12
-
opts := []string{
13
-
"_foreign_keys=1",
14
-
"_journal_mode=WAL",
15
-
"_synchronous=NORMAL",
16
-
"_auto_vacuum=incremental",
17
-
}
18
-
19
-
return sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&"))
20
-
}
21
-
22
-
func Init(d *sql.DB) error {
23
-
_, err := d.Exec(`
24
-
create table if not exists _jetstream (
25
-
id integer primary key autoincrement,
26
-
last_time_us integer not null
27
-
);
28
-
29
-
create table if not exists events (
30
-
rkey text not null,
31
-
nsid text not null,
32
-
event text not null, -- json
33
-
created integer not null -- unix nanos
34
-
);
35
-
36
-
create table if not exists users (
37
-
id integer primary key autoincrement,
38
-
did text not null unique,
39
-
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
40
-
);
41
-
42
-
create table if not exists public_keys (
43
-
id integer primary key autoincrement,
44
-
did text not null,
45
-
key text not null,
46
-
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
47
-
unique(did, key)
48
-
);
49
-
`)
50
-
51
-
return err
52
-
}
-10
knot2/db/pubkeys.go
-10
knot2/db/pubkeys.go
-12
knot2/db/users.go
-12
knot2/db/users.go
-31
knot2/guard/guard.go
-31
knot2/guard/guard.go
···
1
-
package guard
2
-
3
-
import (
4
-
"context"
5
-
6
-
"github.com/urfave/cli/v3"
7
-
"tangled.org/core/log"
8
-
)
9
-
10
-
func Command() *cli.Command {
11
-
return &cli.Command{
12
-
Name: "guard",
13
-
Usage: "role-based access control for git over ssh (not for manual use)",
14
-
Action: Run,
15
-
Flags: []cli.Flag{
16
-
&cli.StringFlag{
17
-
Name: "user",
18
-
Usage: "allowed git user",
19
-
Required: true,
20
-
},
21
-
},
22
-
}
23
-
}
24
-
25
-
func Run(ctx context.Context, cmd *cli.Command) error {
26
-
l := log.FromContext(ctx)
27
-
l = log.SubLogger(l, cmd.Name)
28
-
ctx = log.IntoContext(ctx, l)
29
-
30
-
panic("unimplemented")
31
-
}
-27
knot2/hook/hook.go
-27
knot2/hook/hook.go
···
1
-
package hook
2
-
3
-
import (
4
-
"context"
5
-
6
-
"github.com/urfave/cli/v3"
7
-
"tangled.org/core/log"
8
-
)
9
-
10
-
func Command() *cli.Command {
11
-
return &cli.Command{
12
-
Name: "hook",
13
-
Usage: "run git hooks",
14
-
Action: Run,
15
-
Flags: []cli.Flag{
16
-
// TODO:
17
-
},
18
-
}
19
-
}
20
-
21
-
func Run(ctx context.Context, cmd *cli.Command) error {
22
-
l := log.FromContext(ctx)
23
-
l = log.SubLogger(l, cmd.Name)
24
-
ctx = log.IntoContext(ctx, l)
25
-
26
-
panic("unimplemented")
27
-
}
-103
knot2/keys/keys.go
-103
knot2/keys/keys.go
···
1
-
package keys
2
-
3
-
import (
4
-
"context"
5
-
"encoding/json"
6
-
"fmt"
7
-
"os"
8
-
"strings"
9
-
10
-
"github.com/urfave/cli/v3"
11
-
"tangled.org/core/knot2/config"
12
-
"tangled.org/core/knot2/db"
13
-
"tangled.org/core/log"
14
-
)
15
-
16
-
func Command() *cli.Command {
17
-
return &cli.Command{
18
-
Name: "keys",
19
-
Usage: "fetch public keys from the knot server",
20
-
Action: Run,
21
-
Flags: []cli.Flag{
22
-
&cli.StringFlag{
23
-
Name: "config",
24
-
Aliases: []string{"c"},
25
-
Usage: "config path",
26
-
Required: true,
27
-
},
28
-
&cli.StringFlag{
29
-
Name: "output",
30
-
Aliases: []string{"o"},
31
-
Usage: "output format (table, json, authorized-keys)",
32
-
Value: "table",
33
-
},
34
-
},
35
-
}
36
-
}
37
-
38
-
func Run(ctx context.Context, cmd *cli.Command) error {
39
-
l := log.FromContext(ctx)
40
-
l = log.SubLogger(l, cmd.Name)
41
-
ctx = log.IntoContext(ctx, l)
42
-
43
-
var (
44
-
output = cmd.String("output")
45
-
configPath = cmd.String("config")
46
-
)
47
-
48
-
cfg, err := config.Load(ctx, configPath)
49
-
if err != nil {
50
-
return fmt.Errorf("failed to load config: %w", err)
51
-
}
52
-
53
-
d, err := db.New(cfg.DbPath())
54
-
if err != nil {
55
-
return fmt.Errorf("failed to load db: %w", err)
56
-
}
57
-
58
-
pubkeyDidListMap, err := db.GetPubkeyDidListMap(d)
59
-
if err != nil {
60
-
return err
61
-
}
62
-
63
-
switch output {
64
-
case "json":
65
-
prettyJSON, err := json.MarshalIndent(pubkeyDidListMap, "", " ")
66
-
if err != nil {
67
-
return err
68
-
}
69
-
if _, err := os.Stdout.Write(prettyJSON); err != nil {
70
-
return err
71
-
}
72
-
case "table":
73
-
fmt.Printf("%-40s %-40s\n", "KEY", "DID")
74
-
fmt.Println(strings.Repeat("-", 80))
75
-
76
-
for key, didList := range pubkeyDidListMap {
77
-
fmt.Printf("%-40s %-40s\n", key, strings.Join(didList, ","))
78
-
}
79
-
case "authorized-keys":
80
-
for key, didList := range pubkeyDidListMap {
81
-
executablePath, err := os.Executable()
82
-
if err != nil {
83
-
l.Error("error getting path of executable", "error", err)
84
-
return err
85
-
}
86
-
command := fmt.Sprintf("%s guard", executablePath)
87
-
for _, did := range didList {
88
-
command += fmt.Sprintf(" -user %s", did)
89
-
}
90
-
fmt.Printf(
91
-
`command="%s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s`+"\n",
92
-
command,
93
-
key,
94
-
)
95
-
}
96
-
if err != nil {
97
-
l.Error("error writing to stdout", "error", err)
98
-
return err
99
-
}
100
-
}
101
-
102
-
return nil
103
-
}
-8
knot2/models/pubkeys.go
-8
knot2/models/pubkeys.go
-18
knot2/server/handler/events.go
-18
knot2/server/handler/events.go
···
1
-
package handler
2
-
3
-
import (
4
-
"net/http"
5
-
6
-
"github.com/gorilla/websocket"
7
-
)
8
-
9
-
var upgrader = websocket.Upgrader{
10
-
ReadBufferSize: 1024,
11
-
WriteBufferSize: 1024,
12
-
}
13
-
14
-
func Events() http.HandlerFunc {
15
-
return func(w http.ResponseWriter, r *http.Request) {
16
-
panic("unimplemented")
17
-
}
18
-
}
-9
knot2/server/handler/git_receive_pack.go
-9
knot2/server/handler/git_receive_pack.go
-9
knot2/server/handler/git_upload_pack.go
-9
knot2/server/handler/git_upload_pack.go
-9
knot2/server/handler/info_refs.go
-9
knot2/server/handler/info_refs.go
-241
knot2/server/handler/register.go
-241
knot2/server/handler/register.go
···
1
-
package handler
2
-
3
-
import (
4
-
"context"
5
-
"database/sql"
6
-
_ "embed"
7
-
"encoding/json"
8
-
"fmt"
9
-
"html/template"
10
-
"net/http"
11
-
"strings"
12
-
13
-
"github.com/bluesky-social/indigo/api/agnostic"
14
-
"github.com/bluesky-social/indigo/api/atproto"
15
-
"github.com/bluesky-social/indigo/atproto/auth/oauth"
16
-
"github.com/bluesky-social/indigo/atproto/syntax"
17
-
"github.com/did-method-plc/go-didplc"
18
-
"github.com/gorilla/sessions"
19
-
"tangled.org/core/knot2/config"
20
-
"tangled.org/core/knot2/db"
21
-
"tangled.org/core/log"
22
-
)
23
-
24
-
const (
25
-
// atproto
26
-
serviceId = "tangled_knot"
27
-
serviceType = "TangledKnot"
28
-
// cookies
29
-
sessionName = "oauth-demo"
30
-
sessionId = "sessionId"
31
-
sessionDid = "sessionDID"
32
-
)
33
-
34
-
//go:embed "templates/register.html"
35
-
var tmplRegisgerText string
36
-
var tmplRegister = template.Must(template.New("register.html").Parse(tmplRegisgerText))
37
-
38
-
func Register(jar *sessions.CookieStore) http.HandlerFunc {
39
-
return func(w http.ResponseWriter, r *http.Request) {
40
-
ctx := r.Context()
41
-
l := log.FromContext(ctx).With("handler", "Register")
42
-
43
-
sess, _ := jar.Get(r, sessionName)
44
-
var data map[string]any
45
-
46
-
if !sess.IsNew {
47
-
// render Register { Handle, Web: true }
48
-
did := syntax.DID(sess.Values[sessionDid].(string))
49
-
plcop := did.Method() == "plc" && r.URL.Query().Get("method") != "web"
50
-
data = map[string]any{
51
-
"Did": did,
52
-
"PlcOp": plcop,
53
-
}
54
-
}
55
-
56
-
err := tmplRegister.Execute(w, data)
57
-
if err != nil {
58
-
l.Error("failed to render", "err", err)
59
-
}
60
-
}
61
-
}
62
-
63
-
func OauthClientMetadata(cfg *config.Config, clientApp *oauth.ClientApp) http.HandlerFunc {
64
-
return func(w http.ResponseWriter, r *http.Request) {
65
-
doc := clientApp.Config.ClientMetadata()
66
-
var (
67
-
clientName = cfg.HostName
68
-
clientUri = cfg.Uri()
69
-
jwksUri = clientUri + "/oauth/jwks.json"
70
-
)
71
-
doc.ClientName = &clientName
72
-
doc.ClientURI = &clientUri
73
-
doc.JWKSURI = &jwksUri
74
-
75
-
w.Header().Set("Content-Type", "application/json")
76
-
if err := json.NewEncoder(w).Encode(doc); err != nil {
77
-
http.Error(w, err.Error(), http.StatusInternalServerError)
78
-
return
79
-
}
80
-
}
81
-
}
82
-
83
-
func OauthJwks(clientApp *oauth.ClientApp) http.HandlerFunc {
84
-
return func(w http.ResponseWriter, r *http.Request) {
85
-
w.Header().Set("Content-Type", "application/json")
86
-
body := clientApp.Config.PublicJWKS()
87
-
if err := json.NewEncoder(w).Encode(body); err != nil {
88
-
http.Error(w, err.Error(), http.StatusInternalServerError)
89
-
return
90
-
}
91
-
}
92
-
}
93
-
94
-
func OauthLoginPost(clientApp *oauth.ClientApp) http.HandlerFunc {
95
-
return func(w http.ResponseWriter, r *http.Request) {
96
-
ctx := r.Context()
97
-
l := log.FromContext(ctx).With("handler", "OauthLoginPost")
98
-
99
-
handle := r.FormValue("handle")
100
-
101
-
handle = strings.TrimPrefix(handle, "\u202a")
102
-
handle = strings.TrimSuffix(handle, "\u202c")
103
-
// `@` is harmless
104
-
handle = strings.TrimPrefix(handle, "@")
105
-
106
-
redirectURL, err := clientApp.StartAuthFlow(ctx, handle)
107
-
if err != nil {
108
-
l.Error("failed to start auth flow", "err", err)
109
-
panic(err)
110
-
}
111
-
112
-
w.Header().Set("HX-Redirect", redirectURL)
113
-
w.WriteHeader(http.StatusOK)
114
-
}
115
-
}
116
-
117
-
func OauthCallback(oauth *oauth.ClientApp, jar *sessions.CookieStore) http.HandlerFunc {
118
-
return func(w http.ResponseWriter, r *http.Request) {
119
-
ctx := r.Context()
120
-
l := log.FromContext(ctx).With("handler", "OauthCallback")
121
-
122
-
data, err := oauth.ProcessCallback(ctx, r.URL.Query())
123
-
if err != nil {
124
-
l.Error("failed to process oauth callback", "err", err)
125
-
panic(err)
126
-
}
127
-
128
-
// store session data to cookie jar
129
-
sess, _ := jar.Get(r, sessionName)
130
-
sess.Values[sessionDid] = data.AccountDID.String()
131
-
sess.Values[sessionId] = data.SessionID
132
-
if err = sess.Save(r, w); err != nil {
133
-
l.Error("failed to save session", "err", err)
134
-
panic(err)
135
-
}
136
-
137
-
if data.AccountDID.Method() == "plc" {
138
-
sess, err := oauth.ResumeSession(ctx, data.AccountDID, data.SessionID)
139
-
if err != nil {
140
-
l.Error("failed to resume atproto session", "err", err)
141
-
panic(err)
142
-
}
143
-
client := sess.APIClient()
144
-
err = atproto.IdentityRequestPlcOperationSignature(ctx, client)
145
-
if err != nil {
146
-
l.Error("failed to request plc operation signature", "err", err)
147
-
panic(err)
148
-
}
149
-
}
150
-
151
-
http.Redirect(w, r, "/register", http.StatusSeeOther)
152
-
}
153
-
}
154
-
155
-
func RegisterPost(cfg *config.Config, d *sql.DB, clientApp *oauth.ClientApp, jar *sessions.CookieStore) http.HandlerFunc {
156
-
plcop := func(ctx context.Context, did syntax.DID, sessId, token string) error {
157
-
sess, err := clientApp.ResumeSession(ctx, did, sessId)
158
-
if err != nil {
159
-
return fmt.Errorf("failed to resume atproto session: %w", err)
160
-
}
161
-
client := sess.APIClient()
162
-
163
-
identity, err := clientApp.Dir.LookupDID(ctx, did)
164
-
services := make(map[string]didplc.OpService)
165
-
for id, service := range identity.Services {
166
-
services[id] = didplc.OpService{
167
-
Type: service.Type,
168
-
Endpoint: service.URL,
169
-
}
170
-
}
171
-
services[serviceId] = didplc.OpService{
172
-
Type: serviceType,
173
-
Endpoint: cfg.Uri(),
174
-
}
175
-
176
-
rawServices, err := json.Marshal(services)
177
-
if err != nil {
178
-
return fmt.Errorf("failed to marshal services map: %w", err)
179
-
}
180
-
raw := json.RawMessage(rawServices)
181
-
182
-
signed, err := agnostic.IdentitySignPlcOperation(ctx, client, &agnostic.IdentitySignPlcOperation_Input{
183
-
Services: &raw,
184
-
Token: &token,
185
-
})
186
-
if err != nil {
187
-
return fmt.Errorf("failed to sign plc operatino: %w", err)
188
-
}
189
-
190
-
err = agnostic.IdentitySubmitPlcOperation(ctx, client, &agnostic.IdentitySubmitPlcOperation_Input{
191
-
Operation: signed.Operation,
192
-
})
193
-
if err != nil {
194
-
return fmt.Errorf("failed to submit plc operatino: %w", err)
195
-
}
196
-
197
-
return nil
198
-
}
199
-
return func(w http.ResponseWriter, r *http.Request) {
200
-
ctx := r.Context()
201
-
l := log.FromContext(ctx).With("handler", "RegisterPost")
202
-
203
-
sess, _ := jar.Get(r, sessionName)
204
-
205
-
var (
206
-
did = syntax.DID(sess.Values[sessionDid].(string))
207
-
sessId = sess.Values[sessionId].(string)
208
-
token = r.FormValue("token")
209
-
doPlcOp = r.FormValue("plcop") == "on"
210
-
)
211
-
212
-
tx, err := d.BeginTx(ctx, nil)
213
-
if err != nil {
214
-
l.Error("failed to begin db tx", "err", err)
215
-
panic(err)
216
-
}
217
-
defer tx.Rollback()
218
-
219
-
if err := db.AddUser(tx, did); err != nil {
220
-
l.Error("failed to add user", "err", err)
221
-
http.Error(w, err.Error(), http.StatusInternalServerError)
222
-
return
223
-
}
224
-
225
-
if doPlcOp {
226
-
l.Debug("performing plc op", "did", did, "token", token)
227
-
if err := plcop(ctx, did, sessId, token); err != nil {
228
-
l.Error("failed to perform plc op", "err", err)
229
-
http.Error(w, err.Error(), http.StatusInternalServerError)
230
-
}
231
-
} else {
232
-
// TODO: check if did doc already include the knot service
233
-
tx.Rollback()
234
-
panic("unimplemented")
235
-
}
236
-
if err := tx.Commit(); err != nil {
237
-
l.Error("failed to commit tx", "err", err)
238
-
http.Error(w, err.Error(), http.StatusInternalServerError)
239
-
}
240
-
}
241
-
}
-41
knot2/server/handler/templates/register.html
-41
knot2/server/handler/templates/register.html
···
1
-
<!doctype html>
2
-
<html lang="en" class="dark:bg-gray-900">
3
-
<head>
4
-
<meta charset="UTF-8" />
5
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
-
<meta name="description" content="knot server"/>
7
-
<title>Register to Knot</title>
8
-
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js" integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz" crossorigin="anonymous"></script>
9
-
</head>
10
-
<body>
11
-
{{ if (not .) }}
12
-
{{/* step 1. login */}}
13
-
<form hx-post="/oauth/login" hx-swap="none">
14
-
<input type="text" name="handle">
15
-
<button type="submit">Login</button>
16
-
</form>
17
-
{{ else }}
18
-
{{/* step 2. register user with plc operation */}}
19
-
<form hx-post="/register" hx-swap="none">
20
-
<input type="hidden" name="plcop" value="{{ if .PlcOp }}on{{ end }}">
21
-
22
-
<div>
23
-
<label for="handle">User Handle:</label>
24
-
<input type="text" name="handle" value="{{ .Did }}" readonly>
25
-
</div>
26
-
27
-
{{ if (not .Web) }}
28
-
<h2>Please enter your PLC Token you received in an email</h2>
29
-
<div>
30
-
<label for="token">PLC Token:</label>
31
-
<input type="text" name="token" required placeholder="XXXXX-XXXXX">
32
-
</div>
33
-
34
-
<button type="submit">add Knot to identity</button>
35
-
{{ else }}
36
-
<button type="submit">register to Knot</button>
37
-
{{ end }}
38
-
</form>
39
-
{{ end }}
40
-
</body>
41
-
</html>
-87
knot2/server/handler/xrpc_git_keep_commit.go
-87
knot2/server/handler/xrpc_git_keep_commit.go
···
1
-
package handler
2
-
3
-
import (
4
-
"encoding/json"
5
-
"fmt"
6
-
"net/http"
7
-
"os/exec"
8
-
"path"
9
-
10
-
"github.com/bluesky-social/indigo/atproto/syntax"
11
-
"github.com/go-git/go-git/v5"
12
-
"github.com/go-git/go-git/v5/plumbing"
13
-
"tangled.org/core/api/tangled"
14
-
"tangled.org/core/knot2/config"
15
-
"tangled.org/core/log"
16
-
xrpcerr "tangled.org/core/xrpc/errors"
17
-
)
18
-
19
-
func XrpcGitKeepCommit(cfg *config.Config) http.HandlerFunc {
20
-
return func(w http.ResponseWriter, r *http.Request) {
21
-
ctx := r.Context()
22
-
l := log.FromContext(ctx).With("handler", "XrpcGitKeepCommit")
23
-
24
-
// TODO: get session did
25
-
actorDid := syntax.DID("")
26
-
27
-
var input tangled.GitKeepCommit_Input
28
-
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
29
-
l.Error("failed to decode body", "err", err)
30
-
panic("unimplemented")
31
-
}
32
-
33
-
repoAt, err := syntax.ParseATURI(input.Repo)
34
-
if err != nil {
35
-
l.Error("failed to decode body", "err", err)
36
-
panic("unimplemented")
37
-
}
38
-
repoPath := repoPathFromAtUri(cfg, repoAt)
39
-
40
-
// ensure repo exist (if not, clone it)
41
-
repo, err := git.PlainOpen(repoPath)
42
-
if err != nil {
43
-
// TODO: clone the ref from source repo if repo doesn't exist in this knot yet
44
-
l.Info("repo missing in knot", "err", err)
45
-
panic("unimplemented")
46
-
}
47
-
48
-
commitId, err := repo.ResolveRevision(plumbing.Revision(input.Ref))
49
-
if err != nil {
50
-
l.Error("failed to resolve revision", "ref", input.Ref, "err", err)
51
-
panic("unimplemented")
52
-
}
53
-
54
-
// set keep-ref for given commit
55
-
refspec := fmt.Sprintf("refs/knot/%s/keep/%s", actorDid, commitId)
56
-
updateRefCmd := exec.Command("git", "-C", repoPath, "update-ref", refspec, commitId.String())
57
-
if err := updateRefCmd.Run(); err != nil {
58
-
writeError(w, xrpcerr.GenericError(err), http.StatusBadRequest)
59
-
return
60
-
}
61
-
62
-
output := tangled.GitKeepCommit_Output{
63
-
CommitId: commitId.String(),
64
-
}
65
-
66
-
w.WriteHeader(http.StatusOK)
67
-
writeJson(w, output)
68
-
}
69
-
}
70
-
71
-
func repoPathFromAtUri(cfg *config.Config, repoAt syntax.ATURI) string {
72
-
return path.Join(cfg.RepoDir, repoAt.Authority().String(), repoAt.RecordKey().String())
73
-
}
74
-
75
-
func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {
76
-
w.Header().Set("Content-Type", "application/json")
77
-
w.WriteHeader(status)
78
-
json.NewEncoder(w).Encode(e)
79
-
}
80
-
81
-
func writeJson(w http.ResponseWriter, response any) {
82
-
w.Header().Set("Content-Type", "application/json")
83
-
if err := json.NewEncoder(w).Encode(response); err != nil {
84
-
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
85
-
return
86
-
}
87
-
}
-21
knot2/server/middleware/cors.go
-21
knot2/server/middleware/cors.go
···
1
-
package middleware
2
-
3
-
import "net/http"
4
-
5
-
func CORS(next http.Handler) http.Handler {
6
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7
-
// Set CORS headers
8
-
w.Header().Set("Access-Control-Allow-Origin", "*")
9
-
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
10
-
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
11
-
w.Header().Set("Access-Control-Max-Age", "86400")
12
-
13
-
// Handle preflight requests
14
-
if r.Method == "OPTIONS" {
15
-
w.WriteHeader(http.StatusOK)
16
-
return
17
-
}
18
-
19
-
next.ServeHTTP(w, r)
20
-
})
21
-
}
-40
knot2/server/middleware/requestlogger.go
-40
knot2/server/middleware/requestlogger.go
···
1
-
package middleware
2
-
3
-
import (
4
-
"log/slog"
5
-
"net/http"
6
-
"time"
7
-
8
-
"tangled.org/core/log"
9
-
)
10
-
11
-
func RequestLogger(next http.Handler) http.Handler {
12
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13
-
ctx := r.Context()
14
-
l := log.FromContext(ctx)
15
-
16
-
start := time.Now()
17
-
18
-
next.ServeHTTP(w, r)
19
-
20
-
// Build query params as slog.Attrs for the group
21
-
queryParams := r.URL.Query()
22
-
queryAttrs := make([]any, 0, len(queryParams))
23
-
for key, values := range queryParams {
24
-
if len(values) == 1 {
25
-
queryAttrs = append(queryAttrs, slog.String(key, values[0]))
26
-
} else {
27
-
queryAttrs = append(queryAttrs, slog.Any(key, values))
28
-
}
29
-
}
30
-
31
-
l.LogAttrs(ctx, slog.LevelInfo, "",
32
-
slog.Group("request",
33
-
slog.String("method", r.Method),
34
-
slog.String("path", r.URL.Path),
35
-
slog.Group("query", queryAttrs...),
36
-
slog.Duration("duration", time.Since(start)),
37
-
),
38
-
)
39
-
})
40
-
}
-40
knot2/server/oauth.go
-40
knot2/server/oauth.go
···
1
-
package server
2
-
3
-
import (
4
-
"net/http"
5
-
6
-
atcrypto "github.com/bluesky-social/indigo/atproto/crypto"
7
-
"github.com/bluesky-social/indigo/atproto/auth/oauth"
8
-
"tangled.org/core/idresolver"
9
-
"tangled.org/core/knot2/config"
10
-
)
11
-
12
-
func newAtClientApp(cfg *config.Config) *oauth.ClientApp {
13
-
idResolver := idresolver.DefaultResolver(cfg.PlcUrl)
14
-
scopes := []string{"atproto", "identity:*"}
15
-
var oauthConfig oauth.ClientConfig
16
-
if cfg.Dev {
17
-
oauthConfig = oauth.NewLocalhostConfig(
18
-
cfg.Uri()+"/oauth/callback",
19
-
scopes,
20
-
)
21
-
} else {
22
-
oauthConfig = oauth.NewPublicConfig(
23
-
cfg.Uri()+"/oauth/client-metadata.json",
24
-
cfg.Uri()+"/oauth/callback",
25
-
scopes,
26
-
)
27
-
}
28
-
priv, err := atcrypto.ParsePrivateMultibase(cfg.OAuth.ClientSecret)
29
-
if err != nil {
30
-
panic(err)
31
-
}
32
-
if err := oauthConfig.SetClientSecret(priv, cfg.OAuth.ClientKid); err != nil {
33
-
panic(err)
34
-
}
35
-
// we can just use in-memory auth store
36
-
clientApp := oauth.NewClientApp(&oauthConfig, oauth.NewMemStore())
37
-
clientApp.Dir = idResolver.Directory()
38
-
clientApp.Resolver.Client.Transport = http.DefaultTransport
39
-
return clientApp
40
-
}
-52
knot2/server/routes.go
-52
knot2/server/routes.go
···
1
-
package server
2
-
3
-
import (
4
-
"database/sql"
5
-
"net/http"
6
-
7
-
"github.com/bluesky-social/indigo/atproto/auth/oauth"
8
-
"github.com/go-chi/chi/v5"
9
-
"github.com/gorilla/sessions"
10
-
"tangled.org/core/api/tangled"
11
-
"tangled.org/core/knot2/config"
12
-
"tangled.org/core/knot2/server/handler"
13
-
"tangled.org/core/knot2/server/middleware"
14
-
)
15
-
16
-
func Routes(
17
-
cfg *config.Config,
18
-
d *sql.DB,
19
-
clientApp *oauth.ClientApp,
20
-
) http.Handler {
21
-
r := chi.NewRouter()
22
-
23
-
r.Use(middleware.CORS)
24
-
r.Use(middleware.RequestLogger)
25
-
26
-
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
27
-
w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
28
-
})
29
-
30
-
jar := sessions.NewCookieStore([]byte(cfg.OAuth.CookieSecret))
31
-
32
-
r.Get("/register", handler.Register(jar))
33
-
r.Post("/register", handler.RegisterPost(cfg, d, clientApp, jar))
34
-
r.Post("/oauth/login", handler.OauthLoginPost(clientApp))
35
-
r.Get("/oauth/client-metadata.json", handler.OauthClientMetadata(cfg, clientApp))
36
-
r.Get("/oauth/jwks.json", handler.OauthJwks(clientApp))
37
-
r.Get("/oauth/callback", handler.OauthCallback(clientApp, jar))
38
-
39
-
r.Route("/{did}/{name}", func(r chi.Router) {
40
-
r.Get("/info/refs", handler.InfoRefs())
41
-
r.Post("/git-upload-pack", handler.GitUploadPack())
42
-
r.Post("/git-receive-pack", handler.GitReceivePack())
43
-
})
44
-
45
-
r.Get("/events", handler.Events())
46
-
47
-
r.Route("/xrpc", func(r chi.Router) {
48
-
r.Post("/"+tangled.GitKeepCommitNSID, handler.XrpcGitKeepCommit(cfg))
49
-
})
50
-
51
-
return r
52
-
}
-65
knot2/server/server.go
-65
knot2/server/server.go
···
1
-
package server
2
-
3
-
import (
4
-
"context"
5
-
"fmt"
6
-
"net/http"
7
-
8
-
"github.com/urfave/cli/v3"
9
-
"tangled.org/core/knot2/config"
10
-
"tangled.org/core/knot2/db"
11
-
"tangled.org/core/log"
12
-
)
13
-
14
-
func Command() *cli.Command {
15
-
return &cli.Command{
16
-
Name: "server",
17
-
Usage: "run a knot server",
18
-
Action: Run,
19
-
Flags: []cli.Flag{
20
-
&cli.StringFlag{
21
-
Name: "config",
22
-
Aliases: []string{"c"},
23
-
Usage: "config path",
24
-
Required: true,
25
-
},
26
-
},
27
-
}
28
-
}
29
-
30
-
func Run(ctx context.Context, cmd *cli.Command) error {
31
-
l := log.FromContext(ctx)
32
-
l = log.SubLogger(l, cmd.Name)
33
-
ctx = log.IntoContext(ctx, l)
34
-
35
-
configPath := cmd.String("config")
36
-
37
-
cfg, err := config.Load(ctx, configPath)
38
-
if err != nil {
39
-
return fmt.Errorf("failed to load config: %w", err)
40
-
}
41
-
fmt.Println("config:", cfg)
42
-
43
-
// TODO: start listening to jetstream
44
-
45
-
d, err := db.New(cfg.DbPath())
46
-
if err != nil {
47
-
panic(err)
48
-
}
49
-
err = db.Init(d)
50
-
if err != nil {
51
-
panic(err)
52
-
}
53
-
54
-
clientApp := newAtClientApp(&cfg)
55
-
56
-
mux := Routes(&cfg, d, clientApp)
57
-
58
-
l.Info("starting knot server", "address", cfg.ListenAddr())
59
-
err = http.ListenAndServe(cfg.ListenAddr(), mux)
60
-
if err != nil {
61
-
l.Error("server error", "err", err)
62
-
}
63
-
64
-
return nil
65
-
}
-46
lexicons/git/keepCommit.json
-46
lexicons/git/keepCommit.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.tangled.git.keepCommit",
4
-
"defs": {
5
-
"main": {
6
-
"type": "procedure",
7
-
"input": {
8
-
"encoding": "application/json",
9
-
"schema": {
10
-
"type": "object",
11
-
"required": ["repo", "ref"],
12
-
"properties": {
13
-
"repo": {
14
-
"type": "string",
15
-
"format": "at-uri",
16
-
"description": "AT-URI of the repository"
17
-
},
18
-
"ref": {
19
-
"type": "string",
20
-
"description": "ref to keep"
21
-
}
22
-
}
23
-
}
24
-
},
25
-
"output": {
26
-
"encoding": "application/json",
27
-
"schema": {
28
-
"type": "object",
29
-
"required": ["commitId"],
30
-
"properties": {
31
-
"commitId": {
32
-
"type": "string",
33
-
"description": "Keeped commit hash"
34
-
}
35
-
}
36
-
}
37
-
},
38
-
"errors": [
39
-
{
40
-
"name": "InternalServerError",
41
-
"description": "Failed to keep commit"
42
-
}
43
-
]
44
-
}
45
-
}
46
-
}
-3
nix/gomod2nix.toml
-3
nix/gomod2nix.toml
···
171
171
[mod."github.com/dgryski/go-rendezvous"]
172
172
version = "v0.0.0-20200823014737-9f7001d12a5f"
173
173
hash = "sha256-n/7xo5CQqo4yLaWMSzSN1Muk/oqK6O5dgDOFWapeDUI="
174
-
[mod."github.com/did-method-plc/go-didplc"]
175
-
version = "v0.0.0-20250716171643-635da8b4e038"
176
-
hash = "sha256-o0uB/5tryjdB44ssALFr49PtfY3nRJnEENmE187md1w="
177
174
[mod."github.com/distribution/reference"]
178
175
version = "v0.6.0"
179
176
hash = "sha256-gr4tL+qz4jKyAtl8LINcxMSanztdt+pybj1T+2ulQv4="
+5
-18
nix/modules/knot.nix
+5
-18
nix/modules/knot.nix
···
170
170
description = "Enable development mode (disables signature verification)";
171
171
};
172
172
};
173
-
174
-
environmentFile = mkOption {
175
-
type = with types; nullOr path;
176
-
default = null;
177
-
example = "/etc/appview.env";
178
-
description = ''
179
-
Additional environment file as defined in {manpage}`systemd.exec(5)`.
180
-
181
-
Sensitive secrets such as {env}`KNOT_COOKIE_SECRET`,
182
-
{env}`KNOT_OAUTH_CLIENT_SECRET`, and {env}`KNOT_OAUTH_CLIENT_KID`
183
-
may be passed to the service without making them world readable in the nix store.
184
-
'';
185
-
};
186
173
};
187
174
};
188
175
···
218
205
text = ''
219
206
#!${pkgs.stdenv.shell}
220
207
${cfg.package}/bin/knot keys \
221
-
-config ${cfg.stateDir}/config.yml \
222
-
-output authorized-keys
208
+
-output authorized-keys \
209
+
-internal-api "http://${cfg.server.internalListenAddr}" \
210
+
-git-dir "${cfg.repo.scanPath}" \
211
+
-log-path /tmp/knotguard.log
223
212
'';
224
213
};
225
214
···
284
273
else "false"
285
274
}"
286
275
];
287
-
EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
288
-
ExecStart = "${cfg.package}/bin/knot server -config ${cfg.stateDir}/config.yml";
276
+
ExecStart = "${cfg.package}/bin/knot server";
289
277
Restart = "always";
290
-
RestartSec = 5;
291
278
};
292
279
};
293
280
-1
nix/vm.nix
-1
nix/vm.nix
+122
orm/orm.go
+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
+
}