-1
appview/config.go
-1
appview/config.go
···
17
17
CamoSharedSecret string `env:"TANGLED_CAMO_SHARED_SECRET"`
18
18
AvatarSharedSecret string `env:"TANGLED_AVATAR_SHARED_SECRET"`
19
19
AvatarHost string `env:"TANGLED_AVATAR_HOST, default=https://avatar.tangled.sh"`
20
-
EnableTelemetry bool `env:"TANGLED_TELEMETRY_ENABLED, default=false"`
21
20
}
22
21
23
22
func LoadConfig(ctx context.Context) (*Config, error) {
+6
-31
appview/db/issues.go
+6
-31
appview/db/issues.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"database/sql"
6
5
"time"
7
6
8
7
"github.com/bluesky-social/indigo/atproto/syntax"
9
-
"go.opentelemetry.io/otel"
10
-
"go.opentelemetry.io/otel/attribute"
11
8
"tangled.sh/tangled.sh/core/appview/pagination"
12
9
)
13
10
···
106
103
return ownerDid, err
107
104
}
108
105
109
-
func GetIssues(ctx context.Context, e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) {
110
-
ctx, span := otel.Tracer("db").Start(ctx, "GetIssues")
111
-
defer span.End()
112
-
113
-
span.SetAttributes(
114
-
attribute.String("repo_at", repoAt.String()),
115
-
attribute.Bool("is_open", isOpen),
116
-
attribute.Int("page.offset", page.Offset),
117
-
attribute.Int("page.limit", page.Limit),
118
-
)
119
-
106
+
func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) {
120
107
var issues []Issue
121
108
openValue := 0
122
109
if isOpen {
123
110
openValue = 1
124
111
}
125
112
126
-
rows, err := e.QueryContext(
127
-
ctx,
113
+
rows, err := e.Query(
128
114
`
129
115
with numbered_issue as (
130
116
select
···
153
139
body,
154
140
open,
155
141
comment_count
156
-
from
142
+
from
157
143
numbered_issue
158
-
where
144
+
where
159
145
row_num between ? and ?`,
160
146
repoAt, openValue, page.Offset+1, page.Offset+page.Limit)
161
147
if err != nil {
162
-
span.RecordError(err)
163
148
return nil, err
164
149
}
165
150
defer rows.Close()
···
170
155
var metadata IssueMetadata
171
156
err := rows.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)
172
157
if err != nil {
173
-
span.RecordError(err)
174
158
return nil, err
175
159
}
176
160
177
161
createdTime, err := time.Parse(time.RFC3339, createdAt)
178
162
if err != nil {
179
-
span.RecordError(err)
180
163
return nil, err
181
164
}
182
165
issue.Created = createdTime
···
186
169
}
187
170
188
171
if err := rows.Err(); err != nil {
189
-
span.RecordError(err)
190
172
return nil, err
191
173
}
192
174
193
-
span.SetAttributes(attribute.Int("issues.count", len(issues)))
194
175
return issues, nil
195
176
}
196
177
···
275
256
return issues, nil
276
257
}
277
258
278
-
func GetIssue(ctx context.Context, e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) {
279
-
ctx, span := otel.Tracer("db").Start(ctx, "GetIssue")
280
-
defer span.End()
281
-
259
+
func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) {
282
260
query := `select owner_did, created, title, body, open from issues where repo_at = ? and issue_id = ?`
283
261
row := e.QueryRow(query, repoAt, issueId)
284
262
···
298
276
return &issue, nil
299
277
}
300
278
301
-
func GetIssueWithComments(ctx context.Context, e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {
302
-
ctx, span := otel.Tracer("db").Start(ctx, "GetIssueWithComments")
303
-
defer span.End()
304
-
279
+
func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {
305
280
query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?`
306
281
row := e.QueryRow(query, repoAt, issueId)
307
282
+3
-29
appview/db/profile.go
+3
-29
appview/db/profile.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"fmt"
6
5
"time"
7
-
8
-
"go.opentelemetry.io/otel/attribute"
9
-
"go.opentelemetry.io/otel/codes"
10
-
"go.opentelemetry.io/otel/trace"
11
6
)
12
7
13
8
type RepoEvent struct {
···
88
83
89
84
const TimeframeMonths = 7
90
85
91
-
func MakeProfileTimeline(ctx context.Context, e Execer, forDid string) (*ProfileTimeline, error) {
92
-
span := trace.SpanFromContext(ctx)
93
-
defer span.End()
94
-
95
-
span.SetAttributes(
96
-
attribute.String("forDid", forDid),
97
-
)
98
-
86
+
func MakeProfileTimeline(e Execer, forDid string) (*ProfileTimeline, error) {
99
87
timeline := ProfileTimeline{
100
88
ByMonth: make([]ByMonth, TimeframeMonths),
101
89
}
···
104
92
105
93
pulls, err := GetPullsByOwnerDid(e, forDid, timeframe)
106
94
if err != nil {
107
-
span.RecordError(err)
108
-
span.SetStatus(codes.Error, "error getting pulls by owner did")
109
95
return nil, fmt.Errorf("error getting pulls by owner did: %w", err)
110
96
}
111
97
112
-
span.SetAttributes(attribute.Int("pulls.count", len(pulls)))
113
-
114
98
// group pulls by month
115
99
for _, pull := range pulls {
116
100
pullMonth := pull.Created.Month()
···
128
112
129
113
issues, err := GetIssuesByOwnerDid(e, forDid, timeframe)
130
114
if err != nil {
131
-
span.RecordError(err)
132
-
span.SetStatus(codes.Error, "error getting issues by owner did")
133
115
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
134
116
}
135
117
136
-
span.SetAttributes(attribute.Int("issues.count", len(issues)))
137
-
138
118
for _, issue := range issues {
139
119
issueMonth := issue.Created.Month()
140
120
···
149
129
*items = append(*items, &issue)
150
130
}
151
131
152
-
repos, err := GetAllReposByDid(ctx, e, forDid)
132
+
repos, err := GetAllReposByDid(e, forDid)
153
133
if err != nil {
154
-
span.RecordError(err)
155
-
span.SetStatus(codes.Error, "error getting all repos by did")
156
134
return nil, fmt.Errorf("error getting all repos by did: %w", err)
157
135
}
158
-
159
-
span.SetAttributes(attribute.Int("repos.count", len(repos)))
160
136
161
137
for _, repo := range repos {
162
138
// TODO: get this in the original query; requires COALESCE because nullable
163
139
var sourceRepo *Repo
164
140
if repo.Source != "" {
165
-
sourceRepo, err = GetRepoByAtUri(ctx, e, repo.Source)
141
+
sourceRepo, err = GetRepoByAtUri(e, repo.Source)
166
142
if err != nil {
167
-
span.RecordError(err)
168
-
span.SetStatus(codes.Error, "error getting repo by at uri")
169
143
return nil, err
170
144
}
171
145
}
+25
-111
appview/db/pulls.go
+25
-111
appview/db/pulls.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"database/sql"
6
5
"fmt"
7
6
"log"
···
11
10
12
11
"github.com/bluekeyes/go-gitdiff/gitdiff"
13
12
"github.com/bluesky-social/indigo/atproto/syntax"
14
-
"go.opentelemetry.io/otel/attribute"
15
-
"go.opentelemetry.io/otel/trace"
16
13
"tangled.sh/tangled.sh/core/api/tangled"
17
14
"tangled.sh/tangled.sh/core/patchutil"
18
15
"tangled.sh/tangled.sh/core/types"
···
237
234
return patches
238
235
}
239
236
240
-
func NewPull(ctx context.Context, tx *sql.Tx, pull *Pull) error {
241
-
span := trace.SpanFromContext(ctx)
242
-
defer span.End()
243
-
244
-
span.SetAttributes(
245
-
attribute.String("repo.at", pull.RepoAt.String()),
246
-
attribute.String("owner.did", pull.OwnerDid),
247
-
attribute.String("title", pull.Title),
248
-
attribute.String("target_branch", pull.TargetBranch),
249
-
)
250
-
span.AddEvent("creating new pull request")
251
-
237
+
func NewPull(tx *sql.Tx, pull *Pull) error {
252
238
defer tx.Rollback()
253
239
254
240
_, err := tx.Exec(`
···
256
242
values (?, 1)
257
243
`, pull.RepoAt)
258
244
if err != nil {
259
-
span.RecordError(err)
260
245
return err
261
246
}
262
247
···
268
253
returning next_pull_id - 1
269
254
`, pull.RepoAt).Scan(&nextId)
270
255
if err != nil {
271
-
span.RecordError(err)
272
256
return err
273
257
}
274
258
275
259
pull.PullId = nextId
276
260
pull.State = PullOpen
277
261
278
-
span.SetAttributes(attribute.Int("pull.id", pull.PullId))
279
-
span.AddEvent("assigned pull ID")
280
-
281
262
var sourceBranch, sourceRepoAt *string
282
263
if pull.PullSource != nil {
283
264
sourceBranch = &pull.PullSource.Branch
···
303
284
sourceRepoAt,
304
285
)
305
286
if err != nil {
306
-
span.RecordError(err)
307
287
return err
308
288
}
309
-
310
-
span.AddEvent("inserted pull record")
311
289
312
290
_, err = tx.Exec(`
313
291
insert into pull_submissions (pull_id, repo_at, round_number, patch, source_rev)
314
292
values (?, ?, ?, ?, ?)
315
293
`, pull.PullId, pull.RepoAt, 0, pull.Submissions[0].Patch, pull.Submissions[0].SourceRev)
316
294
if err != nil {
317
-
span.RecordError(err)
318
295
return err
319
296
}
320
-
321
-
span.AddEvent("inserted initial pull submission")
322
297
323
298
if err := tx.Commit(); err != nil {
324
-
span.RecordError(err)
325
299
return err
326
300
}
327
301
328
-
span.AddEvent("transaction committed successfully")
329
302
return nil
330
303
}
331
304
332
-
func GetPullAt(ctx context.Context, e Execer, repoAt syntax.ATURI, pullId int) (syntax.ATURI, error) {
333
-
pull, err := GetPull(ctx, e, repoAt, pullId)
305
+
func GetPullAt(e Execer, repoAt syntax.ATURI, pullId int) (syntax.ATURI, error) {
306
+
pull, err := GetPull(e, repoAt, pullId)
334
307
if err != nil {
335
308
return "", err
336
309
}
···
343
316
return pullId - 1, err
344
317
}
345
318
346
-
func GetPulls(ctx context.Context, e Execer, repoAt syntax.ATURI, state PullState) ([]*Pull, error) {
347
-
span := trace.SpanFromContext(ctx)
348
-
defer span.End()
349
-
350
-
span.SetAttributes(
351
-
attribute.String("repoAt", repoAt.String()),
352
-
attribute.String("state", state.String()),
353
-
)
354
-
span.AddEvent("querying pulls")
355
-
319
+
func GetPulls(e Execer, repoAt syntax.ATURI, state PullState) ([]*Pull, error) {
356
320
pulls := make(map[int]*Pull)
357
321
358
-
rows, err := e.QueryContext(ctx, `
322
+
rows, err := e.Query(`
359
323
select
360
324
owner_did,
361
325
pull_id,
···
372
336
where
373
337
repo_at = ? and state = ?`, repoAt, state)
374
338
if err != nil {
375
-
span.RecordError(err)
376
339
return nil, err
377
340
}
378
341
defer rows.Close()
···
394
357
&sourceRepoAt,
395
358
)
396
359
if err != nil {
397
-
span.RecordError(err)
398
360
return nil, err
399
361
}
400
362
401
363
createdTime, err := time.Parse(time.RFC3339, createdAt)
402
364
if err != nil {
403
-
span.RecordError(err)
404
365
return nil, err
405
366
}
406
367
pull.Created = createdTime
···
412
373
if sourceRepoAt.Valid {
413
374
sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String)
414
375
if err != nil {
415
-
span.RecordError(err)
416
376
return nil, err
417
377
}
418
378
pull.PullSource.RepoAt = &sourceRepoAtParsed
···
422
382
pulls[pull.PullId] = &pull
423
383
}
424
384
425
-
span.AddEvent("querying pull submissions")
426
-
span.SetAttributes(attribute.Int("pull_count", len(pulls)))
427
-
428
385
// get latest round no. for each pull
429
386
inClause := strings.TrimSuffix(strings.Repeat("?, ", len(pulls)), ", ")
430
387
submissionsQuery := fmt.Sprintf(`
···
443
400
args[idx] = p.PullId
444
401
idx += 1
445
402
}
446
-
submissionsRows, err := e.QueryContext(ctx, submissionsQuery, args...)
403
+
submissionsRows, err := e.Query(submissionsQuery, args...)
447
404
if err != nil {
448
-
span.RecordError(err)
449
405
return nil, err
450
406
}
451
407
defer submissionsRows.Close()
···
458
414
&s.RoundNumber,
459
415
)
460
416
if err != nil {
461
-
span.RecordError(err)
462
417
return nil, err
463
418
}
464
419
···
468
423
}
469
424
}
470
425
if err := rows.Err(); err != nil {
471
-
span.RecordError(err)
472
426
return nil, err
473
427
}
474
-
475
-
span.AddEvent("querying pull comments")
476
428
477
429
// get comment count on latest submission on each pull
478
430
inClause = strings.TrimSuffix(strings.Repeat("?, ", len(pulls)), ", ")
···
491
443
for _, p := range pulls {
492
444
args = append(args, p.Submissions[p.LastRoundNumber()].ID)
493
445
}
494
-
commentsRows, err := e.QueryContext(ctx, commentsQuery, args...)
446
+
commentsRows, err := e.Query(commentsQuery, args...)
495
447
if err != nil {
496
-
span.RecordError(err)
497
448
return nil, err
498
449
}
499
450
defer commentsRows.Close()
···
505
456
&pullId,
506
457
)
507
458
if err != nil {
508
-
span.RecordError(err)
509
459
return nil, err
510
460
}
511
461
if p, ok := pulls[pullId]; ok {
···
513
463
}
514
464
}
515
465
if err := rows.Err(); err != nil {
516
-
span.RecordError(err)
517
466
return nil, err
518
467
}
519
-
520
-
span.AddEvent("sorting pulls by date")
521
468
522
469
orderedByDate := []*Pull{}
523
470
for _, p := range pulls {
···
527
474
return orderedByDate[i].Created.After(orderedByDate[j].Created)
528
475
})
529
476
530
-
span.SetAttributes(attribute.Int("result_count", len(orderedByDate)))
531
477
return orderedByDate, nil
532
478
}
533
479
534
-
func GetPull(ctx context.Context, e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) {
535
-
span := trace.SpanFromContext(ctx)
536
-
defer span.End()
537
-
538
-
span.SetAttributes(attribute.String("repoAt", repoAt.String()), attribute.Int("pull.id", pullId))
539
-
span.AddEvent("query pull metadata")
540
-
480
+
func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) {
541
481
query := `
542
482
select
543
483
owner_did,
···
556
496
where
557
497
repo_at = ? and pull_id = ?
558
498
`
559
-
row := e.QueryRowContext(ctx, query, repoAt, pullId)
499
+
row := e.QueryRow(query, repoAt, pullId)
560
500
561
501
var pull Pull
562
502
var createdAt string
···
575
515
&sourceRepoAt,
576
516
)
577
517
if err != nil {
578
-
span.RecordError(err)
579
518
return nil, err
580
519
}
581
520
582
521
createdTime, err := time.Parse(time.RFC3339, createdAt)
583
522
if err != nil {
584
-
span.RecordError(err)
585
523
return nil, err
586
524
}
587
525
pull.Created = createdTime
588
526
527
+
// populate source
589
528
if sourceBranch.Valid {
590
529
pull.PullSource = &PullSource{
591
530
Branch: sourceBranch.String,
···
593
532
if sourceRepoAt.Valid {
594
533
sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String)
595
534
if err != nil {
596
-
span.RecordError(err)
597
535
return nil, err
598
536
}
599
537
pull.PullSource.RepoAt = &sourceRepoAtParsed
600
538
}
601
539
}
602
540
603
-
span.AddEvent("query submissions")
604
541
submissionsQuery := `
605
542
select
606
543
id, pull_id, repo_at, round_number, patch, created, source_rev
···
609
546
where
610
547
repo_at = ? and pull_id = ?
611
548
`
612
-
submissionsRows, err := e.QueryContext(ctx, submissionsQuery, repoAt, pullId)
549
+
submissionsRows, err := e.Query(submissionsQuery, repoAt, pullId)
613
550
if err != nil {
614
-
span.RecordError(err)
615
551
return nil, err
616
552
}
617
553
defer submissionsRows.Close()
···
632
568
&submissionSourceRev,
633
569
)
634
570
if err != nil {
635
-
span.RecordError(err)
636
571
return nil, err
637
572
}
638
573
639
574
submissionCreatedTime, err := time.Parse(time.RFC3339, submissionCreatedStr)
640
575
if err != nil {
641
-
span.RecordError(err)
642
576
return nil, err
643
577
}
644
578
submission.Created = submissionCreatedTime
···
650
584
submissionsMap[submission.ID] = &submission
651
585
}
652
586
if err = submissionsRows.Close(); err != nil {
653
-
span.RecordError(err)
654
587
return nil, err
655
588
}
656
589
if len(submissionsMap) == 0 {
···
662
595
args = append(args, k)
663
596
}
664
597
inClause := strings.TrimSuffix(strings.Repeat("?, ", len(submissionsMap)), ", ")
665
-
666
-
span.AddEvent("query comments")
667
598
commentsQuery := fmt.Sprintf(`
668
599
select
669
600
id,
···
681
612
order by
682
613
created asc
683
614
`, inClause)
684
-
commentsRows, err := e.QueryContext(ctx, commentsQuery, args...)
615
+
commentsRows, err := e.Query(commentsQuery, args...)
685
616
if err != nil {
686
-
span.RecordError(err)
687
617
return nil, err
688
618
}
689
619
defer commentsRows.Close()
···
702
632
&commentCreatedStr,
703
633
)
704
634
if err != nil {
705
-
span.RecordError(err)
706
635
return nil, err
707
636
}
708
637
709
638
commentCreatedTime, err := time.Parse(time.RFC3339, commentCreatedStr)
710
639
if err != nil {
711
-
span.RecordError(err)
712
640
return nil, err
713
641
}
714
642
comment.Created = commentCreatedTime
715
643
644
+
// Add the comment to its submission
716
645
if submission, ok := submissionsMap[comment.SubmissionId]; ok {
717
646
submission.Comments = append(submission.Comments, comment)
718
647
}
648
+
719
649
}
720
650
if err = commentsRows.Err(); err != nil {
721
-
span.RecordError(err)
722
651
return nil, err
723
652
}
724
653
725
-
if pull.PullSource != nil && pull.PullSource.RepoAt != nil {
726
-
span.AddEvent("query pull source repo")
727
-
pullSourceRepo, err := GetRepoByAtUri(ctx, e, pull.PullSource.RepoAt.String())
728
-
if err != nil {
729
-
span.RecordError(err)
730
-
log.Printf("failed to get repo by at uri: %v", err)
731
-
} else {
732
-
pull.PullSource.Repo = pullSourceRepo
654
+
var pullSourceRepo *Repo
655
+
if pull.PullSource != nil {
656
+
if pull.PullSource.RepoAt != nil {
657
+
pullSourceRepo, err = GetRepoByAtUri(e, pull.PullSource.RepoAt.String())
658
+
if err != nil {
659
+
log.Printf("failed to get repo by at uri: %v", err)
660
+
} else {
661
+
pull.PullSource.Repo = pullSourceRepo
662
+
}
733
663
}
734
664
}
735
665
···
817
747
return pulls, nil
818
748
}
819
749
820
-
func NewPullComment(ctx context.Context, e Execer, comment *PullComment) (int64, error) {
821
-
span := trace.SpanFromContext(ctx)
822
-
defer span.End()
823
-
824
-
span.SetAttributes(
825
-
attribute.String("repo.at", comment.RepoAt),
826
-
attribute.Int("pull.id", comment.PullId),
827
-
attribute.Int("submission.id", comment.SubmissionId),
828
-
attribute.String("owner.did", comment.OwnerDid),
829
-
)
830
-
span.AddEvent("inserting new pull comment")
831
-
750
+
func NewPullComment(e Execer, comment *PullComment) (int64, error) {
832
751
query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)`
833
-
res, err := e.ExecContext(
834
-
ctx,
752
+
res, err := e.Exec(
835
753
query,
836
754
comment.OwnerDid,
837
755
comment.RepoAt,
···
841
759
comment.Body,
842
760
)
843
761
if err != nil {
844
-
span.RecordError(err)
845
762
return 0, err
846
763
}
847
764
848
765
i, err := res.LastInsertId()
849
766
if err != nil {
850
-
span.RecordError(err)
851
767
return 0, err
852
768
}
853
769
854
-
span.SetAttributes(attribute.Int64("comment.id", i))
855
-
span.AddEvent("pull comment created successfully")
856
770
return i, nil
857
771
}
858
772
+12
-114
appview/db/repos.go
+12
-114
appview/db/repos.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"database/sql"
6
5
"time"
7
6
8
7
"github.com/bluesky-social/indigo/atproto/syntax"
9
-
"go.opentelemetry.io/otel"
10
-
"go.opentelemetry.io/otel/attribute"
11
8
)
12
9
13
10
type Repo struct {
···
26
23
Source string
27
24
}
28
25
29
-
func GetAllRepos(ctx context.Context, e Execer, limit int) ([]Repo, error) {
30
-
ctx, span := otel.Tracer("db").Start(ctx, "GetAllRepos")
31
-
defer span.End()
32
-
span.SetAttributes(attribute.Int("limit", limit))
33
-
26
+
func GetAllRepos(e Execer, limit int) ([]Repo, error) {
34
27
var repos []Repo
35
28
36
29
rows, err := e.Query(
···
42
35
limit,
43
36
)
44
37
if err != nil {
45
-
span.RecordError(err)
46
38
return nil, err
47
39
}
48
40
defer rows.Close()
···
53
45
rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created, &repo.Source,
54
46
)
55
47
if err != nil {
56
-
span.RecordError(err)
57
48
return nil, err
58
49
}
59
50
repos = append(repos, repo)
60
51
}
61
52
62
53
if err := rows.Err(); err != nil {
63
-
span.RecordError(err)
64
54
return nil, err
65
55
}
66
56
67
-
span.SetAttributes(attribute.Int("repos.count", len(repos)))
68
57
return repos, nil
69
58
}
70
59
71
-
func GetAllReposByDid(ctx context.Context, e Execer, did string) ([]Repo, error) {
72
-
ctx, span := otel.Tracer("db").Start(ctx, "GetAllReposByDid")
73
-
defer span.End()
74
-
span.SetAttributes(attribute.String("did", did))
75
-
60
+
func GetAllReposByDid(e Execer, did string) ([]Repo, error) {
76
61
var repos []Repo
77
62
78
63
rows, err := e.Query(
···
96
81
order by r.created desc`,
97
82
did)
98
83
if err != nil {
99
-
span.RecordError(err)
100
84
return nil, err
101
85
}
102
86
defer rows.Close()
···
110
94
111
95
err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount, &nullableSource)
112
96
if err != nil {
113
-
span.RecordError(err)
114
97
return nil, err
115
98
}
116
99
···
135
118
}
136
119
137
120
if err := rows.Err(); err != nil {
138
-
span.RecordError(err)
139
121
return nil, err
140
122
}
141
123
142
-
span.SetAttributes(attribute.Int("repos.count", len(repos)))
143
124
return repos, nil
144
125
}
145
126
146
-
func GetRepo(ctx context.Context, e Execer, did, name string) (*Repo, error) {
147
-
ctx, span := otel.Tracer("db").Start(ctx, "GetRepo")
148
-
defer span.End()
149
-
span.SetAttributes(
150
-
attribute.String("did", did),
151
-
attribute.String("name", name),
152
-
)
153
-
127
+
func GetRepo(e Execer, did, name string) (*Repo, error) {
154
128
var repo Repo
155
129
var nullableDescription sql.NullString
156
130
···
158
132
159
133
var createdAt string
160
134
if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil {
161
-
span.RecordError(err)
162
135
return nil, err
163
136
}
164
137
createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
···
173
146
return &repo, nil
174
147
}
175
148
176
-
func GetRepoByAtUri(ctx context.Context, e Execer, atUri string) (*Repo, error) {
177
-
ctx, span := otel.Tracer("db").Start(ctx, "GetRepoByAtUri")
178
-
defer span.End()
179
-
span.SetAttributes(attribute.String("atUri", atUri))
180
-
149
+
func GetRepoByAtUri(e Execer, atUri string) (*Repo, error) {
181
150
var repo Repo
182
151
var nullableDescription sql.NullString
183
152
···
185
154
186
155
var createdAt string
187
156
if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil {
188
-
span.RecordError(err)
189
157
return nil, err
190
158
}
191
159
createdAtTime, _ := time.Parse(time.RFC3339, createdAt)
···
200
168
return &repo, nil
201
169
}
202
170
203
-
func AddRepo(ctx context.Context, e Execer, repo *Repo) error {
204
-
ctx, span := otel.Tracer("db").Start(ctx, "AddRepo")
205
-
defer span.End()
206
-
span.SetAttributes(
207
-
attribute.String("did", repo.Did),
208
-
attribute.String("name", repo.Name),
209
-
)
210
-
171
+
func AddRepo(e Execer, repo *Repo) error {
211
172
_, err := e.Exec(
212
173
`insert into repos
213
174
(did, name, knot, rkey, at_uri, description, source)
214
175
values (?, ?, ?, ?, ?, ?, ?)`,
215
176
repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri, repo.Description, repo.Source,
216
177
)
217
-
if err != nil {
218
-
span.RecordError(err)
219
-
}
220
178
return err
221
179
}
222
180
223
-
func RemoveRepo(ctx context.Context, e Execer, did, name string) error {
224
-
ctx, span := otel.Tracer("db").Start(ctx, "RemoveRepo")
225
-
defer span.End()
226
-
span.SetAttributes(
227
-
attribute.String("did", did),
228
-
attribute.String("name", name),
229
-
)
230
-
181
+
func RemoveRepo(e Execer, did, name string) error {
231
182
_, err := e.Exec(`delete from repos where did = ? and name = ?`, did, name)
232
-
if err != nil {
233
-
span.RecordError(err)
234
-
}
235
183
return err
236
184
}
237
185
238
-
func GetRepoSource(ctx context.Context, e Execer, repoAt syntax.ATURI) (string, error) {
239
-
ctx, span := otel.Tracer("db").Start(ctx, "GetRepoSource")
240
-
defer span.End()
241
-
span.SetAttributes(attribute.String("repoAt", repoAt.String()))
242
-
186
+
func GetRepoSource(e Execer, repoAt syntax.ATURI) (string, error) {
243
187
var nullableSource sql.NullString
244
188
err := e.QueryRow(`select source from repos where at_uri = ?`, repoAt).Scan(&nullableSource)
245
189
if err != nil {
246
-
span.RecordError(err)
247
190
return "", err
248
191
}
249
192
return nullableSource.String, nil
250
193
}
251
194
252
-
func GetForksByDid(ctx context.Context, e Execer, did string) ([]Repo, error) {
253
-
ctx, span := otel.Tracer("db").Start(ctx, "GetForksByDid")
254
-
defer span.End()
255
-
span.SetAttributes(attribute.String("did", did))
256
-
195
+
func GetForksByDid(e Execer, did string) ([]Repo, error) {
257
196
var repos []Repo
258
197
259
198
rows, err := e.Query(
···
264
203
did,
265
204
)
266
205
if err != nil {
267
-
span.RecordError(err)
268
206
return nil, err
269
207
}
270
208
defer rows.Close()
···
277
215
278
216
err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource)
279
217
if err != nil {
280
-
span.RecordError(err)
281
218
return nil, err
282
219
}
283
220
···
300
237
}
301
238
302
239
if err := rows.Err(); err != nil {
303
-
span.RecordError(err)
304
240
return nil, err
305
241
}
306
242
307
-
span.SetAttributes(attribute.Int("forks.count", len(repos)))
308
243
return repos, nil
309
244
}
310
245
311
-
func GetForkByDid(ctx context.Context, e Execer, did string, name string) (*Repo, error) {
312
-
ctx, span := otel.Tracer("db").Start(ctx, "GetForkByDid")
313
-
defer span.End()
314
-
span.SetAttributes(
315
-
attribute.String("did", did),
316
-
attribute.String("name", name),
317
-
)
318
-
246
+
func GetForkByDid(e Execer, did string, name string) (*Repo, error) {
319
247
var repo Repo
320
248
var createdAt string
321
249
var nullableDescription sql.NullString
···
330
258
331
259
err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource)
332
260
if err != nil {
333
-
span.RecordError(err)
334
261
return nil, err
335
262
}
336
263
···
352
279
return &repo, nil
353
280
}
354
281
355
-
func AddCollaborator(ctx context.Context, e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error {
356
-
ctx, span := otel.Tracer("db").Start(ctx, "AddCollaborator")
357
-
defer span.End()
358
-
span.SetAttributes(
359
-
attribute.String("collaborator", collaborator),
360
-
attribute.String("repoOwnerDid", repoOwnerDid),
361
-
attribute.String("repoName", repoName),
362
-
)
363
-
282
+
func AddCollaborator(e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error {
364
283
_, err := e.Exec(
365
284
`insert into collaborators (did, repo)
366
285
values (?, (select id from repos where did = ? and name = ? and knot = ?));`,
367
286
collaborator, repoOwnerDid, repoName, repoKnot)
368
-
if err != nil {
369
-
span.RecordError(err)
370
-
}
371
287
return err
372
288
}
373
289
374
-
func UpdateDescription(ctx context.Context, e Execer, repoAt, newDescription string) error {
375
-
ctx, span := otel.Tracer("db").Start(ctx, "UpdateDescription")
376
-
defer span.End()
377
-
span.SetAttributes(
378
-
attribute.String("repoAt", repoAt),
379
-
attribute.String("description", newDescription),
380
-
)
381
-
290
+
func UpdateDescription(e Execer, repoAt, newDescription string) error {
382
291
_, err := e.Exec(
383
292
`update repos set description = ? where at_uri = ?`, newDescription, repoAt)
384
-
if err != nil {
385
-
span.RecordError(err)
386
-
}
387
293
return err
388
294
}
389
295
390
-
func CollaboratingIn(ctx context.Context, e Execer, collaborator string) ([]Repo, error) {
391
-
ctx, span := otel.Tracer("db").Start(ctx, "CollaboratingIn")
392
-
defer span.End()
393
-
span.SetAttributes(attribute.String("collaborator", collaborator))
394
-
296
+
func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) {
395
297
var repos []Repo
396
298
397
299
rows, err := e.Query(
···
408
310
group by
409
311
r.id;`, collaborator)
410
312
if err != nil {
411
-
span.RecordError(err)
412
313
return nil, err
413
314
}
414
315
defer rows.Close()
···
421
322
422
323
err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount)
423
324
if err != nil {
424
-
span.RecordError(err)
425
325
return nil, err
426
326
}
427
327
···
444
344
}
445
345
446
346
if err := rows.Err(); err != nil {
447
-
span.RecordError(err)
448
347
return nil, err
449
348
}
450
349
451
-
span.SetAttributes(attribute.Int("repos.count", len(repos)))
452
350
return repos, nil
453
351
}
454
352
+4
-5
appview/db/star.go
+4
-5
appview/db/star.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"log"
6
5
"time"
7
6
···
18
17
Repo *Repo
19
18
}
20
19
21
-
func (star *Star) ResolveRepo(ctx context.Context, e Execer) error {
20
+
func (star *Star) ResolveRepo(e Execer) error {
22
21
if star.Repo != nil {
23
22
return nil
24
23
}
25
24
26
-
repo, err := GetRepoByAtUri(ctx, e, star.RepoAt.String())
25
+
repo, err := GetRepoByAtUri(e, star.RepoAt.String())
27
26
if err != nil {
28
27
return err
29
28
}
···
41
40
// Get a star record
42
41
func GetStar(e Execer, starredByDid string, repoAt syntax.ATURI) (*Star, error) {
43
42
query := `
44
-
select starred_by_did, repo_at, created, rkey
43
+
select starred_by_did, repo_at, created, rkey
45
44
from stars
46
45
where starred_by_did = ? and repo_at = ?`
47
46
row := e.QueryRow(query, starredByDid, repoAt)
···
98
97
var stars []Star
99
98
100
99
rows, err := e.Query(`
101
-
select
100
+
select
102
101
s.starred_by_did,
103
102
s.repo_at,
104
103
s.rkey,
+3
-28
appview/db/timeline.go
+3
-28
appview/db/timeline.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"sort"
6
5
"time"
7
-
8
-
"go.opentelemetry.io/otel/attribute"
9
-
"go.opentelemetry.io/otel/trace"
10
6
)
11
7
12
8
type TimelineEvent struct {
···
22
18
23
19
// TODO: this gathers heterogenous events from different sources and aggregates
24
20
// them in code; if we did this entirely in sql, we could order and limit and paginate easily
25
-
func MakeTimeline(ctx context.Context, e Execer) ([]TimelineEvent, error) {
26
-
span := trace.SpanFromContext(ctx)
27
-
defer span.End()
28
-
21
+
func MakeTimeline(e Execer) ([]TimelineEvent, error) {
29
22
var events []TimelineEvent
30
23
limit := 50
31
24
32
-
span.SetAttributes(attribute.Int("timeline.limit", limit))
33
-
34
-
repos, err := GetAllRepos(ctx, e, limit)
25
+
repos, err := GetAllRepos(e, limit)
35
26
if err != nil {
36
-
span.RecordError(err)
37
-
span.SetAttributes(attribute.String("error.from", "GetAllRepos"))
38
27
return nil, err
39
28
}
40
-
span.SetAttributes(attribute.Int("timeline.repos.count", len(repos)))
41
29
42
30
follows, err := GetAllFollows(e, limit)
43
31
if err != nil {
44
-
span.RecordError(err)
45
-
span.SetAttributes(attribute.String("error.from", "GetAllFollows"))
46
32
return nil, err
47
33
}
48
-
span.SetAttributes(attribute.Int("timeline.follows.count", len(follows)))
49
34
50
35
stars, err := GetAllStars(e, limit)
51
36
if err != nil {
52
-
span.RecordError(err)
53
-
span.SetAttributes(attribute.String("error.from", "GetAllStars"))
54
37
return nil, err
55
38
}
56
-
span.SetAttributes(attribute.Int("timeline.stars.count", len(stars)))
57
39
58
40
for _, repo := range repos {
59
41
var sourceRepo *Repo
60
42
if repo.Source != "" {
61
-
sourceRepo, err = GetRepoByAtUri(ctx, e, repo.Source)
43
+
sourceRepo, err = GetRepoByAtUri(e, repo.Source)
62
44
if err != nil {
63
-
span.RecordError(err)
64
-
span.SetAttributes(
65
-
attribute.String("error.from", "GetRepoByAtUri"),
66
-
attribute.String("repo.source", repo.Source),
67
-
)
68
45
return nil, err
69
46
}
70
47
}
···
98
75
if len(events) > limit {
99
76
events = events[:limit]
100
77
}
101
-
102
-
span.SetAttributes(attribute.Int("timeline.events.total", len(events)))
103
78
104
79
return events, nil
105
80
}
+1
-1
appview/state/artifact.go
+1
-1
appview/state/artifact.go
+13
-31
appview/state/middleware.go
+13
-31
appview/state/middleware.go
···
12
12
13
13
"github.com/bluesky-social/indigo/atproto/identity"
14
14
"github.com/go-chi/chi/v5"
15
-
"go.opentelemetry.io/otel/attribute"
16
15
"tangled.sh/tangled.sh/core/appview/db"
17
16
"tangled.sh/tangled.sh/core/appview/middleware"
18
17
)
···
20
19
func knotRoleMiddleware(s *State, group string) middleware.Middleware {
21
20
return func(next http.Handler) http.Handler {
22
21
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23
-
ctx, span := s.t.TraceStart(r.Context(), "knotRoleMiddleware")
24
-
defer span.End()
25
-
26
22
// requires auth also
27
-
actor := s.auth.GetUser(r.WithContext(ctx))
23
+
actor := s.auth.GetUser(r)
28
24
if actor == nil {
29
25
// we need a logged in user
30
26
log.Printf("not logged in, redirecting")
···
45
41
return
46
42
}
47
43
48
-
next.ServeHTTP(w, r.WithContext(ctx))
44
+
next.ServeHTTP(w, r)
49
45
})
50
46
}
51
47
}
···
57
53
func RepoPermissionMiddleware(s *State, requiredPerm string) middleware.Middleware {
58
54
return func(next http.Handler) http.Handler {
59
55
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60
-
ctx, span := s.t.TraceStart(r.Context(), "RepoPermissionMiddleware")
61
-
defer span.End()
62
-
63
56
// requires auth also
64
-
actor := s.auth.GetUser(r.WithContext(ctx))
57
+
actor := s.auth.GetUser(r)
65
58
if actor == nil {
66
59
// we need a logged in user
67
60
log.Printf("not logged in, redirecting")
68
61
http.Error(w, "Forbiden", http.StatusUnauthorized)
69
62
return
70
63
}
71
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
64
+
f, err := s.fullyResolvedRepo(r)
72
65
if err != nil {
73
66
http.Error(w, "malformed url", http.StatusBadRequest)
74
67
return
···
82
75
return
83
76
}
84
77
85
-
next.ServeHTTP(w, r.WithContext(ctx))
78
+
next.ServeHTTP(w, r)
86
79
})
87
80
}
88
81
}
···
108
101
return
109
102
}
110
103
111
-
ctx, span := s.t.TraceStart(req.Context(), "ResolveIdent")
112
-
defer span.End()
113
-
114
-
id, err := s.resolver.ResolveIdent(ctx, didOrHandle)
104
+
id, err := s.resolver.ResolveIdent(req.Context(), didOrHandle)
115
105
if err != nil {
116
106
// invalid did or handle
117
107
log.Println("failed to resolve did/handle:", err)
···
119
109
return
120
110
}
121
111
122
-
ctx = context.WithValue(ctx, "resolvedId", *id)
112
+
ctx := context.WithValue(req.Context(), "resolvedId", *id)
123
113
124
114
next.ServeHTTP(w, req.WithContext(ctx))
125
115
})
···
129
119
func ResolveRepo(s *State) middleware.Middleware {
130
120
return func(next http.Handler) http.Handler {
131
121
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
132
-
ctx, span := s.t.TraceStart(req.Context(), "ResolveRepo")
133
-
defer span.End()
134
-
135
122
repoName := chi.URLParam(req, "repo")
136
-
id, ok := ctx.Value("resolvedId").(identity.Identity)
123
+
id, ok := req.Context().Value("resolvedId").(identity.Identity)
137
124
if !ok {
138
125
log.Println("malformed middleware")
139
126
w.WriteHeader(http.StatusInternalServerError)
140
127
return
141
128
}
142
129
143
-
repo, err := db.GetRepo(ctx, s.db, id.DID.String(), repoName)
130
+
repo, err := db.GetRepo(s.db, id.DID.String(), repoName)
144
131
if err != nil {
145
132
// invalid did or handle
146
133
log.Println("failed to resolve repo")
···
148
135
return
149
136
}
150
137
151
-
ctx = context.WithValue(ctx, "knot", repo.Knot)
138
+
ctx := context.WithValue(req.Context(), "knot", repo.Knot)
152
139
ctx = context.WithValue(ctx, "repoAt", repo.AtUri)
153
140
ctx = context.WithValue(ctx, "repoDescription", repo.Description)
154
141
ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339))
···
161
148
func ResolvePull(s *State) middleware.Middleware {
162
149
return func(next http.Handler) http.Handler {
163
150
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
164
-
ctx, span := s.t.TraceStart(r.Context(), "ResolvePull")
165
-
defer span.End()
166
-
167
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
151
+
f, err := s.fullyResolvedRepo(r)
168
152
if err != nil {
169
153
log.Println("failed to fully resolve repo", err)
170
154
http.Error(w, "invalid repo url", http.StatusNotFound)
···
179
163
return
180
164
}
181
165
182
-
pr, err := db.GetPull(ctx, s.db, f.RepoAt, prIdInt)
166
+
pr, err := db.GetPull(s.db, f.RepoAt, prIdInt)
183
167
if err != nil {
184
168
log.Println("failed to get pull and comments", err)
185
169
return
186
170
}
187
171
188
-
span.SetAttributes(attribute.Int("pull.id", prIdInt))
189
-
190
-
ctx = context.WithValue(ctx, "pull", pr)
172
+
ctx := context.WithValue(r.Context(), "pull", pr)
191
173
192
174
next.ServeHTTP(w, r.WithContext(ctx))
193
175
})
+5
-33
appview/state/profile.go
+5
-33
appview/state/profile.go
···
10
10
11
11
"github.com/bluesky-social/indigo/atproto/identity"
12
12
"github.com/go-chi/chi/v5"
13
-
"go.opentelemetry.io/otel/attribute"
14
13
"tangled.sh/tangled.sh/core/appview/db"
15
14
"tangled.sh/tangled.sh/core/appview/pages"
16
15
)
17
16
18
17
func (s *State) ProfilePage(w http.ResponseWriter, r *http.Request) {
19
-
ctx, span := s.t.TraceStart(r.Context(), "ProfilePage")
20
-
defer span.End()
21
-
22
18
didOrHandle := chi.URLParam(r, "user")
23
19
if didOrHandle == "" {
24
20
http.Error(w, "Bad request", http.StatusBadRequest)
25
21
return
26
22
}
27
23
28
-
ident, ok := ctx.Value("resolvedId").(identity.Identity)
24
+
ident, ok := r.Context().Value("resolvedId").(identity.Identity)
29
25
if !ok {
30
26
s.pages.Error404(w)
31
-
span.RecordError(fmt.Errorf("failed to resolve identity"))
32
27
return
33
28
}
34
29
35
-
span.SetAttributes(
36
-
attribute.String("user.did", ident.DID.String()),
37
-
attribute.String("user.handle", ident.Handle.String()),
38
-
)
39
-
40
-
repos, err := db.GetAllReposByDid(ctx, s.db, ident.DID.String())
30
+
repos, err := db.GetAllReposByDid(s.db, ident.DID.String())
41
31
if err != nil {
42
32
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
43
-
span.RecordError(err)
44
-
span.SetAttributes(attribute.String("error.repos", err.Error()))
45
33
}
46
-
span.SetAttributes(attribute.Int("repos.count", len(repos)))
47
34
48
-
collaboratingRepos, err := db.CollaboratingIn(ctx, s.db, ident.DID.String())
35
+
collaboratingRepos, err := db.CollaboratingIn(s.db, ident.DID.String())
49
36
if err != nil {
50
37
log.Printf("getting collaborating repos for %s: %s", ident.DID.String(), err)
51
-
span.RecordError(err)
52
-
span.SetAttributes(attribute.String("error.collaborating_repos", err.Error()))
53
38
}
54
-
span.SetAttributes(attribute.Int("collaborating_repos.count", len(collaboratingRepos)))
55
39
56
-
timeline, err := db.MakeProfileTimeline(ctx, s.db, ident.DID.String())
40
+
timeline, err := db.MakeProfileTimeline(s.db, ident.DID.String())
57
41
if err != nil {
58
42
log.Printf("failed to create profile timeline for %s: %s", ident.DID.String(), err)
59
-
span.RecordError(err)
60
-
span.SetAttributes(attribute.String("error.timeline", err.Error()))
61
43
}
62
44
63
45
var didsToResolve []string
···
78
60
}
79
61
}
80
62
}
81
-
span.SetAttributes(attribute.Int("dids_to_resolve.count", len(didsToResolve)))
82
63
83
-
resolvedIds := s.resolver.ResolveIdents(ctx, didsToResolve)
64
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve)
84
65
didHandleMap := make(map[string]string)
85
66
for _, identity := range resolvedIds {
86
67
if !identity.Handle.IsInvalidHandle() {
···
89
70
didHandleMap[identity.DID.String()] = identity.DID.String()
90
71
}
91
72
}
92
-
span.SetAttributes(attribute.Int("resolved_ids.count", len(resolvedIds)))
93
73
94
74
followers, following, err := db.GetFollowerFollowing(s.db, ident.DID.String())
95
75
if err != nil {
96
76
log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
97
-
span.RecordError(err)
98
-
span.SetAttributes(attribute.String("error.follow_stats", err.Error()))
99
77
}
100
-
span.SetAttributes(
101
-
attribute.Int("followers.count", followers),
102
-
attribute.Int("following.count", following),
103
-
)
104
78
105
79
loggedInUser := s.auth.GetUser(r)
106
80
followStatus := db.IsNotFollowing
107
81
if loggedInUser != nil {
108
82
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String())
109
-
span.SetAttributes(attribute.String("logged_in_user.did", loggedInUser.Did))
110
83
}
111
-
span.SetAttributes(attribute.String("follow_status", string(db.FollowStatus(followStatus))))
112
84
113
85
profileAvatarUri := s.GetAvatarUri(ident.Handle.String())
114
86
s.pages.ProfilePage(w, pages.ProfilePageParams{
+125
-633
appview/state/pull.go
+125
-633
appview/state/pull.go
···
1
1
package state
2
2
3
3
import (
4
-
"context"
5
4
"database/sql"
6
5
"encoding/json"
7
6
"errors"
···
12
11
"strconv"
13
12
"time"
14
13
15
-
"go.opentelemetry.io/otel/attribute"
16
14
"tangled.sh/tangled.sh/core/api/tangled"
17
15
"tangled.sh/tangled.sh/core/appview"
18
16
"tangled.sh/tangled.sh/core/appview/auth"
19
17
"tangled.sh/tangled.sh/core/appview/db"
20
18
"tangled.sh/tangled.sh/core/appview/pages"
21
19
"tangled.sh/tangled.sh/core/patchutil"
22
-
"tangled.sh/tangled.sh/core/telemetry"
23
20
"tangled.sh/tangled.sh/core/types"
24
21
25
22
comatproto "github.com/bluesky-social/indigo/api/atproto"
···
30
27
31
28
// htmx fragment
32
29
func (s *State) PullActions(w http.ResponseWriter, r *http.Request) {
33
-
ctx, span := s.t.TraceStart(r.Context(), "PullActions")
34
-
defer span.End()
35
-
36
30
switch r.Method {
37
31
case http.MethodGet:
38
32
user := s.auth.GetUser(r)
39
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
33
+
f, err := s.fullyResolvedRepo(r)
40
34
if err != nil {
41
35
log.Println("failed to get repo and knot", err)
42
36
return
43
37
}
44
38
45
-
pull, ok := ctx.Value("pull").(*db.Pull)
39
+
pull, ok := r.Context().Value("pull").(*db.Pull)
46
40
if !ok {
47
41
log.Println("failed to get pull")
48
42
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
···
60
54
return
61
55
}
62
56
63
-
_, mergeSpan := s.t.TraceStart(ctx, "mergeCheck")
64
-
mergeCheckResponse := s.mergeCheck(ctx, f, pull)
65
-
mergeSpan.End()
66
-
57
+
mergeCheckResponse := s.mergeCheck(f, pull)
67
58
resubmitResult := pages.Unknown
68
59
if user.Did == pull.OwnerDid {
69
-
_, resubmitSpan := s.t.TraceStart(ctx, "resubmitCheck")
70
-
resubmitResult = s.resubmitCheck(ctx, f, pull)
71
-
resubmitSpan.End()
60
+
resubmitResult = s.resubmitCheck(f, pull)
72
61
}
73
62
74
-
_, renderSpan := s.t.TraceStart(ctx, "renderPullActions")
75
63
s.pages.PullActionsFragment(w, pages.PullActionsParams{
76
64
LoggedInUser: user,
77
-
RepoInfo: f.RepoInfo(ctx, s, user),
65
+
RepoInfo: f.RepoInfo(s, user),
78
66
Pull: pull,
79
67
RoundNumber: roundNumber,
80
68
MergeCheck: mergeCheckResponse,
81
69
ResubmitCheck: resubmitResult,
82
70
})
83
-
renderSpan.End()
84
71
return
85
72
}
86
73
}
87
74
88
75
func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) {
89
-
ctx, span := s.t.TraceStart(r.Context(), "RepoSinglePull")
90
-
defer span.End()
91
-
92
76
user := s.auth.GetUser(r)
93
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
77
+
f, err := s.fullyResolvedRepo(r)
94
78
if err != nil {
95
79
log.Println("failed to get repo and knot", err)
96
-
span.RecordError(err)
97
80
return
98
81
}
99
82
100
-
pull, ok := ctx.Value("pull").(*db.Pull)
83
+
pull, ok := r.Context().Value("pull").(*db.Pull)
101
84
if !ok {
102
-
err := errors.New("failed to get pull from context")
103
-
log.Println(err)
104
-
span.RecordError(err)
85
+
log.Println("failed to get pull")
105
86
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
106
87
return
107
88
}
108
89
109
-
attrs := telemetry.MapAttrs[string](map[string]string{
110
-
"pull.id": fmt.Sprintf("%d", pull.PullId),
111
-
"pull.owner": pull.OwnerDid,
112
-
})
113
-
114
-
span.SetAttributes(attrs...)
115
-
116
90
totalIdents := 1
117
91
for _, submission := range pull.Submissions {
118
92
totalIdents += len(submission.Comments)
···
130
104
}
131
105
}
132
106
133
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
107
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
134
108
didHandleMap := make(map[string]string)
135
109
for _, identity := range resolvedIds {
136
110
if !identity.Handle.IsInvalidHandle() {
···
139
113
didHandleMap[identity.DID.String()] = identity.DID.String()
140
114
}
141
115
}
142
-
span.SetAttributes(attribute.Int("identities.resolved", len(resolvedIds)))
143
116
144
-
mergeCheckResponse := s.mergeCheck(ctx, f, pull)
145
-
117
+
mergeCheckResponse := s.mergeCheck(f, pull)
146
118
resubmitResult := pages.Unknown
147
119
if user != nil && user.Did == pull.OwnerDid {
148
-
resubmitResult = s.resubmitCheck(ctx, f, pull)
120
+
resubmitResult = s.resubmitCheck(f, pull)
149
121
}
150
122
151
123
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
152
124
LoggedInUser: user,
153
-
RepoInfo: f.RepoInfo(ctx, s, user),
125
+
RepoInfo: f.RepoInfo(s, user),
154
126
DidHandleMap: didHandleMap,
155
127
Pull: pull,
156
128
MergeCheck: mergeCheckResponse,
···
158
130
})
159
131
}
160
132
161
-
func (s *State) mergeCheck(ctx context.Context, f *FullyResolvedRepo, pull *db.Pull) types.MergeCheckResponse {
133
+
func (s *State) mergeCheck(f *FullyResolvedRepo, pull *db.Pull) types.MergeCheckResponse {
162
134
if pull.State == db.PullMerged {
163
135
return types.MergeCheckResponse{}
164
136
}
···
218
190
return mergeCheckResponse
219
191
}
220
192
221
-
func (s *State) resubmitCheck(ctx context.Context, f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult {
222
-
ctx, span := s.t.TraceStart(ctx, "resubmitCheck")
223
-
defer span.End()
224
-
225
-
span.SetAttributes(attribute.Int("pull.id", pull.PullId))
226
-
193
+
func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult {
227
194
if pull.State == db.PullMerged || pull.PullSource == nil {
228
-
span.SetAttributes(attribute.String("result", "Unknown"))
229
195
return pages.Unknown
230
196
}
231
197
···
233
199
234
200
if pull.PullSource.RepoAt != nil {
235
201
// fork-based pulls
236
-
span.SetAttributes(attribute.Bool("isForkBased", true))
237
-
sourceRepo, err := db.GetRepoByAtUri(ctx, s.db, pull.PullSource.RepoAt.String())
202
+
sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String())
238
203
if err != nil {
239
204
log.Println("failed to get source repo", err)
240
-
span.RecordError(err)
241
-
span.SetAttributes(attribute.String("error", "failed_to_get_source_repo"))
242
-
span.SetAttributes(attribute.String("result", "Unknown"))
243
205
return pages.Unknown
244
206
}
245
207
···
248
210
repoName = sourceRepo.Name
249
211
} else {
250
212
// pulls within the same repo
251
-
span.SetAttributes(attribute.Bool("isBranchBased", true))
252
213
knot = f.Knot
253
214
ownerDid = f.OwnerDid()
254
215
repoName = f.RepoName
255
216
}
256
217
257
-
span.SetAttributes(
258
-
attribute.String("knot", knot),
259
-
attribute.String("ownerDid", ownerDid),
260
-
attribute.String("repoName", repoName),
261
-
attribute.String("sourceBranch", pull.PullSource.Branch),
262
-
)
263
-
264
218
us, err := NewUnsignedClient(knot, s.config.Dev)
265
219
if err != nil {
266
220
log.Printf("failed to setup client for %s; ignoring: %v", knot, err)
267
-
span.RecordError(err)
268
-
span.SetAttributes(attribute.String("error", "failed_to_setup_client"))
269
-
span.SetAttributes(attribute.String("result", "Unknown"))
270
221
return pages.Unknown
271
222
}
272
223
273
224
resp, err := us.Branch(ownerDid, repoName, pull.PullSource.Branch)
274
225
if err != nil {
275
226
log.Println("failed to reach knotserver", err)
276
-
span.RecordError(err)
277
-
span.SetAttributes(attribute.String("error", "failed_to_reach_knotserver"))
278
-
span.SetAttributes(attribute.String("result", "Unknown"))
279
227
return pages.Unknown
280
228
}
281
229
282
230
body, err := io.ReadAll(resp.Body)
283
231
if err != nil {
284
232
log.Printf("error reading response body: %v", err)
285
-
span.RecordError(err)
286
-
span.SetAttributes(attribute.String("error", "failed_to_read_response"))
287
-
span.SetAttributes(attribute.String("result", "Unknown"))
288
233
return pages.Unknown
289
234
}
290
235
defer resp.Body.Close()
···
292
237
var result types.RepoBranchResponse
293
238
if err := json.Unmarshal(body, &result); err != nil {
294
239
log.Println("failed to parse response:", err)
295
-
span.RecordError(err)
296
-
span.SetAttributes(attribute.String("error", "failed_to_parse_response"))
297
-
span.SetAttributes(attribute.String("result", "Unknown"))
298
240
return pages.Unknown
299
241
}
300
242
301
243
latestSubmission := pull.Submissions[pull.LastRoundNumber()]
302
-
303
-
span.SetAttributes(
304
-
attribute.String("latestSubmission.SourceRev", latestSubmission.SourceRev),
305
-
attribute.String("branch.Hash", result.Branch.Hash),
306
-
)
307
-
308
244
if latestSubmission.SourceRev != result.Branch.Hash {
309
245
fmt.Println(latestSubmission.SourceRev, result.Branch.Hash)
310
-
span.SetAttributes(attribute.String("result", "ShouldResubmit"))
311
246
return pages.ShouldResubmit
312
247
}
313
248
314
-
span.SetAttributes(attribute.String("result", "ShouldNotResubmit"))
315
249
return pages.ShouldNotResubmit
316
250
}
317
251
318
252
func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
319
-
ctx, span := s.t.TraceStart(r.Context(), "RepoPullPatch")
320
-
defer span.End()
321
-
322
-
user := s.auth.GetUser(r.WithContext(ctx))
323
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
253
+
user := s.auth.GetUser(r)
254
+
f, err := s.fullyResolvedRepo(r)
324
255
if err != nil {
325
256
log.Println("failed to get repo and knot", err)
326
-
span.RecordError(err)
327
257
return
328
258
}
329
259
330
-
pull, ok := ctx.Value("pull").(*db.Pull)
260
+
pull, ok := r.Context().Value("pull").(*db.Pull)
331
261
if !ok {
332
-
err := errors.New("failed to get pull from context")
333
-
log.Println(err)
334
-
span.RecordError(err)
262
+
log.Println("failed to get pull")
335
263
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
336
264
return
337
265
}
···
341
269
if err != nil || roundIdInt >= len(pull.Submissions) {
342
270
http.Error(w, "bad round id", http.StatusBadRequest)
343
271
log.Println("failed to parse round id", err)
344
-
span.RecordError(err)
345
-
span.SetAttributes(attribute.String("error", "bad_round_id"))
346
272
return
347
273
}
348
274
349
-
span.SetAttributes(
350
-
attribute.Int("pull.id", pull.PullId),
351
-
attribute.Int("round", roundIdInt),
352
-
attribute.String("pull.owner", pull.OwnerDid),
353
-
)
354
-
355
275
identsToResolve := []string{pull.OwnerDid}
356
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
276
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
357
277
didHandleMap := make(map[string]string)
358
278
for _, identity := range resolvedIds {
359
279
if !identity.Handle.IsInvalidHandle() {
···
362
282
didHandleMap[identity.DID.String()] = identity.DID.String()
363
283
}
364
284
}
365
-
span.SetAttributes(attribute.Int("identities.resolved", len(resolvedIds)))
366
285
367
286
diff := pull.Submissions[roundIdInt].AsNiceDiff(pull.TargetBranch)
368
287
369
288
s.pages.RepoPullPatchPage(w, pages.RepoPullPatchParams{
370
289
LoggedInUser: user,
371
290
DidHandleMap: didHandleMap,
372
-
RepoInfo: f.RepoInfo(ctx, s, user),
291
+
RepoInfo: f.RepoInfo(s, user),
373
292
Pull: pull,
374
293
Round: roundIdInt,
375
294
Submission: pull.Submissions[roundIdInt],
376
295
Diff: &diff,
377
296
})
297
+
378
298
}
379
299
380
300
func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) {
381
-
ctx, span := s.t.TraceStart(r.Context(), "RepoPullInterdiff")
382
-
defer span.End()
383
-
384
301
user := s.auth.GetUser(r)
385
302
386
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
303
+
f, err := s.fullyResolvedRepo(r)
387
304
if err != nil {
388
305
log.Println("failed to get repo and knot", err)
389
306
return
390
307
}
391
308
392
-
pull, ok := ctx.Value("pull").(*db.Pull)
309
+
pull, ok := r.Context().Value("pull").(*db.Pull)
393
310
if !ok {
394
311
log.Println("failed to get pull")
395
312
s.pages.Notice(w, "pull-error", "Failed to get pull.")
396
313
return
397
314
}
398
315
399
-
_, roundSpan := s.t.TraceStart(ctx, "parseRound")
400
316
roundId := chi.URLParam(r, "round")
401
317
roundIdInt, err := strconv.Atoi(roundId)
402
318
if err != nil || roundIdInt >= len(pull.Submissions) {
403
319
http.Error(w, "bad round id", http.StatusBadRequest)
404
320
log.Println("failed to parse round id", err)
405
-
roundSpan.End()
406
321
return
407
322
}
408
323
409
324
if roundIdInt == 0 {
410
325
http.Error(w, "bad round id", http.StatusBadRequest)
411
326
log.Println("cannot interdiff initial submission")
412
-
roundSpan.End()
413
327
return
414
328
}
415
-
roundSpan.End()
416
329
417
-
_, identSpan := s.t.TraceStart(ctx, "resolveIdentities")
418
330
identsToResolve := []string{pull.OwnerDid}
419
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
331
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
420
332
didHandleMap := make(map[string]string)
421
333
for _, identity := range resolvedIds {
422
334
if !identity.Handle.IsInvalidHandle() {
···
425
337
didHandleMap[identity.DID.String()] = identity.DID.String()
426
338
}
427
339
}
428
-
identSpan.End()
429
340
430
-
_, diffSpan := s.t.TraceStart(ctx, "calculateInterdiff")
431
341
currentPatch, err := pull.Submissions[roundIdInt].AsDiff(pull.TargetBranch)
432
342
if err != nil {
433
343
log.Println("failed to interdiff; current patch malformed")
434
344
s.pages.Notice(w, fmt.Sprintf("interdiff-error-%d", roundIdInt), "Failed to calculate interdiff; current patch is invalid.")
435
-
diffSpan.End()
436
345
return
437
346
}
438
347
···
440
349
if err != nil {
441
350
log.Println("failed to interdiff; previous patch malformed")
442
351
s.pages.Notice(w, fmt.Sprintf("interdiff-error-%d", roundIdInt), "Failed to calculate interdiff; previous patch is invalid.")
443
-
diffSpan.End()
444
352
return
445
353
}
446
354
447
355
interdiff := patchutil.Interdiff(previousPatch, currentPatch)
448
-
diffSpan.End()
449
356
450
-
_, renderSpan := s.t.TraceStart(ctx, "renderInterdiffPage")
451
357
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
452
-
LoggedInUser: s.auth.GetUser(r.WithContext(ctx)),
453
-
RepoInfo: f.RepoInfo(ctx, s, user),
358
+
LoggedInUser: s.auth.GetUser(r),
359
+
RepoInfo: f.RepoInfo(s, user),
454
360
Pull: pull,
455
361
Round: roundIdInt,
456
362
DidHandleMap: didHandleMap,
457
363
Interdiff: interdiff,
458
364
})
459
-
renderSpan.End()
460
365
return
461
366
}
462
367
463
368
func (s *State) RepoPullPatchRaw(w http.ResponseWriter, r *http.Request) {
464
-
ctx, span := s.t.TraceStart(r.Context(), "RepoPullPatchRaw")
465
-
defer span.End()
466
-
467
-
pull, ok := ctx.Value("pull").(*db.Pull)
369
+
pull, ok := r.Context().Value("pull").(*db.Pull)
468
370
if !ok {
469
371
log.Println("failed to get pull")
470
372
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
471
373
return
472
374
}
473
375
474
-
_, roundSpan := s.t.TraceStart(ctx, "parseRound")
475
376
roundId := chi.URLParam(r, "round")
476
377
roundIdInt, err := strconv.Atoi(roundId)
477
378
if err != nil || roundIdInt >= len(pull.Submissions) {
478
379
http.Error(w, "bad round id", http.StatusBadRequest)
479
380
log.Println("failed to parse round id", err)
480
-
roundSpan.End()
481
381
return
482
382
}
483
-
roundSpan.End()
484
383
485
-
_, identSpan := s.t.TraceStart(ctx, "resolveIdentities")
486
384
identsToResolve := []string{pull.OwnerDid}
487
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
385
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
488
386
didHandleMap := make(map[string]string)
489
387
for _, identity := range resolvedIds {
490
388
if !identity.Handle.IsInvalidHandle() {
···
493
391
didHandleMap[identity.DID.String()] = identity.DID.String()
494
392
}
495
393
}
496
-
identSpan.End()
497
394
498
-
_, writeSpan := s.t.TraceStart(ctx, "writePatch")
499
395
w.Header().Set("Content-Type", "text/plain")
500
396
w.Write([]byte(pull.Submissions[roundIdInt].Patch))
501
-
writeSpan.End()
502
397
}
503
398
504
399
func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) {
505
-
ctx, span := s.t.TraceStart(r.Context(), "RepoPulls")
506
-
defer span.End()
507
-
508
400
user := s.auth.GetUser(r)
509
401
params := r.URL.Query()
510
402
511
-
_, stateSpan := s.t.TraceStart(ctx, "determinePullState")
512
403
state := db.PullOpen
513
404
switch params.Get("state") {
514
405
case "closed":
···
516
407
case "merged":
517
408
state = db.PullMerged
518
409
}
519
-
stateSpan.End()
520
410
521
-
_, repoSpan := s.t.TraceStart(ctx, "resolveRepo")
522
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
411
+
f, err := s.fullyResolvedRepo(r)
523
412
if err != nil {
524
413
log.Println("failed to get repo and knot", err)
525
-
repoSpan.End()
526
414
return
527
415
}
528
-
repoSpan.End()
529
416
530
-
_, pullsSpan := s.t.TraceStart(ctx, "getPulls")
531
-
pulls, err := db.GetPulls(ctx, s.db, f.RepoAt, state)
417
+
pulls, err := db.GetPulls(s.db, f.RepoAt, state)
532
418
if err != nil {
533
419
log.Println("failed to get pulls", err)
534
420
s.pages.Notice(w, "pulls", "Failed to load pulls. Try again later.")
535
-
pullsSpan.End()
536
421
return
537
422
}
538
-
pullsSpan.End()
539
423
540
-
_, sourceRepoSpan := s.t.TraceStart(ctx, "resolvePullSources")
541
424
for _, p := range pulls {
542
425
var pullSourceRepo *db.Repo
543
426
if p.PullSource != nil {
544
427
if p.PullSource.RepoAt != nil {
545
-
pullSourceRepo, err = db.GetRepoByAtUri(ctx, s.db, p.PullSource.RepoAt.String())
428
+
pullSourceRepo, err = db.GetRepoByAtUri(s.db, p.PullSource.RepoAt.String())
546
429
if err != nil {
547
430
log.Printf("failed to get repo by at uri: %v", err)
548
431
continue
···
552
435
}
553
436
}
554
437
}
555
-
sourceRepoSpan.End()
556
438
557
-
_, identSpan := s.t.TraceStart(ctx, "resolveIdentities")
558
439
identsToResolve := make([]string, len(pulls))
559
440
for i, pull := range pulls {
560
441
identsToResolve[i] = pull.OwnerDid
561
442
}
562
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
443
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
563
444
didHandleMap := make(map[string]string)
564
445
for _, identity := range resolvedIds {
565
446
if !identity.Handle.IsInvalidHandle() {
···
568
449
didHandleMap[identity.DID.String()] = identity.DID.String()
569
450
}
570
451
}
571
-
identSpan.End()
572
452
573
-
_, renderSpan := s.t.TraceStart(ctx, "renderPullsPage")
574
453
s.pages.RepoPulls(w, pages.RepoPullsParams{
575
-
LoggedInUser: s.auth.GetUser(r.WithContext(ctx)),
576
-
RepoInfo: f.RepoInfo(ctx, s, user),
454
+
LoggedInUser: s.auth.GetUser(r),
455
+
RepoInfo: f.RepoInfo(s, user),
577
456
Pulls: pulls,
578
457
DidHandleMap: didHandleMap,
579
458
FilteringBy: state,
580
459
})
581
-
renderSpan.End()
582
460
return
583
461
}
584
462
585
463
func (s *State) PullComment(w http.ResponseWriter, r *http.Request) {
586
-
ctx, span := s.t.TraceStart(r.Context(), "PullComment")
587
-
defer span.End()
588
-
589
-
user := s.auth.GetUser(r.WithContext(ctx))
590
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
464
+
user := s.auth.GetUser(r)
465
+
f, err := s.fullyResolvedRepo(r)
591
466
if err != nil {
592
467
log.Println("failed to get repo and knot", err)
593
468
return
594
469
}
595
470
596
-
pull, ok := ctx.Value("pull").(*db.Pull)
471
+
pull, ok := r.Context().Value("pull").(*db.Pull)
597
472
if !ok {
598
473
log.Println("failed to get pull")
599
474
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
600
475
return
601
476
}
602
477
603
-
_, roundSpan := s.t.TraceStart(ctx, "parseRoundNumber")
604
478
roundNumberStr := chi.URLParam(r, "round")
605
479
roundNumber, err := strconv.Atoi(roundNumberStr)
606
480
if err != nil || roundNumber >= len(pull.Submissions) {
607
481
http.Error(w, "bad round id", http.StatusBadRequest)
608
482
log.Println("failed to parse round id", err)
609
-
roundSpan.End()
610
483
return
611
484
}
612
-
roundSpan.End()
613
485
614
486
switch r.Method {
615
487
case http.MethodGet:
616
-
_, renderSpan := s.t.TraceStart(ctx, "renderCommentFragment")
617
488
s.pages.PullNewCommentFragment(w, pages.PullNewCommentParams{
618
489
LoggedInUser: user,
619
-
RepoInfo: f.RepoInfo(ctx, s, user),
490
+
RepoInfo: f.RepoInfo(s, user),
620
491
Pull: pull,
621
492
RoundNumber: roundNumber,
622
493
})
623
-
renderSpan.End()
624
494
return
625
495
case http.MethodPost:
626
-
postCtx, postSpan := s.t.TraceStart(ctx, "CreateComment")
627
-
defer postSpan.End()
628
-
629
-
_, validateSpan := s.t.TraceStart(postCtx, "validateComment")
630
496
body := r.FormValue("body")
631
497
if body == "" {
632
498
s.pages.Notice(w, "pull", "Comment body is required")
633
-
validateSpan.End()
634
499
return
635
500
}
636
-
validateSpan.End()
637
501
638
502
// Start a transaction
639
-
_, txSpan := s.t.TraceStart(postCtx, "startTransaction")
640
-
tx, err := s.db.BeginTx(postCtx, nil)
503
+
tx, err := s.db.BeginTx(r.Context(), nil)
641
504
if err != nil {
642
505
log.Println("failed to start transaction", err)
643
506
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
644
-
txSpan.End()
645
507
return
646
508
}
647
509
defer tx.Rollback()
648
-
txSpan.End()
649
510
650
511
createdAt := time.Now().Format(time.RFC3339)
651
512
ownerDid := user.Did
652
513
653
-
_, pullAtSpan := s.t.TraceStart(postCtx, "getPullAt")
654
-
pullAt, err := db.GetPullAt(postCtx, s.db, f.RepoAt, pull.PullId)
514
+
pullAt, err := db.GetPullAt(s.db, f.RepoAt, pull.PullId)
655
515
if err != nil {
656
516
log.Println("failed to get pull at", err)
657
517
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
658
-
pullAtSpan.End()
659
518
return
660
519
}
661
-
pullAtSpan.End()
662
520
663
-
_, atProtoSpan := s.t.TraceStart(postCtx, "createAtProtoRecord")
664
521
atUri := f.RepoAt.String()
665
-
client, _ := s.auth.AuthorizedClient(r.WithContext(postCtx))
666
-
atResp, err := comatproto.RepoPutRecord(postCtx, client, &comatproto.RepoPutRecord_Input{
522
+
client, _ := s.auth.AuthorizedClient(r)
523
+
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
667
524
Collection: tangled.RepoPullCommentNSID,
668
525
Repo: user.Did,
669
526
Rkey: appview.TID(),
···
680
537
if err != nil {
681
538
log.Println("failed to create pull comment", err)
682
539
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
683
-
atProtoSpan.End()
684
540
return
685
541
}
686
-
atProtoSpan.End()
687
542
688
543
// Create the pull comment in the database with the commentAt field
689
-
_, dbSpan := s.t.TraceStart(postCtx, "createDbComment")
690
-
commentId, err := db.NewPullComment(postCtx, tx, &db.PullComment{
544
+
commentId, err := db.NewPullComment(tx, &db.PullComment{
691
545
OwnerDid: user.Did,
692
546
RepoAt: f.RepoAt.String(),
693
547
PullId: pull.PullId,
···
698
552
if err != nil {
699
553
log.Println("failed to create pull comment", err)
700
554
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
701
-
dbSpan.End()
702
555
return
703
556
}
704
-
dbSpan.End()
705
557
558
+
// Commit the transaction
706
559
if err = tx.Commit(); err != nil {
707
560
log.Println("failed to commit transaction", err)
708
561
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
···
715
568
}
716
569
717
570
func (s *State) NewPull(w http.ResponseWriter, r *http.Request) {
718
-
ctx, span := s.t.TraceStart(r.Context(), "NewPull")
719
-
defer span.End()
720
-
721
-
user := s.auth.GetUser(r.WithContext(ctx))
722
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
571
+
user := s.auth.GetUser(r)
572
+
f, err := s.fullyResolvedRepo(r)
723
573
if err != nil {
724
574
log.Println("failed to get repo and knot", err)
725
-
span.RecordError(err)
726
575
return
727
576
}
728
577
729
578
switch r.Method {
730
579
case http.MethodGet:
731
-
span.SetAttributes(attribute.String("method", "GET"))
732
-
733
580
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
734
581
if err != nil {
735
582
log.Printf("failed to create unsigned client for %s", f.Knot)
736
-
span.RecordError(err)
737
583
s.pages.Error503(w)
738
584
return
739
585
}
···
741
587
resp, err := us.Branches(f.OwnerDid(), f.RepoName)
742
588
if err != nil {
743
589
log.Println("failed to reach knotserver", err)
744
-
span.RecordError(err)
745
590
return
746
591
}
747
592
748
593
body, err := io.ReadAll(resp.Body)
749
594
if err != nil {
750
595
log.Printf("Error reading response body: %v", err)
751
-
span.RecordError(err)
752
596
return
753
597
}
754
598
···
756
600
err = json.Unmarshal(body, &result)
757
601
if err != nil {
758
602
log.Println("failed to parse response:", err)
759
-
span.RecordError(err)
760
603
return
761
604
}
762
605
763
606
s.pages.RepoNewPull(w, pages.RepoNewPullParams{
764
607
LoggedInUser: user,
765
-
RepoInfo: f.RepoInfo(ctx, s, user),
608
+
RepoInfo: f.RepoInfo(s, user),
766
609
Branches: result.Branches,
767
610
})
768
611
case http.MethodPost:
769
-
span.SetAttributes(attribute.String("method", "POST"))
770
-
771
612
title := r.FormValue("title")
772
613
body := r.FormValue("body")
773
614
targetBranch := r.FormValue("targetBranch")
774
615
fromFork := r.FormValue("fork")
775
616
sourceBranch := r.FormValue("sourceBranch")
776
617
patch := r.FormValue("patch")
777
-
778
-
span.SetAttributes(
779
-
attribute.String("targetBranch", targetBranch),
780
-
attribute.String("sourceBranch", sourceBranch),
781
-
attribute.Bool("hasFork", fromFork != ""),
782
-
attribute.Bool("hasPatch", patch != ""),
783
-
)
784
618
785
619
if targetBranch == "" {
786
620
s.pages.Notice(w, "pull", "Target branch is required.")
787
-
span.SetAttributes(attribute.String("error", "missing_target_branch"))
788
621
return
789
622
}
790
623
791
624
// Determine PR type based on input parameters
792
-
isPushAllowed := f.RepoInfo(ctx, s, user).Roles.IsPushAllowed()
625
+
isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed()
793
626
isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == ""
794
627
isForkBased := fromFork != "" && sourceBranch != ""
795
628
isPatchBased := patch != "" && !isBranchBased && !isForkBased
796
629
797
-
span.SetAttributes(
798
-
attribute.Bool("isPushAllowed", isPushAllowed),
799
-
attribute.Bool("isBranchBased", isBranchBased),
800
-
attribute.Bool("isForkBased", isForkBased),
801
-
attribute.Bool("isPatchBased", isPatchBased),
802
-
)
803
-
804
630
if isPatchBased && !patchutil.IsFormatPatch(patch) {
805
631
if title == "" {
806
632
s.pages.Notice(w, "pull", "Title is required for git-diff patches.")
807
-
span.SetAttributes(attribute.String("error", "missing_title_for_git_diff"))
808
633
return
809
634
}
810
635
}
···
812
637
// Validate we have at least one valid PR creation method
813
638
if !isBranchBased && !isPatchBased && !isForkBased {
814
639
s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.")
815
-
span.SetAttributes(attribute.String("error", "no_valid_pr_method"))
816
640
return
817
641
}
818
642
819
643
// Can't mix branch-based and patch-based approaches
820
644
if isBranchBased && patch != "" {
821
645
s.pages.Notice(w, "pull", "Cannot select both patch and source branch.")
822
-
span.SetAttributes(attribute.String("error", "mixed_pr_methods"))
823
646
return
824
647
}
825
648
826
649
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
827
650
if err != nil {
828
651
log.Printf("failed to create unsigned client to %s: %v", f.Knot, err)
829
-
span.RecordError(err)
830
652
s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.")
831
653
return
832
654
}
···
834
656
caps, err := us.Capabilities()
835
657
if err != nil {
836
658
log.Println("error fetching knot caps", f.Knot, err)
837
-
span.RecordError(err)
838
659
s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.")
839
660
return
840
661
}
841
662
842
-
span.SetAttributes(
843
-
attribute.Bool("caps.pullRequests.formatPatch", caps.PullRequests.FormatPatch),
844
-
attribute.Bool("caps.pullRequests.branchSubmissions", caps.PullRequests.BranchSubmissions),
845
-
attribute.Bool("caps.pullRequests.forkSubmissions", caps.PullRequests.ForkSubmissions),
846
-
attribute.Bool("caps.pullRequests.patchSubmissions", caps.PullRequests.PatchSubmissions),
847
-
)
848
-
849
663
if !caps.PullRequests.FormatPatch {
850
664
s.pages.Notice(w, "pull", "This knot doesn't support format-patch. Unfortunately, there is no fallback for now.")
851
-
span.SetAttributes(attribute.String("error", "formatpatch_not_supported"))
852
665
return
853
666
}
854
667
···
856
669
if isBranchBased {
857
670
if !caps.PullRequests.BranchSubmissions {
858
671
s.pages.Notice(w, "pull", "This knot doesn't support branch-based pull requests. Try another way?")
859
-
span.SetAttributes(attribute.String("error", "branch_submissions_not_supported"))
860
672
return
861
673
}
862
-
s.handleBranchBasedPull(w, r.WithContext(ctx), f, user, title, body, targetBranch, sourceBranch)
674
+
s.handleBranchBasedPull(w, r, f, user, title, body, targetBranch, sourceBranch)
863
675
} else if isForkBased {
864
676
if !caps.PullRequests.ForkSubmissions {
865
677
s.pages.Notice(w, "pull", "This knot doesn't support fork-based pull requests. Try another way?")
866
-
span.SetAttributes(attribute.String("error", "fork_submissions_not_supported"))
867
678
return
868
679
}
869
-
s.handleForkBasedPull(w, r.WithContext(ctx), f, user, fromFork, title, body, targetBranch, sourceBranch)
680
+
s.handleForkBasedPull(w, r, f, user, fromFork, title, body, targetBranch, sourceBranch)
870
681
} else if isPatchBased {
871
682
if !caps.PullRequests.PatchSubmissions {
872
683
s.pages.Notice(w, "pull", "This knot doesn't support patch-based pull requests. Send your patch over email.")
873
-
span.SetAttributes(attribute.String("error", "patch_submissions_not_supported"))
874
684
return
875
685
}
876
-
s.handlePatchBasedPull(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch)
686
+
s.handlePatchBasedPull(w, r, f, user, title, body, targetBranch, patch)
877
687
}
878
688
return
879
689
}
880
690
}
881
691
882
692
func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) {
883
-
ctx, span := s.t.TraceStart(r.Context(), "handleBranchBasedPull")
884
-
defer span.End()
885
-
886
-
span.SetAttributes(
887
-
attribute.String("targetBranch", targetBranch),
888
-
attribute.String("sourceBranch", sourceBranch),
889
-
)
890
-
891
693
pullSource := &db.PullSource{
892
694
Branch: sourceBranch,
893
695
}
···
899
701
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
900
702
if err != nil {
901
703
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
902
-
span.RecordError(err)
903
-
span.SetAttributes(attribute.String("error", "client_creation_failed"))
904
704
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
905
705
return
906
706
}
···
908
708
comparison, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch)
909
709
if err != nil {
910
710
log.Println("failed to compare", err)
911
-
span.RecordError(err)
912
-
span.SetAttributes(attribute.String("error", "comparison_failed"))
913
711
s.pages.Notice(w, "pull", err.Error())
914
712
return
915
713
}
···
917
715
sourceRev := comparison.Rev2
918
716
patch := comparison.Patch
919
717
920
-
span.SetAttributes(attribute.String("sourceRev", sourceRev))
921
-
922
718
if !patchutil.IsPatchValid(patch) {
923
-
span.SetAttributes(attribute.String("error", "invalid_patch_format"))
924
719
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
925
720
return
926
721
}
927
722
928
-
s.createPullRequest(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource)
723
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource)
929
724
}
930
725
931
726
func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) {
932
-
ctx, span := s.t.TraceStart(r.Context(), "handlePatchBasedPull")
933
-
defer span.End()
934
-
935
-
span.SetAttributes(attribute.String("targetBranch", targetBranch))
936
-
937
727
if !patchutil.IsPatchValid(patch) {
938
-
span.SetAttributes(attribute.String("error", "invalid_patch_format"))
939
728
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
940
729
return
941
730
}
942
731
943
-
s.createPullRequest(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch, "", nil, nil)
732
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil)
944
733
}
945
734
946
735
func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) {
947
-
ctx, span := s.t.TraceStart(r.Context(), "handleForkBasedPull")
948
-
defer span.End()
949
-
950
-
span.SetAttributes(
951
-
attribute.String("forkRepo", forkRepo),
952
-
attribute.String("targetBranch", targetBranch),
953
-
attribute.String("sourceBranch", sourceBranch),
954
-
)
955
-
956
-
fork, err := db.GetForkByDid(ctx, s.db, user.Did, forkRepo)
736
+
fork, err := db.GetForkByDid(s.db, user.Did, forkRepo)
957
737
if errors.Is(err, sql.ErrNoRows) {
958
-
span.SetAttributes(attribute.String("error", "fork_not_found"))
959
738
s.pages.Notice(w, "pull", "No such fork.")
960
739
return
961
740
} else if err != nil {
962
741
log.Println("failed to fetch fork:", err)
963
-
span.RecordError(err)
964
-
span.SetAttributes(attribute.String("error", "fork_fetch_failed"))
965
742
s.pages.Notice(w, "pull", "Failed to fetch fork.")
966
743
return
967
744
}
···
969
746
secret, err := db.GetRegistrationKey(s.db, fork.Knot)
970
747
if err != nil {
971
748
log.Println("failed to fetch registration key:", err)
972
-
span.RecordError(err)
973
-
span.SetAttributes(attribute.String("error", "registration_key_fetch_failed"))
974
749
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
975
750
return
976
751
}
···
978
753
sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev)
979
754
if err != nil {
980
755
log.Println("failed to create signed client:", err)
981
-
span.RecordError(err)
982
-
span.SetAttributes(attribute.String("error", "signed_client_creation_failed"))
983
756
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
984
757
return
985
758
}
···
987
760
us, err := NewUnsignedClient(fork.Knot, s.config.Dev)
988
761
if err != nil {
989
762
log.Println("failed to create unsigned client:", err)
990
-
span.RecordError(err)
991
-
span.SetAttributes(attribute.String("error", "unsigned_client_creation_failed"))
992
763
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
993
764
return
994
765
}
···
996
767
resp, err := sc.NewHiddenRef(user.Did, fork.Name, sourceBranch, targetBranch)
997
768
if err != nil {
998
769
log.Println("failed to create hidden ref:", err, resp.StatusCode)
999
-
span.RecordError(err)
1000
-
span.SetAttributes(attribute.String("error", "hidden_ref_creation_failed"))
1001
770
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1002
771
return
1003
772
}
1004
773
1005
774
switch resp.StatusCode {
1006
775
case 404:
1007
-
span.SetAttributes(attribute.String("error", "not_found_status"))
1008
776
case 400:
1009
-
span.SetAttributes(attribute.String("error", "bad_request_status"))
1010
777
s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.")
1011
778
return
1012
779
}
1013
780
1014
781
hiddenRef := fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch)
1015
-
span.SetAttributes(attribute.String("hiddenRef", hiddenRef))
1016
-
1017
782
// We're now comparing the sourceBranch (on the fork) against the hiddenRef which is tracking
1018
783
// the targetBranch on the target repository. This code is a bit confusing, but here's an example:
1019
784
// hiddenRef: hidden/feature-1/main (on repo-fork)
···
1022
787
comparison, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch)
1023
788
if err != nil {
1024
789
log.Println("failed to compare across branches", err)
1025
-
span.RecordError(err)
1026
-
span.SetAttributes(attribute.String("error", "branch_comparison_failed"))
1027
790
s.pages.Notice(w, "pull", err.Error())
1028
791
return
1029
792
}
1030
793
1031
794
sourceRev := comparison.Rev2
1032
795
patch := comparison.Patch
1033
-
span.SetAttributes(attribute.String("sourceRev", sourceRev))
1034
796
1035
797
if !patchutil.IsPatchValid(patch) {
1036
-
span.SetAttributes(attribute.String("error", "invalid_patch_format"))
1037
798
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
1038
799
return
1039
800
}
···
1041
802
forkAtUri, err := syntax.ParseATURI(fork.AtUri)
1042
803
if err != nil {
1043
804
log.Println("failed to parse fork AT URI", err)
1044
-
span.RecordError(err)
1045
-
span.SetAttributes(attribute.String("error", "fork_aturi_parse_failed"))
1046
805
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1047
806
return
1048
807
}
1049
808
1050
-
s.createPullRequest(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{
809
+
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{
1051
810
Branch: sourceBranch,
1052
811
RepoAt: &forkAtUri,
1053
812
}, &tangled.RepoPull_Source{Branch: sourceBranch, Repo: &fork.AtUri})
···
1064
823
pullSource *db.PullSource,
1065
824
recordPullSource *tangled.RepoPull_Source,
1066
825
) {
1067
-
ctx, span := s.t.TraceStart(r.Context(), "createPullRequest")
1068
-
defer span.End()
1069
-
1070
-
span.SetAttributes(
1071
-
attribute.String("targetBranch", targetBranch),
1072
-
attribute.String("sourceRev", sourceRev),
1073
-
attribute.Bool("hasPullSource", pullSource != nil),
1074
-
)
1075
-
1076
-
tx, err := s.db.BeginTx(ctx, nil)
826
+
tx, err := s.db.BeginTx(r.Context(), nil)
1077
827
if err != nil {
1078
828
log.Println("failed to start tx")
1079
-
span.RecordError(err)
1080
-
span.SetAttributes(attribute.String("error", "transaction_start_failed"))
1081
829
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1082
830
return
1083
831
}
···
1088
836
if title == "" {
1089
837
formatPatches, err := patchutil.ExtractPatches(patch)
1090
838
if err != nil {
1091
-
span.RecordError(err)
1092
-
span.SetAttributes(attribute.String("error", "extract_patches_failed"))
1093
839
s.pages.Notice(w, "pull", fmt.Sprintf("Failed to extract patches: %v", err))
1094
840
return
1095
841
}
1096
842
if len(formatPatches) == 0 {
1097
-
span.SetAttributes(attribute.String("error", "no_patches_found"))
1098
843
s.pages.Notice(w, "pull", "No patches found in the supplied format-patch.")
1099
844
return
1100
845
}
1101
846
1102
847
title = formatPatches[0].Title
1103
848
body = formatPatches[0].Body
1104
-
span.SetAttributes(
1105
-
attribute.Bool("title_extracted", true),
1106
-
attribute.Bool("body_extracted", formatPatches[0].Body != ""),
1107
-
)
1108
849
}
1109
850
1110
851
rkey := appview.TID()
···
1112
853
Patch: patch,
1113
854
SourceRev: sourceRev,
1114
855
}
1115
-
err = db.NewPull(ctx, tx, &db.Pull{
856
+
err = db.NewPull(tx, &db.Pull{
1116
857
Title: title,
1117
858
Body: body,
1118
859
TargetBranch: targetBranch,
···
1126
867
})
1127
868
if err != nil {
1128
869
log.Println("failed to create pull request", err)
1129
-
span.RecordError(err)
1130
-
span.SetAttributes(attribute.String("error", "db_create_pull_failed"))
1131
870
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1132
871
return
1133
872
}
1134
-
1135
-
client, _ := s.auth.AuthorizedClient(r.WithContext(ctx))
873
+
client, _ := s.auth.AuthorizedClient(r)
1136
874
pullId, err := db.NextPullId(s.db, f.RepoAt)
1137
875
if err != nil {
1138
876
log.Println("failed to get pull id", err)
1139
-
span.RecordError(err)
1140
-
span.SetAttributes(attribute.String("error", "get_pull_id_failed"))
1141
877
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1142
878
return
1143
879
}
1144
-
span.SetAttributes(attribute.Int("pullId", pullId))
1145
880
1146
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
881
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1147
882
Collection: tangled.RepoPullNSID,
1148
883
Repo: user.Did,
1149
884
Rkey: rkey,
···
1161
896
1162
897
if err != nil {
1163
898
log.Println("failed to create pull request", err)
1164
-
span.RecordError(err)
1165
-
span.SetAttributes(attribute.String("error", "atproto_create_record_failed"))
1166
-
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1167
-
return
1168
-
}
1169
-
1170
-
if err = tx.Commit(); err != nil {
1171
-
log.Println("failed to commit transaction", err)
1172
-
span.RecordError(err)
1173
-
span.SetAttributes(attribute.String("error", "transaction_commit_failed"))
1174
899
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
1175
900
return
1176
901
}
···
1179
904
}
1180
905
1181
906
func (s *State) ValidatePatch(w http.ResponseWriter, r *http.Request) {
1182
-
ctx, span := s.t.TraceStart(r.Context(), "ValidatePatch")
1183
-
defer span.End()
1184
-
1185
-
_, err := s.fullyResolvedRepo(r.WithContext(ctx))
907
+
_, err := s.fullyResolvedRepo(r)
1186
908
if err != nil {
1187
909
log.Println("failed to get repo and knot", err)
1188
-
span.RecordError(err)
1189
-
span.SetAttributes(attribute.String("error", "resolve_repo_failed"))
1190
910
return
1191
911
}
1192
912
1193
913
patch := r.FormValue("patch")
1194
-
span.SetAttributes(attribute.Bool("hasPatch", patch != ""))
1195
-
1196
914
if patch == "" {
1197
-
span.SetAttributes(attribute.String("error", "empty_patch"))
1198
915
s.pages.Notice(w, "patch-error", "Patch is required.")
1199
916
return
1200
917
}
1201
918
1202
-
if !patchutil.IsPatchValid(patch) {
1203
-
span.SetAttributes(attribute.String("error", "invalid_patch_format"))
919
+
if patch == "" || !patchutil.IsPatchValid(patch) {
1204
920
s.pages.Notice(w, "patch-error", "Invalid patch format. Please provide a valid git diff or format-patch.")
1205
921
return
1206
922
}
1207
923
1208
-
isFormatPatch := patchutil.IsFormatPatch(patch)
1209
-
span.SetAttributes(attribute.Bool("isFormatPatch", isFormatPatch))
1210
-
1211
-
if isFormatPatch {
924
+
if patchutil.IsFormatPatch(patch) {
1212
925
s.pages.Notice(w, "patch-preview", "git-format-patch detected. Title and description are optional; if left out, they will be extracted from the first commit.")
1213
926
} else {
1214
927
s.pages.Notice(w, "patch-preview", "Regular git-diff detected. Please provide a title and description.")
···
1216
929
}
1217
930
1218
931
func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
1219
-
ctx, span := s.t.TraceStart(r.Context(), "PatchUploadFragment")
1220
-
defer span.End()
1221
-
1222
-
user := s.auth.GetUser(r.WithContext(ctx))
1223
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
932
+
user := s.auth.GetUser(r)
933
+
f, err := s.fullyResolvedRepo(r)
1224
934
if err != nil {
1225
935
log.Println("failed to get repo and knot", err)
1226
-
span.RecordError(err)
1227
-
span.SetAttributes(attribute.String("error", "resolve_repo_failed"))
1228
936
return
1229
937
}
1230
938
1231
939
s.pages.PullPatchUploadFragment(w, pages.PullPatchUploadParams{
1232
-
RepoInfo: f.RepoInfo(ctx, s, user),
940
+
RepoInfo: f.RepoInfo(s, user),
1233
941
})
1234
942
}
1235
943
1236
944
func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) {
1237
-
ctx, span := s.t.TraceStart(r.Context(), "CompareBranchesFragment")
1238
-
defer span.End()
1239
-
1240
-
user := s.auth.GetUser(r.WithContext(ctx))
1241
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
945
+
user := s.auth.GetUser(r)
946
+
f, err := s.fullyResolvedRepo(r)
1242
947
if err != nil {
1243
948
log.Println("failed to get repo and knot", err)
1244
-
span.RecordError(err)
1245
-
span.SetAttributes(attribute.String("error", "resolve_repo_failed"))
1246
949
return
1247
950
}
1248
951
1249
952
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
1250
953
if err != nil {
1251
954
log.Printf("failed to create unsigned client for %s", f.Knot)
1252
-
span.RecordError(err)
1253
-
span.SetAttributes(attribute.String("error", "client_creation_failed"))
1254
955
s.pages.Error503(w)
1255
956
return
1256
957
}
···
1258
959
resp, err := us.Branches(f.OwnerDid(), f.RepoName)
1259
960
if err != nil {
1260
961
log.Println("failed to reach knotserver", err)
1261
-
span.RecordError(err)
1262
-
span.SetAttributes(attribute.String("error", "knotserver_connection_failed"))
1263
962
return
1264
963
}
1265
964
1266
965
body, err := io.ReadAll(resp.Body)
1267
966
if err != nil {
1268
967
log.Printf("Error reading response body: %v", err)
1269
-
span.RecordError(err)
1270
-
span.SetAttributes(attribute.String("error", "response_read_failed"))
1271
968
return
1272
969
}
1273
-
defer resp.Body.Close()
1274
970
1275
971
var result types.RepoBranchesResponse
1276
972
err = json.Unmarshal(body, &result)
1277
973
if err != nil {
1278
974
log.Println("failed to parse response:", err)
1279
-
span.RecordError(err)
1280
-
span.SetAttributes(attribute.String("error", "response_parse_failed"))
1281
975
return
1282
976
}
1283
-
span.SetAttributes(attribute.Int("branches.count", len(result.Branches)))
1284
977
1285
978
s.pages.PullCompareBranchesFragment(w, pages.PullCompareBranchesParams{
1286
-
RepoInfo: f.RepoInfo(ctx, s, user),
979
+
RepoInfo: f.RepoInfo(s, user),
1287
980
Branches: result.Branches,
1288
981
})
1289
982
}
1290
983
1291
984
func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) {
1292
-
ctx, span := s.t.TraceStart(r.Context(), "CompareForksFragment")
1293
-
defer span.End()
1294
-
1295
-
user := s.auth.GetUser(r.WithContext(ctx))
1296
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
985
+
user := s.auth.GetUser(r)
986
+
f, err := s.fullyResolvedRepo(r)
1297
987
if err != nil {
1298
988
log.Println("failed to get repo and knot", err)
1299
-
span.RecordError(err)
1300
989
return
1301
990
}
1302
991
1303
-
forks, err := db.GetForksByDid(ctx, s.db, user.Did)
992
+
forks, err := db.GetForksByDid(s.db, user.Did)
1304
993
if err != nil {
1305
994
log.Println("failed to get forks", err)
1306
-
span.RecordError(err)
1307
995
return
1308
996
}
1309
997
1310
998
s.pages.PullCompareForkFragment(w, pages.PullCompareForkParams{
1311
-
RepoInfo: f.RepoInfo(ctx, s, user),
999
+
RepoInfo: f.RepoInfo(s, user),
1312
1000
Forks: forks,
1313
1001
})
1314
1002
}
1315
1003
1316
1004
func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) {
1317
-
ctx, span := s.t.TraceStart(r.Context(), "CompareForksBranchesFragment")
1318
-
defer span.End()
1005
+
user := s.auth.GetUser(r)
1319
1006
1320
-
user := s.auth.GetUser(r.WithContext(ctx))
1321
-
1322
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1007
+
f, err := s.fullyResolvedRepo(r)
1323
1008
if err != nil {
1324
1009
log.Println("failed to get repo and knot", err)
1325
-
span.RecordError(err)
1326
1010
return
1327
1011
}
1328
1012
1329
1013
forkVal := r.URL.Query().Get("fork")
1330
-
span.SetAttributes(attribute.String("fork", forkVal))
1331
1014
1332
1015
// fork repo
1333
-
repo, err := db.GetRepo(ctx, s.db, user.Did, forkVal)
1016
+
repo, err := db.GetRepo(s.db, user.Did, forkVal)
1334
1017
if err != nil {
1335
1018
log.Println("failed to get repo", user.Did, forkVal)
1336
-
span.RecordError(err)
1337
1019
return
1338
1020
}
1339
1021
1340
1022
sourceBranchesClient, err := NewUnsignedClient(repo.Knot, s.config.Dev)
1341
1023
if err != nil {
1342
1024
log.Printf("failed to create unsigned client for %s", repo.Knot)
1343
-
span.RecordError(err)
1344
1025
s.pages.Error503(w)
1345
1026
return
1346
1027
}
···
1348
1029
sourceResp, err := sourceBranchesClient.Branches(user.Did, repo.Name)
1349
1030
if err != nil {
1350
1031
log.Println("failed to reach knotserver for source branches", err)
1351
-
span.RecordError(err)
1352
1032
return
1353
1033
}
1354
1034
1355
1035
sourceBody, err := io.ReadAll(sourceResp.Body)
1356
1036
if err != nil {
1357
1037
log.Println("failed to read source response body", err)
1358
-
span.RecordError(err)
1359
1038
return
1360
1039
}
1361
1040
defer sourceResp.Body.Close()
···
1364
1043
err = json.Unmarshal(sourceBody, &sourceResult)
1365
1044
if err != nil {
1366
1045
log.Println("failed to parse source branches response:", err)
1367
-
span.RecordError(err)
1368
1046
return
1369
1047
}
1370
1048
1371
1049
targetBranchesClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
1372
1050
if err != nil {
1373
1051
log.Printf("failed to create unsigned client for target knot %s", f.Knot)
1374
-
span.RecordError(err)
1375
1052
s.pages.Error503(w)
1376
1053
return
1377
1054
}
···
1379
1056
targetResp, err := targetBranchesClient.Branches(f.OwnerDid(), f.RepoName)
1380
1057
if err != nil {
1381
1058
log.Println("failed to reach knotserver for target branches", err)
1382
-
span.RecordError(err)
1383
1059
return
1384
1060
}
1385
1061
1386
1062
targetBody, err := io.ReadAll(targetResp.Body)
1387
1063
if err != nil {
1388
1064
log.Println("failed to read target response body", err)
1389
-
span.RecordError(err)
1390
1065
return
1391
1066
}
1392
1067
defer targetResp.Body.Close()
···
1395
1070
err = json.Unmarshal(targetBody, &targetResult)
1396
1071
if err != nil {
1397
1072
log.Println("failed to parse target branches response:", err)
1398
-
span.RecordError(err)
1399
1073
return
1400
1074
}
1401
1075
1402
1076
s.pages.PullCompareForkBranchesFragment(w, pages.PullCompareForkBranchesParams{
1403
-
RepoInfo: f.RepoInfo(ctx, s, user),
1077
+
RepoInfo: f.RepoInfo(s, user),
1404
1078
SourceBranches: sourceResult.Branches,
1405
1079
TargetBranches: targetResult.Branches,
1406
1080
})
1407
1081
}
1408
1082
1409
1083
func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) {
1410
-
ctx, span := s.t.TraceStart(r.Context(), "ResubmitPull")
1411
-
defer span.End()
1412
-
1413
-
user := s.auth.GetUser(r.WithContext(ctx))
1414
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1084
+
user := s.auth.GetUser(r)
1085
+
f, err := s.fullyResolvedRepo(r)
1415
1086
if err != nil {
1416
1087
log.Println("failed to get repo and knot", err)
1417
-
span.RecordError(err)
1418
1088
return
1419
1089
}
1420
1090
1421
-
pull, ok := ctx.Value("pull").(*db.Pull)
1091
+
pull, ok := r.Context().Value("pull").(*db.Pull)
1422
1092
if !ok {
1423
1093
log.Println("failed to get pull")
1424
-
span.RecordError(errors.New("failed to get pull from context"))
1425
1094
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
1426
1095
return
1427
1096
}
1428
1097
1429
-
span.SetAttributes(
1430
-
attribute.Int("pull.id", pull.PullId),
1431
-
attribute.String("pull.owner", pull.OwnerDid),
1432
-
attribute.String("method", r.Method),
1433
-
)
1434
-
1435
1098
switch r.Method {
1436
1099
case http.MethodGet:
1437
1100
s.pages.PullResubmitFragment(w, pages.PullResubmitParams{
1438
-
RepoInfo: f.RepoInfo(ctx, s, user),
1101
+
RepoInfo: f.RepoInfo(s, user),
1439
1102
Pull: pull,
1440
1103
})
1441
1104
return
1442
1105
case http.MethodPost:
1443
1106
if pull.IsPatchBased() {
1444
-
span.SetAttributes(attribute.String("pull.type", "patch_based"))
1445
-
s.resubmitPatch(w, r.WithContext(ctx))
1107
+
s.resubmitPatch(w, r)
1446
1108
return
1447
1109
} else if pull.IsBranchBased() {
1448
-
span.SetAttributes(attribute.String("pull.type", "branch_based"))
1449
-
s.resubmitBranch(w, r.WithContext(ctx))
1110
+
s.resubmitBranch(w, r)
1450
1111
return
1451
1112
} else if pull.IsForkBased() {
1452
-
span.SetAttributes(attribute.String("pull.type", "fork_based"))
1453
-
s.resubmitFork(w, r.WithContext(ctx))
1113
+
s.resubmitFork(w, r)
1454
1114
return
1455
1115
}
1456
-
span.SetAttributes(attribute.String("pull.type", "unknown"))
1457
1116
}
1458
1117
}
1459
1118
1460
1119
func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) {
1461
-
ctx, span := s.t.TraceStart(r.Context(), "resubmitPatch")
1462
-
defer span.End()
1463
-
1464
-
user := s.auth.GetUser(r.WithContext(ctx))
1120
+
user := s.auth.GetUser(r)
1465
1121
1466
-
pull, ok := ctx.Value("pull").(*db.Pull)
1122
+
pull, ok := r.Context().Value("pull").(*db.Pull)
1467
1123
if !ok {
1468
1124
log.Println("failed to get pull")
1469
-
span.RecordError(errors.New("failed to get pull from context"))
1470
1125
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
1471
1126
return
1472
1127
}
1473
1128
1474
-
span.SetAttributes(
1475
-
attribute.Int("pull.id", pull.PullId),
1476
-
attribute.String("pull.owner", pull.OwnerDid),
1477
-
)
1478
-
1479
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1129
+
f, err := s.fullyResolvedRepo(r)
1480
1130
if err != nil {
1481
1131
log.Println("failed to get repo and knot", err)
1482
-
span.RecordError(err)
1483
1132
return
1484
1133
}
1485
1134
1486
1135
if user.Did != pull.OwnerDid {
1487
1136
log.Println("unauthorized user")
1488
-
span.SetAttributes(attribute.String("error", "unauthorized_user"))
1489
1137
w.WriteHeader(http.StatusUnauthorized)
1490
1138
return
1491
1139
}
1492
1140
1493
1141
patch := r.FormValue("patch")
1494
-
span.SetAttributes(attribute.Bool("has_patch", patch != ""))
1495
1142
1496
1143
if err = validateResubmittedPatch(pull, patch); err != nil {
1497
-
span.SetAttributes(attribute.String("error", "invalid_patch"))
1498
1144
s.pages.Notice(w, "resubmit-error", err.Error())
1499
1145
return
1500
1146
}
1501
1147
1502
-
tx, err := s.db.BeginTx(ctx, nil)
1148
+
tx, err := s.db.BeginTx(r.Context(), nil)
1503
1149
if err != nil {
1504
1150
log.Println("failed to start tx")
1505
-
span.RecordError(err)
1506
1151
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1507
1152
return
1508
1153
}
···
1511
1156
err = db.ResubmitPull(tx, pull, patch, "")
1512
1157
if err != nil {
1513
1158
log.Println("failed to resubmit pull request", err)
1514
-
span.RecordError(err)
1515
1159
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.")
1516
1160
return
1517
1161
}
1518
-
client, _ := s.auth.AuthorizedClient(r.WithContext(ctx))
1162
+
client, _ := s.auth.AuthorizedClient(r)
1519
1163
1520
-
ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1164
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1521
1165
if err != nil {
1522
1166
// failed to get record
1523
-
span.RecordError(err)
1524
-
span.SetAttributes(attribute.String("error", "record_not_found"))
1525
1167
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
1526
1168
return
1527
1169
}
1528
1170
1529
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1171
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1530
1172
Collection: tangled.RepoPullNSID,
1531
1173
Repo: user.Did,
1532
1174
Rkey: pull.Rkey,
···
1543
1185
})
1544
1186
if err != nil {
1545
1187
log.Println("failed to update record", err)
1546
-
span.RecordError(err)
1547
1188
s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.")
1548
1189
return
1549
1190
}
1550
1191
1551
1192
if err = tx.Commit(); err != nil {
1552
1193
log.Println("failed to commit transaction", err)
1553
-
span.RecordError(err)
1554
1194
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.")
1555
1195
return
1556
1196
}
···
1560
1200
}
1561
1201
1562
1202
func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) {
1563
-
ctx, span := s.t.TraceStart(r.Context(), "resubmitBranch")
1564
-
defer span.End()
1203
+
user := s.auth.GetUser(r)
1565
1204
1566
-
user := s.auth.GetUser(r.WithContext(ctx))
1567
-
1568
-
pull, ok := ctx.Value("pull").(*db.Pull)
1205
+
pull, ok := r.Context().Value("pull").(*db.Pull)
1569
1206
if !ok {
1570
1207
log.Println("failed to get pull")
1571
-
span.RecordError(errors.New("failed to get pull from context"))
1572
-
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
1208
+
s.pages.Notice(w, "resubmit-error", "Failed to edit patch. Try again later.")
1573
1209
return
1574
1210
}
1575
1211
1576
-
span.SetAttributes(
1577
-
attribute.Int("pull.id", pull.PullId),
1578
-
attribute.String("pull.owner", pull.OwnerDid),
1579
-
attribute.String("pull.source_branch", pull.PullSource.Branch),
1580
-
attribute.String("pull.target_branch", pull.TargetBranch),
1581
-
)
1582
-
1583
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1212
+
f, err := s.fullyResolvedRepo(r)
1584
1213
if err != nil {
1585
1214
log.Println("failed to get repo and knot", err)
1586
-
span.RecordError(err)
1587
1215
return
1588
1216
}
1589
1217
1590
1218
if user.Did != pull.OwnerDid {
1591
1219
log.Println("unauthorized user")
1592
-
span.SetAttributes(attribute.String("error", "unauthorized_user"))
1593
1220
w.WriteHeader(http.StatusUnauthorized)
1594
1221
return
1595
1222
}
1596
1223
1597
-
if !f.RepoInfo(ctx, s, user).Roles.IsPushAllowed() {
1224
+
if !f.RepoInfo(s, user).Roles.IsPushAllowed() {
1598
1225
log.Println("unauthorized user")
1599
-
span.SetAttributes(attribute.String("error", "push_not_allowed"))
1600
1226
w.WriteHeader(http.StatusUnauthorized)
1601
1227
return
1602
1228
}
···
1604
1230
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
1605
1231
if err != nil {
1606
1232
log.Printf("failed to create client for %s: %s", f.Knot, err)
1607
-
span.RecordError(err)
1608
-
span.SetAttributes(attribute.String("error", "client_creation_failed"))
1609
1233
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1610
1234
return
1611
1235
}
···
1613
1237
comparison, err := ksClient.Compare(f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.PullSource.Branch)
1614
1238
if err != nil {
1615
1239
log.Printf("compare request failed: %s", err)
1616
-
span.RecordError(err)
1617
-
span.SetAttributes(attribute.String("error", "compare_failed"))
1618
1240
s.pages.Notice(w, "resubmit-error", err.Error())
1619
1241
return
1620
1242
}
1621
1243
1622
1244
sourceRev := comparison.Rev2
1623
1245
patch := comparison.Patch
1624
-
span.SetAttributes(attribute.String("source_rev", sourceRev))
1625
1246
1626
1247
if err = validateResubmittedPatch(pull, patch); err != nil {
1627
-
span.SetAttributes(attribute.String("error", "invalid_patch"))
1628
1248
s.pages.Notice(w, "resubmit-error", err.Error())
1629
1249
return
1630
1250
}
1631
1251
1632
1252
if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev {
1633
-
span.SetAttributes(attribute.String("error", "no_changes"))
1634
1253
s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.")
1635
1254
return
1636
1255
}
1637
1256
1638
-
tx, err := s.db.BeginTx(ctx, nil)
1257
+
tx, err := s.db.BeginTx(r.Context(), nil)
1639
1258
if err != nil {
1640
1259
log.Println("failed to start tx")
1641
-
span.RecordError(err)
1642
1260
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1643
1261
return
1644
1262
}
···
1647
1265
err = db.ResubmitPull(tx, pull, patch, sourceRev)
1648
1266
if err != nil {
1649
1267
log.Println("failed to create pull request", err)
1650
-
span.RecordError(err)
1651
1268
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1652
1269
return
1653
1270
}
1654
-
client, _ := s.auth.AuthorizedClient(r.WithContext(ctx))
1271
+
client, _ := s.auth.AuthorizedClient(r)
1655
1272
1656
-
ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1273
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1657
1274
if err != nil {
1658
1275
// failed to get record
1659
-
span.RecordError(err)
1660
-
span.SetAttributes(attribute.String("error", "record_not_found"))
1661
1276
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
1662
1277
return
1663
1278
}
···
1665
1280
recordPullSource := &tangled.RepoPull_Source{
1666
1281
Branch: pull.PullSource.Branch,
1667
1282
}
1668
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1283
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1669
1284
Collection: tangled.RepoPullNSID,
1670
1285
Repo: user.Did,
1671
1286
Rkey: pull.Rkey,
···
1683
1298
})
1684
1299
if err != nil {
1685
1300
log.Println("failed to update record", err)
1686
-
span.RecordError(err)
1687
1301
s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.")
1688
1302
return
1689
1303
}
1690
1304
1691
1305
if err = tx.Commit(); err != nil {
1692
1306
log.Println("failed to commit transaction", err)
1693
-
span.RecordError(err)
1694
1307
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.")
1695
1308
return
1696
1309
}
···
1700
1313
}
1701
1314
1702
1315
func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) {
1703
-
ctx, span := s.t.TraceStart(r.Context(), "resubmitFork")
1704
-
defer span.End()
1705
-
1706
-
user := s.auth.GetUser(r.WithContext(ctx))
1316
+
user := s.auth.GetUser(r)
1707
1317
1708
-
pull, ok := ctx.Value("pull").(*db.Pull)
1318
+
pull, ok := r.Context().Value("pull").(*db.Pull)
1709
1319
if !ok {
1710
1320
log.Println("failed to get pull")
1711
-
span.RecordError(errors.New("failed to get pull from context"))
1712
-
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
1321
+
s.pages.Notice(w, "resubmit-error", "Failed to edit patch. Try again later.")
1713
1322
return
1714
1323
}
1715
1324
1716
-
span.SetAttributes(
1717
-
attribute.Int("pull.id", pull.PullId),
1718
-
attribute.String("pull.owner", pull.OwnerDid),
1719
-
attribute.String("pull.source_branch", pull.PullSource.Branch),
1720
-
attribute.String("pull.target_branch", pull.TargetBranch),
1721
-
)
1722
-
1723
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1325
+
f, err := s.fullyResolvedRepo(r)
1724
1326
if err != nil {
1725
1327
log.Println("failed to get repo and knot", err)
1726
-
span.RecordError(err)
1727
1328
return
1728
1329
}
1729
1330
1730
1331
if user.Did != pull.OwnerDid {
1731
1332
log.Println("unauthorized user")
1732
-
span.SetAttributes(attribute.String("error", "unauthorized_user"))
1733
1333
w.WriteHeader(http.StatusUnauthorized)
1734
1334
return
1735
1335
}
1736
1336
1737
-
forkRepo, err := db.GetRepoByAtUri(ctx, s.db, pull.PullSource.RepoAt.String())
1337
+
forkRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String())
1738
1338
if err != nil {
1739
1339
log.Println("failed to get source repo", err)
1740
-
span.RecordError(err)
1741
-
span.SetAttributes(attribute.String("error", "source_repo_not_found"))
1742
1340
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1743
1341
return
1744
1342
}
1745
1343
1746
-
span.SetAttributes(
1747
-
attribute.String("fork.knot", forkRepo.Knot),
1748
-
attribute.String("fork.did", forkRepo.Did),
1749
-
attribute.String("fork.name", forkRepo.Name),
1750
-
)
1751
-
1752
1344
// extract patch by performing compare
1753
1345
ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Dev)
1754
1346
if err != nil {
1755
1347
log.Printf("failed to create client for %s: %s", forkRepo.Knot, err)
1756
-
span.RecordError(err)
1757
-
span.SetAttributes(attribute.String("error", "client_creation_failed"))
1758
1348
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1759
1349
return
1760
1350
}
···
1762
1352
secret, err := db.GetRegistrationKey(s.db, forkRepo.Knot)
1763
1353
if err != nil {
1764
1354
log.Printf("failed to get registration key for %s: %s", forkRepo.Knot, err)
1765
-
span.RecordError(err)
1766
-
span.SetAttributes(attribute.String("error", "reg_key_not_found"))
1767
1355
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1768
1356
return
1769
1357
}
···
1772
1360
signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Dev)
1773
1361
if err != nil {
1774
1362
log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err)
1775
-
span.RecordError(err)
1776
-
span.SetAttributes(attribute.String("error", "signed_client_creation_failed"))
1777
1363
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1778
1364
return
1779
1365
}
···
1781
1367
resp, err := signedClient.NewHiddenRef(forkRepo.Did, forkRepo.Name, pull.PullSource.Branch, pull.TargetBranch)
1782
1368
if err != nil || resp.StatusCode != http.StatusNoContent {
1783
1369
log.Printf("failed to update tracking branch: %s", err)
1784
-
span.RecordError(err)
1785
-
span.SetAttributes(attribute.String("error", "hidden_ref_update_failed"))
1786
1370
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1787
1371
return
1788
1372
}
1789
1373
1790
1374
hiddenRef := fmt.Sprintf("hidden/%s/%s", pull.PullSource.Branch, pull.TargetBranch)
1791
-
span.SetAttributes(attribute.String("hidden_ref", hiddenRef))
1792
-
1793
1375
comparison, err := ksClient.Compare(forkRepo.Did, forkRepo.Name, hiddenRef, pull.PullSource.Branch)
1794
1376
if err != nil {
1795
1377
log.Printf("failed to compare branches: %s", err)
1796
-
span.RecordError(err)
1797
-
span.SetAttributes(attribute.String("error", "compare_failed"))
1798
1378
s.pages.Notice(w, "resubmit-error", err.Error())
1799
1379
return
1800
1380
}
1801
1381
1802
1382
sourceRev := comparison.Rev2
1803
1383
patch := comparison.Patch
1804
-
span.SetAttributes(attribute.String("source_rev", sourceRev))
1805
1384
1806
1385
if err = validateResubmittedPatch(pull, patch); err != nil {
1807
-
span.SetAttributes(attribute.String("error", "invalid_patch"))
1808
1386
s.pages.Notice(w, "resubmit-error", err.Error())
1809
1387
return
1810
1388
}
1811
1389
1812
1390
if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev {
1813
-
span.SetAttributes(attribute.String("error", "no_changes"))
1814
1391
s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.")
1815
1392
return
1816
1393
}
1817
1394
1818
-
tx, err := s.db.BeginTx(ctx, nil)
1395
+
tx, err := s.db.BeginTx(r.Context(), nil)
1819
1396
if err != nil {
1820
1397
log.Println("failed to start tx")
1821
-
span.RecordError(err)
1822
1398
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1823
1399
return
1824
1400
}
···
1827
1403
err = db.ResubmitPull(tx, pull, patch, sourceRev)
1828
1404
if err != nil {
1829
1405
log.Println("failed to create pull request", err)
1830
-
span.RecordError(err)
1831
1406
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1832
1407
return
1833
1408
}
1834
-
client, _ := s.auth.AuthorizedClient(r.WithContext(ctx))
1409
+
client, _ := s.auth.AuthorizedClient(r)
1835
1410
1836
-
ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1411
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1837
1412
if err != nil {
1838
1413
// failed to get record
1839
-
span.RecordError(err)
1840
-
span.SetAttributes(attribute.String("error", "record_not_found"))
1841
1414
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
1842
1415
return
1843
1416
}
···
1847
1420
Branch: pull.PullSource.Branch,
1848
1421
Repo: &repoAt,
1849
1422
}
1850
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1423
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1851
1424
Collection: tangled.RepoPullNSID,
1852
1425
Repo: user.Did,
1853
1426
Rkey: pull.Rkey,
···
1865
1438
})
1866
1439
if err != nil {
1867
1440
log.Println("failed to update record", err)
1868
-
span.RecordError(err)
1869
1441
s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.")
1870
1442
return
1871
1443
}
1872
1444
1873
1445
if err = tx.Commit(); err != nil {
1874
1446
log.Println("failed to commit transaction", err)
1875
-
span.RecordError(err)
1876
1447
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.")
1877
1448
return
1878
1449
}
···
1899
1470
}
1900
1471
1901
1472
func (s *State) MergePull(w http.ResponseWriter, r *http.Request) {
1902
-
ctx, span := s.t.TraceStart(r.Context(), "MergePull")
1903
-
defer span.End()
1904
-
1905
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1473
+
f, err := s.fullyResolvedRepo(r)
1906
1474
if err != nil {
1907
1475
log.Println("failed to resolve repo:", err)
1908
-
span.RecordError(err)
1909
-
span.SetAttributes(attribute.String("error", "resolve_repo_failed"))
1910
1476
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1911
1477
return
1912
1478
}
1913
1479
1914
-
pull, ok := ctx.Value("pull").(*db.Pull)
1480
+
pull, ok := r.Context().Value("pull").(*db.Pull)
1915
1481
if !ok {
1916
1482
log.Println("failed to get pull")
1917
-
span.SetAttributes(attribute.String("error", "pull_not_in_context"))
1918
1483
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
1919
1484
return
1920
1485
}
1921
1486
1922
-
span.SetAttributes(
1923
-
attribute.Int("pull.id", pull.PullId),
1924
-
attribute.String("pull.owner", pull.OwnerDid),
1925
-
attribute.String("target_branch", pull.TargetBranch),
1926
-
)
1927
-
1928
1487
secret, err := db.GetRegistrationKey(s.db, f.Knot)
1929
1488
if err != nil {
1930
1489
log.Printf("no registration key found for domain %s: %s\n", f.Knot, err)
1931
-
span.RecordError(err)
1932
-
span.SetAttributes(attribute.String("error", "reg_key_not_found"))
1933
1490
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1934
1491
return
1935
1492
}
1936
1493
1937
-
ident, err := s.resolver.ResolveIdent(ctx, pull.OwnerDid)
1494
+
ident, err := s.resolver.ResolveIdent(r.Context(), pull.OwnerDid)
1938
1495
if err != nil {
1939
1496
log.Printf("resolving identity: %s", err)
1940
-
span.RecordError(err)
1941
-
span.SetAttributes(attribute.String("error", "resolve_identity_failed"))
1942
1497
w.WriteHeader(http.StatusNotFound)
1943
1498
return
1944
1499
}
···
1946
1501
email, err := db.GetPrimaryEmail(s.db, pull.OwnerDid)
1947
1502
if err != nil {
1948
1503
log.Printf("failed to get primary email: %s", err)
1949
-
span.RecordError(err)
1950
-
span.SetAttributes(attribute.String("error", "get_email_failed"))
1951
1504
}
1952
1505
1953
1506
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
1954
1507
if err != nil {
1955
1508
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
1956
-
span.RecordError(err)
1957
-
span.SetAttributes(attribute.String("error", "client_creation_failed"))
1958
1509
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1959
1510
return
1960
1511
}
···
1963
1514
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address)
1964
1515
if err != nil {
1965
1516
log.Printf("failed to merge pull request: %s", err)
1966
-
span.RecordError(err)
1967
-
span.SetAttributes(attribute.String("error", "merge_failed"))
1968
1517
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1969
1518
return
1970
1519
}
1971
1520
1972
-
span.SetAttributes(attribute.Int("response.status", resp.StatusCode))
1973
-
1974
1521
if resp.StatusCode == http.StatusOK {
1975
1522
err := db.MergePull(s.db, f.RepoAt, pull.PullId)
1976
1523
if err != nil {
1977
1524
log.Printf("failed to update pull request status in database: %s", err)
1978
-
span.RecordError(err)
1979
-
span.SetAttributes(attribute.String("error", "db_update_failed"))
1980
1525
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1981
1526
return
1982
1527
}
1983
1528
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s/pulls/%d", f.OwnerHandle(), f.RepoName, pull.PullId))
1984
1529
} else {
1985
1530
log.Printf("knotserver returned non-OK status code for merge: %d", resp.StatusCode)
1986
-
span.SetAttributes(attribute.String("error", "non_ok_response"))
1987
1531
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1988
1532
}
1989
1533
}
1990
1534
1991
1535
func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) {
1992
-
ctx, span := s.t.TraceStart(r.Context(), "ClosePull")
1993
-
defer span.End()
1994
-
1995
-
user := s.auth.GetUser(r.WithContext(ctx))
1536
+
user := s.auth.GetUser(r)
1996
1537
1997
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1538
+
f, err := s.fullyResolvedRepo(r)
1998
1539
if err != nil {
1999
1540
log.Println("malformed middleware")
2000
-
span.RecordError(err)
2001
-
span.SetAttributes(attribute.String("error", "resolve_repo_failed"))
2002
1541
return
2003
1542
}
2004
1543
2005
-
pull, ok := ctx.Value("pull").(*db.Pull)
1544
+
pull, ok := r.Context().Value("pull").(*db.Pull)
2006
1545
if !ok {
2007
1546
log.Println("failed to get pull")
2008
-
span.SetAttributes(attribute.String("error", "pull_not_in_context"))
2009
1547
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
2010
1548
return
2011
1549
}
2012
1550
2013
-
span.SetAttributes(
2014
-
attribute.Int("pull.id", pull.PullId),
2015
-
attribute.String("pull.owner", pull.OwnerDid),
2016
-
attribute.String("user.did", user.Did),
2017
-
)
2018
-
2019
1551
// auth filter: only owner or collaborators can close
2020
1552
roles := RolesInRepo(s, user, f)
2021
1553
isCollaborator := roles.IsCollaborator()
2022
1554
isPullAuthor := user.Did == pull.OwnerDid
2023
1555
isCloseAllowed := isCollaborator || isPullAuthor
2024
-
2025
-
span.SetAttributes(
2026
-
attribute.Bool("is_collaborator", isCollaborator),
2027
-
attribute.Bool("is_pull_author", isPullAuthor),
2028
-
attribute.Bool("is_close_allowed", isCloseAllowed),
2029
-
)
2030
-
2031
1556
if !isCloseAllowed {
2032
1557
log.Println("failed to close pull")
2033
-
span.SetAttributes(attribute.String("error", "unauthorized"))
2034
1558
s.pages.Notice(w, "pull-close", "You are unauthorized to close this pull.")
2035
1559
return
2036
1560
}
2037
1561
2038
1562
// Start a transaction
2039
-
tx, err := s.db.BeginTx(ctx, nil)
1563
+
tx, err := s.db.BeginTx(r.Context(), nil)
2040
1564
if err != nil {
2041
1565
log.Println("failed to start transaction", err)
2042
-
span.RecordError(err)
2043
-
span.SetAttributes(attribute.String("error", "transaction_start_failed"))
2044
1566
s.pages.Notice(w, "pull-close", "Failed to close pull.")
2045
1567
return
2046
1568
}
···
2049
1571
err = db.ClosePull(tx, f.RepoAt, pull.PullId)
2050
1572
if err != nil {
2051
1573
log.Println("failed to close pull", err)
2052
-
span.RecordError(err)
2053
-
span.SetAttributes(attribute.String("error", "db_close_failed"))
2054
1574
s.pages.Notice(w, "pull-close", "Failed to close pull.")
2055
1575
return
2056
1576
}
···
2058
1578
// Commit the transaction
2059
1579
if err = tx.Commit(); err != nil {
2060
1580
log.Println("failed to commit transaction", err)
2061
-
span.RecordError(err)
2062
-
span.SetAttributes(attribute.String("error", "transaction_commit_failed"))
2063
1581
s.pages.Notice(w, "pull-close", "Failed to close pull.")
2064
1582
return
2065
1583
}
···
2069
1587
}
2070
1588
2071
1589
func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) {
2072
-
ctx, span := s.t.TraceStart(r.Context(), "ReopenPull")
2073
-
defer span.End()
2074
-
2075
-
user := s.auth.GetUser(r.WithContext(ctx))
1590
+
user := s.auth.GetUser(r)
2076
1591
2077
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1592
+
f, err := s.fullyResolvedRepo(r)
2078
1593
if err != nil {
2079
1594
log.Println("failed to resolve repo", err)
2080
-
span.RecordError(err)
2081
-
span.SetAttributes(attribute.String("error", "resolve_repo_failed"))
2082
1595
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
2083
1596
return
2084
1597
}
2085
1598
2086
-
pull, ok := ctx.Value("pull").(*db.Pull)
1599
+
pull, ok := r.Context().Value("pull").(*db.Pull)
2087
1600
if !ok {
2088
1601
log.Println("failed to get pull")
2089
-
span.SetAttributes(attribute.String("error", "pull_not_in_context"))
2090
1602
s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
2091
1603
return
2092
1604
}
2093
1605
2094
-
span.SetAttributes(
2095
-
attribute.Int("pull.id", pull.PullId),
2096
-
attribute.String("pull.owner", pull.OwnerDid),
2097
-
attribute.String("user.did", user.Did),
2098
-
)
2099
-
2100
-
// auth filter: only owner or collaborators can reopen
1606
+
// auth filter: only owner or collaborators can close
2101
1607
roles := RolesInRepo(s, user, f)
2102
1608
isCollaborator := roles.IsCollaborator()
2103
1609
isPullAuthor := user.Did == pull.OwnerDid
2104
-
isReopenAllowed := isCollaborator || isPullAuthor
2105
-
2106
-
span.SetAttributes(
2107
-
attribute.Bool("is_collaborator", isCollaborator),
2108
-
attribute.Bool("is_pull_author", isPullAuthor),
2109
-
attribute.Bool("is_reopen_allowed", isReopenAllowed),
2110
-
)
2111
-
2112
-
if !isReopenAllowed {
2113
-
log.Println("failed to reopen pull")
2114
-
span.SetAttributes(attribute.String("error", "unauthorized"))
2115
-
s.pages.Notice(w, "pull-close", "You are unauthorized to reopen this pull.")
1610
+
isCloseAllowed := isCollaborator || isPullAuthor
1611
+
if !isCloseAllowed {
1612
+
log.Println("failed to close pull")
1613
+
s.pages.Notice(w, "pull-close", "You are unauthorized to close this pull.")
2116
1614
return
2117
1615
}
2118
1616
2119
1617
// Start a transaction
2120
-
tx, err := s.db.BeginTx(ctx, nil)
1618
+
tx, err := s.db.BeginTx(r.Context(), nil)
2121
1619
if err != nil {
2122
1620
log.Println("failed to start transaction", err)
2123
-
span.RecordError(err)
2124
-
span.SetAttributes(attribute.String("error", "transaction_start_failed"))
2125
1621
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
2126
1622
return
2127
1623
}
···
2130
1626
err = db.ReopenPull(tx, f.RepoAt, pull.PullId)
2131
1627
if err != nil {
2132
1628
log.Println("failed to reopen pull", err)
2133
-
span.RecordError(err)
2134
-
span.SetAttributes(attribute.String("error", "db_reopen_failed"))
2135
1629
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
2136
1630
return
2137
1631
}
···
2139
1633
// Commit the transaction
2140
1634
if err = tx.Commit(); err != nil {
2141
1635
log.Println("failed to commit transaction", err)
2142
-
span.RecordError(err)
2143
-
span.SetAttributes(attribute.String("error", "transaction_commit_failed"))
2144
1636
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
2145
1637
return
2146
1638
}
+95
-699
appview/state/repo.go
+95
-699
appview/state/repo.go
···
16
16
"strings"
17
17
"time"
18
18
19
-
"go.opentelemetry.io/otel/attribute"
20
-
"go.opentelemetry.io/otel/codes"
21
19
"tangled.sh/tangled.sh/core/api/tangled"
22
20
"tangled.sh/tangled.sh/core/appview"
23
21
"tangled.sh/tangled.sh/core/appview/auth"
···
40
38
)
41
39
42
40
func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
43
-
ctx, span := s.t.TraceStart(r.Context(), "RepoIndex")
44
-
defer span.End()
45
-
46
41
ref := chi.URLParam(r, "ref")
47
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
42
+
f, err := s.fullyResolvedRepo(r)
48
43
if err != nil {
49
44
log.Println("failed to fully resolve repo", err)
50
-
span.RecordError(err)
51
-
span.SetStatus(codes.Error, "failed to fully resolve repo")
52
45
return
53
46
}
54
47
55
48
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
56
49
if err != nil {
57
50
log.Printf("failed to create unsigned client for %s", f.Knot)
58
-
span.RecordError(err)
59
-
span.SetStatus(codes.Error, "failed to create unsigned client")
60
51
s.pages.Error503(w)
61
52
return
62
53
}
···
65
56
if err != nil {
66
57
s.pages.Error503(w)
67
58
log.Println("failed to reach knotserver", err)
68
-
span.RecordError(err)
69
-
span.SetStatus(codes.Error, "failed to reach knotserver")
70
59
return
71
60
}
72
61
defer resp.Body.Close()
···
74
63
body, err := io.ReadAll(resp.Body)
75
64
if err != nil {
76
65
log.Printf("Error reading response body: %v", err)
77
-
span.RecordError(err)
78
-
span.SetStatus(codes.Error, "error reading response body")
79
66
return
80
67
}
81
68
···
83
70
err = json.Unmarshal(body, &result)
84
71
if err != nil {
85
72
log.Printf("Error unmarshalling response body: %v", err)
86
-
span.RecordError(err)
87
-
span.SetStatus(codes.Error, "error unmarshalling response body")
88
73
return
89
74
}
90
75
···
127
112
tagCount := len(result.Tags)
128
113
fileCount := len(result.Files)
129
114
130
-
span.SetAttributes(
131
-
attribute.Int("commits.count", commitCount),
132
-
attribute.Int("branches.count", branchCount),
133
-
attribute.Int("tags.count", tagCount),
134
-
attribute.Int("files.count", fileCount),
135
-
)
136
-
137
115
commitCount, branchCount, tagCount = balanceIndexItems(commitCount, branchCount, tagCount, fileCount)
138
116
commitsTrunc := result.Commits[:min(commitCount, len(result.Commits))]
139
117
tagsTrunc := result.Tags[:min(tagCount, len(result.Tags))]
···
144
122
user := s.auth.GetUser(r)
145
123
s.pages.RepoIndexPage(w, pages.RepoIndexParams{
146
124
LoggedInUser: user,
147
-
RepoInfo: f.RepoInfo(ctx, s, user),
125
+
RepoInfo: f.RepoInfo(s, user),
148
126
TagMap: tagMap,
149
127
RepoIndexResponse: result,
150
128
CommitsTrunc: commitsTrunc,
···
156
134
}
157
135
158
136
func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
159
-
ctx, span := s.t.TraceStart(r.Context(), "RepoLog")
160
-
defer span.End()
161
-
162
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
137
+
f, err := s.fullyResolvedRepo(r)
163
138
if err != nil {
164
139
log.Println("failed to fully resolve repo", err)
165
-
span.RecordError(err)
166
-
span.SetStatus(codes.Error, "failed to fully resolve repo")
167
140
return
168
141
}
169
142
···
176
149
}
177
150
178
151
ref := chi.URLParam(r, "ref")
179
-
span.SetAttributes(attribute.Int("page", page), attribute.String("ref", ref))
180
152
181
153
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
182
154
if err != nil {
183
155
log.Println("failed to create unsigned client", err)
184
-
span.RecordError(err)
185
-
span.SetStatus(codes.Error, "failed to create unsigned client")
186
156
return
187
157
}
188
158
189
159
resp, err := us.Log(f.OwnerDid(), f.RepoName, ref, page)
190
160
if err != nil {
191
161
log.Println("failed to reach knotserver", err)
192
-
span.RecordError(err)
193
-
span.SetStatus(codes.Error, "failed to reach knotserver")
194
162
return
195
163
}
196
164
197
165
body, err := io.ReadAll(resp.Body)
198
166
if err != nil {
199
167
log.Printf("error reading response body: %v", err)
200
-
span.RecordError(err)
201
-
span.SetStatus(codes.Error, "error reading response body")
202
168
return
203
169
}
204
170
···
206
172
err = json.Unmarshal(body, &repolog)
207
173
if err != nil {
208
174
log.Println("failed to parse json response", err)
209
-
span.RecordError(err)
210
-
span.SetStatus(codes.Error, "failed to parse json response")
211
175
return
212
176
}
213
-
214
-
span.SetAttributes(attribute.Int("commits.count", len(repolog.Commits)))
215
177
216
178
result, err := us.Tags(f.OwnerDid(), f.RepoName)
217
179
if err != nil {
218
180
log.Println("failed to reach knotserver", err)
219
-
span.RecordError(err)
220
-
span.SetStatus(codes.Error, "failed to reach knotserver for tags")
221
181
return
222
182
}
223
183
···
230
190
tagMap[hash] = append(tagMap[hash], tag.Name)
231
191
}
232
192
233
-
span.SetAttributes(attribute.Int("tags.count", len(result.Tags)))
234
-
235
193
user := s.auth.GetUser(r)
236
194
s.pages.RepoLog(w, pages.RepoLogParams{
237
195
LoggedInUser: user,
238
196
TagMap: tagMap,
239
-
RepoInfo: f.RepoInfo(ctx, s, user),
197
+
RepoInfo: f.RepoInfo(s, user),
240
198
RepoLogResponse: repolog,
241
199
EmailToDidOrHandle: EmailToDidOrHandle(s, uniqueEmails(repolog.Commits)),
242
200
})
···
244
202
}
245
203
246
204
func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
247
-
ctx, span := s.t.TraceStart(r.Context(), "RepoDescriptionEdit")
248
-
defer span.End()
249
-
250
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
205
+
f, err := s.fullyResolvedRepo(r)
251
206
if err != nil {
252
207
log.Println("failed to get repo and knot", err)
253
208
w.WriteHeader(http.StatusBadRequest)
···
256
211
257
212
user := s.auth.GetUser(r)
258
213
s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{
259
-
RepoInfo: f.RepoInfo(ctx, s, user),
214
+
RepoInfo: f.RepoInfo(s, user),
260
215
})
261
216
return
262
217
}
263
218
264
219
func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) {
265
-
ctx, span := s.t.TraceStart(r.Context(), "RepoDescription")
266
-
defer span.End()
267
-
268
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
220
+
f, err := s.fullyResolvedRepo(r)
269
221
if err != nil {
270
222
log.Println("failed to get repo and knot", err)
271
-
span.RecordError(err)
272
-
span.SetStatus(codes.Error, "failed to resolve repo")
273
223
w.WriteHeader(http.StatusBadRequest)
274
224
return
275
225
}
···
278
228
rkey := repoAt.RecordKey().String()
279
229
if rkey == "" {
280
230
log.Println("invalid aturi for repo", err)
281
-
span.RecordError(err)
282
-
span.SetStatus(codes.Error, "invalid aturi for repo")
283
231
w.WriteHeader(http.StatusInternalServerError)
284
232
return
285
233
}
286
234
287
235
user := s.auth.GetUser(r)
288
-
span.SetAttributes(attribute.String("method", r.Method))
289
236
290
237
switch r.Method {
291
238
case http.MethodGet:
292
239
s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
293
-
RepoInfo: f.RepoInfo(ctx, s, user),
240
+
RepoInfo: f.RepoInfo(s, user),
294
241
})
295
242
return
296
243
case http.MethodPut:
297
244
user := s.auth.GetUser(r)
298
245
newDescription := r.FormValue("description")
299
-
span.SetAttributes(attribute.String("description", newDescription))
300
246
client, _ := s.auth.AuthorizedClient(r)
301
247
302
248
// optimistic update
303
-
err = db.UpdateDescription(ctx, s.db, string(repoAt), newDescription)
249
+
err = db.UpdateDescription(s.db, string(repoAt), newDescription)
304
250
if err != nil {
305
-
log.Println("failed to perform update-description query", err)
306
-
span.RecordError(err)
307
-
span.SetStatus(codes.Error, "failed to update description in database")
251
+
log.Println("failed to perferom update-description query", err)
308
252
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
309
253
return
310
254
}
···
312
256
// this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field
313
257
//
314
258
// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests
315
-
ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoNSID, user.Did, rkey)
259
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, user.Did, rkey)
316
260
if err != nil {
317
261
// failed to get record
318
-
span.RecordError(err)
319
-
span.SetStatus(codes.Error, "failed to get record from PDS")
320
262
s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.")
321
263
return
322
264
}
323
-
324
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
265
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
325
266
Collection: tangled.RepoNSID,
326
267
Repo: user.Did,
327
268
Rkey: rkey,
···
338
279
})
339
280
340
281
if err != nil {
341
-
log.Println("failed to perform update-description query", err)
342
-
span.RecordError(err)
343
-
span.SetStatus(codes.Error, "failed to put record to PDS")
282
+
log.Println("failed to perferom update-description query", err)
344
283
// failed to get record
345
284
s.pages.Notice(w, "repo-notice", "Failed to update description, unable to save to PDS.")
346
285
return
347
286
}
348
287
349
-
newRepoInfo := f.RepoInfo(ctx, s, user)
288
+
newRepoInfo := f.RepoInfo(s, user)
350
289
newRepoInfo.Description = newDescription
351
290
352
291
s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
···
357
296
}
358
297
359
298
func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
360
-
ctx, span := s.t.TraceStart(r.Context(), "RepoCommit")
361
-
defer span.End()
362
-
363
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
299
+
f, err := s.fullyResolvedRepo(r)
364
300
if err != nil {
365
301
log.Println("failed to fully resolve repo", err)
366
-
span.RecordError(err)
367
-
span.SetStatus(codes.Error, "failed to fully resolve repo")
368
302
return
369
303
}
370
304
ref := chi.URLParam(r, "ref")
···
373
307
protocol = "https"
374
308
}
375
309
376
-
span.SetAttributes(attribute.String("ref", ref), attribute.String("protocol", protocol))
377
-
378
310
if !plumbing.IsHash(ref) {
379
-
span.SetAttributes(attribute.Bool("invalid_hash", true))
380
311
s.pages.Error404(w)
381
312
return
382
313
}
383
314
384
-
requestURL := fmt.Sprintf("%s://%s/%s/%s/commit/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref)
385
-
span.SetAttributes(attribute.String("request_url", requestURL))
386
-
387
-
resp, err := http.Get(requestURL)
315
+
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/commit/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref))
388
316
if err != nil {
389
317
log.Println("failed to reach knotserver", err)
390
-
span.RecordError(err)
391
-
span.SetStatus(codes.Error, "failed to reach knotserver")
392
318
return
393
319
}
394
320
395
321
body, err := io.ReadAll(resp.Body)
396
322
if err != nil {
397
323
log.Printf("Error reading response body: %v", err)
398
-
span.RecordError(err)
399
-
span.SetStatus(codes.Error, "error reading response body")
400
324
return
401
325
}
402
326
···
404
328
err = json.Unmarshal(body, &result)
405
329
if err != nil {
406
330
log.Println("failed to parse response:", err)
407
-
span.RecordError(err)
408
-
span.SetStatus(codes.Error, "failed to parse response")
409
331
return
410
332
}
411
333
412
334
user := s.auth.GetUser(r)
413
335
s.pages.RepoCommit(w, pages.RepoCommitParams{
414
336
LoggedInUser: user,
415
-
RepoInfo: f.RepoInfo(ctx, s, user),
337
+
RepoInfo: f.RepoInfo(s, user),
416
338
RepoCommitResponse: result,
417
339
EmailToDidOrHandle: EmailToDidOrHandle(s, []string{result.Diff.Commit.Author.Email}),
418
340
})
···
420
342
}
421
343
422
344
func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
423
-
ctx, span := s.t.TraceStart(r.Context(), "RepoTree")
424
-
defer span.End()
425
-
426
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
345
+
f, err := s.fullyResolvedRepo(r)
427
346
if err != nil {
428
347
log.Println("failed to fully resolve repo", err)
429
-
span.RecordError(err)
430
-
span.SetStatus(codes.Error, "failed to fully resolve repo")
431
348
return
432
349
}
433
350
···
437
354
if !s.config.Dev {
438
355
protocol = "https"
439
356
}
440
-
441
-
span.SetAttributes(
442
-
attribute.String("ref", ref),
443
-
attribute.String("tree_path", treePath),
444
-
attribute.String("protocol", protocol),
445
-
)
446
-
447
-
requestURL := fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath)
448
-
span.SetAttributes(attribute.String("request_url", requestURL))
449
-
450
-
resp, err := http.Get(requestURL)
357
+
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
451
358
if err != nil {
452
359
log.Println("failed to reach knotserver", err)
453
-
span.RecordError(err)
454
-
span.SetStatus(codes.Error, "failed to reach knotserver")
455
360
return
456
361
}
457
362
458
363
body, err := io.ReadAll(resp.Body)
459
364
if err != nil {
460
365
log.Printf("Error reading response body: %v", err)
461
-
span.RecordError(err)
462
-
span.SetStatus(codes.Error, "error reading response body")
463
366
return
464
367
}
465
368
···
467
370
err = json.Unmarshal(body, &result)
468
371
if err != nil {
469
372
log.Println("failed to parse response:", err)
470
-
span.RecordError(err)
471
-
span.SetStatus(codes.Error, "failed to parse response")
472
373
return
473
374
}
474
375
475
376
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
476
377
// so we can safely redirect to the "parent" (which is the same file).
477
378
if len(result.Files) == 0 && result.Parent == treePath {
478
-
redirectURL := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent)
479
-
span.SetAttributes(attribute.String("redirect_url", redirectURL))
480
-
http.Redirect(w, r, redirectURL, http.StatusFound)
379
+
http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound)
481
380
return
482
381
}
483
382
···
499
398
BreadCrumbs: breadcrumbs,
500
399
BaseTreeLink: baseTreeLink,
501
400
BaseBlobLink: baseBlobLink,
502
-
RepoInfo: f.RepoInfo(ctx, s, user),
401
+
RepoInfo: f.RepoInfo(s, user),
503
402
RepoTreeResponse: result,
504
403
})
505
404
return
506
405
}
507
406
508
407
func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
509
-
ctx, span := s.t.TraceStart(r.Context(), "RepoTags")
510
-
defer span.End()
511
-
512
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
408
+
f, err := s.fullyResolvedRepo(r)
513
409
if err != nil {
514
410
log.Println("failed to get repo and knot", err)
515
-
span.RecordError(err)
516
-
span.SetStatus(codes.Error, "failed to get repo and knot")
517
411
return
518
412
}
519
413
520
414
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
521
415
if err != nil {
522
416
log.Println("failed to create unsigned client", err)
523
-
span.RecordError(err)
524
-
span.SetStatus(codes.Error, "failed to create unsigned client")
525
417
return
526
418
}
527
419
528
420
result, err := us.Tags(f.OwnerDid(), f.RepoName)
529
421
if err != nil {
530
422
log.Println("failed to reach knotserver", err)
531
-
span.RecordError(err)
532
-
span.SetStatus(codes.Error, "failed to reach knotserver")
533
423
return
534
424
}
535
425
536
-
span.SetAttributes(attribute.Int("tags.count", len(result.Tags)))
537
-
538
426
artifacts, err := db.GetArtifact(s.db, db.Filter("repo_at", f.RepoAt))
539
427
if err != nil {
540
428
log.Println("failed grab artifacts", err)
541
-
span.RecordError(err)
542
-
span.SetStatus(codes.Error, "failed to grab artifacts")
543
429
return
544
430
}
545
-
546
-
span.SetAttributes(attribute.Int("artifacts.count", len(artifacts)))
547
431
548
432
// convert artifacts to map for easy UI building
549
433
artifactMap := make(map[plumbing.Hash][]db.Artifact)
···
567
451
}
568
452
}
569
453
570
-
span.SetAttributes(attribute.Int("dangling_artifacts.count", len(danglingArtifacts)))
571
-
572
454
user := s.auth.GetUser(r)
573
455
s.pages.RepoTags(w, pages.RepoTagsParams{
574
456
LoggedInUser: user,
575
-
RepoInfo: f.RepoInfo(ctx, s, user),
457
+
RepoInfo: f.RepoInfo(s, user),
576
458
RepoTagsResponse: *result,
577
459
ArtifactMap: artifactMap,
578
460
DanglingArtifacts: danglingArtifacts,
···
581
463
}
582
464
583
465
func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) {
584
-
ctx, span := s.t.TraceStart(r.Context(), "RepoBranches")
585
-
defer span.End()
586
-
587
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
466
+
f, err := s.fullyResolvedRepo(r)
588
467
if err != nil {
589
468
log.Println("failed to get repo and knot", err)
590
-
span.RecordError(err)
591
-
span.SetStatus(codes.Error, "failed to get repo and knot")
592
469
return
593
470
}
594
471
595
472
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
596
473
if err != nil {
597
474
log.Println("failed to create unsigned client", err)
598
-
span.RecordError(err)
599
-
span.SetStatus(codes.Error, "failed to create unsigned client")
600
475
return
601
476
}
602
477
603
478
resp, err := us.Branches(f.OwnerDid(), f.RepoName)
604
479
if err != nil {
605
480
log.Println("failed to reach knotserver", err)
606
-
span.RecordError(err)
607
-
span.SetStatus(codes.Error, "failed to reach knotserver")
608
481
return
609
482
}
610
483
611
484
body, err := io.ReadAll(resp.Body)
612
485
if err != nil {
613
486
log.Printf("Error reading response body: %v", err)
614
-
span.RecordError(err)
615
-
span.SetStatus(codes.Error, "error reading response body")
616
487
return
617
488
}
618
489
···
620
491
err = json.Unmarshal(body, &result)
621
492
if err != nil {
622
493
log.Println("failed to parse response:", err)
623
-
span.RecordError(err)
624
-
span.SetStatus(codes.Error, "failed to parse response")
625
494
return
626
495
}
627
-
628
-
span.SetAttributes(attribute.Int("branches.count", len(result.Branches)))
629
496
630
497
slices.SortFunc(result.Branches, func(a, b types.Branch) int {
631
498
if a.IsDefault {
···
647
514
user := s.auth.GetUser(r)
648
515
s.pages.RepoBranches(w, pages.RepoBranchesParams{
649
516
LoggedInUser: user,
650
-
RepoInfo: f.RepoInfo(ctx, s, user),
517
+
RepoInfo: f.RepoInfo(s, user),
651
518
RepoBranchesResponse: result,
652
519
})
653
520
return
654
521
}
655
522
656
523
func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) {
657
-
ctx, span := s.t.TraceStart(r.Context(), "RepoBlob")
658
-
defer span.End()
659
-
660
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
524
+
f, err := s.fullyResolvedRepo(r)
661
525
if err != nil {
662
526
log.Println("failed to get repo and knot", err)
663
-
span.RecordError(err)
664
-
span.SetStatus(codes.Error, "failed to get repo and knot")
665
527
return
666
528
}
667
529
···
671
533
if !s.config.Dev {
672
534
protocol = "https"
673
535
}
674
-
675
-
span.SetAttributes(
676
-
attribute.String("ref", ref),
677
-
attribute.String("file_path", filePath),
678
-
attribute.String("protocol", protocol),
679
-
)
680
-
681
-
requestURL := fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)
682
-
span.SetAttributes(attribute.String("request_url", requestURL))
683
-
684
-
resp, err := http.Get(requestURL)
536
+
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
685
537
if err != nil {
686
538
log.Println("failed to reach knotserver", err)
687
-
span.RecordError(err)
688
-
span.SetStatus(codes.Error, "failed to reach knotserver")
689
539
return
690
540
}
691
541
692
542
body, err := io.ReadAll(resp.Body)
693
543
if err != nil {
694
544
log.Printf("Error reading response body: %v", err)
695
-
span.RecordError(err)
696
-
span.SetStatus(codes.Error, "error reading response body")
697
545
return
698
546
}
699
547
···
701
549
err = json.Unmarshal(body, &result)
702
550
if err != nil {
703
551
log.Println("failed to parse response:", err)
704
-
span.RecordError(err)
705
-
span.SetStatus(codes.Error, "failed to parse response")
706
552
return
707
553
}
708
554
···
722
568
showRendered = r.URL.Query().Get("code") != "true"
723
569
}
724
570
725
-
span.SetAttributes(
726
-
attribute.Bool("is_binary", result.IsBinary),
727
-
attribute.Bool("show_rendered", showRendered),
728
-
attribute.Bool("render_toggle", renderToggle),
729
-
)
730
-
731
571
user := s.auth.GetUser(r)
732
572
s.pages.RepoBlob(w, pages.RepoBlobParams{
733
573
LoggedInUser: user,
734
-
RepoInfo: f.RepoInfo(ctx, s, user),
574
+
RepoInfo: f.RepoInfo(s, user),
735
575
RepoBlobResponse: result,
736
576
BreadCrumbs: breadcrumbs,
737
577
ShowRendered: showRendered,
···
741
581
}
742
582
743
583
func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) {
744
-
ctx, span := s.t.TraceStart(r.Context(), "RepoBlobRaw")
745
-
defer span.End()
746
-
747
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
584
+
f, err := s.fullyResolvedRepo(r)
748
585
if err != nil {
749
586
log.Println("failed to get repo and knot", err)
750
-
span.RecordError(err)
751
-
span.SetStatus(codes.Error, "failed to get repo and knot")
752
587
return
753
588
}
754
589
···
759
594
if !s.config.Dev {
760
595
protocol = "https"
761
596
}
762
-
763
-
span.SetAttributes(
764
-
attribute.String("ref", ref),
765
-
attribute.String("file_path", filePath),
766
-
attribute.String("protocol", protocol),
767
-
)
768
-
769
-
requestURL := fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)
770
-
span.SetAttributes(attribute.String("request_url", requestURL))
771
-
772
-
resp, err := http.Get(requestURL)
597
+
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
773
598
if err != nil {
774
599
log.Println("failed to reach knotserver", err)
775
-
span.RecordError(err)
776
-
span.SetStatus(codes.Error, "failed to reach knotserver")
777
600
return
778
601
}
779
602
780
603
body, err := io.ReadAll(resp.Body)
781
604
if err != nil {
782
605
log.Printf("Error reading response body: %v", err)
783
-
span.RecordError(err)
784
-
span.SetStatus(codes.Error, "error reading response body")
785
606
return
786
607
}
787
608
···
789
610
err = json.Unmarshal(body, &result)
790
611
if err != nil {
791
612
log.Println("failed to parse response:", err)
792
-
span.RecordError(err)
793
-
span.SetStatus(codes.Error, "failed to parse response")
794
613
return
795
614
}
796
-
797
-
span.SetAttributes(attribute.Bool("is_binary", result.IsBinary))
798
615
799
616
if result.IsBinary {
800
617
w.Header().Set("Content-Type", "application/octet-stream")
···
808
625
}
809
626
810
627
func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
811
-
ctx, span := s.t.TraceStart(r.Context(), "AddCollaborator")
812
-
defer span.End()
813
-
814
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
628
+
f, err := s.fullyResolvedRepo(r)
815
629
if err != nil {
816
630
log.Println("failed to get repo and knot", err)
817
-
span.RecordError(err)
818
-
span.SetStatus(codes.Error, "failed to get repo and knot")
819
631
return
820
632
}
821
633
822
634
collaborator := r.FormValue("collaborator")
823
635
if collaborator == "" {
824
-
span.SetAttributes(attribute.String("error", "malformed_form"))
825
636
http.Error(w, "malformed form", http.StatusBadRequest)
826
637
return
827
638
}
828
639
829
-
span.SetAttributes(attribute.String("collaborator", collaborator))
830
-
831
-
collaboratorIdent, err := s.resolver.ResolveIdent(ctx, collaborator)
640
+
collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator)
832
641
if err != nil {
833
-
span.RecordError(err)
834
-
span.SetStatus(codes.Error, "failed to resolve collaborator")
835
642
w.Write([]byte("failed to resolve collaborator did to a handle"))
836
643
return
837
644
}
838
645
log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot)
839
-
span.SetAttributes(
840
-
attribute.String("collaborator_did", collaboratorIdent.DID.String()),
841
-
attribute.String("collaborator_handle", collaboratorIdent.Handle.String()),
842
-
)
843
646
844
647
// TODO: create an atproto record for this
845
648
846
649
secret, err := db.GetRegistrationKey(s.db, f.Knot)
847
650
if err != nil {
848
651
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
849
-
span.RecordError(err)
850
-
span.SetStatus(codes.Error, "no key found for domain")
851
652
return
852
653
}
853
654
854
655
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
855
656
if err != nil {
856
657
log.Println("failed to create client to ", f.Knot)
857
-
span.RecordError(err)
858
-
span.SetStatus(codes.Error, "failed to create signed client")
859
658
return
860
659
}
861
660
862
661
ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String())
863
662
if err != nil {
864
663
log.Printf("failed to make request to %s: %s", f.Knot, err)
865
-
span.RecordError(err)
866
-
span.SetStatus(codes.Error, "failed to make request to knotserver")
867
664
return
868
665
}
869
666
870
667
if ksResp.StatusCode != http.StatusNoContent {
871
-
span.SetAttributes(attribute.Int("status_code", ksResp.StatusCode))
872
668
w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err)))
873
669
return
874
670
}
875
671
876
-
tx, err := s.db.BeginTx(ctx, nil)
672
+
tx, err := s.db.BeginTx(r.Context(), nil)
877
673
if err != nil {
878
674
log.Println("failed to start tx")
879
-
span.RecordError(err)
880
-
span.SetStatus(codes.Error, "failed to start transaction")
881
675
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
882
676
return
883
677
}
···
891
685
892
686
err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo())
893
687
if err != nil {
894
-
span.RecordError(err)
895
-
span.SetStatus(codes.Error, "failed to add collaborator to enforcer")
896
688
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
897
689
return
898
690
}
899
691
900
-
err = db.AddCollaborator(ctx, s.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot)
692
+
err = db.AddCollaborator(s.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot)
901
693
if err != nil {
902
-
span.RecordError(err)
903
-
span.SetStatus(codes.Error, "failed to add collaborator to database")
904
694
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
905
695
return
906
696
}
···
908
698
err = tx.Commit()
909
699
if err != nil {
910
700
log.Println("failed to commit changes", err)
911
-
span.RecordError(err)
912
-
span.SetStatus(codes.Error, "failed to commit transaction")
913
701
http.Error(w, err.Error(), http.StatusInternalServerError)
914
702
return
915
703
}
···
917
705
err = s.enforcer.E.SavePolicy()
918
706
if err != nil {
919
707
log.Println("failed to update ACLs", err)
920
-
span.RecordError(err)
921
-
span.SetStatus(codes.Error, "failed to save enforcer policy")
922
708
http.Error(w, err.Error(), http.StatusInternalServerError)
923
709
return
924
710
}
925
711
926
712
w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String())))
713
+
927
714
}
928
715
929
716
func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) {
930
-
ctx, span := s.t.TraceStart(r.Context(), "DeleteRepo")
931
-
defer span.End()
932
-
933
717
user := s.auth.GetUser(r)
934
718
935
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
719
+
f, err := s.fullyResolvedRepo(r)
936
720
if err != nil {
937
721
log.Println("failed to get repo and knot", err)
938
-
span.RecordError(err)
939
-
span.SetStatus(codes.Error, "failed to get repo and knot")
940
722
return
941
723
}
942
724
943
-
span.SetAttributes(
944
-
attribute.String("repo_name", f.RepoName),
945
-
attribute.String("knot", f.Knot),
946
-
attribute.String("owner_did", f.OwnerDid()),
947
-
)
948
-
949
725
// remove record from pds
950
726
xrpcClient, _ := s.auth.AuthorizedClient(r)
951
727
repoRkey := f.RepoAt.RecordKey().String()
952
-
_, err = comatproto.RepoDeleteRecord(ctx, xrpcClient, &comatproto.RepoDeleteRecord_Input{
728
+
_, err = comatproto.RepoDeleteRecord(r.Context(), xrpcClient, &comatproto.RepoDeleteRecord_Input{
953
729
Collection: tangled.RepoNSID,
954
730
Repo: user.Did,
955
731
Rkey: repoRkey,
956
732
})
957
733
if err != nil {
958
734
log.Printf("failed to delete record: %s", err)
959
-
span.RecordError(err)
960
-
span.SetStatus(codes.Error, "failed to delete record from PDS")
961
735
s.pages.Notice(w, "settings-delete", "Failed to delete repository from PDS.")
962
736
return
963
737
}
964
738
log.Println("removed repo record ", f.RepoAt.String())
965
-
span.SetAttributes(attribute.String("repo_at", f.RepoAt.String()))
966
739
967
740
secret, err := db.GetRegistrationKey(s.db, f.Knot)
968
741
if err != nil {
969
742
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
970
-
span.RecordError(err)
971
-
span.SetStatus(codes.Error, "no key found for domain")
972
743
return
973
744
}
974
745
975
746
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
976
747
if err != nil {
977
748
log.Println("failed to create client to ", f.Knot)
978
-
span.RecordError(err)
979
-
span.SetStatus(codes.Error, "failed to create client")
980
749
return
981
750
}
982
751
983
752
ksResp, err := ksClient.RemoveRepo(f.OwnerDid(), f.RepoName)
984
753
if err != nil {
985
754
log.Printf("failed to make request to %s: %s", f.Knot, err)
986
-
span.RecordError(err)
987
-
span.SetStatus(codes.Error, "failed to make request to knotserver")
988
755
return
989
756
}
990
757
991
-
span.SetAttributes(attribute.Int("ks_status_code", ksResp.StatusCode))
992
758
if ksResp.StatusCode != http.StatusNoContent {
993
759
log.Println("failed to remove repo from knot, continuing anyway ", f.Knot)
994
-
span.SetAttributes(attribute.Bool("knot_remove_failed", true))
995
760
} else {
996
761
log.Println("removed repo from knot ", f.Knot)
997
-
span.SetAttributes(attribute.Bool("knot_remove_success", true))
998
762
}
999
763
1000
-
tx, err := s.db.BeginTx(ctx, nil)
764
+
tx, err := s.db.BeginTx(r.Context(), nil)
1001
765
if err != nil {
1002
766
log.Println("failed to start tx")
1003
-
span.RecordError(err)
1004
-
span.SetStatus(codes.Error, "failed to start transaction")
1005
767
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
1006
768
return
1007
769
}
···
1010
772
err = s.enforcer.E.LoadPolicy()
1011
773
if err != nil {
1012
774
log.Println("failed to rollback policies")
1013
-
span.RecordError(err)
1014
775
}
1015
776
}()
1016
777
1017
778
// remove collaborator RBAC
1018
779
repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot)
1019
780
if err != nil {
1020
-
span.RecordError(err)
1021
-
span.SetStatus(codes.Error, "failed to get collaborators")
1022
781
s.pages.Notice(w, "settings-delete", "Failed to remove collaborators")
1023
782
return
1024
783
}
1025
-
span.SetAttributes(attribute.Int("collaborators.count", len(repoCollaborators)))
1026
-
1027
784
for _, c := range repoCollaborators {
1028
785
did := c[0]
1029
786
s.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo())
···
1033
790
// remove repo RBAC
1034
791
err = s.enforcer.RemoveRepo(f.OwnerDid(), f.Knot, f.DidSlashRepo())
1035
792
if err != nil {
1036
-
span.RecordError(err)
1037
-
span.SetStatus(codes.Error, "failed to remove repo RBAC")
1038
793
s.pages.Notice(w, "settings-delete", "Failed to update RBAC rules")
1039
794
return
1040
795
}
1041
796
1042
797
// remove repo from db
1043
-
err = db.RemoveRepo(ctx, tx, f.OwnerDid(), f.RepoName)
798
+
err = db.RemoveRepo(tx, f.OwnerDid(), f.RepoName)
1044
799
if err != nil {
1045
-
span.RecordError(err)
1046
-
span.SetStatus(codes.Error, "failed to remove repo from db")
1047
800
s.pages.Notice(w, "settings-delete", "Failed to update appview")
1048
801
return
1049
802
}
···
1052
805
err = tx.Commit()
1053
806
if err != nil {
1054
807
log.Println("failed to commit changes", err)
1055
-
span.RecordError(err)
1056
-
span.SetStatus(codes.Error, "failed to commit transaction")
1057
808
http.Error(w, err.Error(), http.StatusInternalServerError)
1058
809
return
1059
810
}
···
1061
812
err = s.enforcer.E.SavePolicy()
1062
813
if err != nil {
1063
814
log.Println("failed to update ACLs", err)
1064
-
span.RecordError(err)
1065
-
span.SetStatus(codes.Error, "failed to save policy")
1066
815
http.Error(w, err.Error(), http.StatusInternalServerError)
1067
816
return
1068
817
}
···
1071
820
}
1072
821
1073
822
func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
1074
-
ctx, span := s.t.TraceStart(r.Context(), "SetDefaultBranch")
1075
-
defer span.End()
1076
-
1077
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
823
+
f, err := s.fullyResolvedRepo(r)
1078
824
if err != nil {
1079
825
log.Println("failed to get repo and knot", err)
1080
-
span.RecordError(err)
1081
-
span.SetStatus(codes.Error, "failed to get repo and knot")
1082
826
return
1083
827
}
1084
828
1085
829
branch := r.FormValue("branch")
1086
830
if branch == "" {
1087
-
span.SetAttributes(attribute.Bool("malformed_form", true))
1088
-
span.SetStatus(codes.Error, "malformed form")
1089
831
http.Error(w, "malformed form", http.StatusBadRequest)
1090
832
return
1091
833
}
1092
834
1093
-
span.SetAttributes(
1094
-
attribute.String("branch", branch),
1095
-
attribute.String("repo_name", f.RepoName),
1096
-
attribute.String("knot", f.Knot),
1097
-
attribute.String("owner_did", f.OwnerDid()),
1098
-
)
1099
-
1100
835
secret, err := db.GetRegistrationKey(s.db, f.Knot)
1101
836
if err != nil {
1102
837
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
1103
-
span.RecordError(err)
1104
-
span.SetStatus(codes.Error, "no key found for domain")
1105
838
return
1106
839
}
1107
840
1108
841
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
1109
842
if err != nil {
1110
843
log.Println("failed to create client to ", f.Knot)
1111
-
span.RecordError(err)
1112
-
span.SetStatus(codes.Error, "failed to create client")
1113
844
return
1114
845
}
1115
846
1116
847
ksResp, err := ksClient.SetDefaultBranch(f.OwnerDid(), f.RepoName, branch)
1117
848
if err != nil {
1118
849
log.Printf("failed to make request to %s: %s", f.Knot, err)
1119
-
span.RecordError(err)
1120
-
span.SetStatus(codes.Error, "failed to make request to knotserver")
1121
850
return
1122
851
}
1123
852
1124
-
span.SetAttributes(attribute.Int("ks_status_code", ksResp.StatusCode))
1125
853
if ksResp.StatusCode != http.StatusNoContent {
1126
-
span.SetStatus(codes.Error, "failed to set default branch")
1127
854
s.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.")
1128
855
return
1129
856
}
···
1132
859
}
1133
860
1134
861
func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
1135
-
ctx, span := s.t.TraceStart(r.Context(), "RepoSettings")
1136
-
defer span.End()
1137
-
1138
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
862
+
f, err := s.fullyResolvedRepo(r)
1139
863
if err != nil {
1140
864
log.Println("failed to get repo and knot", err)
1141
-
span.RecordError(err)
1142
-
span.SetStatus(codes.Error, "failed to get repo and knot")
1143
865
return
1144
866
}
1145
867
1146
-
span.SetAttributes(
1147
-
attribute.String("repo_name", f.RepoName),
1148
-
attribute.String("knot", f.Knot),
1149
-
attribute.String("owner_did", f.OwnerDid()),
1150
-
attribute.String("method", r.Method),
1151
-
)
1152
-
1153
868
switch r.Method {
1154
869
case http.MethodGet:
1155
870
// for now, this is just pubkeys
1156
871
user := s.auth.GetUser(r)
1157
-
repoCollaborators, err := f.Collaborators(ctx, s)
872
+
repoCollaborators, err := f.Collaborators(r.Context(), s)
1158
873
if err != nil {
1159
874
log.Println("failed to get collaborators", err)
1160
-
span.RecordError(err)
1161
-
span.SetAttributes(attribute.String("error", "failed_to_get_collaborators"))
1162
875
}
1163
-
span.SetAttributes(attribute.Int("collaborators.count", len(repoCollaborators)))
1164
876
1165
877
isCollaboratorInviteAllowed := false
1166
878
if user != nil {
···
1169
881
isCollaboratorInviteAllowed = true
1170
882
}
1171
883
}
1172
-
span.SetAttributes(attribute.Bool("invite_allowed", isCollaboratorInviteAllowed))
1173
884
1174
885
var branchNames []string
1175
886
var defaultBranch string
1176
887
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
1177
888
if err != nil {
1178
889
log.Println("failed to create unsigned client", err)
1179
-
span.RecordError(err)
1180
-
span.SetAttributes(attribute.String("error", "failed_to_create_unsigned_client"))
1181
890
} else {
1182
891
resp, err := us.Branches(f.OwnerDid(), f.RepoName)
1183
892
if err != nil {
1184
893
log.Println("failed to reach knotserver", err)
1185
-
span.RecordError(err)
1186
-
span.SetAttributes(attribute.String("error", "failed_to_reach_knotserver_for_branches"))
1187
894
} else {
1188
895
defer resp.Body.Close()
1189
896
1190
897
body, err := io.ReadAll(resp.Body)
1191
898
if err != nil {
1192
899
log.Printf("Error reading response body: %v", err)
1193
-
span.RecordError(err)
1194
-
span.SetAttributes(attribute.String("error", "failed_to_read_branches_response"))
1195
900
} else {
1196
901
var result types.RepoBranchesResponse
1197
902
err = json.Unmarshal(body, &result)
1198
903
if err != nil {
1199
904
log.Println("failed to parse response:", err)
1200
-
span.RecordError(err)
1201
-
span.SetAttributes(attribute.String("error", "failed_to_parse_branches_response"))
1202
905
} else {
1203
906
for _, branch := range result.Branches {
1204
907
branchNames = append(branchNames, branch.Name)
1205
908
}
1206
-
span.SetAttributes(attribute.Int("branches.count", len(branchNames)))
1207
909
}
1208
910
}
1209
911
}
···
1211
913
defaultBranchResp, err := us.DefaultBranch(f.OwnerDid(), f.RepoName)
1212
914
if err != nil {
1213
915
log.Println("failed to reach knotserver", err)
1214
-
span.RecordError(err)
1215
-
span.SetAttributes(attribute.String("error", "failed_to_reach_knotserver_for_default_branch"))
1216
916
} else {
1217
917
defaultBranch = defaultBranchResp.Branch
1218
-
span.SetAttributes(attribute.String("default_branch", defaultBranch))
1219
918
}
1220
919
}
1221
920
s.pages.RepoSettings(w, pages.RepoSettingsParams{
1222
921
LoggedInUser: user,
1223
-
RepoInfo: f.RepoInfo(ctx, s, user),
922
+
RepoInfo: f.RepoInfo(s, user),
1224
923
Collaborators: repoCollaborators,
1225
924
IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed,
1226
925
Branches: branchNames,
···
1309
1008
return collaborators, nil
1310
1009
}
1311
1010
1312
-
func (f *FullyResolvedRepo) RepoInfo(ctx context.Context, s *State, u *auth.User) repoinfo.RepoInfo {
1313
-
ctx, span := s.t.TraceStart(ctx, "RepoInfo")
1314
-
defer span.End()
1315
-
1011
+
func (f *FullyResolvedRepo) RepoInfo(s *State, u *auth.User) repoinfo.RepoInfo {
1316
1012
isStarred := false
1317
1013
if u != nil {
1318
1014
isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt))
1319
-
span.SetAttributes(attribute.Bool("is_starred", isStarred))
1320
1015
}
1321
1016
1322
1017
starCount, err := db.GetStarCount(s.db, f.RepoAt)
1323
1018
if err != nil {
1324
1019
log.Println("failed to get star count for ", f.RepoAt)
1325
-
span.RecordError(err)
1326
1020
}
1327
-
1328
1021
issueCount, err := db.GetIssueCount(s.db, f.RepoAt)
1329
1022
if err != nil {
1330
1023
log.Println("failed to get issue count for ", f.RepoAt)
1331
-
span.RecordError(err)
1332
1024
}
1333
-
1334
1025
pullCount, err := db.GetPullCount(s.db, f.RepoAt)
1335
1026
if err != nil {
1336
1027
log.Println("failed to get issue count for ", f.RepoAt)
1337
-
span.RecordError(err)
1338
1028
}
1339
-
1340
-
span.SetAttributes(
1341
-
attribute.Int("stats.stars", starCount),
1342
-
attribute.Int("stats.issues.open", issueCount.Open),
1343
-
attribute.Int("stats.issues.closed", issueCount.Closed),
1344
-
attribute.Int("stats.pulls.open", pullCount.Open),
1345
-
attribute.Int("stats.pulls.closed", pullCount.Closed),
1346
-
attribute.Int("stats.pulls.merged", pullCount.Merged),
1347
-
)
1348
-
1349
-
source, err := db.GetRepoSource(ctx, s.db, f.RepoAt)
1029
+
source, err := db.GetRepoSource(s.db, f.RepoAt)
1350
1030
if errors.Is(err, sql.ErrNoRows) {
1351
1031
source = ""
1352
1032
} else if err != nil {
1353
1033
log.Println("failed to get repo source for ", f.RepoAt, err)
1354
-
span.RecordError(err)
1355
1034
}
1356
1035
1357
1036
var sourceRepo *db.Repo
1358
1037
if source != "" {
1359
-
span.SetAttributes(attribute.String("source", source))
1360
-
sourceRepo, err = db.GetRepoByAtUri(ctx, s.db, source)
1038
+
sourceRepo, err = db.GetRepoByAtUri(s.db, source)
1361
1039
if err != nil {
1362
1040
log.Println("failed to get repo by at uri", err)
1363
-
span.RecordError(err)
1364
1041
}
1365
1042
}
1366
1043
1367
1044
var sourceHandle *identity.Identity
1368
1045
if sourceRepo != nil {
1369
-
sourceHandle, err = s.resolver.ResolveIdent(ctx, sourceRepo.Did)
1046
+
sourceHandle, err = s.resolver.ResolveIdent(context.Background(), sourceRepo.Did)
1370
1047
if err != nil {
1371
1048
log.Println("failed to resolve source repo", err)
1372
-
span.RecordError(err)
1373
-
} else if sourceHandle != nil {
1374
-
span.SetAttributes(attribute.String("source_handle", sourceHandle.Handle.String()))
1375
1049
}
1376
1050
}
1377
1051
1378
1052
knot := f.Knot
1379
-
span.SetAttributes(attribute.String("knot", knot))
1380
-
1381
1053
var disableFork bool
1382
1054
us, err := NewUnsignedClient(knot, s.config.Dev)
1383
1055
if err != nil {
1384
1056
log.Printf("failed to create unsigned client for %s: %v", knot, err)
1385
-
span.RecordError(err)
1386
1057
} else {
1387
1058
resp, err := us.Branches(f.OwnerDid(), f.RepoName)
1388
1059
if err != nil {
1389
1060
log.Printf("failed to get branches for %s/%s: %v", f.OwnerDid(), f.RepoName, err)
1390
-
span.RecordError(err)
1391
1061
} else {
1392
1062
defer resp.Body.Close()
1393
1063
body, err := io.ReadAll(resp.Body)
1394
1064
if err != nil {
1395
1065
log.Printf("error reading branch response body: %v", err)
1396
-
span.RecordError(err)
1397
1066
} else {
1398
1067
var branchesResp types.RepoBranchesResponse
1399
1068
if err := json.Unmarshal(body, &branchesResp); err != nil {
1400
1069
log.Printf("error parsing branch response: %v", err)
1401
-
span.RecordError(err)
1402
1070
} else {
1403
1071
disableFork = false
1404
1072
}
···
1406
1074
if len(branchesResp.Branches) == 0 {
1407
1075
disableFork = true
1408
1076
}
1409
-
span.SetAttributes(
1410
-
attribute.Int("branches.count", len(branchesResp.Branches)),
1411
-
attribute.Bool("disable_fork", disableFork),
1412
-
)
1413
1077
}
1414
1078
}
1415
1079
}
···
1441
1105
}
1442
1106
1443
1107
func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
1444
-
ctx, span := s.t.TraceStart(r.Context(), "RepoSingleIssue")
1445
-
defer span.End()
1446
-
1447
1108
user := s.auth.GetUser(r)
1448
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1109
+
f, err := s.fullyResolvedRepo(r)
1449
1110
if err != nil {
1450
1111
log.Println("failed to get repo and knot", err)
1451
-
span.RecordError(err)
1452
-
span.SetStatus(codes.Error, "failed to resolve repo")
1453
1112
return
1454
1113
}
1455
1114
···
1458
1117
if err != nil {
1459
1118
http.Error(w, "bad issue id", http.StatusBadRequest)
1460
1119
log.Println("failed to parse issue id", err)
1461
-
span.RecordError(err)
1462
-
span.SetStatus(codes.Error, "failed to parse issue id")
1463
1120
return
1464
1121
}
1465
1122
1466
-
span.SetAttributes(attribute.Int("issue_id", issueIdInt))
1467
-
1468
-
issue, comments, err := db.GetIssueWithComments(ctx, s.db, f.RepoAt, issueIdInt)
1123
+
issue, comments, err := db.GetIssueWithComments(s.db, f.RepoAt, issueIdInt)
1469
1124
if err != nil {
1470
1125
log.Println("failed to get issue and comments", err)
1471
-
span.RecordError(err)
1472
-
span.SetStatus(codes.Error, "failed to get issue and comments")
1473
1126
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1474
1127
return
1475
1128
}
1476
1129
1477
-
span.SetAttributes(
1478
-
attribute.Int("comments.count", len(comments)),
1479
-
attribute.String("issue.title", issue.Title),
1480
-
attribute.String("issue.owner_did", issue.OwnerDid),
1481
-
)
1482
-
1483
-
issueOwnerIdent, err := s.resolver.ResolveIdent(ctx, issue.OwnerDid)
1130
+
issueOwnerIdent, err := s.resolver.ResolveIdent(r.Context(), issue.OwnerDid)
1484
1131
if err != nil {
1485
1132
log.Println("failed to resolve issue owner", err)
1486
-
span.RecordError(err)
1487
-
span.SetStatus(codes.Error, "failed to resolve issue owner")
1488
1133
}
1489
1134
1490
1135
identsToResolve := make([]string, len(comments))
1491
1136
for i, comment := range comments {
1492
1137
identsToResolve[i] = comment.OwnerDid
1493
1138
}
1494
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
1139
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
1495
1140
didHandleMap := make(map[string]string)
1496
1141
for _, identity := range resolvedIds {
1497
1142
if !identity.Handle.IsInvalidHandle() {
···
1503
1148
1504
1149
s.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
1505
1150
LoggedInUser: user,
1506
-
RepoInfo: f.RepoInfo(ctx, s, user),
1151
+
RepoInfo: f.RepoInfo(s, user),
1507
1152
Issue: *issue,
1508
1153
Comments: comments,
1509
1154
1510
1155
IssueOwnerHandle: issueOwnerIdent.Handle.String(),
1511
1156
DidHandleMap: didHandleMap,
1512
1157
})
1158
+
1513
1159
}
1514
1160
1515
1161
func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) {
1516
-
ctx, span := s.t.TraceStart(r.Context(), "CloseIssue")
1517
-
defer span.End()
1518
-
1519
1162
user := s.auth.GetUser(r)
1520
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1163
+
f, err := s.fullyResolvedRepo(r)
1521
1164
if err != nil {
1522
1165
log.Println("failed to get repo and knot", err)
1523
-
span.RecordError(err)
1524
-
span.SetStatus(codes.Error, "failed to resolve repo")
1525
1166
return
1526
1167
}
1527
1168
···
1530
1171
if err != nil {
1531
1172
http.Error(w, "bad issue id", http.StatusBadRequest)
1532
1173
log.Println("failed to parse issue id", err)
1533
-
span.RecordError(err)
1534
-
span.SetStatus(codes.Error, "failed to parse issue id")
1535
1174
return
1536
1175
}
1537
1176
1538
-
span.SetAttributes(attribute.Int("issue_id", issueIdInt))
1539
-
1540
-
issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt)
1177
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1541
1178
if err != nil {
1542
1179
log.Println("failed to get issue", err)
1543
-
span.RecordError(err)
1544
-
span.SetStatus(codes.Error, "failed to get issue")
1545
1180
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1546
1181
return
1547
1182
}
1548
1183
1549
-
collaborators, err := f.Collaborators(ctx, s)
1184
+
collaborators, err := f.Collaborators(r.Context(), s)
1550
1185
if err != nil {
1551
1186
log.Println("failed to fetch repo collaborators: %w", err)
1552
-
span.RecordError(err)
1553
-
span.SetStatus(codes.Error, "failed to fetch repo collaborators")
1554
1187
}
1555
1188
isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool {
1556
1189
return user.Did == collab.Did
1557
1190
})
1558
1191
isIssueOwner := user.Did == issue.OwnerDid
1559
1192
1560
-
span.SetAttributes(
1561
-
attribute.Bool("is_collaborator", isCollaborator),
1562
-
attribute.Bool("is_issue_owner", isIssueOwner),
1563
-
)
1564
-
1565
1193
// TODO: make this more granular
1566
1194
if isIssueOwner || isCollaborator {
1195
+
1567
1196
closed := tangled.RepoIssueStateClosed
1568
1197
1569
1198
client, _ := s.auth.AuthorizedClient(r)
1570
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1199
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1571
1200
Collection: tangled.RepoIssueStateNSID,
1572
1201
Repo: user.Did,
1573
1202
Rkey: appview.TID(),
···
1581
1210
1582
1211
if err != nil {
1583
1212
log.Println("failed to update issue state", err)
1584
-
span.RecordError(err)
1585
-
span.SetStatus(codes.Error, "failed to update issue state in PDS")
1586
1213
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1587
1214
return
1588
1215
}
···
1590
1217
err := db.CloseIssue(s.db, f.RepoAt, issueIdInt)
1591
1218
if err != nil {
1592
1219
log.Println("failed to close issue", err)
1593
-
span.RecordError(err)
1594
-
span.SetStatus(codes.Error, "failed to close issue in database")
1595
1220
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1596
1221
return
1597
1222
}
···
1600
1225
return
1601
1226
} else {
1602
1227
log.Println("user is not permitted to close issue")
1603
-
span.SetAttributes(attribute.Bool("permission_denied", true))
1604
1228
http.Error(w, "for biden", http.StatusUnauthorized)
1605
1229
return
1606
1230
}
1607
1231
}
1608
1232
1609
1233
func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
1610
-
ctx, span := s.t.TraceStart(r.Context(), "ReopenIssue")
1611
-
defer span.End()
1612
-
1613
1234
user := s.auth.GetUser(r)
1614
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1235
+
f, err := s.fullyResolvedRepo(r)
1615
1236
if err != nil {
1616
1237
log.Println("failed to get repo and knot", err)
1617
-
span.RecordError(err)
1618
-
span.SetStatus(codes.Error, "failed to resolve repo")
1619
1238
return
1620
1239
}
1621
1240
···
1624
1243
if err != nil {
1625
1244
http.Error(w, "bad issue id", http.StatusBadRequest)
1626
1245
log.Println("failed to parse issue id", err)
1627
-
span.RecordError(err)
1628
-
span.SetStatus(codes.Error, "failed to parse issue id")
1629
1246
return
1630
1247
}
1631
1248
1632
-
span.SetAttributes(attribute.Int("issue_id", issueIdInt))
1633
-
1634
-
issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt)
1249
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1635
1250
if err != nil {
1636
1251
log.Println("failed to get issue", err)
1637
-
span.RecordError(err)
1638
-
span.SetStatus(codes.Error, "failed to get issue")
1639
1252
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1640
1253
return
1641
1254
}
1642
1255
1643
-
collaborators, err := f.Collaborators(ctx, s)
1256
+
collaborators, err := f.Collaborators(r.Context(), s)
1644
1257
if err != nil {
1645
1258
log.Println("failed to fetch repo collaborators: %w", err)
1646
-
span.RecordError(err)
1647
-
span.SetStatus(codes.Error, "failed to fetch repo collaborators")
1648
1259
}
1649
1260
isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool {
1650
1261
return user.Did == collab.Did
1651
1262
})
1652
1263
isIssueOwner := user.Did == issue.OwnerDid
1653
-
1654
-
span.SetAttributes(
1655
-
attribute.Bool("is_collaborator", isCollaborator),
1656
-
attribute.Bool("is_issue_owner", isIssueOwner),
1657
-
)
1658
1264
1659
1265
if isCollaborator || isIssueOwner {
1660
1266
err := db.ReopenIssue(s.db, f.RepoAt, issueIdInt)
1661
1267
if err != nil {
1662
1268
log.Println("failed to reopen issue", err)
1663
-
span.RecordError(err)
1664
-
span.SetStatus(codes.Error, "failed to reopen issue")
1665
1269
s.pages.Notice(w, "issue-action", "Failed to reopen issue. Try again later.")
1666
1270
return
1667
1271
}
···
1669
1273
return
1670
1274
} else {
1671
1275
log.Println("user is not the owner of the repo")
1672
-
span.SetAttributes(attribute.Bool("permission_denied", true))
1673
1276
http.Error(w, "forbidden", http.StatusUnauthorized)
1674
1277
return
1675
1278
}
1676
1279
}
1677
1280
1678
1281
func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
1679
-
ctx, span := s.t.TraceStart(r.Context(), "NewIssueComment")
1680
-
defer span.End()
1681
-
1682
1282
user := s.auth.GetUser(r)
1683
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1283
+
f, err := s.fullyResolvedRepo(r)
1684
1284
if err != nil {
1685
1285
log.Println("failed to get repo and knot", err)
1686
-
span.RecordError(err)
1687
-
span.SetStatus(codes.Error, "failed to resolve repo")
1688
1286
return
1689
1287
}
1690
1288
···
1693
1291
if err != nil {
1694
1292
http.Error(w, "bad issue id", http.StatusBadRequest)
1695
1293
log.Println("failed to parse issue id", err)
1696
-
span.RecordError(err)
1697
-
span.SetStatus(codes.Error, "failed to parse issue id")
1698
1294
return
1699
1295
}
1700
1296
1701
-
span.SetAttributes(
1702
-
attribute.Int("issue_id", issueIdInt),
1703
-
attribute.String("method", r.Method),
1704
-
)
1705
-
1706
1297
switch r.Method {
1707
1298
case http.MethodPost:
1708
1299
body := r.FormValue("body")
1709
1300
if body == "" {
1710
-
span.SetAttributes(attribute.Bool("missing_body", true))
1711
1301
s.pages.Notice(w, "issue", "Body is required")
1712
1302
return
1713
1303
}
···
1715
1305
commentId := mathrand.IntN(1000000)
1716
1306
rkey := appview.TID()
1717
1307
1718
-
span.SetAttributes(
1719
-
attribute.Int("comment_id", commentId),
1720
-
attribute.String("rkey", rkey),
1721
-
)
1722
-
1723
1308
err := db.NewIssueComment(s.db, &db.Comment{
1724
1309
OwnerDid: user.Did,
1725
1310
RepoAt: f.RepoAt,
···
1730
1315
})
1731
1316
if err != nil {
1732
1317
log.Println("failed to create comment", err)
1733
-
span.RecordError(err)
1734
-
span.SetStatus(codes.Error, "failed to create comment in database")
1735
1318
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1736
1319
return
1737
1320
}
···
1742
1325
issueAt, err := db.GetIssueAt(s.db, f.RepoAt, issueIdInt)
1743
1326
if err != nil {
1744
1327
log.Println("failed to get issue at", err)
1745
-
span.RecordError(err)
1746
-
span.SetStatus(codes.Error, "failed to get issue at")
1747
1328
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1748
1329
return
1749
1330
}
1750
1331
1751
-
span.SetAttributes(attribute.String("issue_at", issueAt))
1752
-
1753
1332
atUri := f.RepoAt.String()
1754
1333
client, _ := s.auth.AuthorizedClient(r)
1755
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1334
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1756
1335
Collection: tangled.RepoIssueCommentNSID,
1757
1336
Repo: user.Did,
1758
1337
Rkey: rkey,
···
1769
1348
})
1770
1349
if err != nil {
1771
1350
log.Println("failed to create comment", err)
1772
-
span.RecordError(err)
1773
-
span.SetStatus(codes.Error, "failed to create comment in PDS")
1774
1351
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1775
1352
return
1776
1353
}
···
1781
1358
}
1782
1359
1783
1360
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
1784
-
ctx, span := s.t.TraceStart(r.Context(), "IssueComment")
1785
-
defer span.End()
1786
-
1787
1361
user := s.auth.GetUser(r)
1788
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1362
+
f, err := s.fullyResolvedRepo(r)
1789
1363
if err != nil {
1790
1364
log.Println("failed to get repo and knot", err)
1791
-
span.RecordError(err)
1792
-
span.SetStatus(codes.Error, "failed to resolve repo")
1793
1365
return
1794
1366
}
1795
1367
···
1798
1370
if err != nil {
1799
1371
http.Error(w, "bad issue id", http.StatusBadRequest)
1800
1372
log.Println("failed to parse issue id", err)
1801
-
span.RecordError(err)
1802
-
span.SetStatus(codes.Error, "failed to parse issue id")
1803
1373
return
1804
1374
}
1805
1375
···
1808
1378
if err != nil {
1809
1379
http.Error(w, "bad comment id", http.StatusBadRequest)
1810
1380
log.Println("failed to parse issue id", err)
1811
-
span.RecordError(err)
1812
-
span.SetStatus(codes.Error, "failed to parse comment id")
1813
1381
return
1814
1382
}
1815
1383
1816
-
span.SetAttributes(
1817
-
attribute.Int("issue_id", issueIdInt),
1818
-
attribute.Int("comment_id", commentIdInt),
1819
-
)
1820
-
1821
-
issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt)
1384
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1822
1385
if err != nil {
1823
1386
log.Println("failed to get issue", err)
1824
-
span.RecordError(err)
1825
-
span.SetStatus(codes.Error, "failed to get issue")
1826
1387
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1827
1388
return
1828
1389
}
···
1830
1391
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1831
1392
if err != nil {
1832
1393
http.Error(w, "bad comment id", http.StatusBadRequest)
1833
-
span.RecordError(err)
1834
-
span.SetStatus(codes.Error, "failed to get comment")
1835
1394
return
1836
1395
}
1837
1396
1838
-
identity, err := s.resolver.ResolveIdent(ctx, comment.OwnerDid)
1397
+
identity, err := s.resolver.ResolveIdent(r.Context(), comment.OwnerDid)
1839
1398
if err != nil {
1840
1399
log.Println("failed to resolve did")
1841
-
span.RecordError(err)
1842
-
span.SetStatus(codes.Error, "failed to resolve did")
1843
1400
return
1844
1401
}
1845
1402
···
1852
1409
1853
1410
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1854
1411
LoggedInUser: user,
1855
-
RepoInfo: f.RepoInfo(ctx, s, user),
1412
+
RepoInfo: f.RepoInfo(s, user),
1856
1413
DidHandleMap: didHandleMap,
1857
1414
Issue: issue,
1858
1415
Comment: comment,
···
1860
1417
}
1861
1418
1862
1419
func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
1863
-
ctx, span := s.t.TraceStart(r.Context(), "EditIssueComment")
1864
-
defer span.End()
1865
-
1866
1420
user := s.auth.GetUser(r)
1867
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1421
+
f, err := s.fullyResolvedRepo(r)
1868
1422
if err != nil {
1869
1423
log.Println("failed to get repo and knot", err)
1870
-
span.RecordError(err)
1871
-
span.SetStatus(codes.Error, "failed to resolve repo")
1872
1424
return
1873
1425
}
1874
1426
···
1877
1429
if err != nil {
1878
1430
http.Error(w, "bad issue id", http.StatusBadRequest)
1879
1431
log.Println("failed to parse issue id", err)
1880
-
span.RecordError(err)
1881
-
span.SetStatus(codes.Error, "failed to parse issue id")
1882
1432
return
1883
1433
}
1884
1434
···
1887
1437
if err != nil {
1888
1438
http.Error(w, "bad comment id", http.StatusBadRequest)
1889
1439
log.Println("failed to parse issue id", err)
1890
-
span.RecordError(err)
1891
-
span.SetStatus(codes.Error, "failed to parse comment id")
1892
1440
return
1893
1441
}
1894
1442
1895
-
span.SetAttributes(
1896
-
attribute.Int("issue_id", issueIdInt),
1897
-
attribute.Int("comment_id", commentIdInt),
1898
-
attribute.String("method", r.Method),
1899
-
)
1900
-
1901
-
issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt)
1443
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1902
1444
if err != nil {
1903
1445
log.Println("failed to get issue", err)
1904
-
span.RecordError(err)
1905
-
span.SetStatus(codes.Error, "failed to get issue")
1906
1446
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1907
1447
return
1908
1448
}
···
1910
1450
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1911
1451
if err != nil {
1912
1452
http.Error(w, "bad comment id", http.StatusBadRequest)
1913
-
span.RecordError(err)
1914
-
span.SetStatus(codes.Error, "failed to get comment")
1915
1453
return
1916
1454
}
1917
1455
1918
1456
if comment.OwnerDid != user.Did {
1919
1457
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
1920
-
span.SetAttributes(attribute.Bool("permission_denied", true))
1921
1458
return
1922
1459
}
1923
1460
···
1925
1462
case http.MethodGet:
1926
1463
s.pages.EditIssueCommentFragment(w, pages.EditIssueCommentParams{
1927
1464
LoggedInUser: user,
1928
-
RepoInfo: f.RepoInfo(ctx, s, user),
1465
+
RepoInfo: f.RepoInfo(s, user),
1929
1466
Issue: issue,
1930
1467
Comment: comment,
1931
1468
})
···
1935
1472
client, _ := s.auth.AuthorizedClient(r)
1936
1473
rkey := comment.Rkey
1937
1474
1938
-
span.SetAttributes(
1939
-
attribute.String("new_body", newBody),
1940
-
attribute.String("rkey", rkey),
1941
-
)
1942
-
1943
1475
// optimistic update
1944
1476
edited := time.Now()
1945
1477
err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody)
1946
1478
if err != nil {
1947
1479
log.Println("failed to perferom update-description query", err)
1948
-
span.RecordError(err)
1949
-
span.SetStatus(codes.Error, "failed to edit comment in database")
1950
1480
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
1951
1481
return
1952
1482
}
···
1954
1484
// rkey is optional, it was introduced later
1955
1485
if comment.Rkey != "" {
1956
1486
// update the record on pds
1957
-
ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoIssueCommentNSID, user.Did, rkey)
1487
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey)
1958
1488
if err != nil {
1959
1489
// failed to get record
1960
1490
log.Println(err, rkey)
1961
-
span.RecordError(err)
1962
-
span.SetStatus(codes.Error, "failed to get record from PDS")
1963
1491
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
1964
1492
return
1965
1493
}
···
1971
1499
createdAt := record["createdAt"].(string)
1972
1500
commentIdInt64 := int64(commentIdInt)
1973
1501
1974
-
_, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1502
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1975
1503
Collection: tangled.RepoIssueCommentNSID,
1976
1504
Repo: user.Did,
1977
1505
Rkey: rkey,
···
1989
1517
})
1990
1518
if err != nil {
1991
1519
log.Println(err)
1992
-
span.RecordError(err)
1993
-
span.SetStatus(codes.Error, "failed to put record to PDS")
1994
1520
}
1995
1521
}
1996
1522
···
2004
1530
// return new comment body with htmx
2005
1531
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
2006
1532
LoggedInUser: user,
2007
-
RepoInfo: f.RepoInfo(ctx, s, user),
1533
+
RepoInfo: f.RepoInfo(s, user),
2008
1534
DidHandleMap: didHandleMap,
2009
1535
Issue: issue,
2010
1536
Comment: comment,
2011
1537
})
2012
1538
return
1539
+
2013
1540
}
1541
+
2014
1542
}
2015
1543
2016
1544
func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
2017
-
ctx, span := s.t.TraceStart(r.Context(), "DeleteIssueComment")
2018
-
defer span.End()
2019
-
2020
1545
user := s.auth.GetUser(r)
2021
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1546
+
f, err := s.fullyResolvedRepo(r)
2022
1547
if err != nil {
2023
1548
log.Println("failed to get repo and knot", err)
2024
-
span.RecordError(err)
2025
-
span.SetStatus(codes.Error, "failed to resolve repo")
2026
1549
return
2027
1550
}
2028
1551
···
2031
1554
if err != nil {
2032
1555
http.Error(w, "bad issue id", http.StatusBadRequest)
2033
1556
log.Println("failed to parse issue id", err)
2034
-
span.RecordError(err)
2035
-
span.SetStatus(codes.Error, "failed to parse issue id")
2036
1557
return
2037
1558
}
2038
1559
2039
-
issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt)
1560
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
2040
1561
if err != nil {
2041
1562
log.Println("failed to get issue", err)
2042
-
span.RecordError(err)
2043
-
span.SetStatus(codes.Error, "failed to get issue")
2044
1563
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
2045
1564
return
2046
1565
}
···
2050
1569
if err != nil {
2051
1570
http.Error(w, "bad comment id", http.StatusBadRequest)
2052
1571
log.Println("failed to parse issue id", err)
2053
-
span.RecordError(err)
2054
-
span.SetStatus(codes.Error, "failed to parse comment id")
2055
1572
return
2056
1573
}
2057
1574
2058
-
span.SetAttributes(
2059
-
attribute.Int("issue_id", issueIdInt),
2060
-
attribute.Int("comment_id", commentIdInt),
2061
-
)
2062
-
2063
1575
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
2064
1576
if err != nil {
2065
1577
http.Error(w, "bad comment id", http.StatusBadRequest)
2066
-
span.RecordError(err)
2067
-
span.SetStatus(codes.Error, "failed to get comment")
2068
1578
return
2069
1579
}
2070
1580
2071
1581
if comment.OwnerDid != user.Did {
2072
1582
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
2073
-
span.SetAttributes(attribute.Bool("permission_denied", true))
2074
1583
return
2075
1584
}
2076
1585
2077
1586
if comment.Deleted != nil {
2078
1587
http.Error(w, "comment already deleted", http.StatusBadRequest)
2079
-
span.SetAttributes(attribute.Bool("already_deleted", true))
2080
1588
return
2081
1589
}
2082
1590
···
2085
1593
err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
2086
1594
if err != nil {
2087
1595
log.Println("failed to delete comment")
2088
-
span.RecordError(err)
2089
-
span.SetStatus(codes.Error, "failed to delete comment in database")
2090
1596
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
2091
1597
return
2092
1598
}
···
2094
1600
// delete from pds
2095
1601
if comment.Rkey != "" {
2096
1602
client, _ := s.auth.AuthorizedClient(r)
2097
-
_, err = comatproto.RepoDeleteRecord(ctx, client, &comatproto.RepoDeleteRecord_Input{
1603
+
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
2098
1604
Collection: tangled.GraphFollowNSID,
2099
1605
Repo: user.Did,
2100
1606
Rkey: comment.Rkey,
2101
1607
})
2102
1608
if err != nil {
2103
1609
log.Println(err)
2104
-
span.RecordError(err)
2105
-
span.SetStatus(codes.Error, "failed to delete record from PDS")
2106
1610
}
2107
1611
}
2108
1612
···
2116
1620
// htmx fragment of comment after deletion
2117
1621
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
2118
1622
LoggedInUser: user,
2119
-
RepoInfo: f.RepoInfo(ctx, s, user),
1623
+
RepoInfo: f.RepoInfo(s, user),
2120
1624
DidHandleMap: didHandleMap,
2121
1625
Issue: issue,
2122
1626
Comment: comment,
···
2125
1629
}
2126
1630
2127
1631
func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) {
2128
-
ctx, span := s.t.TraceStart(r.Context(), "RepoIssues")
2129
-
defer span.End()
2130
-
2131
1632
params := r.URL.Query()
2132
1633
state := params.Get("state")
2133
1634
isOpen := true
···
2140
1641
isOpen = true
2141
1642
}
2142
1643
2143
-
span.SetAttributes(
2144
-
attribute.Bool("is_open", isOpen),
2145
-
attribute.String("state_param", state),
2146
-
)
2147
-
2148
1644
page, ok := r.Context().Value("page").(pagination.Page)
2149
1645
if !ok {
2150
1646
log.Println("failed to get page")
2151
-
span.SetAttributes(attribute.Bool("page_not_found", true))
2152
1647
page = pagination.FirstPage()
2153
1648
}
2154
1649
2155
1650
user := s.auth.GetUser(r)
2156
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1651
+
f, err := s.fullyResolvedRepo(r)
2157
1652
if err != nil {
2158
1653
log.Println("failed to get repo and knot", err)
2159
-
span.RecordError(err)
2160
-
span.SetStatus(codes.Error, "failed to resolve repo")
2161
1654
return
2162
1655
}
2163
1656
2164
-
issues, err := db.GetIssues(ctx, s.db, f.RepoAt, isOpen, page)
1657
+
issues, err := db.GetIssues(s.db, f.RepoAt, isOpen, page)
2165
1658
if err != nil {
2166
1659
log.Println("failed to get issues", err)
2167
-
span.RecordError(err)
2168
-
span.SetStatus(codes.Error, "failed to get issues")
2169
1660
s.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
2170
1661
return
2171
1662
}
2172
1663
2173
-
span.SetAttributes(attribute.Int("issues.count", len(issues)))
2174
-
2175
1664
identsToResolve := make([]string, len(issues))
2176
1665
for i, issue := range issues {
2177
1666
identsToResolve[i] = issue.OwnerDid
2178
1667
}
2179
-
resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve)
1668
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve)
2180
1669
didHandleMap := make(map[string]string)
2181
1670
for _, identity := range resolvedIds {
2182
1671
if !identity.Handle.IsInvalidHandle() {
···
2188
1677
2189
1678
s.pages.RepoIssues(w, pages.RepoIssuesParams{
2190
1679
LoggedInUser: s.auth.GetUser(r),
2191
-
RepoInfo: f.RepoInfo(ctx, s, user),
1680
+
RepoInfo: f.RepoInfo(s, user),
2192
1681
Issues: issues,
2193
1682
DidHandleMap: didHandleMap,
2194
1683
FilteringByOpen: isOpen,
···
2198
1687
}
2199
1688
2200
1689
func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) {
2201
-
ctx, span := s.t.TraceStart(r.Context(), "NewIssue")
2202
-
defer span.End()
2203
-
2204
1690
user := s.auth.GetUser(r)
2205
1691
2206
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1692
+
f, err := s.fullyResolvedRepo(r)
2207
1693
if err != nil {
2208
1694
log.Println("failed to get repo and knot", err)
2209
-
span.RecordError(err)
2210
-
span.SetStatus(codes.Error, "failed to resolve repo")
2211
1695
return
2212
1696
}
2213
-
2214
-
span.SetAttributes(attribute.String("method", r.Method))
2215
1697
2216
1698
switch r.Method {
2217
1699
case http.MethodGet:
2218
1700
s.pages.RepoNewIssue(w, pages.RepoNewIssueParams{
2219
1701
LoggedInUser: user,
2220
-
RepoInfo: f.RepoInfo(ctx, s, user),
1702
+
RepoInfo: f.RepoInfo(s, user),
2221
1703
})
2222
1704
case http.MethodPost:
2223
1705
title := r.FormValue("title")
2224
1706
body := r.FormValue("body")
2225
1707
2226
-
span.SetAttributes(
2227
-
attribute.String("title", title),
2228
-
attribute.String("body_length", fmt.Sprintf("%d", len(body))),
2229
-
)
2230
-
2231
1708
if title == "" || body == "" {
2232
-
span.SetAttributes(attribute.Bool("form_validation_failed", true))
2233
1709
s.pages.Notice(w, "issues", "Title and body are required")
2234
1710
return
2235
1711
}
2236
1712
2237
-
tx, err := s.db.BeginTx(ctx, nil)
1713
+
tx, err := s.db.BeginTx(r.Context(), nil)
2238
1714
if err != nil {
2239
-
span.RecordError(err)
2240
-
span.SetStatus(codes.Error, "failed to begin transaction")
2241
1715
s.pages.Notice(w, "issues", "Failed to create issue, try again later")
2242
1716
return
2243
1717
}
···
2250
1724
})
2251
1725
if err != nil {
2252
1726
log.Println("failed to create issue", err)
2253
-
span.RecordError(err)
2254
-
span.SetStatus(codes.Error, "failed to create issue in database")
2255
1727
s.pages.Notice(w, "issues", "Failed to create issue.")
2256
1728
return
2257
1729
}
···
2259
1731
issueId, err := db.GetIssueId(s.db, f.RepoAt)
2260
1732
if err != nil {
2261
1733
log.Println("failed to get issue id", err)
2262
-
span.RecordError(err)
2263
-
span.SetStatus(codes.Error, "failed to get issue id")
2264
1734
s.pages.Notice(w, "issues", "Failed to create issue.")
2265
1735
return
2266
1736
}
2267
1737
2268
-
span.SetAttributes(attribute.Int("issue_id", issueId))
2269
-
2270
1738
client, _ := s.auth.AuthorizedClient(r)
2271
1739
atUri := f.RepoAt.String()
2272
-
rkey := appview.TID()
2273
-
span.SetAttributes(attribute.String("rkey", rkey))
2274
-
2275
-
resp, err := comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{
1740
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
2276
1741
Collection: tangled.RepoIssueNSID,
2277
1742
Repo: user.Did,
2278
-
Rkey: rkey,
1743
+
Rkey: appview.TID(),
2279
1744
Record: &lexutil.LexiconTypeDecoder{
2280
1745
Val: &tangled.RepoIssue{
2281
1746
Repo: atUri,
···
2288
1753
})
2289
1754
if err != nil {
2290
1755
log.Println("failed to create issue", err)
2291
-
span.RecordError(err)
2292
-
span.SetStatus(codes.Error, "failed to create issue in PDS")
2293
1756
s.pages.Notice(w, "issues", "Failed to create issue.")
2294
1757
return
2295
1758
}
2296
1759
2297
-
span.SetAttributes(attribute.String("issue_uri", resp.Uri))
2298
-
2299
1760
err = db.SetIssueAt(s.db, f.RepoAt, issueId, resp.Uri)
2300
1761
if err != nil {
2301
1762
log.Println("failed to set issue at", err)
2302
-
span.RecordError(err)
2303
-
span.SetStatus(codes.Error, "failed to set issue URI in database")
2304
1763
s.pages.Notice(w, "issues", "Failed to create issue.")
2305
1764
return
2306
1765
}
···
2311
1770
}
2312
1771
2313
1772
func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) {
2314
-
ctx, span := s.t.TraceStart(r.Context(), "ForkRepo")
2315
-
defer span.End()
2316
-
2317
1773
user := s.auth.GetUser(r)
2318
-
f, err := s.fullyResolvedRepo(r.WithContext(ctx))
1774
+
f, err := s.fullyResolvedRepo(r)
2319
1775
if err != nil {
2320
1776
log.Printf("failed to resolve source repo: %v", err)
2321
-
span.RecordError(err)
2322
-
span.SetStatus(codes.Error, "failed to resolve source repo")
2323
1777
return
2324
1778
}
2325
1779
2326
-
span.SetAttributes(
2327
-
attribute.String("method", r.Method),
2328
-
attribute.String("repo_name", f.RepoName),
2329
-
attribute.String("owner_did", f.OwnerDid()),
2330
-
attribute.String("knot", f.Knot),
2331
-
)
2332
-
2333
1780
switch r.Method {
2334
1781
case http.MethodGet:
2335
1782
user := s.auth.GetUser(r)
2336
1783
knots, err := s.enforcer.GetDomainsForUser(user.Did)
2337
1784
if err != nil {
2338
-
span.RecordError(err)
2339
-
span.SetStatus(codes.Error, "failed to get domains for user")
2340
1785
s.pages.Notice(w, "repo", "Invalid user account.")
2341
1786
return
2342
1787
}
2343
1788
2344
-
span.SetAttributes(attribute.Int("knots.count", len(knots)))
2345
-
2346
1789
s.pages.ForkRepo(w, pages.ForkRepoParams{
2347
1790
LoggedInUser: user,
2348
1791
Knots: knots,
2349
-
RepoInfo: f.RepoInfo(ctx, s, user),
1792
+
RepoInfo: f.RepoInfo(s, user),
2350
1793
})
2351
1794
2352
1795
case http.MethodPost:
1796
+
2353
1797
knot := r.FormValue("knot")
2354
1798
if knot == "" {
2355
-
span.SetAttributes(attribute.Bool("missing_knot", true))
2356
1799
s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.")
2357
1800
return
2358
1801
}
2359
1802
2360
-
span.SetAttributes(attribute.String("target_knot", knot))
2361
-
2362
1803
ok, err := s.enforcer.E.Enforce(user.Did, knot, knot, "repo:create")
2363
1804
if err != nil || !ok {
2364
-
span.SetAttributes(
2365
-
attribute.Bool("permission_denied", true),
2366
-
attribute.Bool("enforce_error", err != nil),
2367
-
)
2368
1805
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
2369
1806
return
2370
1807
}
2371
1808
2372
1809
forkName := fmt.Sprintf("%s", f.RepoName)
2373
-
span.SetAttributes(attribute.String("fork_name", forkName))
2374
1810
2375
1811
// this check is *only* to see if the forked repo name already exists
2376
1812
// in the user's account.
2377
-
existingRepo, err := db.GetRepo(ctx, s.db, user.Did, f.RepoName)
1813
+
existingRepo, err := db.GetRepo(s.db, user.Did, f.RepoName)
2378
1814
if err != nil {
2379
1815
if errors.Is(err, sql.ErrNoRows) {
2380
1816
// no existing repo with this name found, we can use the name as is
2381
-
span.SetAttributes(attribute.Bool("repo_name_available", true))
2382
1817
} else {
2383
1818
log.Println("error fetching existing repo from db", err)
2384
-
span.RecordError(err)
2385
-
span.SetStatus(codes.Error, "failed to check for existing repo")
2386
1819
s.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.")
2387
1820
return
2388
1821
}
2389
1822
} else if existingRepo != nil {
2390
1823
// repo with this name already exists, append random string
2391
1824
forkName = fmt.Sprintf("%s-%s", forkName, randomString(3))
2392
-
span.SetAttributes(
2393
-
attribute.Bool("repo_name_conflict", true),
2394
-
attribute.String("adjusted_fork_name", forkName),
2395
-
)
2396
1825
}
2397
-
2398
1826
secret, err := db.GetRegistrationKey(s.db, knot)
2399
1827
if err != nil {
2400
-
span.RecordError(err)
2401
-
span.SetStatus(codes.Error, "failed to get registration key")
2402
1828
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot))
2403
1829
return
2404
1830
}
2405
1831
2406
1832
client, err := NewSignedClient(knot, secret, s.config.Dev)
2407
1833
if err != nil {
2408
-
span.RecordError(err)
2409
-
span.SetStatus(codes.Error, "failed to create signed client")
2410
1834
s.pages.Notice(w, "repo", "Failed to reach knot server.")
2411
1835
return
2412
1836
}
···
2420
1844
forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.RepoName)
2421
1845
sourceAt := f.RepoAt.String()
2422
1846
2423
-
span.SetAttributes(
2424
-
attribute.String("fork_source_url", forkSourceUrl),
2425
-
attribute.String("source_at", sourceAt),
2426
-
)
2427
-
2428
1847
rkey := appview.TID()
2429
1848
repo := &db.Repo{
2430
1849
Did: user.Did,
···
2434
1853
Source: sourceAt,
2435
1854
}
2436
1855
2437
-
span.SetAttributes(attribute.String("rkey", rkey))
2438
-
2439
-
tx, err := s.db.BeginTx(ctx, nil)
1856
+
tx, err := s.db.BeginTx(r.Context(), nil)
2440
1857
if err != nil {
2441
1858
log.Println(err)
2442
-
span.RecordError(err)
2443
-
span.SetStatus(codes.Error, "failed to begin transaction")
2444
1859
s.pages.Notice(w, "repo", "Failed to save repository information.")
2445
1860
return
2446
1861
}
···
2449
1864
err = s.enforcer.E.LoadPolicy()
2450
1865
if err != nil {
2451
1866
log.Println("failed to rollback policies")
2452
-
span.RecordError(err)
2453
1867
}
2454
1868
}()
2455
1869
2456
1870
resp, err := client.ForkRepo(user.Did, forkSourceUrl, forkName)
2457
1871
if err != nil {
2458
-
span.RecordError(err)
2459
-
span.SetStatus(codes.Error, "failed to fork repo on knot server")
2460
1872
s.pages.Notice(w, "repo", "Failed to create repository on knot server.")
2461
1873
return
2462
1874
}
2463
-
2464
-
span.SetAttributes(attribute.Int("fork_response_status", resp.StatusCode))
2465
1875
2466
1876
switch resp.StatusCode {
2467
1877
case http.StatusConflict:
2468
-
span.SetAttributes(attribute.Bool("name_conflict", true))
2469
1878
s.pages.Notice(w, "repo", "A repository with that name already exists.")
2470
1879
return
2471
1880
case http.StatusInternalServerError:
2472
-
span.SetAttributes(attribute.Bool("server_error", true))
2473
1881
s.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.")
2474
-
return
2475
1882
case http.StatusNoContent:
2476
1883
// continue
2477
1884
}
···
2479
1886
xrpcClient, _ := s.auth.AuthorizedClient(r)
2480
1887
2481
1888
createdAt := time.Now().Format(time.RFC3339)
2482
-
atresp, err := comatproto.RepoPutRecord(ctx, xrpcClient, &comatproto.RepoPutRecord_Input{
1889
+
atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{
2483
1890
Collection: tangled.RepoNSID,
2484
1891
Repo: user.Did,
2485
1892
Rkey: rkey,
···
2494
1901
})
2495
1902
if err != nil {
2496
1903
log.Printf("failed to create record: %s", err)
2497
-
span.RecordError(err)
2498
-
span.SetStatus(codes.Error, "failed to create record in PDS")
2499
1904
s.pages.Notice(w, "repo", "Failed to announce repository creation.")
2500
1905
return
2501
1906
}
2502
1907
log.Println("created repo record: ", atresp.Uri)
2503
-
span.SetAttributes(attribute.String("repo_uri", atresp.Uri))
2504
1908
2505
1909
repo.AtUri = atresp.Uri
2506
-
err = db.AddRepo(ctx, tx, repo)
1910
+
err = db.AddRepo(tx, repo)
2507
1911
if err != nil {
2508
1912
log.Println(err)
2509
-
span.RecordError(err)
2510
-
span.SetStatus(codes.Error, "failed to add repo to database")
2511
1913
s.pages.Notice(w, "repo", "Failed to save repository information.")
2512
1914
return
2513
1915
}
···
2517
1919
err = s.enforcer.AddRepo(user.Did, knot, p)
2518
1920
if err != nil {
2519
1921
log.Println(err)
2520
-
span.RecordError(err)
2521
-
span.SetStatus(codes.Error, "failed to set up repository permissions")
2522
1922
s.pages.Notice(w, "repo", "Failed to set up repository permissions.")
2523
1923
return
2524
1924
}
···
2526
1926
err = tx.Commit()
2527
1927
if err != nil {
2528
1928
log.Println("failed to commit changes", err)
2529
-
span.RecordError(err)
2530
-
span.SetStatus(codes.Error, "failed to commit transaction")
2531
1929
http.Error(w, err.Error(), http.StatusInternalServerError)
2532
1930
return
2533
1931
}
···
2535
1933
err = s.enforcer.E.SavePolicy()
2536
1934
if err != nil {
2537
1935
log.Println("failed to update ACLs", err)
2538
-
span.RecordError(err)
2539
-
span.SetStatus(codes.Error, "failed to save policy")
2540
1936
http.Error(w, err.Error(), http.StatusInternalServerError)
2541
1937
return
2542
1938
}
+6
-24
appview/state/repo_util.go
+6
-24
appview/state/repo_util.go
···
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
13
"github.com/go-chi/chi/v5"
14
14
"github.com/go-git/go-git/v5/plumbing/object"
15
-
"go.opentelemetry.io/otel/attribute"
16
15
"tangled.sh/tangled.sh/core/appview/auth"
17
16
"tangled.sh/tangled.sh/core/appview/db"
18
17
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
19
-
"tangled.sh/tangled.sh/core/telemetry"
20
18
)
21
19
22
20
func (s *State) fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
23
-
ctx := r.Context()
24
-
25
-
attrs := telemetry.MapAttrs(map[string]string{
26
-
"repo": chi.URLParam(r, "repo"),
27
-
"ref": chi.URLParam(r, "ref"),
28
-
})
29
-
30
-
ctx, span := s.t.TraceStart(ctx, "fullyResolvedRepo", attrs...)
31
-
defer span.End()
32
-
33
21
repoName := chi.URLParam(r, "repo")
34
-
knot, ok := ctx.Value("knot").(string)
22
+
knot, ok := r.Context().Value("knot").(string)
35
23
if !ok {
36
24
log.Println("malformed middleware")
37
25
return nil, fmt.Errorf("malformed middleware")
38
26
}
39
-
40
-
span.SetAttributes(attribute.String("knot", knot))
41
-
42
-
id, ok := ctx.Value("resolvedId").(identity.Identity)
27
+
id, ok := r.Context().Value("resolvedId").(identity.Identity)
43
28
if !ok {
44
29
log.Println("malformed middleware")
45
30
return nil, fmt.Errorf("malformed middleware")
46
31
}
47
32
48
-
span.SetAttributes(attribute.String("did", id.DID.String()))
49
-
50
-
repoAt, ok := ctx.Value("repoAt").(string)
33
+
repoAt, ok := r.Context().Value("repoAt").(string)
51
34
if !ok {
52
35
log.Println("malformed middleware")
53
36
return nil, fmt.Errorf("malformed middleware")
···
73
56
}
74
57
75
58
ref = defaultBranch.Branch
76
-
77
-
span.SetAttributes(attribute.String("default_branch", ref))
78
59
}
79
60
80
-
description, ok := ctx.Value("repoDescription").(string)
81
-
addedAt, ok := ctx.Value("repoAddedAt").(string)
61
+
// pass through values from the middleware
62
+
description, ok := r.Context().Value("repoDescription").(string)
63
+
addedAt, ok := r.Context().Value("repoAddedAt").(string)
82
64
83
65
return &FullyResolvedRepo{
84
66
Knot: knot,
+1
-6
appview/state/router.go
+1
-6
appview/state/router.go
···
13
13
func (s *State) Router() http.Handler {
14
14
router := chi.NewRouter()
15
15
16
-
if s.t != nil {
17
-
// top-level telemetry middleware
18
-
// router.Use(s.t.RequestDuration())
19
-
// router.Use(s.t.RequestInFlight())
20
-
}
21
-
22
16
router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
23
17
pat := chi.URLParam(r, "*")
24
18
if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") {
···
54
48
55
49
func (s *State) UserRouter() http.Handler {
56
50
r := chi.NewRouter()
51
+
57
52
// strip @ from user
58
53
r.Use(StripLeadingAt)
59
54
+9
-69
appview/state/state.go
+9
-69
appview/state/state.go
···
9
9
"log"
10
10
"log/slog"
11
11
"net/http"
12
-
"runtime/debug"
13
12
"strings"
14
13
"time"
15
14
···
18
17
lexutil "github.com/bluesky-social/indigo/lex/util"
19
18
securejoin "github.com/cyphar/filepath-securejoin"
20
19
"github.com/go-chi/chi/v5"
21
-
"go.opentelemetry.io/otel/attribute"
22
20
"tangled.sh/tangled.sh/core/api/tangled"
23
21
"tangled.sh/tangled.sh/core/appview"
24
22
"tangled.sh/tangled.sh/core/appview/auth"
···
26
24
"tangled.sh/tangled.sh/core/appview/pages"
27
25
"tangled.sh/tangled.sh/core/jetstream"
28
26
"tangled.sh/tangled.sh/core/rbac"
29
-
"tangled.sh/tangled.sh/core/telemetry"
30
27
)
31
28
32
29
type State struct {
···
37
34
pages *pages.Pages
38
35
resolver *appview.Resolver
39
36
jc *jetstream.JetstreamClient
40
-
t *telemetry.Telemetry
41
37
config *appview.Config
42
38
}
43
39
44
-
func Make(ctx context.Context, config *appview.Config) (*State, error) {
40
+
func Make(config *appview.Config) (*State, error) {
45
41
d, err := db.Make(config.DbPath)
46
42
if err != nil {
47
43
return nil, err
···
63
59
64
60
resolver := appview.NewResolver()
65
61
66
-
bi, ok := debug.ReadBuildInfo()
67
-
var version string
68
-
if ok {
69
-
version = bi.Main.Version
70
-
} else {
71
-
version = "v0.0.0-unknown"
72
-
}
73
-
74
62
wrapper := db.DbWrapper{d}
75
63
jc, err := jetstream.NewJetstreamClient(
76
64
config.JetstreamEndpoint,
···
84
72
if err != nil {
85
73
return nil, fmt.Errorf("failed to create jetstream client: %w", err)
86
74
}
87
-
err = jc.StartJetstream(ctx, appview.Ingest(wrapper))
75
+
err = jc.StartJetstream(context.Background(), appview.Ingest(wrapper))
88
76
if err != nil {
89
77
return nil, fmt.Errorf("failed to start jetstream watcher: %w", err)
90
78
}
91
79
92
-
var tele *telemetry.Telemetry
93
-
if config.EnableTelemetry {
94
-
tele, err = telemetry.NewTelemetry(ctx, "appview", version, config.Dev)
95
-
if err != nil {
96
-
return nil, fmt.Errorf("failed to setup telemetry: %w", err)
97
-
}
98
-
}
99
-
100
80
state := &State{
101
81
d,
102
82
auth,
···
105
85
pgs,
106
86
resolver,
107
87
jc,
108
-
tele,
109
88
config,
110
89
}
111
90
···
199
178
}
200
179
201
180
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
202
-
ctx, span := s.t.TraceStart(r.Context(), "Timeline")
203
-
defer span.End()
204
-
205
181
user := s.auth.GetUser(r)
206
-
span.SetAttributes(attribute.String("user.did", user.Did))
207
182
208
-
timeline, err := db.MakeTimeline(ctx, s.db)
183
+
timeline, err := db.MakeTimeline(s.db)
209
184
if err != nil {
210
185
log.Println(err)
211
-
span.RecordError(err)
212
186
s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
213
187
}
214
188
···
227
201
didsToResolve = append(didsToResolve, ev.Star.StarredByDid, ev.Star.Repo.Did)
228
202
}
229
203
}
230
-
span.SetAttributes(attribute.Int("dids.to_resolve.count", len(didsToResolve)))
231
204
232
-
resolvedIds := s.resolver.ResolveIdents(ctx, didsToResolve)
205
+
resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve)
233
206
didHandleMap := make(map[string]string)
234
207
for _, identity := range resolvedIds {
235
208
if !identity.Handle.IsInvalidHandle() {
···
238
211
didHandleMap[identity.DID.String()] = identity.DID.String()
239
212
}
240
213
}
241
-
span.SetAttributes(attribute.Int("dids.resolved.count", len(resolvedIds)))
242
214
243
215
s.pages.Timeline(w, pages.TimelineParams{
244
216
LoggedInUser: user,
···
634
606
}
635
607
636
608
func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) {
637
-
ctx, span := s.t.TraceStart(r.Context(), "NewRepo")
638
-
defer span.End()
639
-
640
609
switch r.Method {
641
610
case http.MethodGet:
642
611
user := s.auth.GetUser(r)
643
-
span.SetAttributes(attribute.String("user.did", user.Did))
644
-
span.SetAttributes(attribute.String("request.method", "GET"))
645
-
646
612
knots, err := s.enforcer.GetDomainsForUser(user.Did)
647
613
if err != nil {
648
-
span.RecordError(err)
649
614
s.pages.Notice(w, "repo", "Invalid user account.")
650
615
return
651
616
}
652
-
span.SetAttributes(attribute.Int("knots.count", len(knots)))
653
617
654
618
s.pages.NewRepo(w, pages.NewRepoParams{
655
619
LoggedInUser: user,
···
658
622
659
623
case http.MethodPost:
660
624
user := s.auth.GetUser(r)
661
-
span.SetAttributes(attribute.String("user.did", user.Did))
662
-
span.SetAttributes(attribute.String("request.method", "POST"))
663
625
664
626
domain := r.FormValue("domain")
665
627
if domain == "" {
666
628
s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.")
667
629
return
668
630
}
669
-
span.SetAttributes(attribute.String("domain", domain))
670
631
671
632
repoName := r.FormValue("name")
672
633
if repoName == "" {
673
634
s.pages.Notice(w, "repo", "Repository name cannot be empty.")
674
635
return
675
636
}
676
-
span.SetAttributes(attribute.String("repo.name", repoName))
677
637
678
638
if err := validateRepoName(repoName); err != nil {
679
639
s.pages.Notice(w, "repo", err.Error())
···
684
644
if defaultBranch == "" {
685
645
defaultBranch = "main"
686
646
}
687
-
span.SetAttributes(attribute.String("repo.default_branch", defaultBranch))
688
647
689
648
description := r.FormValue("description")
690
649
691
650
ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create")
692
651
if err != nil || !ok {
693
-
if err != nil {
694
-
span.RecordError(err)
695
-
}
696
-
span.SetAttributes(attribute.Bool("permission.granted", false))
697
652
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
698
653
return
699
654
}
700
-
span.SetAttributes(attribute.Bool("permission.granted", true))
701
655
702
-
existingRepo, err := db.GetRepo(ctx, s.db, user.Did, repoName)
656
+
existingRepo, err := db.GetRepo(s.db, user.Did, repoName)
703
657
if err == nil && existingRepo != nil {
704
-
span.SetAttributes(attribute.Bool("repo.exists", true))
705
658
s.pages.Notice(w, "repo", fmt.Sprintf("A repo by this name already exists on %s", existingRepo.Knot))
706
659
return
707
660
}
708
-
span.SetAttributes(attribute.Bool("repo.exists", false))
709
661
710
662
secret, err := db.GetRegistrationKey(s.db, domain)
711
663
if err != nil {
712
-
span.RecordError(err)
713
664
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", domain))
714
665
return
715
666
}
716
667
717
668
client, err := NewSignedClient(domain, secret, s.config.Dev)
718
669
if err != nil {
719
-
span.RecordError(err)
720
670
s.pages.Notice(w, "repo", "Failed to connect to knot server.")
721
671
return
722
672
}
···
730
680
Description: description,
731
681
}
732
682
733
-
rWithCtx := r.WithContext(ctx)
734
-
xrpcClient, _ := s.auth.AuthorizedClient(rWithCtx)
683
+
xrpcClient, _ := s.auth.AuthorizedClient(r)
735
684
736
685
createdAt := time.Now().Format(time.RFC3339)
737
-
atresp, err := comatproto.RepoPutRecord(ctx, xrpcClient, &comatproto.RepoPutRecord_Input{
686
+
atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{
738
687
Collection: tangled.RepoNSID,
739
688
Repo: user.Did,
740
689
Rkey: rkey,
···
747
696
}},
748
697
})
749
698
if err != nil {
750
-
span.RecordError(err)
751
699
log.Printf("failed to create record: %s", err)
752
700
s.pages.Notice(w, "repo", "Failed to announce repository creation.")
753
701
return
754
702
}
755
703
log.Println("created repo record: ", atresp.Uri)
756
-
span.SetAttributes(attribute.String("repo.uri", atresp.Uri))
757
704
758
-
tx, err := s.db.BeginTx(ctx, nil)
705
+
tx, err := s.db.BeginTx(r.Context(), nil)
759
706
if err != nil {
760
-
span.RecordError(err)
761
707
log.Println(err)
762
708
s.pages.Notice(w, "repo", "Failed to save repository information.")
763
709
return
···
772
718
773
719
resp, err := client.NewRepo(user.Did, repoName, defaultBranch)
774
720
if err != nil {
775
-
span.RecordError(err)
776
721
s.pages.Notice(w, "repo", "Failed to create repository on knot server.")
777
722
return
778
723
}
779
-
span.SetAttributes(attribute.Int("knot_response.status", resp.StatusCode))
780
724
781
725
switch resp.StatusCode {
782
726
case http.StatusConflict:
···
789
733
}
790
734
791
735
repo.AtUri = atresp.Uri
792
-
err = db.AddRepo(ctx, tx, repo)
736
+
err = db.AddRepo(tx, repo)
793
737
if err != nil {
794
-
span.RecordError(err)
795
738
log.Println(err)
796
739
s.pages.Notice(w, "repo", "Failed to save repository information.")
797
740
return
···
801
744
p, _ := securejoin.SecureJoin(user.Did, repoName)
802
745
err = s.enforcer.AddRepo(user.Did, domain, p)
803
746
if err != nil {
804
-
span.RecordError(err)
805
747
log.Println(err)
806
748
s.pages.Notice(w, "repo", "Failed to set up repository permissions.")
807
749
return
···
809
751
810
752
err = tx.Commit()
811
753
if err != nil {
812
-
span.RecordError(err)
813
754
log.Println("failed to commit changes", err)
814
755
http.Error(w, err.Error(), http.StatusInternalServerError)
815
756
return
···
817
758
818
759
err = s.enforcer.E.SavePolicy()
819
760
if err != nil {
820
-
span.RecordError(err)
821
761
log.Println("failed to update ACLs", err)
822
762
http.Error(w, err.Error(), http.StatusInternalServerError)
823
763
return
+2
-4
cmd/appview/main.go
+2
-4
cmd/appview/main.go
···
14
14
func main() {
15
15
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil)))
16
16
17
-
ctx := context.Background()
18
-
19
-
c, err := appview.LoadConfig(ctx)
17
+
c, err := appview.LoadConfig(context.Background())
20
18
if err != nil {
21
19
log.Println("failed to load config", "error", err)
22
20
return
23
21
}
24
22
25
-
state, err := state.Make(ctx, c)
23
+
state, err := state.Make(c)
26
24
27
25
if err != nil {
28
26
log.Fatal(err)
+6
-21
go.mod
+6
-21
go.mod
···
26
26
github.com/sethvargo/go-envconfig v1.1.0
27
27
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
28
28
github.com/yuin/goldmark v1.4.13
29
-
go.opentelemetry.io/otel v1.35.0
30
-
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0
31
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0
32
-
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0
33
-
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0
34
-
go.opentelemetry.io/otel/metric v1.35.0
35
-
go.opentelemetry.io/otel/sdk v1.35.0
36
-
go.opentelemetry.io/otel/sdk/metric v1.35.0
37
-
go.opentelemetry.io/otel/trace v1.35.0
38
29
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
39
30
)
40
31
···
48
39
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
49
40
github.com/carlmjohnson/versioninfo v0.22.5 // indirect
50
41
github.com/casbin/govaluate v1.3.0 // indirect
51
-
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
52
42
github.com/cespare/xxhash/v2 v2.3.0 // indirect
53
43
github.com/cloudflare/circl v1.6.0 // indirect
54
44
github.com/davecgh/go-spew v1.1.1 // indirect
···
57
47
github.com/felixge/httpsnoop v1.0.4 // indirect
58
48
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
59
49
github.com/go-git/go-billy/v5 v5.6.2 // indirect
60
-
github.com/go-logr/logr v1.4.2 // indirect
50
+
github.com/go-logr/logr v1.4.1 // indirect
61
51
github.com/go-logr/stdr v1.2.2 // indirect
62
52
github.com/goccy/go-json v0.10.2 // indirect
63
53
github.com/gogo/protobuf v1.3.2 // indirect
64
54
github.com/gorilla/css v1.0.1 // indirect
65
55
github.com/gorilla/securecookie v1.1.2 // indirect
66
56
github.com/gorilla/websocket v1.5.1 // indirect
67
-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
68
57
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
69
58
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
70
59
github.com/hashicorp/golang-lru v1.0.2 // indirect
···
110
99
github.com/xanzy/ssh-agent v0.3.3 // indirect
111
100
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
112
101
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
113
-
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
114
102
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
115
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
116
-
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
103
+
go.opentelemetry.io/otel v1.21.0 // indirect
104
+
go.opentelemetry.io/otel/metric v1.21.0 // indirect
105
+
go.opentelemetry.io/otel/trace v1.21.0 // indirect
117
106
go.uber.org/atomic v1.11.0 // indirect
118
107
go.uber.org/multierr v1.11.0 // indirect
119
108
go.uber.org/zap v1.26.0 // indirect
120
109
golang.org/x/crypto v0.37.0 // indirect
121
110
golang.org/x/net v0.39.0 // indirect
122
-
golang.org/x/sys v0.33.0 // indirect
123
-
golang.org/x/text v0.24.0 // indirect
111
+
golang.org/x/sys v0.32.0 // indirect
124
112
golang.org/x/time v0.5.0 // indirect
125
-
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
126
-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
127
-
google.golang.org/grpc v1.71.0 // indirect
128
-
google.golang.org/protobuf v1.36.5 // indirect
113
+
google.golang.org/protobuf v1.34.2 // indirect
129
114
gopkg.in/warnings.v0 v0.1.2 // indirect
130
115
gopkg.in/yaml.v3 v3.0.1 // indirect
131
116
lukechampine.com/blake3 v1.2.1 // indirect
+18
-48
go.sum
+18
-48
go.sum
···
42
42
github.com/casbin/govaluate v1.2.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
43
43
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
44
44
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
45
-
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
46
-
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
47
45
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
48
46
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
49
47
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
···
84
82
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
85
83
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
86
84
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
87
-
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
88
-
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
85
+
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
86
+
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
89
87
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
90
88
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
91
89
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
···
95
93
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
96
94
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
97
95
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
98
-
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
99
-
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
100
96
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
101
-
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
102
-
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
97
+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
98
+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
103
99
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
104
100
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
105
101
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
···
115
111
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
116
112
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
117
113
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
118
-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
119
-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
120
114
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
121
115
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
122
116
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
···
233
227
github.com/resend/resend-go/v2 v2.15.0 h1:B6oMEPf8IEQwn2Ovx/9yymkESLDSeNfLFaNMw+mzHhE=
234
228
github.com/resend/resend-go/v2 v2.15.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
235
229
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
236
-
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
237
-
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
230
+
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
231
+
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
238
232
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
239
233
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
240
234
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
···
274
268
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
275
269
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
276
270
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
277
-
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
278
-
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
279
271
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
280
272
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
281
-
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
282
-
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
283
-
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
284
-
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
285
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
286
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
287
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
288
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
289
-
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k=
290
-
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY=
291
-
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE=
292
-
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg=
293
-
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
294
-
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
295
-
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
296
-
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
297
-
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
298
-
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
299
-
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
300
-
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
301
-
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
302
-
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
273
+
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
274
+
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
275
+
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
276
+
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
277
+
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
278
+
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
303
279
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
304
280
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
305
281
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
306
282
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
307
283
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
308
-
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
309
-
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
284
+
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
285
+
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
310
286
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
311
287
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
312
288
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
···
381
357
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
382
358
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
383
359
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
384
-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
385
-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
360
+
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
361
+
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
386
362
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
387
363
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
388
364
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
···
419
395
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
420
396
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
421
397
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
422
-
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
423
-
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
424
-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
425
-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
426
-
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
427
-
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
428
-
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
429
-
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
398
+
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
399
+
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
430
400
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
431
401
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
432
402
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-88
telemetry/middleware.go
-88
telemetry/middleware.go
···
1
-
package telemetry
2
-
3
-
import (
4
-
"fmt"
5
-
"net/http"
6
-
"time"
7
-
8
-
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
9
-
otelmetric "go.opentelemetry.io/otel/metric"
10
-
"go.opentelemetry.io/otel/semconv/v1.13.0/httpconv"
11
-
)
12
-
13
-
func (t *Telemetry) RequestDuration() func(next http.Handler) http.Handler {
14
-
const (
15
-
metricNameRequestDurationMs = "request_duration_millis"
16
-
metricUnitRequestDurationMs = "ms"
17
-
metricDescRequestDurationMs = "Measures the latency of HTTP requests processed by the server, in milliseconds."
18
-
)
19
-
histogram, err := t.meter.Int64Histogram(
20
-
metricNameRequestDurationMs,
21
-
otelmetric.WithDescription(metricDescRequestDurationMs),
22
-
otelmetric.WithUnit(metricUnitRequestDurationMs),
23
-
)
24
-
if err != nil {
25
-
panic(fmt.Sprintf("unable to create %s histogram: %v", metricNameRequestDurationMs, err))
26
-
}
27
-
28
-
return func(next http.Handler) http.Handler {
29
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30
-
// capture the start time of the request
31
-
startTime := time.Now()
32
-
33
-
// execute next http handler
34
-
next.ServeHTTP(w, r)
35
-
36
-
// record the request duration
37
-
duration := time.Since(startTime)
38
-
histogram.Record(
39
-
r.Context(),
40
-
int64(duration.Milliseconds()),
41
-
otelmetric.WithAttributes(
42
-
httpconv.ServerRequest(t.serviceName, r)...,
43
-
),
44
-
)
45
-
})
46
-
}
47
-
}
48
-
49
-
func (t *Telemetry) RequestInFlight() func(next http.Handler) http.Handler {
50
-
const (
51
-
metricNameRequestInFlight = "request_in_flight"
52
-
metricDescRequestInFlight = "Measures the number of concurrent HTTP requests being processed by the server."
53
-
metricUnitRequestInFlight = "1"
54
-
)
55
-
56
-
// counter to capture requests in flight
57
-
counter, err := t.meter.Int64UpDownCounter(
58
-
metricNameRequestInFlight,
59
-
otelmetric.WithDescription(metricDescRequestInFlight),
60
-
otelmetric.WithUnit(metricUnitRequestInFlight),
61
-
)
62
-
if err != nil {
63
-
panic(fmt.Sprintf("unable to create %s counter: %v", metricNameRequestInFlight, err))
64
-
}
65
-
66
-
return func(next http.Handler) http.Handler {
67
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
68
-
attrs := otelmetric.WithAttributes(httpconv.ServerRequest(t.serviceName, r)...)
69
-
70
-
// increase the number of requests in flight
71
-
counter.Add(r.Context(), 1, attrs)
72
-
73
-
// execute next http handler
74
-
next.ServeHTTP(w, r)
75
-
76
-
// decrease the number of requests in flight
77
-
counter.Add(r.Context(), -1, attrs)
78
-
})
79
-
}
80
-
}
81
-
82
-
func (t *Telemetry) WithRouteTag() func(next http.Handler) http.Handler {
83
-
return func(next http.Handler) http.Handler {
84
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85
-
otelhttp.WithRouteTag(r.URL.Path, next)
86
-
})
87
-
}
88
-
}
-65
telemetry/provider.go
-65
telemetry/provider.go
···
1
-
package telemetry
2
-
3
-
import (
4
-
"context"
5
-
"fmt"
6
-
"time"
7
-
8
-
"go.opentelemetry.io/otel"
9
-
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
10
-
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
11
-
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
12
-
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
13
-
"go.opentelemetry.io/otel/sdk/metric"
14
-
"go.opentelemetry.io/otel/sdk/resource"
15
-
"go.opentelemetry.io/otel/sdk/trace"
16
-
)
17
-
18
-
func NewTracerProvider(ctx context.Context, res *resource.Resource, isDev bool) (*trace.TracerProvider, error) {
19
-
var exporter trace.SpanExporter
20
-
var err error
21
-
22
-
if isDev {
23
-
exporter, err = stdouttrace.New()
24
-
if err != nil {
25
-
return nil, fmt.Errorf("failed to create stdout trace exporter: %w", err)
26
-
}
27
-
} else {
28
-
exporter, err = otlptracegrpc.New(ctx)
29
-
if err != nil {
30
-
return nil, fmt.Errorf("failed to create OTLP trace exporter: %w", err)
31
-
}
32
-
}
33
-
34
-
tp := trace.NewTracerProvider(
35
-
trace.WithBatcher(exporter, trace.WithBatchTimeout(1*time.Second)),
36
-
trace.WithResource(res),
37
-
)
38
-
otel.SetTracerProvider(tp)
39
-
40
-
return tp, nil
41
-
}
42
-
43
-
func NewMeterProvider(ctx context.Context, res *resource.Resource, isDev bool) (*metric.MeterProvider, error) {
44
-
var exporter metric.Exporter
45
-
var err error
46
-
47
-
if isDev {
48
-
exporter, err = stdoutmetric.New()
49
-
if err != nil {
50
-
return nil, fmt.Errorf("failed to create stdout metric exporter: %w", err)
51
-
}
52
-
} else {
53
-
exporter, err = otlpmetricgrpc.New(ctx)
54
-
if err != nil {
55
-
return nil, fmt.Errorf("failed to create OTLP metric exporter: %w", err)
56
-
}
57
-
}
58
-
59
-
mp := metric.NewMeterProvider(
60
-
metric.WithReader(metric.NewPeriodicReader(exporter, metric.WithInterval(10*time.Second))),
61
-
metric.WithResource(res),
62
-
)
63
-
otel.SetMeterProvider(mp)
64
-
return mp, nil
65
-
}
-76
telemetry/telemetry.go
-76
telemetry/telemetry.go
···
1
-
package telemetry
2
-
3
-
import (
4
-
"context"
5
-
"fmt"
6
-
7
-
"go.opentelemetry.io/otel/attribute"
8
-
otelmetric "go.opentelemetry.io/otel/metric"
9
-
"go.opentelemetry.io/otel/sdk/metric"
10
-
"go.opentelemetry.io/otel/sdk/resource"
11
-
"go.opentelemetry.io/otel/sdk/trace"
12
-
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
13
-
oteltrace "go.opentelemetry.io/otel/trace"
14
-
)
15
-
16
-
type Telemetry struct {
17
-
tp *trace.TracerProvider
18
-
mp *metric.MeterProvider
19
-
20
-
meter otelmetric.Meter
21
-
tracer oteltrace.Tracer
22
-
23
-
serviceName string
24
-
serviceVersion string
25
-
}
26
-
27
-
func NewTelemetry(ctx context.Context, serviceName, serviceVersion string, isDev bool) (*Telemetry, error) {
28
-
res := resource.NewWithAttributes(
29
-
semconv.SchemaURL,
30
-
semconv.ServiceName(serviceName),
31
-
semconv.ServiceVersion(serviceVersion),
32
-
)
33
-
34
-
tp, err := NewTracerProvider(ctx, res, isDev)
35
-
if err != nil {
36
-
return nil, err
37
-
}
38
-
39
-
// mp, err := NewMeterProvider(ctx, res, isDev)
40
-
// if err != nil {
41
-
// return nil, err
42
-
// }
43
-
44
-
return &Telemetry{
45
-
tp: tp,
46
-
//mp: mp,
47
-
48
-
//meter: mp.Meter(serviceName),
49
-
tracer: tp.Tracer(serviceVersion),
50
-
51
-
serviceName: serviceName,
52
-
serviceVersion: serviceVersion,
53
-
}, nil
54
-
}
55
-
56
-
func (t *Telemetry) Meter() otelmetric.Meter {
57
-
return t.meter
58
-
}
59
-
60
-
func (t *Telemetry) Tracer() oteltrace.Tracer {
61
-
return t.tracer
62
-
}
63
-
64
-
func (t *Telemetry) TraceStart(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, oteltrace.Span) {
65
-
ctx, span := t.tracer.Start(ctx, name)
66
-
span.SetAttributes(attrs...)
67
-
return ctx, span
68
-
}
69
-
70
-
func MapAttrs[T any](attrs map[string]T) []attribute.KeyValue {
71
-
var result []attribute.KeyValue
72
-
for k, v := range attrs {
73
-
result = append(result, attribute.Key(k).String(fmt.Sprintf("%v", v)))
74
-
}
75
-
return result
76
-
}