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