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