+416
api/tangled/cbor_gen.go
+416
api/tangled/cbor_gen.go
···
561
561
562
562
return nil
563
563
}
564
+
func (t *Comment) MarshalCBOR(w io.Writer) error {
565
+
if t == nil {
566
+
_, err := w.Write(cbg.CborNull)
567
+
return err
568
+
}
569
+
570
+
cw := cbg.NewCborWriter(w)
571
+
fieldCount := 7
572
+
573
+
if t.Mentions == nil {
574
+
fieldCount--
575
+
}
576
+
577
+
if t.References == nil {
578
+
fieldCount--
579
+
}
580
+
581
+
if t.ReplyTo == nil {
582
+
fieldCount--
583
+
}
584
+
585
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
586
+
return err
587
+
}
588
+
589
+
// t.Body (string) (string)
590
+
if len("body") > 1000000 {
591
+
return xerrors.Errorf("Value in field \"body\" was too long")
592
+
}
593
+
594
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil {
595
+
return err
596
+
}
597
+
if _, err := cw.WriteString(string("body")); err != nil {
598
+
return err
599
+
}
600
+
601
+
if len(t.Body) > 1000000 {
602
+
return xerrors.Errorf("Value in field t.Body was too long")
603
+
}
604
+
605
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Body))); err != nil {
606
+
return err
607
+
}
608
+
if _, err := cw.WriteString(string(t.Body)); err != nil {
609
+
return err
610
+
}
611
+
612
+
// t.LexiconTypeID (string) (string)
613
+
if len("$type") > 1000000 {
614
+
return xerrors.Errorf("Value in field \"$type\" was too long")
615
+
}
616
+
617
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
618
+
return err
619
+
}
620
+
if _, err := cw.WriteString(string("$type")); err != nil {
621
+
return err
622
+
}
623
+
624
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.comment"))); err != nil {
625
+
return err
626
+
}
627
+
if _, err := cw.WriteString(string("sh.tangled.comment")); err != nil {
628
+
return err
629
+
}
630
+
631
+
// t.ReplyTo (string) (string)
632
+
if t.ReplyTo != nil {
633
+
634
+
if len("replyTo") > 1000000 {
635
+
return xerrors.Errorf("Value in field \"replyTo\" was too long")
636
+
}
637
+
638
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("replyTo"))); err != nil {
639
+
return err
640
+
}
641
+
if _, err := cw.WriteString(string("replyTo")); err != nil {
642
+
return err
643
+
}
644
+
645
+
if t.ReplyTo == nil {
646
+
if _, err := cw.Write(cbg.CborNull); err != nil {
647
+
return err
648
+
}
649
+
} else {
650
+
if len(*t.ReplyTo) > 1000000 {
651
+
return xerrors.Errorf("Value in field t.ReplyTo was too long")
652
+
}
653
+
654
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.ReplyTo))); err != nil {
655
+
return err
656
+
}
657
+
if _, err := cw.WriteString(string(*t.ReplyTo)); err != nil {
658
+
return err
659
+
}
660
+
}
661
+
}
662
+
663
+
// t.Subject (string) (string)
664
+
if len("subject") > 1000000 {
665
+
return xerrors.Errorf("Value in field \"subject\" was too long")
666
+
}
667
+
668
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
669
+
return err
670
+
}
671
+
if _, err := cw.WriteString(string("subject")); err != nil {
672
+
return err
673
+
}
674
+
675
+
if len(t.Subject) > 1000000 {
676
+
return xerrors.Errorf("Value in field t.Subject was too long")
677
+
}
678
+
679
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
680
+
return err
681
+
}
682
+
if _, err := cw.WriteString(string(t.Subject)); err != nil {
683
+
return err
684
+
}
685
+
686
+
// t.Mentions ([]string) (slice)
687
+
if t.Mentions != nil {
688
+
689
+
if len("mentions") > 1000000 {
690
+
return xerrors.Errorf("Value in field \"mentions\" was too long")
691
+
}
692
+
693
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mentions"))); err != nil {
694
+
return err
695
+
}
696
+
if _, err := cw.WriteString(string("mentions")); err != nil {
697
+
return err
698
+
}
699
+
700
+
if len(t.Mentions) > 8192 {
701
+
return xerrors.Errorf("Slice value in field t.Mentions was too long")
702
+
}
703
+
704
+
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Mentions))); err != nil {
705
+
return err
706
+
}
707
+
for _, v := range t.Mentions {
708
+
if len(v) > 1000000 {
709
+
return xerrors.Errorf("Value in field v was too long")
710
+
}
711
+
712
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
713
+
return err
714
+
}
715
+
if _, err := cw.WriteString(string(v)); err != nil {
716
+
return err
717
+
}
718
+
719
+
}
720
+
}
721
+
722
+
// t.CreatedAt (string) (string)
723
+
if len("createdAt") > 1000000 {
724
+
return xerrors.Errorf("Value in field \"createdAt\" was too long")
725
+
}
726
+
727
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
728
+
return err
729
+
}
730
+
if _, err := cw.WriteString(string("createdAt")); err != nil {
731
+
return err
732
+
}
733
+
734
+
if len(t.CreatedAt) > 1000000 {
735
+
return xerrors.Errorf("Value in field t.CreatedAt was too long")
736
+
}
737
+
738
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
739
+
return err
740
+
}
741
+
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
742
+
return err
743
+
}
744
+
745
+
// t.References ([]string) (slice)
746
+
if t.References != nil {
747
+
748
+
if len("references") > 1000000 {
749
+
return xerrors.Errorf("Value in field \"references\" was too long")
750
+
}
751
+
752
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("references"))); err != nil {
753
+
return err
754
+
}
755
+
if _, err := cw.WriteString(string("references")); err != nil {
756
+
return err
757
+
}
758
+
759
+
if len(t.References) > 8192 {
760
+
return xerrors.Errorf("Slice value in field t.References was too long")
761
+
}
762
+
763
+
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.References))); err != nil {
764
+
return err
765
+
}
766
+
for _, v := range t.References {
767
+
if len(v) > 1000000 {
768
+
return xerrors.Errorf("Value in field v was too long")
769
+
}
770
+
771
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
772
+
return err
773
+
}
774
+
if _, err := cw.WriteString(string(v)); err != nil {
775
+
return err
776
+
}
777
+
778
+
}
779
+
}
780
+
return nil
781
+
}
782
+
783
+
func (t *Comment) UnmarshalCBOR(r io.Reader) (err error) {
784
+
*t = Comment{}
785
+
786
+
cr := cbg.NewCborReader(r)
787
+
788
+
maj, extra, err := cr.ReadHeader()
789
+
if err != nil {
790
+
return err
791
+
}
792
+
defer func() {
793
+
if err == io.EOF {
794
+
err = io.ErrUnexpectedEOF
795
+
}
796
+
}()
797
+
798
+
if maj != cbg.MajMap {
799
+
return fmt.Errorf("cbor input should be of type map")
800
+
}
801
+
802
+
if extra > cbg.MaxLength {
803
+
return fmt.Errorf("Comment: map struct too large (%d)", extra)
804
+
}
805
+
806
+
n := extra
807
+
808
+
nameBuf := make([]byte, 10)
809
+
for i := uint64(0); i < n; i++ {
810
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
811
+
if err != nil {
812
+
return err
813
+
}
814
+
815
+
if !ok {
816
+
// Field doesn't exist on this type, so ignore it
817
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
818
+
return err
819
+
}
820
+
continue
821
+
}
822
+
823
+
switch string(nameBuf[:nameLen]) {
824
+
// t.Body (string) (string)
825
+
case "body":
826
+
827
+
{
828
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
829
+
if err != nil {
830
+
return err
831
+
}
832
+
833
+
t.Body = string(sval)
834
+
}
835
+
// t.LexiconTypeID (string) (string)
836
+
case "$type":
837
+
838
+
{
839
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
840
+
if err != nil {
841
+
return err
842
+
}
843
+
844
+
t.LexiconTypeID = string(sval)
845
+
}
846
+
// t.ReplyTo (string) (string)
847
+
case "replyTo":
848
+
849
+
{
850
+
b, err := cr.ReadByte()
851
+
if err != nil {
852
+
return err
853
+
}
854
+
if b != cbg.CborNull[0] {
855
+
if err := cr.UnreadByte(); err != nil {
856
+
return err
857
+
}
858
+
859
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
860
+
if err != nil {
861
+
return err
862
+
}
863
+
864
+
t.ReplyTo = (*string)(&sval)
865
+
}
866
+
}
867
+
// t.Subject (string) (string)
868
+
case "subject":
869
+
870
+
{
871
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
872
+
if err != nil {
873
+
return err
874
+
}
875
+
876
+
t.Subject = string(sval)
877
+
}
878
+
// t.Mentions ([]string) (slice)
879
+
case "mentions":
880
+
881
+
maj, extra, err = cr.ReadHeader()
882
+
if err != nil {
883
+
return err
884
+
}
885
+
886
+
if extra > 8192 {
887
+
return fmt.Errorf("t.Mentions: array too large (%d)", extra)
888
+
}
889
+
890
+
if maj != cbg.MajArray {
891
+
return fmt.Errorf("expected cbor array")
892
+
}
893
+
894
+
if extra > 0 {
895
+
t.Mentions = make([]string, extra)
896
+
}
897
+
898
+
for i := 0; i < int(extra); i++ {
899
+
{
900
+
var maj byte
901
+
var extra uint64
902
+
var err error
903
+
_ = maj
904
+
_ = extra
905
+
_ = err
906
+
907
+
{
908
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
909
+
if err != nil {
910
+
return err
911
+
}
912
+
913
+
t.Mentions[i] = string(sval)
914
+
}
915
+
916
+
}
917
+
}
918
+
// t.CreatedAt (string) (string)
919
+
case "createdAt":
920
+
921
+
{
922
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
923
+
if err != nil {
924
+
return err
925
+
}
926
+
927
+
t.CreatedAt = string(sval)
928
+
}
929
+
// t.References ([]string) (slice)
930
+
case "references":
931
+
932
+
maj, extra, err = cr.ReadHeader()
933
+
if err != nil {
934
+
return err
935
+
}
936
+
937
+
if extra > 8192 {
938
+
return fmt.Errorf("t.References: array too large (%d)", extra)
939
+
}
940
+
941
+
if maj != cbg.MajArray {
942
+
return fmt.Errorf("expected cbor array")
943
+
}
944
+
945
+
if extra > 0 {
946
+
t.References = make([]string, extra)
947
+
}
948
+
949
+
for i := 0; i < int(extra); i++ {
950
+
{
951
+
var maj byte
952
+
var extra uint64
953
+
var err error
954
+
_ = maj
955
+
_ = extra
956
+
_ = err
957
+
958
+
{
959
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
960
+
if err != nil {
961
+
return err
962
+
}
963
+
964
+
t.References[i] = string(sval)
965
+
}
966
+
967
+
}
968
+
}
969
+
970
+
default:
971
+
// Field doesn't exist on this type, so ignore it
972
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
973
+
return err
974
+
}
975
+
}
976
+
}
977
+
978
+
return nil
979
+
}
564
980
func (t *FeedReaction) MarshalCBOR(w io.Writer) error {
565
981
if t == nil {
566
982
_, err := w.Write(cbg.CborNull)
+27
api/tangled/tangledcomment.go
+27
api/tangled/tangledcomment.go
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package tangled
4
+
5
+
// schema: sh.tangled.comment
6
+
7
+
import (
8
+
"github.com/bluesky-social/indigo/lex/util"
9
+
)
10
+
11
+
const (
12
+
CommentNSID = "sh.tangled.comment"
13
+
)
14
+
15
+
func init() {
16
+
util.RegisterType("sh.tangled.comment", &Comment{})
17
+
} //
18
+
// RECORDTYPE: Comment
19
+
type Comment struct {
20
+
LexiconTypeID string `json:"$type,const=sh.tangled.comment" cborgen:"$type,const=sh.tangled.comment"`
21
+
Body string `json:"body" cborgen:"body"`
22
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
23
+
Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"`
24
+
References []string `json:"references,omitempty" cborgen:"references,omitempty"`
25
+
ReplyTo *string `json:"replyTo,omitempty" cborgen:"replyTo,omitempty"`
26
+
Subject string `json:"subject" cborgen:"subject"`
27
+
}
+199
appview/db/comments.go
+199
appview/db/comments.go
···
1
+
package db
2
+
3
+
import (
4
+
"database/sql"
5
+
"fmt"
6
+
"maps"
7
+
"slices"
8
+
"sort"
9
+
"strings"
10
+
"time"
11
+
12
+
"github.com/bluesky-social/indigo/atproto/syntax"
13
+
"tangled.org/core/appview/models"
14
+
"tangled.org/core/orm"
15
+
)
16
+
17
+
func PutComment(tx *sql.Tx, c *models.Comment) error {
18
+
result, err := tx.Exec(
19
+
`insert into comments (
20
+
did,
21
+
rkey,
22
+
subject_at,
23
+
reply_to,
24
+
body,
25
+
pull_submission_id,
26
+
created
27
+
)
28
+
values (?, ?, ?, ?, ?, ?, ?)
29
+
on conflict(did, rkey) do update set
30
+
subject_at = excluded.subject_at,
31
+
reply_to = excluded.reply_to,
32
+
body = excluded.body,
33
+
edited = case
34
+
when
35
+
comments.subject_at != excluded.subject_at
36
+
or comments.body != excluded.body
37
+
or comments.reply_to != excluded.reply_to
38
+
then ?
39
+
else comments.edited
40
+
end`,
41
+
c.Did,
42
+
c.Rkey,
43
+
c.Subject,
44
+
c.ReplyTo,
45
+
c.Body,
46
+
c.PullSubmissionId,
47
+
c.Created.Format(time.RFC3339),
48
+
time.Now().Format(time.RFC3339),
49
+
)
50
+
if err != nil {
51
+
return err
52
+
}
53
+
54
+
c.Id, err = result.LastInsertId()
55
+
if err != nil {
56
+
return err
57
+
}
58
+
59
+
if err := putReferences(tx, c.AtUri(), c.References); err != nil {
60
+
return fmt.Errorf("put reference_links: %w", err)
61
+
}
62
+
63
+
return nil
64
+
}
65
+
66
+
func DeleteComments(e Execer, filters ...orm.Filter) error {
67
+
var conditions []string
68
+
var args []any
69
+
for _, filter := range filters {
70
+
conditions = append(conditions, filter.Condition())
71
+
args = append(args, filter.Arg()...)
72
+
}
73
+
74
+
whereClause := ""
75
+
if conditions != nil {
76
+
whereClause = " where " + strings.Join(conditions, " and ")
77
+
}
78
+
79
+
query := fmt.Sprintf(`update comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)
80
+
81
+
_, err := e.Exec(query, args...)
82
+
return err
83
+
}
84
+
85
+
func GetComments(e Execer, filters ...orm.Filter) ([]models.Comment, error) {
86
+
commentMap := make(map[string]*models.Comment)
87
+
88
+
var conditions []string
89
+
var args []any
90
+
for _, filter := range filters {
91
+
conditions = append(conditions, filter.Condition())
92
+
args = append(args, filter.Arg()...)
93
+
}
94
+
95
+
whereClause := ""
96
+
if conditions != nil {
97
+
whereClause = " where " + strings.Join(conditions, " and ")
98
+
}
99
+
100
+
query := fmt.Sprintf(`
101
+
select
102
+
id,
103
+
did,
104
+
rkey,
105
+
subject_at,
106
+
reply_to,
107
+
body,
108
+
pull_submission_id,
109
+
created,
110
+
edited,
111
+
deleted
112
+
from
113
+
comments
114
+
%s
115
+
`, whereClause)
116
+
117
+
rows, err := e.Query(query, args...)
118
+
if err != nil {
119
+
return nil, err
120
+
}
121
+
122
+
for rows.Next() {
123
+
var comment models.Comment
124
+
var created string
125
+
var rkey, edited, deleted, replyTo sql.Null[string]
126
+
err := rows.Scan(
127
+
&comment.Id,
128
+
&comment.Did,
129
+
&rkey,
130
+
&comment.Subject,
131
+
&replyTo,
132
+
&comment.Body,
133
+
&comment.PullSubmissionId,
134
+
&created,
135
+
&edited,
136
+
&deleted,
137
+
)
138
+
if err != nil {
139
+
return nil, err
140
+
}
141
+
142
+
// this is a remnant from old times, newer comments always have rkey
143
+
if rkey.Valid {
144
+
comment.Rkey = rkey.V
145
+
}
146
+
147
+
if t, err := time.Parse(time.RFC3339, created); err == nil {
148
+
comment.Created = t
149
+
}
150
+
151
+
if edited.Valid {
152
+
if t, err := time.Parse(time.RFC3339, edited.V); err == nil {
153
+
comment.Edited = &t
154
+
}
155
+
}
156
+
157
+
if deleted.Valid {
158
+
if t, err := time.Parse(time.RFC3339, deleted.V); err == nil {
159
+
comment.Deleted = &t
160
+
}
161
+
}
162
+
163
+
if replyTo.Valid {
164
+
rt := syntax.ATURI(replyTo.V)
165
+
comment.ReplyTo = &rt
166
+
}
167
+
168
+
atUri := comment.AtUri().String()
169
+
commentMap[atUri] = &comment
170
+
}
171
+
172
+
if err := rows.Err(); err != nil {
173
+
return nil, err
174
+
}
175
+
defer rows.Close()
176
+
177
+
// collect references from each comments
178
+
commentAts := slices.Collect(maps.Keys(commentMap))
179
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
180
+
if err != nil {
181
+
return nil, fmt.Errorf("failed to query reference_links: %w", err)
182
+
}
183
+
for commentAt, references := range allReferencs {
184
+
if comment, ok := commentMap[commentAt.String()]; ok {
185
+
comment.References = references
186
+
}
187
+
}
188
+
189
+
var comments []models.Comment
190
+
for _, c := range commentMap {
191
+
comments = append(comments, *c)
192
+
}
193
+
194
+
sort.Slice(comments, func(i, j int) bool {
195
+
return comments[i].Created.After(comments[j].Created)
196
+
})
197
+
198
+
return comments, nil
199
+
}
+81
appview/db/db.go
+81
appview/db/db.go
···
1173
1173
return err
1174
1174
})
1175
1175
1176
+
orm.RunMigration(conn, logger, "add-comments-table", func(tx *sql.Tx) error {
1177
+
_, err := tx.Exec(`
1178
+
drop table if exists comments;
1179
+
1180
+
create table comments (
1181
+
-- identifiers
1182
+
id integer primary key autoincrement,
1183
+
did text not null,
1184
+
collection text not null default 'sh.tangled.comment',
1185
+
rkey text not null,
1186
+
at_uri text generated always as ('at://' || did || '/' || collection || '/' || rkey) stored,
1187
+
1188
+
-- at identifiers
1189
+
subject_at text not null,
1190
+
reply_to text, -- at_uri of parent comment
1191
+
1192
+
pull_submission_id integer, -- dirty fix until we atprotate the pull-rounds
1193
+
1194
+
-- content
1195
+
body text not null,
1196
+
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
1197
+
edited text,
1198
+
deleted text,
1199
+
1200
+
-- constraints
1201
+
unique(did, rkey)
1202
+
);
1203
+
1204
+
insert into comments (
1205
+
did,
1206
+
collection,
1207
+
rkey,
1208
+
subject_at,
1209
+
reply_to,
1210
+
body,
1211
+
created,
1212
+
edited,
1213
+
deleted
1214
+
)
1215
+
select
1216
+
did,
1217
+
'sh.tangled.repo.issue.comment',
1218
+
rkey,
1219
+
issue_at,
1220
+
reply_to,
1221
+
body,
1222
+
created,
1223
+
edited,
1224
+
deleted
1225
+
from issue_comments
1226
+
where rkey is not null;
1227
+
1228
+
insert into comments (
1229
+
did,
1230
+
collection,
1231
+
rkey,
1232
+
subject_at,
1233
+
pull_submission_id,
1234
+
body,
1235
+
created
1236
+
)
1237
+
select
1238
+
c.owner_did,
1239
+
'sh.tangled.repo.pull.comment',
1240
+
substr(
1241
+
substr(c.comment_at, 6 + instr(substr(c.comment_at, 6), '/')), -- nsid/rkey
1242
+
instr(
1243
+
substr(c.comment_at, 6 + instr(substr(c.comment_at, 6), '/')), -- nsid/rkey
1244
+
'/'
1245
+
) + 1
1246
+
), -- rkey
1247
+
p.at_uri,
1248
+
c.submission_id,
1249
+
c.body,
1250
+
c.created
1251
+
from pull_comments c
1252
+
join pulls p on c.repo_at = p.repo_at and c.pull_id = p.pull_id;
1253
+
`)
1254
+
return err
1255
+
})
1256
+
1176
1257
return &DB{
1177
1258
db,
1178
1259
logger,
+6
-186
appview/db/issues.go
+6
-186
appview/db/issues.go
···
100
100
}
101
101
102
102
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]models.Issue, error) {
103
-
issueMap := make(map[string]*models.Issue) // at-uri -> issue
103
+
issueMap := make(map[syntax.ATURI]*models.Issue) // at-uri -> issue
104
104
105
105
var conditions []string
106
106
var args []any
···
196
196
}
197
197
}
198
198
199
-
atUri := issue.AtUri().String()
200
-
issueMap[atUri] = &issue
199
+
issueMap[issue.AtUri()] = &issue
201
200
}
202
201
203
202
// collect reverse repos
···
229
228
// collect comments
230
229
issueAts := slices.Collect(maps.Keys(issueMap))
231
230
232
-
comments, err := GetIssueComments(e, orm.FilterIn("issue_at", issueAts))
231
+
comments, err := GetComments(e, orm.FilterIn("subject_at", issueAts))
233
232
if err != nil {
234
233
return nil, fmt.Errorf("failed to query comments: %w", err)
235
234
}
236
235
for i := range comments {
237
-
issueAt := comments[i].IssueAt
236
+
issueAt := comments[i].Subject
238
237
if issue, ok := issueMap[issueAt]; ok {
239
238
issue.Comments = append(issue.Comments, comments[i])
240
239
}
···
246
245
return nil, fmt.Errorf("failed to query labels: %w", err)
247
246
}
248
247
for issueAt, labels := range allLabels {
249
-
if issue, ok := issueMap[issueAt.String()]; ok {
248
+
if issue, ok := issueMap[issueAt]; ok {
250
249
issue.Labels = labels
251
250
}
252
251
}
···
257
256
return nil, fmt.Errorf("failed to query reference_links: %w", err)
258
257
}
259
258
for issueAt, references := range allReferencs {
260
-
if issue, ok := issueMap[issueAt.String()]; ok {
259
+
if issue, ok := issueMap[issueAt]; ok {
261
260
issue.References = references
262
261
}
263
262
}
···
349
348
}
350
349
351
350
return ids, nil
352
-
}
353
-
354
-
func AddIssueComment(tx *sql.Tx, c models.IssueComment) (int64, error) {
355
-
result, err := tx.Exec(
356
-
`insert into issue_comments (
357
-
did,
358
-
rkey,
359
-
issue_at,
360
-
body,
361
-
reply_to,
362
-
created,
363
-
edited
364
-
)
365
-
values (?, ?, ?, ?, ?, ?, null)
366
-
on conflict(did, rkey) do update set
367
-
issue_at = excluded.issue_at,
368
-
body = excluded.body,
369
-
edited = case
370
-
when
371
-
issue_comments.issue_at != excluded.issue_at
372
-
or issue_comments.body != excluded.body
373
-
or issue_comments.reply_to != excluded.reply_to
374
-
then ?
375
-
else issue_comments.edited
376
-
end`,
377
-
c.Did,
378
-
c.Rkey,
379
-
c.IssueAt,
380
-
c.Body,
381
-
c.ReplyTo,
382
-
c.Created.Format(time.RFC3339),
383
-
time.Now().Format(time.RFC3339),
384
-
)
385
-
if err != nil {
386
-
return 0, err
387
-
}
388
-
389
-
id, err := result.LastInsertId()
390
-
if err != nil {
391
-
return 0, err
392
-
}
393
-
394
-
if err := putReferences(tx, c.AtUri(), c.References); err != nil {
395
-
return 0, fmt.Errorf("put reference_links: %w", err)
396
-
}
397
-
398
-
return id, nil
399
-
}
400
-
401
-
func DeleteIssueComments(e Execer, filters ...orm.Filter) error {
402
-
var conditions []string
403
-
var args []any
404
-
for _, filter := range filters {
405
-
conditions = append(conditions, filter.Condition())
406
-
args = append(args, filter.Arg()...)
407
-
}
408
-
409
-
whereClause := ""
410
-
if conditions != nil {
411
-
whereClause = " where " + strings.Join(conditions, " and ")
412
-
}
413
-
414
-
query := fmt.Sprintf(`update issue_comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)
415
-
416
-
_, err := e.Exec(query, args...)
417
-
return err
418
-
}
419
-
420
-
func GetIssueComments(e Execer, filters ...orm.Filter) ([]models.IssueComment, error) {
421
-
commentMap := make(map[string]*models.IssueComment)
422
-
423
-
var conditions []string
424
-
var args []any
425
-
for _, filter := range filters {
426
-
conditions = append(conditions, filter.Condition())
427
-
args = append(args, filter.Arg()...)
428
-
}
429
-
430
-
whereClause := ""
431
-
if conditions != nil {
432
-
whereClause = " where " + strings.Join(conditions, " and ")
433
-
}
434
-
435
-
query := fmt.Sprintf(`
436
-
select
437
-
id,
438
-
did,
439
-
rkey,
440
-
issue_at,
441
-
reply_to,
442
-
body,
443
-
created,
444
-
edited,
445
-
deleted
446
-
from
447
-
issue_comments
448
-
%s
449
-
`, whereClause)
450
-
451
-
rows, err := e.Query(query, args...)
452
-
if err != nil {
453
-
return nil, err
454
-
}
455
-
defer rows.Close()
456
-
457
-
for rows.Next() {
458
-
var comment models.IssueComment
459
-
var created string
460
-
var rkey, edited, deleted, replyTo sql.Null[string]
461
-
err := rows.Scan(
462
-
&comment.Id,
463
-
&comment.Did,
464
-
&rkey,
465
-
&comment.IssueAt,
466
-
&replyTo,
467
-
&comment.Body,
468
-
&created,
469
-
&edited,
470
-
&deleted,
471
-
)
472
-
if err != nil {
473
-
return nil, err
474
-
}
475
-
476
-
// this is a remnant from old times, newer comments always have rkey
477
-
if rkey.Valid {
478
-
comment.Rkey = rkey.V
479
-
}
480
-
481
-
if t, err := time.Parse(time.RFC3339, created); err == nil {
482
-
comment.Created = t
483
-
}
484
-
485
-
if edited.Valid {
486
-
if t, err := time.Parse(time.RFC3339, edited.V); err == nil {
487
-
comment.Edited = &t
488
-
}
489
-
}
490
-
491
-
if deleted.Valid {
492
-
if t, err := time.Parse(time.RFC3339, deleted.V); err == nil {
493
-
comment.Deleted = &t
494
-
}
495
-
}
496
-
497
-
if replyTo.Valid {
498
-
comment.ReplyTo = &replyTo.V
499
-
}
500
-
501
-
atUri := comment.AtUri().String()
502
-
commentMap[atUri] = &comment
503
-
}
504
-
505
-
if err = rows.Err(); err != nil {
506
-
return nil, err
507
-
}
508
-
509
-
// collect references for each comments
510
-
commentAts := slices.Collect(maps.Keys(commentMap))
511
-
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
512
-
if err != nil {
513
-
return nil, fmt.Errorf("failed to query reference_links: %w", err)
514
-
}
515
-
for commentAt, references := range allReferencs {
516
-
if comment, ok := commentMap[commentAt.String()]; ok {
517
-
comment.References = references
518
-
}
519
-
}
520
-
521
-
var comments []models.IssueComment
522
-
for _, c := range commentMap {
523
-
comments = append(comments, *c)
524
-
}
525
-
526
-
sort.Slice(comments, func(i, j int) bool {
527
-
return comments[i].Created.After(comments[j].Created)
528
-
})
529
-
530
-
return comments, nil
531
351
}
532
352
533
353
func DeleteIssues(tx *sql.Tx, did, rkey string) error {
+6
-121
appview/db/pulls.go
+6
-121
appview/db/pulls.go
···
447
447
return nil, err
448
448
}
449
449
450
-
// Get comments for all submissions using GetPullComments
450
+
// Get comments for all submissions using GetComments
451
451
submissionIds := slices.Collect(maps.Keys(submissionMap))
452
-
comments, err := GetPullComments(e, orm.FilterIn("submission_id", submissionIds))
452
+
comments, err := GetComments(e, orm.FilterIn("pull_submission_id", submissionIds))
453
453
if err != nil {
454
454
return nil, fmt.Errorf("failed to get pull comments: %w", err)
455
455
}
456
456
for _, comment := range comments {
457
-
if submission, ok := submissionMap[comment.SubmissionId]; ok {
458
-
submission.Comments = append(submission.Comments, comment)
457
+
if comment.PullSubmissionId != nil {
458
+
if submission, ok := submissionMap[*comment.PullSubmissionId]; ok {
459
+
submission.Comments = append(submission.Comments, comment)
460
+
}
459
461
}
460
462
}
461
463
···
475
477
return m, nil
476
478
}
477
479
478
-
func GetPullComments(e Execer, filters ...orm.Filter) ([]models.PullComment, error) {
479
-
var conditions []string
480
-
var args []any
481
-
for _, filter := range filters {
482
-
conditions = append(conditions, filter.Condition())
483
-
args = append(args, filter.Arg()...)
484
-
}
485
-
486
-
whereClause := ""
487
-
if conditions != nil {
488
-
whereClause = " where " + strings.Join(conditions, " and ")
489
-
}
490
-
491
-
query := fmt.Sprintf(`
492
-
select
493
-
id,
494
-
pull_id,
495
-
submission_id,
496
-
repo_at,
497
-
owner_did,
498
-
comment_at,
499
-
body,
500
-
created
501
-
from
502
-
pull_comments
503
-
%s
504
-
order by
505
-
created asc
506
-
`, whereClause)
507
-
508
-
rows, err := e.Query(query, args...)
509
-
if err != nil {
510
-
return nil, err
511
-
}
512
-
defer rows.Close()
513
-
514
-
commentMap := make(map[string]*models.PullComment)
515
-
for rows.Next() {
516
-
var comment models.PullComment
517
-
var createdAt string
518
-
err := rows.Scan(
519
-
&comment.ID,
520
-
&comment.PullId,
521
-
&comment.SubmissionId,
522
-
&comment.RepoAt,
523
-
&comment.OwnerDid,
524
-
&comment.CommentAt,
525
-
&comment.Body,
526
-
&createdAt,
527
-
)
528
-
if err != nil {
529
-
return nil, err
530
-
}
531
-
532
-
if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
533
-
comment.Created = t
534
-
}
535
-
536
-
atUri := comment.AtUri().String()
537
-
commentMap[atUri] = &comment
538
-
}
539
-
540
-
if err := rows.Err(); err != nil {
541
-
return nil, err
542
-
}
543
-
544
-
// collect references for each comments
545
-
commentAts := slices.Collect(maps.Keys(commentMap))
546
-
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
547
-
if err != nil {
548
-
return nil, fmt.Errorf("failed to query reference_links: %w", err)
549
-
}
550
-
for commentAt, references := range allReferencs {
551
-
if comment, ok := commentMap[commentAt.String()]; ok {
552
-
comment.References = references
553
-
}
554
-
}
555
-
556
-
var comments []models.PullComment
557
-
for _, c := range commentMap {
558
-
comments = append(comments, *c)
559
-
}
560
-
561
-
sort.Slice(comments, func(i, j int) bool {
562
-
return comments[i].Created.Before(comments[j].Created)
563
-
})
564
-
565
-
return comments, nil
566
-
}
567
-
568
480
// timeframe here is directly passed into the sql query filter, and any
569
481
// timeframe in the past should be negative; e.g.: "-3 months"
570
482
func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) {
···
639
551
}
640
552
641
553
return pulls, nil
642
-
}
643
-
644
-
func NewPullComment(tx *sql.Tx, comment *models.PullComment) (int64, error) {
645
-
query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)`
646
-
res, err := tx.Exec(
647
-
query,
648
-
comment.OwnerDid,
649
-
comment.RepoAt,
650
-
comment.SubmissionId,
651
-
comment.CommentAt,
652
-
comment.PullId,
653
-
comment.Body,
654
-
)
655
-
if err != nil {
656
-
return 0, err
657
-
}
658
-
659
-
i, err := res.LastInsertId()
660
-
if err != nil {
661
-
return 0, err
662
-
}
663
-
664
-
if err := putReferences(tx, comment.AtUri(), comment.References); err != nil {
665
-
return 0, fmt.Errorf("put reference_links: %w", err)
666
-
}
667
-
668
-
return i, nil
669
554
}
670
555
671
556
func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error {
+20
-32
appview/db/reference.go
+20
-32
appview/db/reference.go
···
11
11
"tangled.org/core/orm"
12
12
)
13
13
14
-
// ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs.
14
+
// ValidateReferenceLinks resolves refLinks to Issue/PR/Comment ATURIs.
15
15
// It will ignore missing refLinks.
16
16
func ValidateReferenceLinks(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) {
17
17
var (
···
53
53
values %s
54
54
)
55
55
select
56
-
i.did, i.rkey,
57
-
c.did, c.rkey
56
+
i.at_uri, c.at_uri
58
57
from input inp
59
58
join repos r
60
59
on r.did = inp.owner_did
···
62
61
join issues i
63
62
on i.repo_at = r.at_uri
64
63
and i.issue_id = inp.issue_id
65
-
left join issue_comments c
64
+
left join comments c
66
65
on inp.comment_id is not null
67
-
and c.issue_at = i.at_uri
66
+
and c.subject_at = i.at_uri
68
67
and c.id = inp.comment_id
69
68
`,
70
69
strings.Join(vals, ","),
···
79
78
80
79
for rows.Next() {
81
80
// Scan rows
82
-
var issueOwner, issueRkey string
83
-
var commentOwner, commentRkey sql.NullString
81
+
var issueUri string
82
+
var commentUri sql.NullString
84
83
var uri syntax.ATURI
85
-
if err := rows.Scan(&issueOwner, &issueRkey, &commentOwner, &commentRkey); err != nil {
84
+
if err := rows.Scan(&issueUri, &commentUri); err != nil {
86
85
return nil, err
87
86
}
88
-
if commentOwner.Valid && commentRkey.Valid {
89
-
uri = syntax.ATURI(fmt.Sprintf(
90
-
"at://%s/%s/%s",
91
-
commentOwner.String,
92
-
tangled.RepoIssueCommentNSID,
93
-
commentRkey.String,
94
-
))
87
+
if commentUri.Valid {
88
+
uri = syntax.ATURI(commentUri.String)
95
89
} else {
96
-
uri = syntax.ATURI(fmt.Sprintf(
97
-
"at://%s/%s/%s",
98
-
issueOwner,
99
-
tangled.RepoIssueNSID,
100
-
issueRkey,
101
-
))
90
+
uri = syntax.ATURI(issueUri)
102
91
}
103
92
uris = append(uris, uri)
104
93
}
···
124
113
values %s
125
114
)
126
115
select
127
-
p.owner_did, p.rkey,
128
-
c.comment_at
116
+
p.owner_did, p.rkey, c.at_uri
129
117
from input inp
130
118
join repos r
131
119
on r.did = inp.owner_did
···
133
121
join pulls p
134
122
on p.repo_at = r.at_uri
135
123
and p.pull_id = inp.pull_id
136
-
left join pull_comments c
124
+
left join comments c
137
125
on inp.comment_id is not null
138
-
and c.repo_at = r.at_uri and c.pull_id = p.pull_id
126
+
and c.subject_at = ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey)
139
127
and c.id = inp.comment_id
140
128
`,
141
129
strings.Join(vals, ","),
···
283
271
return nil, fmt.Errorf("get issue backlinks: %w", err)
284
272
}
285
273
backlinks = append(backlinks, ls...)
286
-
ls, err = getIssueCommentBacklinks(e, backlinksMap[tangled.RepoIssueCommentNSID])
274
+
ls, err = getIssueCommentBacklinks(e, backlinksMap[tangled.CommentNSID])
287
275
if err != nil {
288
276
return nil, fmt.Errorf("get issue_comment backlinks: %w", err)
289
277
}
···
293
281
return nil, fmt.Errorf("get pull backlinks: %w", err)
294
282
}
295
283
backlinks = append(backlinks, ls...)
296
-
ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.RepoPullCommentNSID])
284
+
ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.CommentNSID])
297
285
if err != nil {
298
286
return nil, fmt.Errorf("get pull_comment backlinks: %w", err)
299
287
}
···
352
340
rows, err := e.Query(
353
341
fmt.Sprintf(
354
342
`select r.did, r.name, i.issue_id, c.id, i.title, i.open
355
-
from issue_comments c
343
+
from comments c
356
344
join issues i
357
-
on i.at_uri = c.issue_at
345
+
on i.at_uri = c.subject_at
358
346
join repos r
359
347
on r.at_uri = i.repo_at
360
348
where %s`,
···
428
416
if len(aturis) == 0 {
429
417
return nil, nil
430
418
}
431
-
filter := orm.FilterIn("c.comment_at", aturis)
419
+
filter := orm.FilterIn("c.at_uri", aturis)
432
420
rows, err := e.Query(
433
421
fmt.Sprintf(
434
422
`select r.did, r.name, p.pull_id, c.id, p.title, p.state
435
423
from repos r
436
424
join pulls p
437
425
on r.at_uri = p.repo_at
438
-
join pull_comments c
439
-
on r.at_uri = c.repo_at and p.pull_id = c.pull_id
426
+
join comments c
427
+
on ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey) = c.subject_at
440
428
where %s`,
441
429
filter.Condition(),
442
430
),
+19
-11
appview/ingester.go
+19
-11
appview/ingester.go
···
79
79
err = i.ingestString(e)
80
80
case tangled.RepoIssueNSID:
81
81
err = i.ingestIssue(ctx, e)
82
-
case tangled.RepoIssueCommentNSID:
83
-
err = i.ingestIssueComment(e)
82
+
case tangled.CommentNSID:
83
+
err = i.ingestComment(e)
84
84
case tangled.LabelDefinitionNSID:
85
85
err = i.ingestLabelDefinition(e)
86
86
case tangled.LabelOpNSID:
···
868
868
return nil
869
869
}
870
870
871
-
func (i *Ingester) ingestIssueComment(e *jmodels.Event) error {
871
+
func (i *Ingester) ingestComment(e *jmodels.Event) error {
872
872
did := e.Did
873
873
rkey := e.Commit.RKey
874
874
875
875
var err error
876
876
877
-
l := i.Logger.With("handler", "ingestIssueComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
877
+
l := i.Logger.With("handler", "ingestComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
878
878
l.Info("ingesting record")
879
879
880
880
ddb, ok := i.Db.Execer.(*db.DB)
···
885
885
switch e.Commit.Operation {
886
886
case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
887
887
raw := json.RawMessage(e.Commit.Record)
888
-
record := tangled.RepoIssueComment{}
888
+
record := tangled.Comment{}
889
889
err = json.Unmarshal(raw, &record)
890
890
if err != nil {
891
891
return fmt.Errorf("invalid record: %w", err)
892
892
}
893
893
894
-
comment, err := models.IssueCommentFromRecord(did, rkey, record)
894
+
comment, err := models.CommentFromRecord(did, rkey, record)
895
895
if err != nil {
896
896
return fmt.Errorf("failed to parse comment from record: %w", err)
897
897
}
898
898
899
-
if err := i.Validator.ValidateIssueComment(comment); err != nil {
899
+
// TODO: ingest pull comments
900
+
// we aren't ingesting pull comments yet because pull itself isn't fully atprotated.
901
+
// so we cannot know which round this comment is pointing to
902
+
if comment.Subject.Collection().String() == tangled.RepoPullNSID {
903
+
l.Info("skip ingesting pull comments")
904
+
return nil
905
+
}
906
+
907
+
if err := comment.Validate(); err != nil {
900
908
return fmt.Errorf("failed to validate comment: %w", err)
901
909
}
902
910
···
906
914
}
907
915
defer tx.Rollback()
908
916
909
-
_, err = db.AddIssueComment(tx, *comment)
917
+
err = db.PutComment(tx, comment)
910
918
if err != nil {
911
-
return fmt.Errorf("failed to create issue comment: %w", err)
919
+
return fmt.Errorf("failed to create comment: %w", err)
912
920
}
913
921
914
922
return tx.Commit()
915
923
916
924
case jmodels.CommitOperationDelete:
917
-
if err := db.DeleteIssueComments(
925
+
if err := db.DeleteComments(
918
926
ddb,
919
927
orm.FilterEq("did", did),
920
928
orm.FilterEq("rkey", rkey),
921
929
); err != nil {
922
-
return fmt.Errorf("failed to delete issue comment record: %w", err)
930
+
return fmt.Errorf("failed to delete comment record: %w", err)
923
931
}
924
932
925
933
return nil
+59
-57
appview/issues/issues.go
+59
-57
appview/issues/issues.go
···
81
81
82
82
func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
83
83
l := rp.logger.With("handler", "RepoSingleIssue")
84
-
user := rp.oauth.GetMultiAccountUser(r)
84
+
user := rp.oauth.GetUser(r)
85
85
f, err := rp.repoResolver.Resolve(r)
86
86
if err != nil {
87
87
l.Error("failed to get repo and knot", "err", err)
···
102
102
103
103
userReactions := map[models.ReactionKind]bool{}
104
104
if user != nil {
105
-
userReactions = db.GetReactionStatusMap(rp.db, user.Active.Did, issue.AtUri())
105
+
userReactions = db.GetReactionStatusMap(rp.db, user.Did, issue.AtUri())
106
106
}
107
107
108
108
backlinks, err := db.GetBacklinks(rp.db, issue.AtUri())
···
143
143
144
144
func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) {
145
145
l := rp.logger.With("handler", "EditIssue")
146
-
user := rp.oauth.GetMultiAccountUser(r)
146
+
user := rp.oauth.GetUser(r)
147
147
148
148
issue, ok := r.Context().Value("issue").(*models.Issue)
149
149
if !ok {
···
182
182
return
183
183
}
184
184
185
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueNSID, user.Active.Did, newIssue.Rkey)
185
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueNSID, user.Did, newIssue.Rkey)
186
186
if err != nil {
187
187
l.Error("failed to get record", "err", err)
188
188
rp.pages.Notice(w, noticeId, "Failed to edit issue, no record found on PDS.")
···
191
191
192
192
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
193
193
Collection: tangled.RepoIssueNSID,
194
-
Repo: user.Active.Did,
194
+
Repo: user.Did,
195
195
Rkey: newIssue.Rkey,
196
196
SwapRecord: ex.Cid,
197
197
Record: &lexutil.LexiconTypeDecoder{
···
292
292
293
293
func (rp *Issues) CloseIssue(w http.ResponseWriter, r *http.Request) {
294
294
l := rp.logger.With("handler", "CloseIssue")
295
-
user := rp.oauth.GetMultiAccountUser(r)
295
+
user := rp.oauth.GetUser(r)
296
296
f, err := rp.repoResolver.Resolve(r)
297
297
if err != nil {
298
298
l.Error("failed to get repo and knot", "err", err)
···
306
306
return
307
307
}
308
308
309
-
roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
309
+
roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
310
310
isRepoOwner := roles.IsOwner()
311
311
isCollaborator := roles.IsCollaborator()
312
-
isIssueOwner := user.Active.Did == issue.Did
312
+
isIssueOwner := user.Did == issue.Did
313
313
314
314
// TODO: make this more granular
315
315
if isIssueOwner || isRepoOwner || isCollaborator {
···
326
326
issue.Open = false
327
327
328
328
// notify about the issue closure
329
-
rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Active.Did), issue)
329
+
rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Did), issue)
330
330
331
331
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
332
332
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
···
340
340
341
341
func (rp *Issues) ReopenIssue(w http.ResponseWriter, r *http.Request) {
342
342
l := rp.logger.With("handler", "ReopenIssue")
343
-
user := rp.oauth.GetMultiAccountUser(r)
343
+
user := rp.oauth.GetUser(r)
344
344
f, err := rp.repoResolver.Resolve(r)
345
345
if err != nil {
346
346
l.Error("failed to get repo and knot", "err", err)
···
354
354
return
355
355
}
356
356
357
-
roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
357
+
roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
358
358
isRepoOwner := roles.IsOwner()
359
359
isCollaborator := roles.IsCollaborator()
360
-
isIssueOwner := user.Active.Did == issue.Did
360
+
isIssueOwner := user.Did == issue.Did
361
361
362
362
if isCollaborator || isRepoOwner || isIssueOwner {
363
363
err := db.ReopenIssues(
···
373
373
issue.Open = true
374
374
375
375
// notify about the issue reopen
376
-
rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Active.Did), issue)
376
+
rp.notifier.NewIssueState(r.Context(), syntax.DID(user.Did), issue)
377
377
378
378
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
379
379
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", ownerSlashRepo, issue.IssueId))
···
387
387
388
388
func (rp *Issues) NewIssueComment(w http.ResponseWriter, r *http.Request) {
389
389
l := rp.logger.With("handler", "NewIssueComment")
390
-
user := rp.oauth.GetMultiAccountUser(r)
390
+
user := rp.oauth.GetUser(r)
391
391
f, err := rp.repoResolver.Resolve(r)
392
392
if err != nil {
393
393
l.Error("failed to get repo and knot", "err", err)
···
403
403
404
404
body := r.FormValue("body")
405
405
if body == "" {
406
-
rp.pages.Notice(w, "issue", "Body is required")
406
+
rp.pages.Notice(w, "issue-comment", "Body is required")
407
407
return
408
408
}
409
409
410
-
replyToUri := r.FormValue("reply-to")
411
-
var replyTo *string
412
-
if replyToUri != "" {
413
-
replyTo = &replyToUri
410
+
var replyTo *syntax.ATURI
411
+
replyToRaw := r.FormValue("reply-to")
412
+
if replyToRaw != "" {
413
+
aturi, err := syntax.ParseATURI(r.FormValue("reply-to"))
414
+
if err != nil {
415
+
rp.pages.Notice(w, "issue-comment", "reply-to should be valid AT-URI")
416
+
return
417
+
}
418
+
replyTo = &aturi
414
419
}
415
420
416
421
mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
417
422
418
-
comment := models.IssueComment{
419
-
Did: user.Active.Did,
423
+
comment := models.Comment{
424
+
Did: syntax.DID(user.Did),
420
425
Rkey: tid.TID(),
421
-
IssueAt: issue.AtUri().String(),
426
+
Subject: issue.AtUri(),
422
427
ReplyTo: replyTo,
423
428
Body: body,
424
429
Created: time.Now(),
425
430
Mentions: mentions,
426
431
References: references,
427
432
}
428
-
if err = rp.validator.ValidateIssueComment(&comment); err != nil {
433
+
if err = comment.Validate(); err != nil {
429
434
l.Error("failed to validate comment", "err", err)
430
435
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
431
436
return
···
441
446
442
447
// create a record first
443
448
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
444
-
Collection: tangled.RepoIssueCommentNSID,
445
-
Repo: comment.Did,
449
+
Collection: tangled.CommentNSID,
450
+
Repo: user.Did,
446
451
Rkey: comment.Rkey,
447
452
Record: &lexutil.LexiconTypeDecoder{
448
453
Val: &record,
···
468
473
}
469
474
defer tx.Rollback()
470
475
471
-
commentId, err := db.AddIssueComment(tx, comment)
476
+
err = db.PutComment(tx, &comment)
472
477
if err != nil {
473
478
l.Error("failed to create comment", "err", err)
474
479
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
···
484
489
// reset atUri to make rollback a no-op
485
490
atUri = ""
486
491
487
-
// notify about the new comment
488
-
comment.Id = commentId
489
-
490
-
rp.notifier.NewIssueComment(r.Context(), &comment, mentions)
492
+
rp.notifier.NewComment(r.Context(), &comment)
491
493
492
494
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
493
-
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, commentId))
495
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, comment.Id))
494
496
}
495
497
496
498
func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) {
497
499
l := rp.logger.With("handler", "IssueComment")
498
-
user := rp.oauth.GetMultiAccountUser(r)
500
+
user := rp.oauth.GetUser(r)
499
501
500
502
issue, ok := r.Context().Value("issue").(*models.Issue)
501
503
if !ok {
···
505
507
}
506
508
507
509
commentId := chi.URLParam(r, "commentId")
508
-
comments, err := db.GetIssueComments(
510
+
comments, err := db.GetComments(
509
511
rp.db,
510
512
orm.FilterEq("id", commentId),
511
513
)
···
531
533
532
534
func (rp *Issues) EditIssueComment(w http.ResponseWriter, r *http.Request) {
533
535
l := rp.logger.With("handler", "EditIssueComment")
534
-
user := rp.oauth.GetMultiAccountUser(r)
536
+
user := rp.oauth.GetUser(r)
535
537
536
538
issue, ok := r.Context().Value("issue").(*models.Issue)
537
539
if !ok {
···
541
543
}
542
544
543
545
commentId := chi.URLParam(r, "commentId")
544
-
comments, err := db.GetIssueComments(
546
+
comments, err := db.GetComments(
545
547
rp.db,
546
548
orm.FilterEq("id", commentId),
547
549
)
···
557
559
}
558
560
comment := comments[0]
559
561
560
-
if comment.Did != user.Active.Did {
561
-
l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Active.Did)
562
+
if comment.Did.String() != user.Did {
563
+
l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Did)
562
564
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
563
565
return
564
566
}
···
597
599
}
598
600
defer tx.Rollback()
599
601
600
-
_, err = db.AddIssueComment(tx, newComment)
602
+
err = db.PutComment(tx, &newComment)
601
603
if err != nil {
602
604
l.Error("failed to perferom update-description query", "err", err)
603
605
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
···
608
610
// rkey is optional, it was introduced later
609
611
if newComment.Rkey != "" {
610
612
// update the record on pds
611
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Active.Did, comment.Rkey)
613
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.CommentNSID, user.Did, comment.Rkey)
612
614
if err != nil {
613
615
l.Error("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)
614
616
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
···
616
618
}
617
619
618
620
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
619
-
Collection: tangled.RepoIssueCommentNSID,
620
-
Repo: user.Active.Did,
621
+
Collection: tangled.CommentNSID,
622
+
Repo: user.Did,
621
623
Rkey: newComment.Rkey,
622
624
SwapRecord: ex.Cid,
623
625
Record: &lexutil.LexiconTypeDecoder{
···
641
643
642
644
func (rp *Issues) ReplyIssueCommentPlaceholder(w http.ResponseWriter, r *http.Request) {
643
645
l := rp.logger.With("handler", "ReplyIssueCommentPlaceholder")
644
-
user := rp.oauth.GetMultiAccountUser(r)
646
+
user := rp.oauth.GetUser(r)
645
647
646
648
issue, ok := r.Context().Value("issue").(*models.Issue)
647
649
if !ok {
···
651
653
}
652
654
653
655
commentId := chi.URLParam(r, "commentId")
654
-
comments, err := db.GetIssueComments(
656
+
comments, err := db.GetComments(
655
657
rp.db,
656
658
orm.FilterEq("id", commentId),
657
659
)
···
677
679
678
680
func (rp *Issues) ReplyIssueComment(w http.ResponseWriter, r *http.Request) {
679
681
l := rp.logger.With("handler", "ReplyIssueComment")
680
-
user := rp.oauth.GetMultiAccountUser(r)
682
+
user := rp.oauth.GetUser(r)
681
683
682
684
issue, ok := r.Context().Value("issue").(*models.Issue)
683
685
if !ok {
···
687
689
}
688
690
689
691
commentId := chi.URLParam(r, "commentId")
690
-
comments, err := db.GetIssueComments(
692
+
comments, err := db.GetComments(
691
693
rp.db,
692
694
orm.FilterEq("id", commentId),
693
695
)
···
713
715
714
716
func (rp *Issues) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
715
717
l := rp.logger.With("handler", "DeleteIssueComment")
716
-
user := rp.oauth.GetMultiAccountUser(r)
718
+
user := rp.oauth.GetUser(r)
717
719
718
720
issue, ok := r.Context().Value("issue").(*models.Issue)
719
721
if !ok {
···
723
725
}
724
726
725
727
commentId := chi.URLParam(r, "commentId")
726
-
comments, err := db.GetIssueComments(
728
+
comments, err := db.GetComments(
727
729
rp.db,
728
730
orm.FilterEq("id", commentId),
729
731
)
···
739
741
}
740
742
comment := comments[0]
741
743
742
-
if comment.Did != user.Active.Did {
743
-
l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Active.Did)
744
+
if comment.Did.String() != user.Did {
745
+
l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Did)
744
746
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
745
747
return
746
748
}
···
752
754
753
755
// optimistic deletion
754
756
deleted := time.Now()
755
-
err = db.DeleteIssueComments(rp.db, orm.FilterEq("id", comment.Id))
757
+
err = db.DeleteComments(rp.db, orm.FilterEq("id", comment.Id))
756
758
if err != nil {
757
759
l.Error("failed to delete comment", "err", err)
758
760
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
···
768
770
return
769
771
}
770
772
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
771
-
Collection: tangled.RepoIssueCommentNSID,
772
-
Repo: user.Active.Did,
773
+
Collection: tangled.CommentNSID,
774
+
Repo: user.Did,
773
775
Rkey: comment.Rkey,
774
776
})
775
777
if err != nil {
···
807
809
808
810
page := pagination.FromContext(r.Context())
809
811
810
-
user := rp.oauth.GetMultiAccountUser(r)
812
+
user := rp.oauth.GetUser(r)
811
813
f, err := rp.repoResolver.Resolve(r)
812
814
if err != nil {
813
815
l.Error("failed to get repo and knot", "err", err)
···
884
886
}
885
887
886
888
rp.pages.RepoIssues(w, pages.RepoIssuesParams{
887
-
LoggedInUser: rp.oauth.GetMultiAccountUser(r),
889
+
LoggedInUser: rp.oauth.GetUser(r),
888
890
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
889
891
Issues: issues,
890
892
IssueCount: totalIssues,
···
897
899
898
900
func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) {
899
901
l := rp.logger.With("handler", "NewIssue")
900
-
user := rp.oauth.GetMultiAccountUser(r)
902
+
user := rp.oauth.GetUser(r)
901
903
902
904
f, err := rp.repoResolver.Resolve(r)
903
905
if err != nil {
···
921
923
Title: r.FormValue("title"),
922
924
Body: body,
923
925
Open: true,
924
-
Did: user.Active.Did,
926
+
Did: user.Did,
925
927
Created: time.Now(),
926
928
Mentions: mentions,
927
929
References: references,
···
945
947
}
946
948
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
947
949
Collection: tangled.RepoIssueNSID,
948
-
Repo: user.Active.Did,
950
+
Repo: user.Did,
949
951
Rkey: issue.Rkey,
950
952
Record: &lexutil.LexiconTypeDecoder{
951
953
Val: &record,
+31
-31
appview/knots/knots.go
+31
-31
appview/knots/knots.go
···
70
70
}
71
71
72
72
func (k *Knots) knots(w http.ResponseWriter, r *http.Request) {
73
-
user := k.OAuth.GetMultiAccountUser(r)
73
+
user := k.OAuth.GetUser(r)
74
74
registrations, err := db.GetRegistrations(
75
75
k.Db,
76
-
orm.FilterEq("did", user.Active.Did),
76
+
orm.FilterEq("did", user.Did),
77
77
)
78
78
if err != nil {
79
79
k.Logger.Error("failed to fetch knot registrations", "err", err)
···
92
92
func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) {
93
93
l := k.Logger.With("handler", "dashboard")
94
94
95
-
user := k.OAuth.GetMultiAccountUser(r)
96
-
l = l.With("user", user.Active.Did)
95
+
user := k.OAuth.GetUser(r)
96
+
l = l.With("user", user.Did)
97
97
98
98
domain := chi.URLParam(r, "domain")
99
99
if domain == "" {
···
103
103
104
104
registrations, err := db.GetRegistrations(
105
105
k.Db,
106
-
orm.FilterEq("did", user.Active.Did),
106
+
orm.FilterEq("did", user.Did),
107
107
orm.FilterEq("domain", domain),
108
108
)
109
109
if err != nil {
···
154
154
}
155
155
156
156
func (k *Knots) register(w http.ResponseWriter, r *http.Request) {
157
-
user := k.OAuth.GetMultiAccountUser(r)
157
+
user := k.OAuth.GetUser(r)
158
158
l := k.Logger.With("handler", "register")
159
159
160
160
noticeId := "register-error"
···
175
175
return
176
176
}
177
177
l = l.With("domain", domain)
178
-
l = l.With("user", user.Active.Did)
178
+
l = l.With("user", user.Did)
179
179
180
180
tx, err := k.Db.Begin()
181
181
if err != nil {
···
188
188
k.Enforcer.E.LoadPolicy()
189
189
}()
190
190
191
-
err = db.AddKnot(tx, domain, user.Active.Did)
191
+
err = db.AddKnot(tx, domain, user.Did)
192
192
if err != nil {
193
193
l.Error("failed to insert", "err", err)
194
194
fail()
···
210
210
return
211
211
}
212
212
213
-
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Active.Did, domain)
213
+
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Did, domain)
214
214
var exCid *string
215
215
if ex != nil {
216
216
exCid = ex.Cid
···
219
219
// re-announce by registering under same rkey
220
220
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
221
221
Collection: tangled.KnotNSID,
222
-
Repo: user.Active.Did,
222
+
Repo: user.Did,
223
223
Rkey: domain,
224
224
Record: &lexutil.LexiconTypeDecoder{
225
225
Val: &tangled.Knot{
···
250
250
}
251
251
252
252
// begin verification
253
-
err = serververify.RunVerification(r.Context(), domain, user.Active.Did, k.Config.Core.Dev)
253
+
err = serververify.RunVerification(r.Context(), domain, user.Did, k.Config.Core.Dev)
254
254
if err != nil {
255
255
l.Error("verification failed", "err", err)
256
256
k.Pages.HxRefresh(w)
257
257
return
258
258
}
259
259
260
-
err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Active.Did)
260
+
err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Did)
261
261
if err != nil {
262
262
l.Error("failed to mark verified", "err", err)
263
263
k.Pages.HxRefresh(w)
···
275
275
}
276
276
277
277
func (k *Knots) delete(w http.ResponseWriter, r *http.Request) {
278
-
user := k.OAuth.GetMultiAccountUser(r)
278
+
user := k.OAuth.GetUser(r)
279
279
l := k.Logger.With("handler", "delete")
280
280
281
281
noticeId := "operation-error"
···
294
294
// get record from db first
295
295
registrations, err := db.GetRegistrations(
296
296
k.Db,
297
-
orm.FilterEq("did", user.Active.Did),
297
+
orm.FilterEq("did", user.Did),
298
298
orm.FilterEq("domain", domain),
299
299
)
300
300
if err != nil {
···
322
322
323
323
err = db.DeleteKnot(
324
324
tx,
325
-
orm.FilterEq("did", user.Active.Did),
325
+
orm.FilterEq("did", user.Did),
326
326
orm.FilterEq("domain", domain),
327
327
)
328
328
if err != nil {
···
350
350
351
351
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
352
352
Collection: tangled.KnotNSID,
353
-
Repo: user.Active.Did,
353
+
Repo: user.Did,
354
354
Rkey: domain,
355
355
})
356
356
if err != nil {
···
382
382
}
383
383
384
384
func (k *Knots) retry(w http.ResponseWriter, r *http.Request) {
385
-
user := k.OAuth.GetMultiAccountUser(r)
385
+
user := k.OAuth.GetUser(r)
386
386
l := k.Logger.With("handler", "retry")
387
387
388
388
noticeId := "operation-error"
···
398
398
return
399
399
}
400
400
l = l.With("domain", domain)
401
-
l = l.With("user", user.Active.Did)
401
+
l = l.With("user", user.Did)
402
402
403
403
// get record from db first
404
404
registrations, err := db.GetRegistrations(
405
405
k.Db,
406
-
orm.FilterEq("did", user.Active.Did),
406
+
orm.FilterEq("did", user.Did),
407
407
orm.FilterEq("domain", domain),
408
408
)
409
409
if err != nil {
···
419
419
registration := registrations[0]
420
420
421
421
// begin verification
422
-
err = serververify.RunVerification(r.Context(), domain, user.Active.Did, k.Config.Core.Dev)
422
+
err = serververify.RunVerification(r.Context(), domain, user.Did, k.Config.Core.Dev)
423
423
if err != nil {
424
424
l.Error("verification failed", "err", err)
425
425
···
437
437
return
438
438
}
439
439
440
-
err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Active.Did)
440
+
err = serververify.MarkKnotVerified(k.Db, k.Enforcer, domain, user.Did)
441
441
if err != nil {
442
442
l.Error("failed to mark verified", "err", err)
443
443
k.Pages.Notice(w, noticeId, err.Error())
···
456
456
return
457
457
}
458
458
459
-
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Active.Did, domain)
459
+
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.KnotNSID, user.Did, domain)
460
460
var exCid *string
461
461
if ex != nil {
462
462
exCid = ex.Cid
···
465
465
// ignore the error here
466
466
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
467
467
Collection: tangled.KnotNSID,
468
-
Repo: user.Active.Did,
468
+
Repo: user.Did,
469
469
Rkey: domain,
470
470
Record: &lexutil.LexiconTypeDecoder{
471
471
Val: &tangled.Knot{
···
494
494
// Get updated registration to show
495
495
registrations, err = db.GetRegistrations(
496
496
k.Db,
497
-
orm.FilterEq("did", user.Active.Did),
497
+
orm.FilterEq("did", user.Did),
498
498
orm.FilterEq("domain", domain),
499
499
)
500
500
if err != nil {
···
516
516
}
517
517
518
518
func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) {
519
-
user := k.OAuth.GetMultiAccountUser(r)
519
+
user := k.OAuth.GetUser(r)
520
520
l := k.Logger.With("handler", "addMember")
521
521
522
522
domain := chi.URLParam(r, "domain")
···
526
526
return
527
527
}
528
528
l = l.With("domain", domain)
529
-
l = l.With("user", user.Active.Did)
529
+
l = l.With("user", user.Did)
530
530
531
531
registrations, err := db.GetRegistrations(
532
532
k.Db,
533
-
orm.FilterEq("did", user.Active.Did),
533
+
orm.FilterEq("did", user.Did),
534
534
orm.FilterEq("domain", domain),
535
535
orm.FilterIsNot("registered", "null"),
536
536
)
···
583
583
584
584
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
585
585
Collection: tangled.KnotMemberNSID,
586
-
Repo: user.Active.Did,
586
+
Repo: user.Did,
587
587
Rkey: rkey,
588
588
Record: &lexutil.LexiconTypeDecoder{
589
589
Val: &tangled.KnotMember{
···
618
618
}
619
619
620
620
func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) {
621
-
user := k.OAuth.GetMultiAccountUser(r)
621
+
user := k.OAuth.GetUser(r)
622
622
l := k.Logger.With("handler", "removeMember")
623
623
624
624
noticeId := "operation-error"
···
634
634
return
635
635
}
636
636
l = l.With("domain", domain)
637
-
l = l.With("user", user.Active.Did)
637
+
l = l.With("user", user.Did)
638
638
639
639
registrations, err := db.GetRegistrations(
640
640
k.Db,
641
-
orm.FilterEq("did", user.Active.Did),
641
+
orm.FilterEq("did", user.Did),
642
642
orm.FilterEq("domain", domain),
643
643
orm.FilterIsNot("registered", "null"),
644
644
)
+2
-2
appview/labels/labels.go
+2
-2
appview/labels/labels.go
···
68
68
// - this handler should calculate the diff in order to create the labelop record
69
69
// - we need the diff in order to maintain a "history" of operations performed by users
70
70
func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) {
71
-
user := l.oauth.GetMultiAccountUser(r)
71
+
user := l.oauth.GetUser(r)
72
72
73
73
noticeId := "add-label-error"
74
74
···
82
82
return
83
83
}
84
84
85
-
did := user.Active.Did
85
+
did := user.Did
86
86
rkey := tid.TID()
87
87
performedAt := time.Now()
88
88
indexedAt := time.Now()
+8
-6
appview/middleware/middleware.go
+8
-6
appview/middleware/middleware.go
···
115
115
return func(next http.Handler) http.Handler {
116
116
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
117
117
// requires auth also
118
-
actor := mw.oauth.GetMultiAccountUser(r)
118
+
actor := mw.oauth.GetUser(r)
119
119
if actor == nil {
120
120
// we need a logged in user
121
121
log.Printf("not logged in, redirecting")
···
128
128
return
129
129
}
130
130
131
-
ok, err := mw.enforcer.E.HasGroupingPolicy(actor.Active.Did, group, domain)
131
+
ok, err := mw.enforcer.E.HasGroupingPolicy(actor.Did, group, domain)
132
132
if err != nil || !ok {
133
-
log.Printf("%s does not have perms of a %s in domain %s", actor.Active.Did, group, domain)
133
+
// we need a logged in user
134
+
log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain)
134
135
http.Error(w, "Forbiden", http.StatusUnauthorized)
135
136
return
136
137
}
···
148
149
return func(next http.Handler) http.Handler {
149
150
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
150
151
// requires auth also
151
-
actor := mw.oauth.GetMultiAccountUser(r)
152
+
actor := mw.oauth.GetUser(r)
152
153
if actor == nil {
153
154
// we need a logged in user
154
155
log.Printf("not logged in, redirecting")
···
161
162
return
162
163
}
163
164
164
-
ok, err := mw.enforcer.E.Enforce(actor.Active.Did, f.Knot, f.DidSlashRepo(), requiredPerm)
165
+
ok, err := mw.enforcer.E.Enforce(actor.Did, f.Knot, f.DidSlashRepo(), requiredPerm)
165
166
if err != nil || !ok {
166
-
log.Printf("%s does not have perms of a %s in repo %s", actor.Active.Did, requiredPerm, f.DidSlashRepo())
167
+
// we need a logged in user
168
+
log.Printf("%s does not have perms of a %s in repo %s", actor.Did, requiredPerm, f.DidSlashRepo())
167
169
http.Error(w, "Forbiden", http.StatusUnauthorized)
168
170
return
169
171
}
+117
appview/models/comment.go
+117
appview/models/comment.go
···
1
+
package models
2
+
3
+
import (
4
+
"fmt"
5
+
"strings"
6
+
"time"
7
+
8
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
+
"tangled.org/core/api/tangled"
10
+
)
11
+
12
+
type Comment struct {
13
+
Id int64
14
+
Did syntax.DID
15
+
Rkey string
16
+
Subject syntax.ATURI
17
+
ReplyTo *syntax.ATURI
18
+
Body string
19
+
Created time.Time
20
+
Edited *time.Time
21
+
Deleted *time.Time
22
+
Mentions []syntax.DID
23
+
References []syntax.ATURI
24
+
PullSubmissionId *int
25
+
}
26
+
27
+
func (c *Comment) AtUri() syntax.ATURI {
28
+
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", c.Did, tangled.CommentNSID, c.Rkey))
29
+
}
30
+
31
+
func (c *Comment) AsRecord() tangled.Comment {
32
+
mentions := make([]string, len(c.Mentions))
33
+
for i, did := range c.Mentions {
34
+
mentions[i] = string(did)
35
+
}
36
+
references := make([]string, len(c.References))
37
+
for i, uri := range c.References {
38
+
references[i] = string(uri)
39
+
}
40
+
var replyTo *string
41
+
if c.ReplyTo != nil {
42
+
replyToStr := c.ReplyTo.String()
43
+
replyTo = &replyToStr
44
+
}
45
+
return tangled.Comment{
46
+
Subject: c.Subject.String(),
47
+
Body: c.Body,
48
+
CreatedAt: c.Created.Format(time.RFC3339),
49
+
ReplyTo: replyTo,
50
+
Mentions: mentions,
51
+
References: references,
52
+
}
53
+
}
54
+
55
+
func (c *Comment) IsTopLevel() bool {
56
+
return c.ReplyTo == nil
57
+
}
58
+
59
+
func (c *Comment) IsReply() bool {
60
+
return c.ReplyTo != nil
61
+
}
62
+
63
+
func (c *Comment) Validate() error {
64
+
// TODO: sanitize the body and then trim space
65
+
if sb := strings.TrimSpace(c.Body); sb == "" {
66
+
return fmt.Errorf("body is empty after HTML sanitization")
67
+
}
68
+
69
+
// if it's for PR, PullSubmissionId should not be nil
70
+
if c.Subject.Collection().String() == tangled.RepoPullNSID {
71
+
if c.PullSubmissionId == nil {
72
+
return fmt.Errorf("PullSubmissionId should not be nil")
73
+
}
74
+
}
75
+
return nil
76
+
}
77
+
78
+
func CommentFromRecord(did, rkey string, record tangled.Comment) (*Comment, error) {
79
+
created, err := time.Parse(time.RFC3339, record.CreatedAt)
80
+
if err != nil {
81
+
created = time.Now()
82
+
}
83
+
84
+
ownerDid := did
85
+
86
+
if _, err = syntax.ParseATURI(record.Subject); err != nil {
87
+
return nil, err
88
+
}
89
+
90
+
i := record
91
+
mentions := make([]syntax.DID, len(record.Mentions))
92
+
for i, did := range record.Mentions {
93
+
mentions[i] = syntax.DID(did)
94
+
}
95
+
references := make([]syntax.ATURI, len(record.References))
96
+
for i, uri := range i.References {
97
+
references[i] = syntax.ATURI(uri)
98
+
}
99
+
var replyTo *syntax.ATURI
100
+
if record.ReplyTo != nil {
101
+
replyToAtUri := syntax.ATURI(*record.ReplyTo)
102
+
replyTo = &replyToAtUri
103
+
}
104
+
105
+
comment := Comment{
106
+
Did: syntax.DID(ownerDid),
107
+
Rkey: rkey,
108
+
Body: record.Body,
109
+
Subject: syntax.ATURI(record.Subject),
110
+
ReplyTo: replyTo,
111
+
Created: created,
112
+
Mentions: mentions,
113
+
References: references,
114
+
}
115
+
116
+
return &comment, nil
117
+
}
+8
-89
appview/models/issue.go
+8
-89
appview/models/issue.go
···
26
26
27
27
// optionally, populate this when querying for reverse mappings
28
28
// like comment counts, parent repo etc.
29
-
Comments []IssueComment
29
+
Comments []Comment
30
30
Labels LabelState
31
31
Repo *Repo
32
32
}
···
62
62
}
63
63
64
64
type CommentListItem struct {
65
-
Self *IssueComment
66
-
Replies []*IssueComment
65
+
Self *Comment
66
+
Replies []*Comment
67
67
}
68
68
69
69
func (it *CommentListItem) Participants() []syntax.DID {
···
88
88
89
89
func (i *Issue) CommentList() []CommentListItem {
90
90
// Create a map to quickly find comments by their aturi
91
-
toplevel := make(map[string]*CommentListItem)
92
-
var replies []*IssueComment
91
+
toplevel := make(map[syntax.ATURI]*CommentListItem)
92
+
var replies []*Comment
93
93
94
94
// collect top level comments into the map
95
95
for _, comment := range i.Comments {
96
96
if comment.IsTopLevel() {
97
-
toplevel[comment.AtUri().String()] = &CommentListItem{
97
+
toplevel[comment.AtUri()] = &CommentListItem{
98
98
Self: &comment,
99
99
}
100
100
} else {
···
115
115
}
116
116
117
117
// sort everything
118
-
sortFunc := func(a, b *IssueComment) bool {
118
+
sortFunc := func(a, b *Comment) bool {
119
119
return a.Created.Before(b.Created)
120
120
}
121
121
sort.Slice(listing, func(i, j int) bool {
···
144
144
addParticipant(i.Did)
145
145
146
146
for _, c := range i.Comments {
147
-
addParticipant(c.Did)
147
+
addParticipant(c.Did.String())
148
148
}
149
149
150
150
return participants
···
171
171
Open: true, // new issues are open by default
172
172
}
173
173
}
174
-
175
-
type IssueComment struct {
176
-
Id int64
177
-
Did string
178
-
Rkey string
179
-
IssueAt string
180
-
ReplyTo *string
181
-
Body string
182
-
Created time.Time
183
-
Edited *time.Time
184
-
Deleted *time.Time
185
-
Mentions []syntax.DID
186
-
References []syntax.ATURI
187
-
}
188
-
189
-
func (i *IssueComment) AtUri() syntax.ATURI {
190
-
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
191
-
}
192
-
193
-
func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
194
-
mentions := make([]string, len(i.Mentions))
195
-
for i, did := range i.Mentions {
196
-
mentions[i] = string(did)
197
-
}
198
-
references := make([]string, len(i.References))
199
-
for i, uri := range i.References {
200
-
references[i] = string(uri)
201
-
}
202
-
return tangled.RepoIssueComment{
203
-
Body: i.Body,
204
-
Issue: i.IssueAt,
205
-
CreatedAt: i.Created.Format(time.RFC3339),
206
-
ReplyTo: i.ReplyTo,
207
-
Mentions: mentions,
208
-
References: references,
209
-
}
210
-
}
211
-
212
-
func (i *IssueComment) IsTopLevel() bool {
213
-
return i.ReplyTo == nil
214
-
}
215
-
216
-
func (i *IssueComment) IsReply() bool {
217
-
return i.ReplyTo != nil
218
-
}
219
-
220
-
func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
221
-
created, err := time.Parse(time.RFC3339, record.CreatedAt)
222
-
if err != nil {
223
-
created = time.Now()
224
-
}
225
-
226
-
ownerDid := did
227
-
228
-
if _, err = syntax.ParseATURI(record.Issue); err != nil {
229
-
return nil, err
230
-
}
231
-
232
-
i := record
233
-
mentions := make([]syntax.DID, len(record.Mentions))
234
-
for i, did := range record.Mentions {
235
-
mentions[i] = syntax.DID(did)
236
-
}
237
-
references := make([]syntax.ATURI, len(record.References))
238
-
for i, uri := range i.References {
239
-
references[i] = syntax.ATURI(uri)
240
-
}
241
-
242
-
comment := IssueComment{
243
-
Did: ownerDid,
244
-
Rkey: rkey,
245
-
Body: record.Body,
246
-
IssueAt: record.Issue,
247
-
ReplyTo: record.ReplyTo,
248
-
Created: created,
249
-
Mentions: mentions,
250
-
References: references,
251
-
}
252
-
253
-
return &comment, nil
254
-
}
+2
-46
appview/models/pull.go
+2
-46
appview/models/pull.go
···
138
138
RoundNumber int
139
139
Patch string
140
140
Combined string
141
-
Comments []PullComment
141
+
Comments []Comment
142
142
SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs
143
143
144
144
// meta
145
145
Created time.Time
146
146
}
147
-
148
-
type PullComment struct {
149
-
// ids
150
-
ID int
151
-
PullId int
152
-
SubmissionId int
153
-
154
-
// at ids
155
-
RepoAt string
156
-
OwnerDid string
157
-
CommentAt string
158
-
159
-
// content
160
-
Body string
161
-
162
-
// meta
163
-
Mentions []syntax.DID
164
-
References []syntax.ATURI
165
-
166
-
// meta
167
-
Created time.Time
168
-
}
169
-
170
-
func (p *PullComment) AtUri() syntax.ATURI {
171
-
return syntax.ATURI(p.CommentAt)
172
-
}
173
-
174
-
// func (p *PullComment) AsRecord() tangled.RepoPullComment {
175
-
// mentions := make([]string, len(p.Mentions))
176
-
// for i, did := range p.Mentions {
177
-
// mentions[i] = string(did)
178
-
// }
179
-
// references := make([]string, len(p.References))
180
-
// for i, uri := range p.References {
181
-
// references[i] = string(uri)
182
-
// }
183
-
// return tangled.RepoPullComment{
184
-
// Pull: p.PullAt,
185
-
// Body: p.Body,
186
-
// Mentions: mentions,
187
-
// References: references,
188
-
// CreatedAt: p.Created.Format(time.RFC3339),
189
-
// }
190
-
// }
191
147
192
148
func (p *Pull) LastRoundNumber() int {
193
149
return len(p.Submissions) - 1
···
289
245
addParticipant(s.PullAt.Authority().String())
290
246
291
247
for _, c := range s.Comments {
292
-
addParticipant(c.OwnerDid)
248
+
addParticipant(c.Did.String())
293
249
}
294
250
295
251
return participants
+6
-6
appview/notifications/notifications.go
+6
-6
appview/notifications/notifications.go
···
48
48
49
49
func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request) {
50
50
l := n.logger.With("handler", "notificationsPage")
51
-
user := n.oauth.GetMultiAccountUser(r)
51
+
user := n.oauth.GetUser(r)
52
52
53
53
page := pagination.FromContext(r.Context())
54
54
55
55
total, err := db.CountNotifications(
56
56
n.db,
57
-
orm.FilterEq("recipient_did", user.Active.Did),
57
+
orm.FilterEq("recipient_did", user.Did),
58
58
)
59
59
if err != nil {
60
60
l.Error("failed to get total notifications", "err", err)
···
65
65
notifications, err := db.GetNotificationsWithEntities(
66
66
n.db,
67
67
page,
68
-
orm.FilterEq("recipient_did", user.Active.Did),
68
+
orm.FilterEq("recipient_did", user.Did),
69
69
)
70
70
if err != nil {
71
71
l.Error("failed to get notifications", "err", err)
···
73
73
return
74
74
}
75
75
76
-
err = db.MarkAllNotificationsRead(n.db, user.Active.Did)
76
+
err = db.MarkAllNotificationsRead(n.db, user.Did)
77
77
if err != nil {
78
78
l.Error("failed to mark notifications as read", "err", err)
79
79
}
···
90
90
}
91
91
92
92
func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) {
93
-
user := n.oauth.GetMultiAccountUser(r)
93
+
user := n.oauth.GetUser(r)
94
94
if user == nil {
95
95
return
96
96
}
97
97
98
98
count, err := db.CountNotifications(
99
99
n.db,
100
-
orm.FilterEq("recipient_did", user.Active.Did),
100
+
orm.FilterEq("recipient_did", user.Did),
101
101
orm.FilterEq("read", 0),
102
102
)
103
103
if err != nil {
+111
-113
appview/notify/db/db.go
+111
-113
appview/notify/db/db.go
···
74
74
// no-op
75
75
}
76
76
77
-
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
78
-
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt()))
77
+
func (n *databaseNotifier) NewComment(ctx context.Context, comment *models.Comment) {
78
+
var (
79
+
// built the recipients list:
80
+
// - the owner of the repo
81
+
// - | if the comment is a reply -> everybody on that thread
82
+
// | if the comment is a top level -> just the issue owner
83
+
// - remove mentioned users from the recipients list
84
+
recipients = sets.New[syntax.DID]()
85
+
entityType string
86
+
entityId string
87
+
repoId *int64
88
+
issueId *int64
89
+
pullId *int64
90
+
)
91
+
92
+
subjectDid, err := comment.Subject.Authority().AsDID()
79
93
if err != nil {
80
-
log.Printf("failed to fetch collaborators: %v", err)
94
+
log.Printf("NewComment: expected did based at-uri for comment.subject")
81
95
return
82
96
}
97
+
switch comment.Subject.Collection() {
98
+
case tangled.RepoIssueNSID:
99
+
issues, err := db.GetIssues(
100
+
n.db,
101
+
orm.FilterEq("did", subjectDid),
102
+
orm.FilterEq("rkey", comment.Subject.RecordKey()),
103
+
)
104
+
if err != nil {
105
+
log.Printf("NewComment: failed to get issues: %v", err)
106
+
return
107
+
}
108
+
if len(issues) == 0 {
109
+
log.Printf("NewComment: no issue found for %s", comment.Subject)
110
+
return
111
+
}
112
+
issue := issues[0]
83
113
84
-
// build the recipients list
85
-
// - owner of the repo
86
-
// - collaborators in the repo
87
-
// - remove users already mentioned
88
-
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
89
-
for _, c := range collaborators {
90
-
recipients.Insert(c.SubjectDid)
114
+
recipients.Insert(syntax.DID(issue.Repo.Did))
115
+
if comment.IsReply() {
116
+
// if this comment is a reply, then notify everybody in that thread
117
+
parentAtUri := *comment.ReplyTo
118
+
119
+
// find the parent thread, and add all DIDs from here to the recipient list
120
+
for _, t := range issue.CommentList() {
121
+
if t.Self.AtUri() == parentAtUri {
122
+
for _, p := range t.Participants() {
123
+
recipients.Insert(p)
124
+
}
125
+
}
126
+
}
127
+
} else {
128
+
// not a reply, notify just the issue author
129
+
recipients.Insert(syntax.DID(issue.Did))
130
+
}
131
+
132
+
entityType = "issue"
133
+
entityId = issue.AtUri().String()
134
+
repoId = &issue.Repo.Id
135
+
issueId = &issue.Id
136
+
case tangled.RepoPullNSID:
137
+
pulls, err := db.GetPullsWithLimit(
138
+
n.db,
139
+
1,
140
+
orm.FilterEq("owner_did", subjectDid),
141
+
orm.FilterEq("rkey", comment.Subject.RecordKey()),
142
+
)
143
+
if err != nil {
144
+
log.Printf("NewComment: failed to get pulls: %v", err)
145
+
return
146
+
}
147
+
if len(pulls) == 0 {
148
+
log.Printf("NewComment: no pull found for %s", comment.Subject)
149
+
return
150
+
}
151
+
pull := pulls[0]
152
+
153
+
pull.Repo, err = db.GetRepo(n.db, orm.FilterEq("at_uri", pull.RepoAt))
154
+
if err != nil {
155
+
log.Printf("NewComment: failed to get repos: %v", err)
156
+
return
157
+
}
158
+
159
+
recipients.Insert(syntax.DID(pull.Repo.Did))
160
+
for _, p := range pull.Participants() {
161
+
recipients.Insert(syntax.DID(p))
162
+
}
163
+
164
+
entityType = "pull"
165
+
entityId = pull.AtUri().String()
166
+
repoId = &pull.Repo.Id
167
+
p := int64(pull.ID)
168
+
pullId = &p
169
+
default:
170
+
return // no-op
91
171
}
92
-
for _, m := range mentions {
172
+
173
+
for _, m := range comment.Mentions {
93
174
recipients.Remove(m)
94
175
}
95
176
96
-
actorDid := syntax.DID(issue.Did)
97
-
entityType := "issue"
98
-
entityId := issue.AtUri().String()
99
-
repoId := &issue.Repo.Id
100
-
issueId := &issue.Id
101
-
var pullId *int64
102
-
103
177
n.notifyEvent(
104
-
actorDid,
178
+
comment.Did,
105
179
recipients,
106
-
models.NotificationTypeIssueCreated,
180
+
models.NotificationTypeIssueCommented,
107
181
entityType,
108
182
entityId,
109
183
repoId,
···
111
185
pullId,
112
186
)
113
187
n.notifyEvent(
114
-
actorDid,
115
-
sets.Collect(slices.Values(mentions)),
188
+
comment.Did,
189
+
sets.Collect(slices.Values(comment.Mentions)),
116
190
models.NotificationTypeUserMentioned,
117
191
entityType,
118
192
entityId,
···
122
196
)
123
197
}
124
198
125
-
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
126
-
issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.IssueAt))
199
+
func (n *databaseNotifier) DeleteComment(ctx context.Context, comment *models.Comment) {
200
+
// no-op
201
+
}
202
+
203
+
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
204
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt()))
127
205
if err != nil {
128
-
log.Printf("NewIssueComment: failed to get issues: %v", err)
206
+
log.Printf("failed to fetch collaborators: %v", err)
129
207
return
130
208
}
131
-
if len(issues) == 0 {
132
-
log.Printf("NewIssueComment: no issue found for %s", comment.IssueAt)
133
-
return
134
-
}
135
-
issue := issues[0]
136
209
137
-
// built the recipients list:
138
-
// - the owner of the repo
139
-
// - | if the comment is a reply -> everybody on that thread
140
-
// | if the comment is a top level -> just the issue owner
141
-
// - remove mentioned users from the recipients list
210
+
// build the recipients list
211
+
// - owner of the repo
212
+
// - collaborators in the repo
213
+
// - remove users already mentioned
142
214
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
143
-
144
-
if comment.IsReply() {
145
-
// if this comment is a reply, then notify everybody in that thread
146
-
parentAtUri := *comment.ReplyTo
147
-
148
-
// find the parent thread, and add all DIDs from here to the recipient list
149
-
for _, t := range issue.CommentList() {
150
-
if t.Self.AtUri().String() == parentAtUri {
151
-
for _, p := range t.Participants() {
152
-
recipients.Insert(p)
153
-
}
154
-
}
155
-
}
156
-
} else {
157
-
// not a reply, notify just the issue author
158
-
recipients.Insert(syntax.DID(issue.Did))
215
+
for _, c := range collaborators {
216
+
recipients.Insert(c.SubjectDid)
159
217
}
160
-
161
218
for _, m := range mentions {
162
219
recipients.Remove(m)
163
220
}
164
221
165
-
actorDid := syntax.DID(comment.Did)
222
+
actorDid := syntax.DID(issue.Did)
166
223
entityType := "issue"
167
224
entityId := issue.AtUri().String()
168
225
repoId := &issue.Repo.Id
···
172
229
n.notifyEvent(
173
230
actorDid,
174
231
recipients,
175
-
models.NotificationTypeIssueCommented,
232
+
models.NotificationTypeIssueCreated,
176
233
entityType,
177
234
entityId,
178
235
repoId,
···
252
309
actorDid,
253
310
recipients,
254
311
eventType,
255
-
entityType,
256
-
entityId,
257
-
repoId,
258
-
issueId,
259
-
pullId,
260
-
)
261
-
}
262
-
263
-
func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) {
264
-
pull, err := db.GetPull(n.db,
265
-
syntax.ATURI(comment.RepoAt),
266
-
comment.PullId,
267
-
)
268
-
if err != nil {
269
-
log.Printf("NewPullComment: failed to get pulls: %v", err)
270
-
return
271
-
}
272
-
273
-
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", comment.RepoAt))
274
-
if err != nil {
275
-
log.Printf("NewPullComment: failed to get repos: %v", err)
276
-
return
277
-
}
278
-
279
-
// build up the recipients list:
280
-
// - repo owner
281
-
// - all pull participants
282
-
// - remove those already mentioned
283
-
recipients := sets.Singleton(syntax.DID(repo.Did))
284
-
for _, p := range pull.Participants() {
285
-
recipients.Insert(syntax.DID(p))
286
-
}
287
-
for _, m := range mentions {
288
-
recipients.Remove(m)
289
-
}
290
-
291
-
actorDid := syntax.DID(comment.OwnerDid)
292
-
eventType := models.NotificationTypePullCommented
293
-
entityType := "pull"
294
-
entityId := pull.AtUri().String()
295
-
repoId := &repo.Id
296
-
var issueId *int64
297
-
p := int64(pull.ID)
298
-
pullId := &p
299
-
300
-
n.notifyEvent(
301
-
actorDid,
302
-
recipients,
303
-
eventType,
304
-
entityType,
305
-
entityId,
306
-
repoId,
307
-
issueId,
308
-
pullId,
309
-
)
310
-
n.notifyEvent(
311
-
actorDid,
312
-
sets.Collect(slices.Values(mentions)),
313
-
models.NotificationTypeUserMentioned,
314
312
entityType,
315
313
entityId,
316
314
repoId,
+8
-8
appview/notify/merged_notifier.go
+8
-8
appview/notify/merged_notifier.go
···
53
53
m.fanout("DeleteStar", ctx, star)
54
54
}
55
55
56
-
func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
57
-
m.fanout("NewIssue", ctx, issue, mentions)
56
+
func (m *mergedNotifier) NewComment(ctx context.Context, comment *models.Comment) {
57
+
m.fanout("NewComment", ctx, comment)
58
58
}
59
59
60
-
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
61
-
m.fanout("NewIssueComment", ctx, comment, mentions)
60
+
func (m *mergedNotifier) DeleteComment(ctx context.Context, comment *models.Comment) {
61
+
m.fanout("DeleteComment", ctx, comment)
62
+
}
63
+
64
+
func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
65
+
m.fanout("NewIssue", ctx, issue, mentions)
62
66
}
63
67
64
68
func (m *mergedNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
···
79
83
80
84
func (m *mergedNotifier) NewPull(ctx context.Context, pull *models.Pull) {
81
85
m.fanout("NewPull", ctx, pull)
82
-
}
83
-
84
-
func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) {
85
-
m.fanout("NewPullComment", ctx, comment, mentions)
86
86
}
87
87
88
88
func (m *mergedNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {
+7
-7
appview/notify/notifier.go
+7
-7
appview/notify/notifier.go
···
13
13
NewStar(ctx context.Context, star *models.Star)
14
14
DeleteStar(ctx context.Context, star *models.Star)
15
15
16
+
NewComment(ctx context.Context, comment *models.Comment)
17
+
DeleteComment(ctx context.Context, comment *models.Comment)
18
+
16
19
NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID)
17
-
NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID)
18
20
NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue)
19
21
DeleteIssue(ctx context.Context, issue *models.Issue)
20
22
···
22
24
DeleteFollow(ctx context.Context, follow *models.Follow)
23
25
24
26
NewPull(ctx context.Context, pull *models.Pull)
25
-
NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID)
26
27
NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull)
27
28
28
29
UpdateProfile(ctx context.Context, profile *models.Profile)
···
42
43
func (m *BaseNotifier) NewStar(ctx context.Context, star *models.Star) {}
43
44
func (m *BaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {}
44
45
46
+
func (m *BaseNotifier) NewComment(ctx context.Context, comment *models.Comment) {}
47
+
func (m *BaseNotifier) DeleteComment(ctx context.Context, comment *models.Comment) {}
48
+
45
49
func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {}
46
-
func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
47
-
}
48
50
func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {}
49
51
func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {}
50
52
51
53
func (m *BaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {}
52
54
func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {}
53
55
54
-
func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {}
55
-
func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.PullComment, mentions []syntax.DID) {
56
-
}
56
+
func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {}
57
57
func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {}
58
58
59
59
func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {}
+5
-20
appview/notify/posthog/notifier.go
+5
-20
appview/notify/posthog/notifier.go
···
86
86
}
87
87
}
88
88
89
-
func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) {
90
-
err := n.client.Enqueue(posthog.Capture{
91
-
DistinctId: comment.OwnerDid,
92
-
Event: "new_pull_comment",
93
-
Properties: posthog.Properties{
94
-
"repo_at": comment.RepoAt,
95
-
"pull_id": comment.PullId,
96
-
"mentions": mentions,
97
-
},
98
-
})
99
-
if err != nil {
100
-
log.Println("failed to enqueue posthog event:", err)
101
-
}
102
-
}
103
-
104
89
func (n *posthogNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
105
90
err := n.client.Enqueue(posthog.Capture{
106
91
DistinctId: pull.OwnerDid,
···
180
165
}
181
166
}
182
167
183
-
func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
168
+
func (n *posthogNotifier) NewComment(ctx context.Context, comment *models.Comment) {
184
169
err := n.client.Enqueue(posthog.Capture{
185
-
DistinctId: comment.Did,
186
-
Event: "new_issue_comment",
170
+
DistinctId: comment.Did.String(),
171
+
Event: "new_comment",
187
172
Properties: posthog.Properties{
188
-
"issue_at": comment.IssueAt,
189
-
"mentions": mentions,
173
+
"subject_at": comment.Subject,
174
+
"mentions": comment.Mentions,
190
175
},
191
176
})
192
177
if err != nil {
-191
appview/oauth/accounts.go
-191
appview/oauth/accounts.go
···
1
-
package oauth
2
-
3
-
import (
4
-
"encoding/json"
5
-
"errors"
6
-
"net/http"
7
-
"time"
8
-
)
9
-
10
-
const MaxAccounts = 20
11
-
12
-
var ErrMaxAccountsReached = errors.New("maximum number of linked accounts reached")
13
-
14
-
type AccountInfo struct {
15
-
Did string `json:"did"`
16
-
Handle string `json:"handle"`
17
-
SessionId string `json:"session_id"`
18
-
AddedAt int64 `json:"added_at"`
19
-
}
20
-
21
-
type AccountRegistry struct {
22
-
Accounts []AccountInfo `json:"accounts"`
23
-
}
24
-
25
-
type MultiAccountUser struct {
26
-
Active *User
27
-
Accounts []AccountInfo
28
-
}
29
-
30
-
func (m *MultiAccountUser) Did() string {
31
-
if m.Active == nil {
32
-
return ""
33
-
}
34
-
return m.Active.Did
35
-
}
36
-
37
-
func (m *MultiAccountUser) Pds() string {
38
-
if m.Active == nil {
39
-
return ""
40
-
}
41
-
return m.Active.Pds
42
-
}
43
-
44
-
func (o *OAuth) GetAccounts(r *http.Request) *AccountRegistry {
45
-
session, err := o.SessStore.Get(r, AccountsName)
46
-
if err != nil || session.IsNew {
47
-
return &AccountRegistry{Accounts: []AccountInfo{}}
48
-
}
49
-
50
-
data, ok := session.Values["accounts"].(string)
51
-
if !ok {
52
-
return &AccountRegistry{Accounts: []AccountInfo{}}
53
-
}
54
-
55
-
var registry AccountRegistry
56
-
if err := json.Unmarshal([]byte(data), ®istry); err != nil {
57
-
return &AccountRegistry{Accounts: []AccountInfo{}}
58
-
}
59
-
60
-
return ®istry
61
-
}
62
-
63
-
func (o *OAuth) SaveAccounts(w http.ResponseWriter, r *http.Request, registry *AccountRegistry) error {
64
-
session, err := o.SessStore.Get(r, AccountsName)
65
-
if err != nil {
66
-
return err
67
-
}
68
-
69
-
data, err := json.Marshal(registry)
70
-
if err != nil {
71
-
return err
72
-
}
73
-
74
-
session.Values["accounts"] = string(data)
75
-
session.Options.MaxAge = 60 * 60 * 24 * 365
76
-
session.Options.HttpOnly = true
77
-
session.Options.Secure = !o.Config.Core.Dev
78
-
session.Options.SameSite = http.SameSiteLaxMode
79
-
80
-
return session.Save(r, w)
81
-
}
82
-
83
-
func (r *AccountRegistry) AddAccount(did, handle, sessionId string) error {
84
-
for i, acc := range r.Accounts {
85
-
if acc.Did == did {
86
-
r.Accounts[i].SessionId = sessionId
87
-
r.Accounts[i].Handle = handle
88
-
return nil
89
-
}
90
-
}
91
-
92
-
if len(r.Accounts) >= MaxAccounts {
93
-
return ErrMaxAccountsReached
94
-
}
95
-
96
-
r.Accounts = append(r.Accounts, AccountInfo{
97
-
Did: did,
98
-
Handle: handle,
99
-
SessionId: sessionId,
100
-
AddedAt: time.Now().Unix(),
101
-
})
102
-
return nil
103
-
}
104
-
105
-
func (r *AccountRegistry) RemoveAccount(did string) {
106
-
filtered := make([]AccountInfo, 0, len(r.Accounts))
107
-
for _, acc := range r.Accounts {
108
-
if acc.Did != did {
109
-
filtered = append(filtered, acc)
110
-
}
111
-
}
112
-
r.Accounts = filtered
113
-
}
114
-
115
-
func (r *AccountRegistry) FindAccount(did string) *AccountInfo {
116
-
for i := range r.Accounts {
117
-
if r.Accounts[i].Did == did {
118
-
return &r.Accounts[i]
119
-
}
120
-
}
121
-
return nil
122
-
}
123
-
124
-
func (r *AccountRegistry) OtherAccounts(activeDid string) []AccountInfo {
125
-
result := make([]AccountInfo, 0, len(r.Accounts))
126
-
for _, acc := range r.Accounts {
127
-
if acc.Did != activeDid {
128
-
result = append(result, acc)
129
-
}
130
-
}
131
-
return result
132
-
}
133
-
134
-
func (o *OAuth) GetMultiAccountUser(r *http.Request) *MultiAccountUser {
135
-
user := o.GetUser(r)
136
-
if user == nil {
137
-
return nil
138
-
}
139
-
140
-
registry := o.GetAccounts(r)
141
-
return &MultiAccountUser{
142
-
Active: user,
143
-
Accounts: registry.Accounts,
144
-
}
145
-
}
146
-
147
-
type AuthReturnInfo struct {
148
-
ReturnURL string
149
-
AddAccount bool
150
-
}
151
-
152
-
func (o *OAuth) SetAuthReturn(w http.ResponseWriter, r *http.Request, returnURL string, addAccount bool) error {
153
-
session, err := o.SessStore.Get(r, AuthReturnName)
154
-
if err != nil {
155
-
return err
156
-
}
157
-
158
-
session.Values[AuthReturnURL] = returnURL
159
-
session.Values[AuthAddAccount] = addAccount
160
-
session.Options.MaxAge = 60 * 30
161
-
session.Options.HttpOnly = true
162
-
session.Options.Secure = !o.Config.Core.Dev
163
-
session.Options.SameSite = http.SameSiteLaxMode
164
-
165
-
return session.Save(r, w)
166
-
}
167
-
168
-
func (o *OAuth) GetAuthReturn(r *http.Request) *AuthReturnInfo {
169
-
session, err := o.SessStore.Get(r, AuthReturnName)
170
-
if err != nil || session.IsNew {
171
-
return &AuthReturnInfo{}
172
-
}
173
-
174
-
returnURL, _ := session.Values[AuthReturnURL].(string)
175
-
addAccount, _ := session.Values[AuthAddAccount].(bool)
176
-
177
-
return &AuthReturnInfo{
178
-
ReturnURL: returnURL,
179
-
AddAccount: addAccount,
180
-
}
181
-
}
182
-
183
-
func (o *OAuth) ClearAuthReturn(w http.ResponseWriter, r *http.Request) error {
184
-
session, err := o.SessStore.Get(r, AuthReturnName)
185
-
if err != nil {
186
-
return err
187
-
}
188
-
189
-
session.Options.MaxAge = -1
190
-
return session.Save(r, w)
191
-
}
-265
appview/oauth/accounts_test.go
-265
appview/oauth/accounts_test.go
···
1
-
package oauth
2
-
3
-
import (
4
-
"testing"
5
-
)
6
-
7
-
func TestAccountRegistry_AddAccount(t *testing.T) {
8
-
tests := []struct {
9
-
name string
10
-
initial []AccountInfo
11
-
addDid string
12
-
addHandle string
13
-
addSessionId string
14
-
wantErr error
15
-
wantLen int
16
-
wantSessionId string
17
-
}{
18
-
{
19
-
name: "add first account",
20
-
initial: []AccountInfo{},
21
-
addDid: "did:plc:abc123",
22
-
addHandle: "alice.bsky.social",
23
-
addSessionId: "session-1",
24
-
wantErr: nil,
25
-
wantLen: 1,
26
-
wantSessionId: "session-1",
27
-
},
28
-
{
29
-
name: "add second account",
30
-
initial: []AccountInfo{
31
-
{Did: "did:plc:abc123", Handle: "alice.bsky.social", SessionId: "session-1", AddedAt: 1000},
32
-
},
33
-
addDid: "did:plc:def456",
34
-
addHandle: "bob.bsky.social",
35
-
addSessionId: "session-2",
36
-
wantErr: nil,
37
-
wantLen: 2,
38
-
wantSessionId: "session-2",
39
-
},
40
-
{
41
-
name: "update existing account session",
42
-
initial: []AccountInfo{
43
-
{Did: "did:plc:abc123", Handle: "alice.bsky.social", SessionId: "old-session", AddedAt: 1000},
44
-
},
45
-
addDid: "did:plc:abc123",
46
-
addHandle: "alice.bsky.social",
47
-
addSessionId: "new-session",
48
-
wantErr: nil,
49
-
wantLen: 1,
50
-
wantSessionId: "new-session",
51
-
},
52
-
}
53
-
54
-
for _, tt := range tests {
55
-
t.Run(tt.name, func(t *testing.T) {
56
-
registry := &AccountRegistry{Accounts: tt.initial}
57
-
err := registry.AddAccount(tt.addDid, tt.addHandle, tt.addSessionId)
58
-
59
-
if err != tt.wantErr {
60
-
t.Errorf("AddAccount() error = %v, want %v", err, tt.wantErr)
61
-
}
62
-
63
-
if len(registry.Accounts) != tt.wantLen {
64
-
t.Errorf("AddAccount() len = %d, want %d", len(registry.Accounts), tt.wantLen)
65
-
}
66
-
67
-
found := registry.FindAccount(tt.addDid)
68
-
if found == nil {
69
-
t.Errorf("AddAccount() account not found after add")
70
-
return
71
-
}
72
-
73
-
if found.SessionId != tt.wantSessionId {
74
-
t.Errorf("AddAccount() sessionId = %s, want %s", found.SessionId, tt.wantSessionId)
75
-
}
76
-
})
77
-
}
78
-
}
79
-
80
-
func TestAccountRegistry_AddAccount_MaxLimit(t *testing.T) {
81
-
registry := &AccountRegistry{Accounts: make([]AccountInfo, 0, MaxAccounts)}
82
-
83
-
for i := range MaxAccounts {
84
-
err := registry.AddAccount("did:plc:user"+string(rune('a'+i)), "handle", "session")
85
-
if err != nil {
86
-
t.Fatalf("AddAccount() unexpected error on account %d: %v", i, err)
87
-
}
88
-
}
89
-
90
-
if len(registry.Accounts) != MaxAccounts {
91
-
t.Errorf("expected %d accounts, got %d", MaxAccounts, len(registry.Accounts))
92
-
}
93
-
94
-
err := registry.AddAccount("did:plc:overflow", "overflow", "session-overflow")
95
-
if err != ErrMaxAccountsReached {
96
-
t.Errorf("AddAccount() error = %v, want %v", err, ErrMaxAccountsReached)
97
-
}
98
-
99
-
if len(registry.Accounts) != MaxAccounts {
100
-
t.Errorf("account added despite max limit, got %d", len(registry.Accounts))
101
-
}
102
-
}
103
-
104
-
func TestAccountRegistry_RemoveAccount(t *testing.T) {
105
-
tests := []struct {
106
-
name string
107
-
initial []AccountInfo
108
-
removeDid string
109
-
wantLen int
110
-
wantDids []string
111
-
}{
112
-
{
113
-
name: "remove existing account",
114
-
initial: []AccountInfo{
115
-
{Did: "did:plc:abc123", Handle: "alice", SessionId: "s1"},
116
-
{Did: "did:plc:def456", Handle: "bob", SessionId: "s2"},
117
-
},
118
-
removeDid: "did:plc:abc123",
119
-
wantLen: 1,
120
-
wantDids: []string{"did:plc:def456"},
121
-
},
122
-
{
123
-
name: "remove non-existing account",
124
-
initial: []AccountInfo{
125
-
{Did: "did:plc:abc123", Handle: "alice", SessionId: "s1"},
126
-
},
127
-
removeDid: "did:plc:notfound",
128
-
wantLen: 1,
129
-
wantDids: []string{"did:plc:abc123"},
130
-
},
131
-
{
132
-
name: "remove last account",
133
-
initial: []AccountInfo{
134
-
{Did: "did:plc:abc123", Handle: "alice", SessionId: "s1"},
135
-
},
136
-
removeDid: "did:plc:abc123",
137
-
wantLen: 0,
138
-
wantDids: []string{},
139
-
},
140
-
{
141
-
name: "remove from empty registry",
142
-
initial: []AccountInfo{},
143
-
removeDid: "did:plc:abc123",
144
-
wantLen: 0,
145
-
wantDids: []string{},
146
-
},
147
-
}
148
-
149
-
for _, tt := range tests {
150
-
t.Run(tt.name, func(t *testing.T) {
151
-
registry := &AccountRegistry{Accounts: tt.initial}
152
-
registry.RemoveAccount(tt.removeDid)
153
-
154
-
if len(registry.Accounts) != tt.wantLen {
155
-
t.Errorf("RemoveAccount() len = %d, want %d", len(registry.Accounts), tt.wantLen)
156
-
}
157
-
158
-
for _, wantDid := range tt.wantDids {
159
-
if registry.FindAccount(wantDid) == nil {
160
-
t.Errorf("RemoveAccount() expected %s to remain", wantDid)
161
-
}
162
-
}
163
-
164
-
if registry.FindAccount(tt.removeDid) != nil && tt.wantLen < len(tt.initial) {
165
-
t.Errorf("RemoveAccount() %s should have been removed", tt.removeDid)
166
-
}
167
-
})
168
-
}
169
-
}
170
-
171
-
func TestAccountRegistry_FindAccount(t *testing.T) {
172
-
registry := &AccountRegistry{
173
-
Accounts: []AccountInfo{
174
-
{Did: "did:plc:first", Handle: "first", SessionId: "s1", AddedAt: 1000},
175
-
{Did: "did:plc:second", Handle: "second", SessionId: "s2", AddedAt: 2000},
176
-
{Did: "did:plc:third", Handle: "third", SessionId: "s3", AddedAt: 3000},
177
-
},
178
-
}
179
-
180
-
t.Run("find existing account", func(t *testing.T) {
181
-
found := registry.FindAccount("did:plc:second")
182
-
if found == nil {
183
-
t.Fatal("FindAccount() returned nil for existing account")
184
-
}
185
-
if found.Handle != "second" {
186
-
t.Errorf("FindAccount() handle = %s, want second", found.Handle)
187
-
}
188
-
if found.SessionId != "s2" {
189
-
t.Errorf("FindAccount() sessionId = %s, want s2", found.SessionId)
190
-
}
191
-
})
192
-
193
-
t.Run("find non-existing account", func(t *testing.T) {
194
-
found := registry.FindAccount("did:plc:notfound")
195
-
if found != nil {
196
-
t.Errorf("FindAccount() = %v, want nil", found)
197
-
}
198
-
})
199
-
200
-
t.Run("returned pointer is mutable", func(t *testing.T) {
201
-
found := registry.FindAccount("did:plc:first")
202
-
if found == nil {
203
-
t.Fatal("FindAccount() returned nil")
204
-
}
205
-
found.SessionId = "modified"
206
-
207
-
refetch := registry.FindAccount("did:plc:first")
208
-
if refetch.SessionId != "modified" {
209
-
t.Errorf("FindAccount() pointer not referencing original, got %s", refetch.SessionId)
210
-
}
211
-
})
212
-
}
213
-
214
-
func TestAccountRegistry_OtherAccounts(t *testing.T) {
215
-
registry := &AccountRegistry{
216
-
Accounts: []AccountInfo{
217
-
{Did: "did:plc:active", Handle: "active", SessionId: "s1"},
218
-
{Did: "did:plc:other1", Handle: "other1", SessionId: "s2"},
219
-
{Did: "did:plc:other2", Handle: "other2", SessionId: "s3"},
220
-
},
221
-
}
222
-
223
-
others := registry.OtherAccounts("did:plc:active")
224
-
225
-
if len(others) != 2 {
226
-
t.Errorf("OtherAccounts() len = %d, want 2", len(others))
227
-
}
228
-
229
-
for _, acc := range others {
230
-
if acc.Did == "did:plc:active" {
231
-
t.Errorf("OtherAccounts() should not include active account")
232
-
}
233
-
}
234
-
235
-
hasDid := func(did string) bool {
236
-
for _, acc := range others {
237
-
if acc.Did == did {
238
-
return true
239
-
}
240
-
}
241
-
return false
242
-
}
243
-
244
-
if !hasDid("did:plc:other1") || !hasDid("did:plc:other2") {
245
-
t.Errorf("OtherAccounts() missing expected accounts")
246
-
}
247
-
}
248
-
249
-
func TestMultiAccountUser_Did(t *testing.T) {
250
-
t.Run("with active user", func(t *testing.T) {
251
-
user := &MultiAccountUser{
252
-
Active: &User{Did: "did:plc:test", Pds: "https://bsky.social"},
253
-
}
254
-
if user.Did() != "did:plc:test" {
255
-
t.Errorf("Did() = %s, want did:plc:test", user.Did())
256
-
}
257
-
})
258
-
259
-
t.Run("with nil active", func(t *testing.T) {
260
-
user := &MultiAccountUser{Active: nil}
261
-
if user.Did() != "" {
262
-
t.Errorf("Did() = %s, want empty string", user.Did())
263
-
}
264
-
})
265
-
}
+1
-5
appview/oauth/consts.go
+1
-5
appview/oauth/consts.go
···
1
1
package oauth
2
2
3
3
const (
4
-
SessionName = "appview-session-v2"
5
-
AccountsName = "appview-accounts-v2"
6
-
AuthReturnName = "appview-auth-return"
7
-
AuthReturnURL = "return_url"
8
-
AuthAddAccount = "add_account"
4
+
SessionName = "appview-session-v2"
9
5
SessionHandle = "handle"
10
6
SessionDid = "did"
11
7
SessionId = "id"
+2
-14
appview/oauth/handler.go
+2
-14
appview/oauth/handler.go
···
55
55
ctx := r.Context()
56
56
l := o.Logger.With("query", r.URL.Query())
57
57
58
-
authReturn := o.GetAuthReturn(r)
59
-
_ = o.ClearAuthReturn(w, r)
60
-
61
58
sessData, err := o.ClientApp.ProcessCallback(ctx, r.URL.Query())
62
59
if err != nil {
63
60
var callbackErr *oauth.AuthRequestCallbackError
···
73
70
74
71
if err := o.SaveSession(w, r, sessData); err != nil {
75
72
l.Error("failed to save session", "data", sessData, "err", err)
76
-
errorCode := "session"
77
-
if errors.Is(err, ErrMaxAccountsReached) {
78
-
errorCode = "max_accounts"
79
-
}
80
-
http.Redirect(w, r, fmt.Sprintf("/login?error=%s", errorCode), http.StatusFound)
73
+
http.Redirect(w, r, "/login?error=session", http.StatusFound)
81
74
return
82
75
}
83
76
···
95
88
}
96
89
}
97
90
98
-
redirectURL := "/"
99
-
if authReturn.ReturnURL != "" {
100
-
redirectURL = authReturn.ReturnURL
101
-
}
102
-
103
-
http.Redirect(w, r, redirectURL, http.StatusFound)
91
+
http.Redirect(w, r, "/", http.StatusFound)
104
92
}
105
93
106
94
func (o *OAuth) addToDefaultSpindle(did string) {
+4
-66
appview/oauth/oauth.go
+4
-66
appview/oauth/oauth.go
···
98
98
}
99
99
100
100
func (o *OAuth) SaveSession(w http.ResponseWriter, r *http.Request, sessData *oauth.ClientSessionData) error {
101
+
// first we save the did in the user session
101
102
userSession, err := o.SessStore.Get(r, SessionName)
102
103
if err != nil {
103
104
return err
···
107
108
userSession.Values[SessionPds] = sessData.HostURL
108
109
userSession.Values[SessionId] = sessData.SessionID
109
110
userSession.Values[SessionAuthenticated] = true
110
-
111
-
if err := userSession.Save(r, w); err != nil {
112
-
return err
113
-
}
114
-
115
-
handle := ""
116
-
resolved, err := o.IdResolver.ResolveIdent(r.Context(), sessData.AccountDID.String())
117
-
if err == nil && resolved.Handle.String() != "" {
118
-
handle = resolved.Handle.String()
119
-
}
120
-
121
-
registry := o.GetAccounts(r)
122
-
if err := registry.AddAccount(sessData.AccountDID.String(), handle, sessData.SessionID); err != nil {
123
-
return err
124
-
}
125
-
return o.SaveAccounts(w, r, registry)
111
+
return userSession.Save(r, w)
126
112
}
127
113
128
114
func (o *OAuth) ResumeSession(r *http.Request) (*oauth.ClientSession, error) {
···
177
163
return errors.Join(err1, err2)
178
164
}
179
165
180
-
func (o *OAuth) SwitchAccount(w http.ResponseWriter, r *http.Request, targetDid string) error {
181
-
registry := o.GetAccounts(r)
182
-
account := registry.FindAccount(targetDid)
183
-
if account == nil {
184
-
return fmt.Errorf("account not found in registry: %s", targetDid)
185
-
}
186
-
187
-
did, err := syntax.ParseDID(targetDid)
188
-
if err != nil {
189
-
return fmt.Errorf("invalid DID: %w", err)
190
-
}
191
-
192
-
sess, err := o.ClientApp.ResumeSession(r.Context(), did, account.SessionId)
193
-
if err != nil {
194
-
registry.RemoveAccount(targetDid)
195
-
_ = o.SaveAccounts(w, r, registry)
196
-
return fmt.Errorf("session expired for account: %w", err)
197
-
}
198
-
199
-
userSession, err := o.SessStore.Get(r, SessionName)
200
-
if err != nil {
201
-
return err
202
-
}
203
-
204
-
userSession.Values[SessionDid] = sess.Data.AccountDID.String()
205
-
userSession.Values[SessionPds] = sess.Data.HostURL
206
-
userSession.Values[SessionId] = sess.Data.SessionID
207
-
userSession.Values[SessionAuthenticated] = true
208
-
209
-
return userSession.Save(r, w)
210
-
}
211
-
212
-
func (o *OAuth) RemoveAccount(w http.ResponseWriter, r *http.Request, targetDid string) error {
213
-
registry := o.GetAccounts(r)
214
-
account := registry.FindAccount(targetDid)
215
-
if account == nil {
216
-
return nil
217
-
}
218
-
219
-
did, err := syntax.ParseDID(targetDid)
220
-
if err == nil {
221
-
_ = o.ClientApp.Logout(r.Context(), did, account.SessionId)
222
-
}
223
-
224
-
registry.RemoveAccount(targetDid)
225
-
return o.SaveAccounts(w, r, registry)
226
-
}
227
-
228
166
type User struct {
229
167
Did string
230
168
Pds string
···
243
181
}
244
182
245
183
func (o *OAuth) GetDid(r *http.Request) string {
246
-
if u := o.GetMultiAccountUser(r); u != nil {
247
-
return u.Did()
184
+
if u := o.GetUser(r); u != nil {
185
+
return u.Did
248
186
}
249
187
250
188
return ""
-10
appview/pages/funcmap.go
-10
appview/pages/funcmap.go
···
28
28
emoji "github.com/yuin/goldmark-emoji"
29
29
"tangled.org/core/appview/filetree"
30
30
"tangled.org/core/appview/models"
31
-
"tangled.org/core/appview/oauth"
32
31
"tangled.org/core/appview/pages/markup"
33
32
"tangled.org/core/crypto"
34
33
)
···
385
384
return "error"
386
385
}
387
386
return fp
388
-
},
389
-
"otherAccounts": func(activeDid string, accounts []oauth.AccountInfo) []oauth.AccountInfo {
390
-
result := make([]oauth.AccountInfo, 0, len(accounts))
391
-
for _, acc := range accounts {
392
-
if acc.Did != activeDid {
393
-
result = append(result, acc)
394
-
}
395
-
}
396
-
return result
397
387
},
398
388
}
399
389
}
+70
-72
appview/pages/pages.go
+70
-72
appview/pages/pages.go
···
215
215
}
216
216
217
217
type LoginParams struct {
218
-
ReturnUrl string
219
-
ErrorCode string
220
-
AddAccount bool
221
-
LoggedInUser *oauth.MultiAccountUser
218
+
ReturnUrl string
219
+
ErrorCode string
222
220
}
223
221
224
222
func (p *Pages) Login(w io.Writer, params LoginParams) error {
···
238
236
}
239
237
240
238
type TermsOfServiceParams struct {
241
-
LoggedInUser *oauth.MultiAccountUser
239
+
LoggedInUser *oauth.User
242
240
Content template.HTML
243
241
}
244
242
···
266
264
}
267
265
268
266
type PrivacyPolicyParams struct {
269
-
LoggedInUser *oauth.MultiAccountUser
267
+
LoggedInUser *oauth.User
270
268
Content template.HTML
271
269
}
272
270
···
294
292
}
295
293
296
294
type BrandParams struct {
297
-
LoggedInUser *oauth.MultiAccountUser
295
+
LoggedInUser *oauth.User
298
296
}
299
297
300
298
func (p *Pages) Brand(w io.Writer, params BrandParams) error {
···
302
300
}
303
301
304
302
type TimelineParams struct {
305
-
LoggedInUser *oauth.MultiAccountUser
303
+
LoggedInUser *oauth.User
306
304
Timeline []models.TimelineEvent
307
305
Repos []models.Repo
308
306
GfiLabel *models.LabelDefinition
···
313
311
}
314
312
315
313
type GoodFirstIssuesParams struct {
316
-
LoggedInUser *oauth.MultiAccountUser
314
+
LoggedInUser *oauth.User
317
315
Issues []models.Issue
318
316
RepoGroups []*models.RepoGroup
319
317
LabelDefs map[string]*models.LabelDefinition
···
326
324
}
327
325
328
326
type UserProfileSettingsParams struct {
329
-
LoggedInUser *oauth.MultiAccountUser
327
+
LoggedInUser *oauth.User
330
328
Tabs []map[string]any
331
329
Tab string
332
330
}
···
336
334
}
337
335
338
336
type NotificationsParams struct {
339
-
LoggedInUser *oauth.MultiAccountUser
337
+
LoggedInUser *oauth.User
340
338
Notifications []*models.NotificationWithEntity
341
339
UnreadCount int
342
340
Page pagination.Page
···
364
362
}
365
363
366
364
type UserKeysSettingsParams struct {
367
-
LoggedInUser *oauth.MultiAccountUser
365
+
LoggedInUser *oauth.User
368
366
PubKeys []models.PublicKey
369
367
Tabs []map[string]any
370
368
Tab string
···
375
373
}
376
374
377
375
type UserEmailsSettingsParams struct {
378
-
LoggedInUser *oauth.MultiAccountUser
376
+
LoggedInUser *oauth.User
379
377
Emails []models.Email
380
378
Tabs []map[string]any
381
379
Tab string
···
386
384
}
387
385
388
386
type UserNotificationSettingsParams struct {
389
-
LoggedInUser *oauth.MultiAccountUser
387
+
LoggedInUser *oauth.User
390
388
Preferences *models.NotificationPreferences
391
389
Tabs []map[string]any
392
390
Tab string
···
406
404
}
407
405
408
406
type KnotsParams struct {
409
-
LoggedInUser *oauth.MultiAccountUser
407
+
LoggedInUser *oauth.User
410
408
Registrations []models.Registration
411
409
Tabs []map[string]any
412
410
Tab string
···
417
415
}
418
416
419
417
type KnotParams struct {
420
-
LoggedInUser *oauth.MultiAccountUser
418
+
LoggedInUser *oauth.User
421
419
Registration *models.Registration
422
420
Members []string
423
421
Repos map[string][]models.Repo
···
439
437
}
440
438
441
439
type SpindlesParams struct {
442
-
LoggedInUser *oauth.MultiAccountUser
440
+
LoggedInUser *oauth.User
443
441
Spindles []models.Spindle
444
442
Tabs []map[string]any
445
443
Tab string
···
460
458
}
461
459
462
460
type SpindleDashboardParams struct {
463
-
LoggedInUser *oauth.MultiAccountUser
461
+
LoggedInUser *oauth.User
464
462
Spindle models.Spindle
465
463
Members []string
466
464
Repos map[string][]models.Repo
···
473
471
}
474
472
475
473
type NewRepoParams struct {
476
-
LoggedInUser *oauth.MultiAccountUser
474
+
LoggedInUser *oauth.User
477
475
Knots []string
478
476
}
479
477
···
482
480
}
483
481
484
482
type ForkRepoParams struct {
485
-
LoggedInUser *oauth.MultiAccountUser
483
+
LoggedInUser *oauth.User
486
484
Knots []string
487
485
RepoInfo repoinfo.RepoInfo
488
486
}
···
520
518
}
521
519
522
520
type ProfileOverviewParams struct {
523
-
LoggedInUser *oauth.MultiAccountUser
521
+
LoggedInUser *oauth.User
524
522
Repos []models.Repo
525
523
CollaboratingRepos []models.Repo
526
524
ProfileTimeline *models.ProfileTimeline
···
534
532
}
535
533
536
534
type ProfileReposParams struct {
537
-
LoggedInUser *oauth.MultiAccountUser
535
+
LoggedInUser *oauth.User
538
536
Repos []models.Repo
539
537
Card *ProfileCard
540
538
Active string
···
546
544
}
547
545
548
546
type ProfileStarredParams struct {
549
-
LoggedInUser *oauth.MultiAccountUser
547
+
LoggedInUser *oauth.User
550
548
Repos []models.Repo
551
549
Card *ProfileCard
552
550
Active string
···
558
556
}
559
557
560
558
type ProfileStringsParams struct {
561
-
LoggedInUser *oauth.MultiAccountUser
559
+
LoggedInUser *oauth.User
562
560
Strings []models.String
563
561
Card *ProfileCard
564
562
Active string
···
571
569
572
570
type FollowCard struct {
573
571
UserDid string
574
-
LoggedInUser *oauth.MultiAccountUser
572
+
LoggedInUser *oauth.User
575
573
FollowStatus models.FollowStatus
576
574
FollowersCount int64
577
575
FollowingCount int64
···
579
577
}
580
578
581
579
type ProfileFollowersParams struct {
582
-
LoggedInUser *oauth.MultiAccountUser
580
+
LoggedInUser *oauth.User
583
581
Followers []FollowCard
584
582
Card *ProfileCard
585
583
Active string
···
591
589
}
592
590
593
591
type ProfileFollowingParams struct {
594
-
LoggedInUser *oauth.MultiAccountUser
592
+
LoggedInUser *oauth.User
595
593
Following []FollowCard
596
594
Card *ProfileCard
597
595
Active string
···
612
610
}
613
611
614
612
type EditBioParams struct {
615
-
LoggedInUser *oauth.MultiAccountUser
613
+
LoggedInUser *oauth.User
616
614
Profile *models.Profile
617
615
}
618
616
···
621
619
}
622
620
623
621
type EditPinsParams struct {
624
-
LoggedInUser *oauth.MultiAccountUser
622
+
LoggedInUser *oauth.User
625
623
Profile *models.Profile
626
624
AllRepos []PinnedRepo
627
625
}
···
646
644
}
647
645
648
646
type RepoIndexParams struct {
649
-
LoggedInUser *oauth.MultiAccountUser
647
+
LoggedInUser *oauth.User
650
648
RepoInfo repoinfo.RepoInfo
651
649
Active string
652
650
TagMap map[string][]string
···
695
693
}
696
694
697
695
type RepoLogParams struct {
698
-
LoggedInUser *oauth.MultiAccountUser
696
+
LoggedInUser *oauth.User
699
697
RepoInfo repoinfo.RepoInfo
700
698
TagMap map[string][]string
701
699
Active string
···
712
710
}
713
711
714
712
type RepoCommitParams struct {
715
-
LoggedInUser *oauth.MultiAccountUser
713
+
LoggedInUser *oauth.User
716
714
RepoInfo repoinfo.RepoInfo
717
715
Active string
718
716
EmailToDid map[string]string
···
731
729
}
732
730
733
731
type RepoTreeParams struct {
734
-
LoggedInUser *oauth.MultiAccountUser
732
+
LoggedInUser *oauth.User
735
733
RepoInfo repoinfo.RepoInfo
736
734
Active string
737
735
BreadCrumbs [][]string
···
786
784
}
787
785
788
786
type RepoBranchesParams struct {
789
-
LoggedInUser *oauth.MultiAccountUser
787
+
LoggedInUser *oauth.User
790
788
RepoInfo repoinfo.RepoInfo
791
789
Active string
792
790
types.RepoBranchesResponse
···
798
796
}
799
797
800
798
type RepoTagsParams struct {
801
-
LoggedInUser *oauth.MultiAccountUser
799
+
LoggedInUser *oauth.User
802
800
RepoInfo repoinfo.RepoInfo
803
801
Active string
804
802
types.RepoTagsResponse
···
812
810
}
813
811
814
812
type RepoArtifactParams struct {
815
-
LoggedInUser *oauth.MultiAccountUser
813
+
LoggedInUser *oauth.User
816
814
RepoInfo repoinfo.RepoInfo
817
815
Artifact models.Artifact
818
816
}
···
822
820
}
823
821
824
822
type RepoBlobParams struct {
825
-
LoggedInUser *oauth.MultiAccountUser
823
+
LoggedInUser *oauth.User
826
824
RepoInfo repoinfo.RepoInfo
827
825
Active string
828
826
BreadCrumbs [][]string
···
846
844
}
847
845
848
846
type RepoSettingsParams struct {
849
-
LoggedInUser *oauth.MultiAccountUser
847
+
LoggedInUser *oauth.User
850
848
RepoInfo repoinfo.RepoInfo
851
849
Collaborators []Collaborator
852
850
Active string
···
865
863
}
866
864
867
865
type RepoGeneralSettingsParams struct {
868
-
LoggedInUser *oauth.MultiAccountUser
866
+
LoggedInUser *oauth.User
869
867
RepoInfo repoinfo.RepoInfo
870
868
Labels []models.LabelDefinition
871
869
DefaultLabels []models.LabelDefinition
···
883
881
}
884
882
885
883
type RepoAccessSettingsParams struct {
886
-
LoggedInUser *oauth.MultiAccountUser
884
+
LoggedInUser *oauth.User
887
885
RepoInfo repoinfo.RepoInfo
888
886
Active string
889
887
Tabs []map[string]any
···
897
895
}
898
896
899
897
type RepoPipelineSettingsParams struct {
900
-
LoggedInUser *oauth.MultiAccountUser
898
+
LoggedInUser *oauth.User
901
899
RepoInfo repoinfo.RepoInfo
902
900
Active string
903
901
Tabs []map[string]any
···
913
911
}
914
912
915
913
type RepoIssuesParams struct {
916
-
LoggedInUser *oauth.MultiAccountUser
914
+
LoggedInUser *oauth.User
917
915
RepoInfo repoinfo.RepoInfo
918
916
Active string
919
917
Issues []models.Issue
···
930
928
}
931
929
932
930
type RepoSingleIssueParams struct {
933
-
LoggedInUser *oauth.MultiAccountUser
931
+
LoggedInUser *oauth.User
934
932
RepoInfo repoinfo.RepoInfo
935
933
Active string
936
934
Issue *models.Issue
···
949
947
}
950
948
951
949
type EditIssueParams struct {
952
-
LoggedInUser *oauth.MultiAccountUser
950
+
LoggedInUser *oauth.User
953
951
RepoInfo repoinfo.RepoInfo
954
952
Issue *models.Issue
955
953
Action string
···
973
971
}
974
972
975
973
type RepoNewIssueParams struct {
976
-
LoggedInUser *oauth.MultiAccountUser
974
+
LoggedInUser *oauth.User
977
975
RepoInfo repoinfo.RepoInfo
978
976
Issue *models.Issue // existing issue if any -- passed when editing
979
977
Active string
···
987
985
}
988
986
989
987
type EditIssueCommentParams struct {
990
-
LoggedInUser *oauth.MultiAccountUser
988
+
LoggedInUser *oauth.User
991
989
RepoInfo repoinfo.RepoInfo
992
990
Issue *models.Issue
993
-
Comment *models.IssueComment
991
+
Comment *models.Comment
994
992
}
995
993
996
994
func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error {
···
998
996
}
999
997
1000
998
type ReplyIssueCommentPlaceholderParams struct {
1001
-
LoggedInUser *oauth.MultiAccountUser
999
+
LoggedInUser *oauth.User
1002
1000
RepoInfo repoinfo.RepoInfo
1003
1001
Issue *models.Issue
1004
-
Comment *models.IssueComment
1002
+
Comment *models.Comment
1005
1003
}
1006
1004
1007
1005
func (p *Pages) ReplyIssueCommentPlaceholderFragment(w io.Writer, params ReplyIssueCommentPlaceholderParams) error {
···
1009
1007
}
1010
1008
1011
1009
type ReplyIssueCommentParams struct {
1012
-
LoggedInUser *oauth.MultiAccountUser
1010
+
LoggedInUser *oauth.User
1013
1011
RepoInfo repoinfo.RepoInfo
1014
1012
Issue *models.Issue
1015
-
Comment *models.IssueComment
1013
+
Comment *models.Comment
1016
1014
}
1017
1015
1018
1016
func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentParams) error {
···
1020
1018
}
1021
1019
1022
1020
type IssueCommentBodyParams struct {
1023
-
LoggedInUser *oauth.MultiAccountUser
1021
+
LoggedInUser *oauth.User
1024
1022
RepoInfo repoinfo.RepoInfo
1025
1023
Issue *models.Issue
1026
-
Comment *models.IssueComment
1024
+
Comment *models.Comment
1027
1025
}
1028
1026
1029
1027
func (p *Pages) IssueCommentBodyFragment(w io.Writer, params IssueCommentBodyParams) error {
···
1031
1029
}
1032
1030
1033
1031
type RepoNewPullParams struct {
1034
-
LoggedInUser *oauth.MultiAccountUser
1032
+
LoggedInUser *oauth.User
1035
1033
RepoInfo repoinfo.RepoInfo
1036
1034
Branches []types.Branch
1037
1035
Strategy string
···
1048
1046
}
1049
1047
1050
1048
type RepoPullsParams struct {
1051
-
LoggedInUser *oauth.MultiAccountUser
1049
+
LoggedInUser *oauth.User
1052
1050
RepoInfo repoinfo.RepoInfo
1053
1051
Pulls []*models.Pull
1054
1052
Active string
···
1083
1081
}
1084
1082
1085
1083
type RepoSinglePullParams struct {
1086
-
LoggedInUser *oauth.MultiAccountUser
1084
+
LoggedInUser *oauth.User
1087
1085
RepoInfo repoinfo.RepoInfo
1088
1086
Active string
1089
1087
Pull *models.Pull
···
1108
1106
}
1109
1107
1110
1108
type RepoPullPatchParams struct {
1111
-
LoggedInUser *oauth.MultiAccountUser
1109
+
LoggedInUser *oauth.User
1112
1110
RepoInfo repoinfo.RepoInfo
1113
1111
Pull *models.Pull
1114
1112
Stack models.Stack
···
1125
1123
}
1126
1124
1127
1125
type RepoPullInterdiffParams struct {
1128
-
LoggedInUser *oauth.MultiAccountUser
1126
+
LoggedInUser *oauth.User
1129
1127
RepoInfo repoinfo.RepoInfo
1130
1128
Pull *models.Pull
1131
1129
Round int
···
1178
1176
}
1179
1177
1180
1178
type PullResubmitParams struct {
1181
-
LoggedInUser *oauth.MultiAccountUser
1179
+
LoggedInUser *oauth.User
1182
1180
RepoInfo repoinfo.RepoInfo
1183
1181
Pull *models.Pull
1184
1182
SubmissionId int
···
1189
1187
}
1190
1188
1191
1189
type PullActionsParams struct {
1192
-
LoggedInUser *oauth.MultiAccountUser
1190
+
LoggedInUser *oauth.User
1193
1191
RepoInfo repoinfo.RepoInfo
1194
1192
Pull *models.Pull
1195
1193
RoundNumber int
···
1204
1202
}
1205
1203
1206
1204
type PullNewCommentParams struct {
1207
-
LoggedInUser *oauth.MultiAccountUser
1205
+
LoggedInUser *oauth.User
1208
1206
RepoInfo repoinfo.RepoInfo
1209
1207
Pull *models.Pull
1210
1208
RoundNumber int
···
1215
1213
}
1216
1214
1217
1215
type RepoCompareParams struct {
1218
-
LoggedInUser *oauth.MultiAccountUser
1216
+
LoggedInUser *oauth.User
1219
1217
RepoInfo repoinfo.RepoInfo
1220
1218
Forks []models.Repo
1221
1219
Branches []types.Branch
···
1234
1232
}
1235
1233
1236
1234
type RepoCompareNewParams struct {
1237
-
LoggedInUser *oauth.MultiAccountUser
1235
+
LoggedInUser *oauth.User
1238
1236
RepoInfo repoinfo.RepoInfo
1239
1237
Forks []models.Repo
1240
1238
Branches []types.Branch
···
1251
1249
}
1252
1250
1253
1251
type RepoCompareAllowPullParams struct {
1254
-
LoggedInUser *oauth.MultiAccountUser
1252
+
LoggedInUser *oauth.User
1255
1253
RepoInfo repoinfo.RepoInfo
1256
1254
Base string
1257
1255
Head string
···
1271
1269
}
1272
1270
1273
1271
type LabelPanelParams struct {
1274
-
LoggedInUser *oauth.MultiAccountUser
1272
+
LoggedInUser *oauth.User
1275
1273
RepoInfo repoinfo.RepoInfo
1276
1274
Defs map[string]*models.LabelDefinition
1277
1275
Subject string
···
1283
1281
}
1284
1282
1285
1283
type EditLabelPanelParams struct {
1286
-
LoggedInUser *oauth.MultiAccountUser
1284
+
LoggedInUser *oauth.User
1287
1285
RepoInfo repoinfo.RepoInfo
1288
1286
Defs map[string]*models.LabelDefinition
1289
1287
Subject string
···
1295
1293
}
1296
1294
1297
1295
type PipelinesParams struct {
1298
-
LoggedInUser *oauth.MultiAccountUser
1296
+
LoggedInUser *oauth.User
1299
1297
RepoInfo repoinfo.RepoInfo
1300
1298
Pipelines []models.Pipeline
1301
1299
Active string
···
1338
1336
}
1339
1337
1340
1338
type WorkflowParams struct {
1341
-
LoggedInUser *oauth.MultiAccountUser
1339
+
LoggedInUser *oauth.User
1342
1340
RepoInfo repoinfo.RepoInfo
1343
1341
Pipeline models.Pipeline
1344
1342
Workflow string
···
1352
1350
}
1353
1351
1354
1352
type PutStringParams struct {
1355
-
LoggedInUser *oauth.MultiAccountUser
1353
+
LoggedInUser *oauth.User
1356
1354
Action string
1357
1355
1358
1356
// this is supplied in the case of editing an existing string
···
1364
1362
}
1365
1363
1366
1364
type StringsDashboardParams struct {
1367
-
LoggedInUser *oauth.MultiAccountUser
1365
+
LoggedInUser *oauth.User
1368
1366
Card ProfileCard
1369
1367
Strings []models.String
1370
1368
}
···
1374
1372
}
1375
1373
1376
1374
type StringTimelineParams struct {
1377
-
LoggedInUser *oauth.MultiAccountUser
1375
+
LoggedInUser *oauth.User
1378
1376
Strings []models.String
1379
1377
}
1380
1378
···
1383
1381
}
1384
1382
1385
1383
type SingleStringParams struct {
1386
-
LoggedInUser *oauth.MultiAccountUser
1384
+
LoggedInUser *oauth.User
1387
1385
ShowRendered bool
1388
1386
RenderToggle bool
1389
1387
RenderedContents template.HTML
+11
-49
appview/pages/templates/layouts/fragments/topbar.html
+11
-49
appview/pages/templates/layouts/fragments/topbar.html
···
49
49
{{ define "profileDropdown" }}
50
50
<details class="relative inline-block text-left nav-dropdown">
51
51
<summary class="cursor-pointer list-none flex items-center gap-1">
52
-
{{ $user := .Active.Did }}
52
+
{{ $user := .Did }}
53
53
<img
54
54
src="{{ tinyAvatar $user }}"
55
55
alt=""
···
57
57
/>
58
58
<span class="hidden md:inline">{{ $user | resolve | truncateAt30 }}</span>
59
59
</summary>
60
-
<div class="absolute right-0 mt-4 p-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg z-50" style="width: 14rem;">
61
-
{{ $active := .Active.Did }}
62
-
63
-
<div class="pb-2 mb-2 border-b border-gray-200 dark:border-gray-700">
64
-
<div class="flex items-center gap-2">
65
-
<img src="{{ tinyAvatar $active }}" alt="" class="rounded-full h-8 w-8 flex-shrink-0 border border-gray-300 dark:border-gray-700" />
66
-
<div class="flex-1 overflow-hidden">
67
-
<p class="font-medium text-sm truncate">{{ $active | resolve }}</p>
68
-
<p class="text-xs text-green-600 dark:text-green-400">active</p>
69
-
</div>
70
-
</div>
71
-
</div>
72
-
73
-
{{ $others := .Accounts | otherAccounts $active }}
74
-
{{ if $others }}
75
-
<div class="pb-2 mb-2 border-b border-gray-200 dark:border-gray-700">
76
-
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-1">Switch Account</p>
77
-
{{ range $others }}
78
-
<button
79
-
type="button"
80
-
hx-post="/account/switch"
81
-
hx-vals='{"did": "{{ .Did }}"}'
82
-
hx-swap="none"
83
-
class="flex items-center gap-2 w-full py-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-left"
84
-
>
85
-
<img src="{{ tinyAvatar .Did }}" alt="" class="rounded-full h-6 w-6 flex-shrink-0 border border-gray-300 dark:border-gray-700" />
86
-
<span class="text-sm truncate flex-1">{{ .Did | resolve }}</span>
87
-
</button>
88
-
{{ end }}
89
-
</div>
90
-
{{ end }}
91
-
92
-
<a href="/login?mode=add_account" class="flex items-center gap-2 py-1 text-sm">
93
-
{{ i "plus" "w-4 h-4 flex-shrink-0" }}
94
-
<span>Add another account</span>
60
+
<div class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700">
61
+
<a href="/{{ $user }}">profile</a>
62
+
<a href="/{{ $user }}?tab=repos">repositories</a>
63
+
<a href="/{{ $user }}?tab=strings">strings</a>
64
+
<a href="/settings">settings</a>
65
+
<a href="#"
66
+
hx-post="/logout"
67
+
hx-swap="none"
68
+
class="text-red-400 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
69
+
logout
95
70
</a>
96
-
97
-
<div class="pt-2 mt-2 border-t border-gray-200 dark:border-gray-700 space-y-1">
98
-
<a href="/{{ $active }}" class="block py-1 text-sm">profile</a>
99
-
<a href="/{{ $active }}?tab=repos" class="block py-1 text-sm">repositories</a>
100
-
<a href="/{{ $active }}?tab=strings" class="block py-1 text-sm">strings</a>
101
-
<a href="/settings" class="block py-1 text-sm">settings</a>
102
-
<a href="#"
103
-
hx-post="/logout"
104
-
hx-swap="none"
105
-
class="block py-1 text-sm text-red-400 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
106
-
logout
107
-
</a>
108
-
</div>
109
71
</div>
110
72
</details>
111
73
+2
-2
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
+2
-2
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
···
1
1
{{ define "repo/issues/fragments/issueCommentHeader" }}
2
2
<div class="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400 ">
3
-
{{ template "user/fragments/picHandleLink" .Comment.Did }}
3
+
{{ template "user/fragments/picHandleLink" .Comment.Did.String }}
4
4
{{ template "hats" $ }}
5
5
{{ template "timestamp" . }}
6
-
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did) }}
6
+
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did.String) }}
7
7
{{ if and $isCommentOwner (not .Comment.Deleted) }}
8
8
{{ template "editIssueComment" . }}
9
9
{{ template "deleteIssueComment" . }}
+3
-3
appview/pages/templates/repo/pulls/pull.html
+3
-3
appview/pages/templates/repo/pulls/pull.html
···
165
165
166
166
<div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative">
167
167
{{ range $cidx, $c := .Comments }}
168
-
<div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full">
168
+
<div id="comment-{{$c.Id}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full">
169
169
{{ if gt $cidx 0 }}
170
170
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
171
171
{{ end }}
172
172
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
173
-
{{ template "user/fragments/picHandleLink" $c.OwnerDid }}
173
+
{{ template "user/fragments/picHandleLink" $c.Did.String }}
174
174
<span class="before:content-['ยท']"></span>
175
-
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}">{{ template "repo/fragments/time" $c.Created }}</a>
175
+
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.Id}}">{{ template "repo/fragments/time" $c.Created }}</a>
176
176
</div>
177
177
<div class="prose dark:prose-invert">
178
178
{{ $c.Body | markdown }}
-53
appview/pages/templates/user/login.html
-53
appview/pages/templates/user/login.html
···
20
20
<h2 class="text-center text-xl italic dark:text-white">
21
21
tightly-knit social coding.
22
22
</h2>
23
-
24
-
{{ if .AddAccount }}
25
-
<div class="flex gap-2 my-4 bg-blue-50 dark:bg-blue-900/30 border border-blue-300 dark:border-sky-800 rounded px-3 py-2 text-blue-600 dark:text-blue-300">
26
-
<span class="py-1">{{ i "user-plus" "w-4 h-4" }}</span>
27
-
<div>
28
-
<h5 class="font-medium">Add another account</h5>
29
-
<p class="text-sm">Sign in with a different account to add it to your account list.</p>
30
-
</div>
31
-
</div>
32
-
{{ end }}
33
-
34
-
{{ if and .LoggedInUser .LoggedInUser.Accounts }}
35
-
{{ $accounts := .LoggedInUser.Accounts }}
36
-
{{ if $accounts }}
37
-
<div class="my-4 border border-gray-200 dark:border-gray-700 rounded overflow-hidden">
38
-
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
39
-
<span class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide font-medium">Saved accounts</span>
40
-
</div>
41
-
<div class="divide-y divide-gray-200 dark:divide-gray-700">
42
-
{{ range $accounts }}
43
-
<div class="flex items-center justify-between px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700">
44
-
<button
45
-
type="button"
46
-
hx-post="/account/switch"
47
-
hx-vals='{"did": "{{ .Did }}"}'
48
-
hx-swap="none"
49
-
class="flex items-center gap-2 flex-1 text-left min-w-0"
50
-
>
51
-
<img src="{{ tinyAvatar .Did }}" alt="" class="rounded-full h-8 w-8 flex-shrink-0 border border-gray-300 dark:border-gray-700" />
52
-
<div class="flex flex-col min-w-0">
53
-
<span class="text-sm font-medium dark:text-white truncate">{{ .Did | resolve | truncateAt30 }}</span>
54
-
<span class="text-xs text-gray-500 dark:text-gray-400">Click to switch</span>
55
-
</div>
56
-
</button>
57
-
<button
58
-
type="button"
59
-
hx-delete="/account/{{ .Did }}"
60
-
hx-swap="none"
61
-
class="p-1 text-gray-400 hover:text-red-500 dark:hover:text-red-400 flex-shrink-0"
62
-
title="Remove account"
63
-
>
64
-
{{ i "x" "w-4 h-4" }}
65
-
</button>
66
-
</div>
67
-
{{ end }}
68
-
</div>
69
-
</div>
70
-
{{ end }}
71
-
{{ end }}
72
-
73
23
<form
74
24
class="mt-4"
75
25
hx-post="/login"
···
96
46
</span>
97
47
</div>
98
48
<input type="hidden" name="return_url" value="{{ .ReturnUrl }}">
99
-
<input type="hidden" name="add_account" value="{{ if .AddAccount }}true{{ end }}">
100
49
101
50
<button
102
51
class="btn w-full my-2 mt-6 text-base "
···
117
66
You have not authorized the app.
118
67
{{ else if eq .ErrorCode "session" }}
119
68
Server failed to create user session.
120
-
{{ else if eq .ErrorCode "max_accounts" }}
121
-
You have reached the maximum of 20 linked accounts. Please remove an account before adding a new one.
122
69
{{ else }}
123
70
Internal Server error.
124
71
{{ end }}
+2
-2
appview/pipelines/pipelines.go
+2
-2
appview/pipelines/pipelines.go
···
70
70
}
71
71
72
72
func (p *Pipelines) Index(w http.ResponseWriter, r *http.Request) {
73
-
user := p.oauth.GetMultiAccountUser(r)
73
+
user := p.oauth.GetUser(r)
74
74
l := p.logger.With("handler", "Index")
75
75
76
76
f, err := p.repoResolver.Resolve(r)
···
99
99
}
100
100
101
101
func (p *Pipelines) Workflow(w http.ResponseWriter, r *http.Request) {
102
-
user := p.oauth.GetMultiAccountUser(r)
102
+
user := p.oauth.GetUser(r)
103
103
l := p.logger.With("handler", "Workflow")
104
104
105
105
f, err := p.repoResolver.Resolve(r)
+1
-1
appview/pulls/opengraph.go
+1
-1
appview/pulls/opengraph.go
···
277
277
}
278
278
279
279
// Get comment count from database
280
-
comments, err := db.GetPullComments(s.db, orm.FilterEq("pull_id", pull.ID))
280
+
comments, err := db.GetComments(s.db, orm.FilterEq("subject_at", pull.AtUri()))
281
281
if err != nil {
282
282
log.Printf("failed to get pull comments: %v", err)
283
283
}
+78
-77
appview/pulls/pulls.go
+78
-77
appview/pulls/pulls.go
···
93
93
func (s *Pulls) PullActions(w http.ResponseWriter, r *http.Request) {
94
94
switch r.Method {
95
95
case http.MethodGet:
96
-
user := s.oauth.GetMultiAccountUser(r)
96
+
user := s.oauth.GetUser(r)
97
97
f, err := s.repoResolver.Resolve(r)
98
98
if err != nil {
99
99
log.Println("failed to get repo and knot", err)
···
124
124
mergeCheckResponse := s.mergeCheck(r, f, pull, stack)
125
125
branchDeleteStatus := s.branchDeleteStatus(r, f, pull)
126
126
resubmitResult := pages.Unknown
127
-
if user.Active.Did == pull.OwnerDid {
127
+
if user.Did == pull.OwnerDid {
128
128
resubmitResult = s.resubmitCheck(r, f, pull, stack)
129
129
}
130
130
···
143
143
}
144
144
145
145
func (s *Pulls) RepoSinglePull(w http.ResponseWriter, r *http.Request) {
146
-
user := s.oauth.GetMultiAccountUser(r)
146
+
user := s.oauth.GetUser(r)
147
147
f, err := s.repoResolver.Resolve(r)
148
148
if err != nil {
149
149
log.Println("failed to get repo and knot", err)
···
171
171
mergeCheckResponse := s.mergeCheck(r, f, pull, stack)
172
172
branchDeleteStatus := s.branchDeleteStatus(r, f, pull)
173
173
resubmitResult := pages.Unknown
174
-
if user != nil && user.Active != nil && user.Active.Did == pull.OwnerDid {
174
+
if user != nil && user.Did == pull.OwnerDid {
175
175
resubmitResult = s.resubmitCheck(r, f, pull, stack)
176
176
}
177
177
···
213
213
214
214
userReactions := map[models.ReactionKind]bool{}
215
215
if user != nil {
216
-
userReactions = db.GetReactionStatusMap(s.db, user.Active.Did, pull.AtUri())
216
+
userReactions = db.GetReactionStatusMap(s.db, user.Did, pull.AtUri())
217
217
}
218
218
219
219
labelDefs, err := db.GetLabelDefinitions(
···
324
324
return nil
325
325
}
326
326
327
-
user := s.oauth.GetMultiAccountUser(r)
327
+
user := s.oauth.GetUser(r)
328
328
if user == nil {
329
329
return nil
330
330
}
···
347
347
}
348
348
349
349
// user can only delete branch if they are a collaborator in the repo that the branch belongs to
350
-
perms := s.enforcer.GetPermissionsInRepo(user.Active.Did, repo.Knot, repo.DidSlashRepo())
350
+
perms := s.enforcer.GetPermissionsInRepo(user.Did, repo.Knot, repo.DidSlashRepo())
351
351
if !slices.Contains(perms, "repo:push") {
352
352
return nil
353
353
}
···
434
434
}
435
435
436
436
func (s *Pulls) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
437
-
user := s.oauth.GetMultiAccountUser(r)
437
+
user := s.oauth.GetUser(r)
438
438
439
439
var diffOpts types.DiffOpts
440
440
if d := r.URL.Query().Get("diff"); d == "split" {
···
475
475
}
476
476
477
477
func (s *Pulls) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) {
478
-
user := s.oauth.GetMultiAccountUser(r)
478
+
user := s.oauth.GetUser(r)
479
479
480
480
var diffOpts types.DiffOpts
481
481
if d := r.URL.Query().Get("diff"); d == "split" {
···
520
520
interdiff := patchutil.Interdiff(previousPatch, currentPatch)
521
521
522
522
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
523
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
523
+
LoggedInUser: s.oauth.GetUser(r),
524
524
RepoInfo: s.repoResolver.GetRepoInfo(r, user),
525
525
Pull: pull,
526
526
Round: roundIdInt,
···
552
552
func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) {
553
553
l := s.logger.With("handler", "RepoPulls")
554
554
555
-
user := s.oauth.GetMultiAccountUser(r)
555
+
user := s.oauth.GetUser(r)
556
556
params := r.URL.Query()
557
557
558
558
state := models.PullOpen
···
680
680
}
681
681
682
682
s.pages.RepoPulls(w, pages.RepoPullsParams{
683
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
683
+
LoggedInUser: s.oauth.GetUser(r),
684
684
RepoInfo: s.repoResolver.GetRepoInfo(r, user),
685
685
Pulls: pulls,
686
686
LabelDefs: defs,
···
692
692
}
693
693
694
694
func (s *Pulls) PullComment(w http.ResponseWriter, r *http.Request) {
695
-
user := s.oauth.GetMultiAccountUser(r)
695
+
user := s.oauth.GetUser(r)
696
696
f, err := s.repoResolver.Resolve(r)
697
697
if err != nil {
698
698
log.Println("failed to get repo and knot", err)
···
741
741
}
742
742
defer tx.Rollback()
743
743
744
-
createdAt := time.Now().Format(time.RFC3339)
744
+
comment := models.Comment{
745
+
Did: syntax.DID(user.Did),
746
+
Rkey: tid.TID(),
747
+
Subject: pull.AtUri(),
748
+
ReplyTo: nil,
749
+
Body: body,
750
+
Created: time.Now(),
751
+
Mentions: mentions,
752
+
References: references,
753
+
PullSubmissionId: &pull.Submissions[roundNumber].ID,
754
+
}
755
+
if err = comment.Validate(); err != nil {
756
+
log.Println("failed to validate comment", err)
757
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
758
+
return
759
+
}
760
+
record := comment.AsRecord()
745
761
746
762
client, err := s.oauth.AuthorizedClient(r)
747
763
if err != nil {
···
749
765
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
750
766
return
751
767
}
752
-
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
753
-
Collection: tangled.RepoPullCommentNSID,
754
-
Repo: user.Active.Did,
755
-
Rkey: tid.TID(),
768
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
769
+
Collection: tangled.CommentNSID,
770
+
Repo: user.Did,
771
+
Rkey: comment.Rkey,
756
772
Record: &lexutil.LexiconTypeDecoder{
757
-
Val: &tangled.RepoPullComment{
758
-
Pull: pull.AtUri().String(),
759
-
Body: body,
760
-
CreatedAt: createdAt,
761
-
},
773
+
Val: &record,
762
774
},
763
775
})
764
776
if err != nil {
···
767
779
return
768
780
}
769
781
770
-
comment := &models.PullComment{
771
-
OwnerDid: user.Active.Did,
772
-
RepoAt: f.RepoAt().String(),
773
-
PullId: pull.PullId,
774
-
Body: body,
775
-
CommentAt: atResp.Uri,
776
-
SubmissionId: pull.Submissions[roundNumber].ID,
777
-
Mentions: mentions,
778
-
References: references,
779
-
}
780
-
781
782
// Create the pull comment in the database with the commentAt field
782
-
commentId, err := db.NewPullComment(tx, comment)
783
+
err = db.PutComment(tx, &comment)
783
784
if err != nil {
784
785
log.Println("failed to create pull comment", err)
785
786
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
···
793
794
return
794
795
}
795
796
796
-
s.notifier.NewPullComment(r.Context(), comment, mentions)
797
+
s.notifier.NewComment(r.Context(), &comment)
797
798
798
799
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
799
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, commentId))
800
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id))
800
801
return
801
802
}
802
803
}
803
804
804
805
func (s *Pulls) NewPull(w http.ResponseWriter, r *http.Request) {
805
-
user := s.oauth.GetMultiAccountUser(r)
806
+
user := s.oauth.GetUser(r)
806
807
f, err := s.repoResolver.Resolve(r)
807
808
if err != nil {
808
809
log.Println("failed to get repo and knot", err)
···
870
871
}
871
872
872
873
// Determine PR type based on input parameters
873
-
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
874
+
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
874
875
isPushAllowed := roles.IsPushAllowed()
875
876
isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == ""
876
877
isForkBased := fromFork != "" && sourceBranch != ""
···
970
971
w http.ResponseWriter,
971
972
r *http.Request,
972
973
repo *models.Repo,
973
-
user *oauth.MultiAccountUser,
974
+
user *oauth.User,
974
975
title,
975
976
body,
976
977
targetBranch,
···
1027
1028
s.createPullRequest(w, r, repo, user, title, body, targetBranch, patch, combined, sourceRev, pullSource, recordPullSource, isStacked)
1028
1029
}
1029
1030
1030
-
func (s *Pulls) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, user *oauth.MultiAccountUser, title, body, targetBranch, patch string, isStacked bool) {
1031
+
func (s *Pulls) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, user *oauth.User, title, body, targetBranch, patch string, isStacked bool) {
1031
1032
if err := s.validator.ValidatePatch(&patch); err != nil {
1032
1033
s.logger.Error("patch validation failed", "err", err)
1033
1034
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
···
1037
1038
s.createPullRequest(w, r, repo, user, title, body, targetBranch, patch, "", "", nil, nil, isStacked)
1038
1039
}
1039
1040
1040
-
func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, user *oauth.MultiAccountUser, forkRepo string, title, body, targetBranch, sourceBranch string, isStacked bool) {
1041
+
func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, user *oauth.User, forkRepo string, title, body, targetBranch, sourceBranch string, isStacked bool) {
1041
1042
repoString := strings.SplitN(forkRepo, "/", 2)
1042
1043
forkOwnerDid := repoString[0]
1043
1044
repoName := repoString[1]
···
1146
1147
w http.ResponseWriter,
1147
1148
r *http.Request,
1148
1149
repo *models.Repo,
1149
-
user *oauth.MultiAccountUser,
1150
+
user *oauth.User,
1150
1151
title, body, targetBranch string,
1151
1152
patch string,
1152
1153
combined string,
···
1218
1219
Title: title,
1219
1220
Body: body,
1220
1221
TargetBranch: targetBranch,
1221
-
OwnerDid: user.Active.Did,
1222
+
OwnerDid: user.Did,
1222
1223
RepoAt: repo.RepoAt(),
1223
1224
Rkey: rkey,
1224
1225
Mentions: mentions,
···
1250
1251
1251
1252
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1252
1253
Collection: tangled.RepoPullNSID,
1253
-
Repo: user.Active.Did,
1254
+
Repo: user.Did,
1254
1255
Rkey: rkey,
1255
1256
Record: &lexutil.LexiconTypeDecoder{
1256
1257
Val: &tangled.RepoPull{
···
1287
1288
w http.ResponseWriter,
1288
1289
r *http.Request,
1289
1290
repo *models.Repo,
1290
-
user *oauth.MultiAccountUser,
1291
+
user *oauth.User,
1291
1292
targetBranch string,
1292
1293
patch string,
1293
1294
sourceRev string,
···
1355
1356
})
1356
1357
}
1357
1358
_, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
1358
-
Repo: user.Active.Did,
1359
+
Repo: user.Did,
1359
1360
Writes: writes,
1360
1361
})
1361
1362
if err != nil {
···
1427
1428
}
1428
1429
1429
1430
func (s *Pulls) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
1430
-
user := s.oauth.GetMultiAccountUser(r)
1431
+
user := s.oauth.GetUser(r)
1431
1432
1432
1433
s.pages.PullPatchUploadFragment(w, pages.PullPatchUploadParams{
1433
1434
RepoInfo: s.repoResolver.GetRepoInfo(r, user),
···
1435
1436
}
1436
1437
1437
1438
func (s *Pulls) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) {
1438
-
user := s.oauth.GetMultiAccountUser(r)
1439
+
user := s.oauth.GetUser(r)
1439
1440
f, err := s.repoResolver.Resolve(r)
1440
1441
if err != nil {
1441
1442
log.Println("failed to get repo and knot", err)
···
1490
1491
}
1491
1492
1492
1493
func (s *Pulls) CompareForksFragment(w http.ResponseWriter, r *http.Request) {
1493
-
user := s.oauth.GetMultiAccountUser(r)
1494
+
user := s.oauth.GetUser(r)
1494
1495
1495
-
forks, err := db.GetForksByDid(s.db, user.Active.Did)
1496
+
forks, err := db.GetForksByDid(s.db, user.Did)
1496
1497
if err != nil {
1497
1498
log.Println("failed to get forks", err)
1498
1499
return
···
1506
1507
}
1507
1508
1508
1509
func (s *Pulls) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) {
1509
-
user := s.oauth.GetMultiAccountUser(r)
1510
+
user := s.oauth.GetUser(r)
1510
1511
1511
1512
f, err := s.repoResolver.Resolve(r)
1512
1513
if err != nil {
···
1599
1600
}
1600
1601
1601
1602
func (s *Pulls) ResubmitPull(w http.ResponseWriter, r *http.Request) {
1602
-
user := s.oauth.GetMultiAccountUser(r)
1603
+
user := s.oauth.GetUser(r)
1603
1604
1604
1605
pull, ok := r.Context().Value("pull").(*models.Pull)
1605
1606
if !ok {
···
1630
1631
}
1631
1632
1632
1633
func (s *Pulls) resubmitPatch(w http.ResponseWriter, r *http.Request) {
1633
-
user := s.oauth.GetMultiAccountUser(r)
1634
+
user := s.oauth.GetUser(r)
1634
1635
1635
1636
pull, ok := r.Context().Value("pull").(*models.Pull)
1636
1637
if !ok {
···
1645
1646
return
1646
1647
}
1647
1648
1648
-
if user.Active.Did != pull.OwnerDid {
1649
+
if user.Did != pull.OwnerDid {
1649
1650
log.Println("unauthorized user")
1650
1651
w.WriteHeader(http.StatusUnauthorized)
1651
1652
return
···
1657
1658
}
1658
1659
1659
1660
func (s *Pulls) resubmitBranch(w http.ResponseWriter, r *http.Request) {
1660
-
user := s.oauth.GetMultiAccountUser(r)
1661
+
user := s.oauth.GetUser(r)
1661
1662
1662
1663
pull, ok := r.Context().Value("pull").(*models.Pull)
1663
1664
if !ok {
···
1672
1673
return
1673
1674
}
1674
1675
1675
-
if user.Active.Did != pull.OwnerDid {
1676
+
if user.Did != pull.OwnerDid {
1676
1677
log.Println("unauthorized user")
1677
1678
w.WriteHeader(http.StatusUnauthorized)
1678
1679
return
1679
1680
}
1680
1681
1681
-
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
1682
+
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
1682
1683
if !roles.IsPushAllowed() {
1683
1684
log.Println("unauthorized user")
1684
1685
w.WriteHeader(http.StatusUnauthorized)
···
1722
1723
}
1723
1724
1724
1725
func (s *Pulls) resubmitFork(w http.ResponseWriter, r *http.Request) {
1725
-
user := s.oauth.GetMultiAccountUser(r)
1726
+
user := s.oauth.GetUser(r)
1726
1727
1727
1728
pull, ok := r.Context().Value("pull").(*models.Pull)
1728
1729
if !ok {
···
1737
1738
return
1738
1739
}
1739
1740
1740
-
if user.Active.Did != pull.OwnerDid {
1741
+
if user.Did != pull.OwnerDid {
1741
1742
log.Println("unauthorized user")
1742
1743
w.WriteHeader(http.StatusUnauthorized)
1743
1744
return
···
1822
1823
w http.ResponseWriter,
1823
1824
r *http.Request,
1824
1825
repo *models.Repo,
1825
-
user *oauth.MultiAccountUser,
1826
+
user *oauth.User,
1826
1827
pull *models.Pull,
1827
1828
patch string,
1828
1829
combined string,
···
1878
1879
return
1879
1880
}
1880
1881
1881
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Active.Did, pull.Rkey)
1882
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1882
1883
if err != nil {
1883
1884
// failed to get record
1884
1885
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
···
1897
1898
1898
1899
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1899
1900
Collection: tangled.RepoPullNSID,
1900
-
Repo: user.Active.Did,
1901
+
Repo: user.Did,
1901
1902
Rkey: pull.Rkey,
1902
1903
SwapRecord: ex.Cid,
1903
1904
Record: &lexutil.LexiconTypeDecoder{
···
1924
1925
w http.ResponseWriter,
1925
1926
r *http.Request,
1926
1927
repo *models.Repo,
1927
-
user *oauth.MultiAccountUser,
1928
+
user *oauth.User,
1928
1929
pull *models.Pull,
1929
1930
patch string,
1930
1931
stackId string,
···
2114
2115
}
2115
2116
2116
2117
_, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
2117
-
Repo: user.Active.Did,
2118
+
Repo: user.Did,
2118
2119
Writes: writes,
2119
2120
})
2120
2121
if err != nil {
···
2128
2129
}
2129
2130
2130
2131
func (s *Pulls) MergePull(w http.ResponseWriter, r *http.Request) {
2131
-
user := s.oauth.GetMultiAccountUser(r)
2132
+
user := s.oauth.GetUser(r)
2132
2133
f, err := s.repoResolver.Resolve(r)
2133
2134
if err != nil {
2134
2135
log.Println("failed to resolve repo:", err)
···
2239
2240
2240
2241
// notify about the pull merge
2241
2242
for _, p := range pullsToMerge {
2242
-
s.notifier.NewPullState(r.Context(), syntax.DID(user.Active.Did), p)
2243
+
s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), p)
2243
2244
}
2244
2245
2245
2246
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
···
2247
2248
}
2248
2249
2249
2250
func (s *Pulls) ClosePull(w http.ResponseWriter, r *http.Request) {
2250
-
user := s.oauth.GetMultiAccountUser(r)
2251
+
user := s.oauth.GetUser(r)
2251
2252
2252
2253
f, err := s.repoResolver.Resolve(r)
2253
2254
if err != nil {
···
2263
2264
}
2264
2265
2265
2266
// auth filter: only owner or collaborators can close
2266
-
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
2267
+
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
2267
2268
isOwner := roles.IsOwner()
2268
2269
isCollaborator := roles.IsCollaborator()
2269
-
isPullAuthor := user.Active.Did == pull.OwnerDid
2270
+
isPullAuthor := user.Did == pull.OwnerDid
2270
2271
isCloseAllowed := isOwner || isCollaborator || isPullAuthor
2271
2272
if !isCloseAllowed {
2272
2273
log.Println("failed to close pull")
···
2312
2313
}
2313
2314
2314
2315
for _, p := range pullsToClose {
2315
-
s.notifier.NewPullState(r.Context(), syntax.DID(user.Active.Did), p)
2316
+
s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), p)
2316
2317
}
2317
2318
2318
2319
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
···
2320
2321
}
2321
2322
2322
2323
func (s *Pulls) ReopenPull(w http.ResponseWriter, r *http.Request) {
2323
-
user := s.oauth.GetMultiAccountUser(r)
2324
+
user := s.oauth.GetUser(r)
2324
2325
2325
2326
f, err := s.repoResolver.Resolve(r)
2326
2327
if err != nil {
···
2337
2338
}
2338
2339
2339
2340
// auth filter: only owner or collaborators can close
2340
-
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())}
2341
+
roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Did, f.Knot, f.DidSlashRepo())}
2341
2342
isOwner := roles.IsOwner()
2342
2343
isCollaborator := roles.IsCollaborator()
2343
-
isPullAuthor := user.Active.Did == pull.OwnerDid
2344
+
isPullAuthor := user.Did == pull.OwnerDid
2344
2345
isCloseAllowed := isOwner || isCollaborator || isPullAuthor
2345
2346
if !isCloseAllowed {
2346
2347
log.Println("failed to close pull")
···
2386
2387
}
2387
2388
2388
2389
for _, p := range pullsToReopen {
2389
-
s.notifier.NewPullState(r.Context(), syntax.DID(user.Active.Did), p)
2390
+
s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), p)
2390
2391
}
2391
2392
2392
2393
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
2393
2394
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId))
2394
2395
}
2395
2396
2396
-
func (s *Pulls) newStack(ctx context.Context, repo *models.Repo, user *oauth.MultiAccountUser, targetBranch, patch string, pullSource *models.PullSource, stackId string) (models.Stack, error) {
2397
+
func (s *Pulls) newStack(ctx context.Context, repo *models.Repo, user *oauth.User, targetBranch, patch string, pullSource *models.PullSource, stackId string) (models.Stack, error) {
2397
2398
formatPatches, err := patchutil.ExtractPatches(patch)
2398
2399
if err != nil {
2399
2400
return nil, fmt.Errorf("Failed to extract patches: %v", err)
···
2429
2430
Title: title,
2430
2431
Body: body,
2431
2432
TargetBranch: targetBranch,
2432
-
OwnerDid: user.Active.Did,
2433
+
OwnerDid: user.Did,
2433
2434
RepoAt: repo.RepoAt(),
2434
2435
Rkey: rkey,
2435
2436
Mentions: mentions,
+6
-6
appview/repo/artifact.go
+6
-6
appview/repo/artifact.go
···
30
30
31
31
// TODO: proper statuses here on early exit
32
32
func (rp *Repo) AttachArtifact(w http.ResponseWriter, r *http.Request) {
33
-
user := rp.oauth.GetMultiAccountUser(r)
33
+
user := rp.oauth.GetUser(r)
34
34
tagParam := chi.URLParam(r, "tag")
35
35
f, err := rp.repoResolver.Resolve(r)
36
36
if err != nil {
···
75
75
76
76
putRecordResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
77
77
Collection: tangled.RepoArtifactNSID,
78
-
Repo: user.Active.Did,
78
+
Repo: user.Did,
79
79
Rkey: rkey,
80
80
Record: &lexutil.LexiconTypeDecoder{
81
81
Val: &tangled.RepoArtifact{
···
104
104
defer tx.Rollback()
105
105
106
106
artifact := models.Artifact{
107
-
Did: user.Active.Did,
107
+
Did: user.Did,
108
108
Rkey: rkey,
109
109
RepoAt: f.RepoAt(),
110
110
Tag: tag.Tag.Hash,
···
220
220
221
221
// TODO: proper statuses here on early exit
222
222
func (rp *Repo) DeleteArtifact(w http.ResponseWriter, r *http.Request) {
223
-
user := rp.oauth.GetMultiAccountUser(r)
223
+
user := rp.oauth.GetUser(r)
224
224
tagParam := chi.URLParam(r, "tag")
225
225
filename := chi.URLParam(r, "file")
226
226
f, err := rp.repoResolver.Resolve(r)
···
251
251
252
252
artifact := artifacts[0]
253
253
254
-
if user.Active.Did != artifact.Did {
254
+
if user.Did != artifact.Did {
255
255
log.Println("user not authorized to delete artifact", err)
256
256
rp.pages.Notice(w, "remove", "Unauthorized deletion of artifact.")
257
257
return
···
259
259
260
260
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
261
261
Collection: tangled.RepoArtifactNSID,
262
-
Repo: user.Active.Did,
262
+
Repo: user.Did,
263
263
Rkey: artifact.Rkey,
264
264
})
265
265
if err != nil {
+1
-1
appview/repo/blob.go
+1
-1
appview/repo/blob.go
+1
-1
appview/repo/branches.go
+1
-1
appview/repo/branches.go
+2
-2
appview/repo/compare.go
+2
-2
appview/repo/compare.go
···
20
20
func (rp *Repo) CompareNew(w http.ResponseWriter, r *http.Request) {
21
21
l := rp.logger.With("handler", "RepoCompareNew")
22
22
23
-
user := rp.oauth.GetMultiAccountUser(r)
23
+
user := rp.oauth.GetUser(r)
24
24
f, err := rp.repoResolver.Resolve(r)
25
25
if err != nil {
26
26
l.Error("failed to get repo and knot", "err", err)
···
101
101
func (rp *Repo) Compare(w http.ResponseWriter, r *http.Request) {
102
102
l := rp.logger.With("handler", "RepoCompare")
103
103
104
-
user := rp.oauth.GetMultiAccountUser(r)
104
+
user := rp.oauth.GetUser(r)
105
105
f, err := rp.repoResolver.Resolve(r)
106
106
if err != nil {
107
107
l.Error("failed to get repo and knot", "err", err)
+1
-1
appview/repo/index.go
+1
-1
appview/repo/index.go
+2
-2
appview/repo/log.go
+2
-2
appview/repo/log.go
···
109
109
}
110
110
}
111
111
112
-
user := rp.oauth.GetMultiAccountUser(r)
112
+
user := rp.oauth.GetUser(r)
113
113
114
114
emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(xrpcResp.Commits), true)
115
115
if err != nil {
···
197
197
l.Error("failed to GetVerifiedCommits", "err", err)
198
198
}
199
199
200
-
user := rp.oauth.GetMultiAccountUser(r)
200
+
user := rp.oauth.GetUser(r)
201
201
pipelines, err := getPipelineStatuses(rp.db, f, []string{result.Diff.Commit.This})
202
202
if err != nil {
203
203
l.Error("failed to getPipelineStatuses", "err", err)
+34
-34
appview/repo/repo.go
+34
-34
appview/repo/repo.go
···
81
81
82
82
// modify the spindle configured for this repo
83
83
func (rp *Repo) EditSpindle(w http.ResponseWriter, r *http.Request) {
84
-
user := rp.oauth.GetMultiAccountUser(r)
84
+
user := rp.oauth.GetUser(r)
85
85
l := rp.logger.With("handler", "EditSpindle")
86
-
l = l.With("did", user.Active.Did)
86
+
l = l.With("did", user.Did)
87
87
88
88
errorId := "operation-error"
89
89
fail := func(msg string, err error) {
···
107
107
108
108
if !removingSpindle {
109
109
// ensure that this is a valid spindle for this user
110
-
validSpindles, err := rp.enforcer.GetSpindlesForUser(user.Active.Did)
110
+
validSpindles, err := rp.enforcer.GetSpindlesForUser(user.Did)
111
111
if err != nil {
112
112
fail("Failed to find spindles. Try again later.", err)
113
113
return
···
168
168
}
169
169
170
170
func (rp *Repo) AddLabelDef(w http.ResponseWriter, r *http.Request) {
171
-
user := rp.oauth.GetMultiAccountUser(r)
171
+
user := rp.oauth.GetUser(r)
172
172
l := rp.logger.With("handler", "AddLabel")
173
-
l = l.With("did", user.Active.Did)
173
+
l = l.With("did", user.Did)
174
174
175
175
f, err := rp.repoResolver.Resolve(r)
176
176
if err != nil {
···
216
216
}
217
217
218
218
label := models.LabelDefinition{
219
-
Did: user.Active.Did,
219
+
Did: user.Did,
220
220
Rkey: tid.TID(),
221
221
Name: name,
222
222
ValueType: valueType,
···
327
327
}
328
328
329
329
func (rp *Repo) DeleteLabelDef(w http.ResponseWriter, r *http.Request) {
330
-
user := rp.oauth.GetMultiAccountUser(r)
330
+
user := rp.oauth.GetUser(r)
331
331
l := rp.logger.With("handler", "DeleteLabel")
332
-
l = l.With("did", user.Active.Did)
332
+
l = l.With("did", user.Did)
333
333
334
334
f, err := rp.repoResolver.Resolve(r)
335
335
if err != nil {
···
435
435
}
436
436
437
437
func (rp *Repo) SubscribeLabel(w http.ResponseWriter, r *http.Request) {
438
-
user := rp.oauth.GetMultiAccountUser(r)
438
+
user := rp.oauth.GetUser(r)
439
439
l := rp.logger.With("handler", "SubscribeLabel")
440
-
l = l.With("did", user.Active.Did)
440
+
l = l.With("did", user.Did)
441
441
442
442
f, err := rp.repoResolver.Resolve(r)
443
443
if err != nil {
···
521
521
}
522
522
523
523
func (rp *Repo) UnsubscribeLabel(w http.ResponseWriter, r *http.Request) {
524
-
user := rp.oauth.GetMultiAccountUser(r)
524
+
user := rp.oauth.GetUser(r)
525
525
l := rp.logger.With("handler", "UnsubscribeLabel")
526
-
l = l.With("did", user.Active.Did)
526
+
l = l.With("did", user.Did)
527
527
528
528
f, err := rp.repoResolver.Resolve(r)
529
529
if err != nil {
···
633
633
}
634
634
state := states[subject]
635
635
636
-
user := rp.oauth.GetMultiAccountUser(r)
636
+
user := rp.oauth.GetUser(r)
637
637
rp.pages.LabelPanel(w, pages.LabelPanelParams{
638
638
LoggedInUser: user,
639
639
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
···
681
681
}
682
682
state := states[subject]
683
683
684
-
user := rp.oauth.GetMultiAccountUser(r)
684
+
user := rp.oauth.GetUser(r)
685
685
rp.pages.EditLabelPanel(w, pages.EditLabelPanelParams{
686
686
LoggedInUser: user,
687
687
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
···
692
692
}
693
693
694
694
func (rp *Repo) AddCollaborator(w http.ResponseWriter, r *http.Request) {
695
-
user := rp.oauth.GetMultiAccountUser(r)
695
+
user := rp.oauth.GetUser(r)
696
696
l := rp.logger.With("handler", "AddCollaborator")
697
-
l = l.With("did", user.Active.Did)
697
+
l = l.With("did", user.Did)
698
698
699
699
f, err := rp.repoResolver.Resolve(r)
700
700
if err != nil {
···
723
723
return
724
724
}
725
725
726
-
if collaboratorIdent.DID.String() == user.Active.Did {
726
+
if collaboratorIdent.DID.String() == user.Did {
727
727
fail("You seem to be adding yourself as a collaborator.", nil)
728
728
return
729
729
}
···
738
738
}
739
739
740
740
// emit a record
741
-
currentUser := rp.oauth.GetMultiAccountUser(r)
741
+
currentUser := rp.oauth.GetUser(r)
742
742
rkey := tid.TID()
743
743
createdAt := time.Now()
744
744
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
745
745
Collection: tangled.RepoCollaboratorNSID,
746
-
Repo: currentUser.Active.Did,
746
+
Repo: currentUser.Did,
747
747
Rkey: rkey,
748
748
Record: &lexutil.LexiconTypeDecoder{
749
749
Val: &tangled.RepoCollaborator{
···
792
792
}
793
793
794
794
err = db.AddCollaborator(tx, models.Collaborator{
795
-
Did: syntax.DID(currentUser.Active.Did),
795
+
Did: syntax.DID(currentUser.Did),
796
796
Rkey: rkey,
797
797
SubjectDid: collaboratorIdent.DID,
798
798
RepoAt: f.RepoAt(),
···
822
822
}
823
823
824
824
func (rp *Repo) DeleteRepo(w http.ResponseWriter, r *http.Request) {
825
-
user := rp.oauth.GetMultiAccountUser(r)
825
+
user := rp.oauth.GetUser(r)
826
826
l := rp.logger.With("handler", "DeleteRepo")
827
827
828
828
noticeId := "operation-error"
···
840
840
}
841
841
_, err = comatproto.RepoDeleteRecord(r.Context(), atpClient, &comatproto.RepoDeleteRecord_Input{
842
842
Collection: tangled.RepoNSID,
843
-
Repo: user.Active.Did,
843
+
Repo: user.Did,
844
844
Rkey: f.Rkey,
845
845
})
846
846
if err != nil {
···
940
940
ref := chi.URLParam(r, "ref")
941
941
ref, _ = url.PathUnescape(ref)
942
942
943
-
user := rp.oauth.GetMultiAccountUser(r)
943
+
user := rp.oauth.GetUser(r)
944
944
f, err := rp.repoResolver.Resolve(r)
945
945
if err != nil {
946
946
l.Error("failed to resolve source repo", "err", err)
···
969
969
r.Context(),
970
970
client,
971
971
&tangled.RepoForkSync_Input{
972
-
Did: user.Active.Did,
972
+
Did: user.Did,
973
973
Name: f.Name,
974
974
Source: f.Source,
975
975
Branch: ref,
···
988
988
func (rp *Repo) ForkRepo(w http.ResponseWriter, r *http.Request) {
989
989
l := rp.logger.With("handler", "ForkRepo")
990
990
991
-
user := rp.oauth.GetMultiAccountUser(r)
991
+
user := rp.oauth.GetUser(r)
992
992
f, err := rp.repoResolver.Resolve(r)
993
993
if err != nil {
994
994
l.Error("failed to resolve source repo", "err", err)
···
997
997
998
998
switch r.Method {
999
999
case http.MethodGet:
1000
-
user := rp.oauth.GetMultiAccountUser(r)
1001
-
knots, err := rp.enforcer.GetKnotsForUser(user.Active.Did)
1000
+
user := rp.oauth.GetUser(r)
1001
+
knots, err := rp.enforcer.GetKnotsForUser(user.Did)
1002
1002
if err != nil {
1003
1003
rp.pages.Notice(w, "repo", "Invalid user account.")
1004
1004
return
···
1020
1020
}
1021
1021
l = l.With("targetKnot", targetKnot)
1022
1022
1023
-
ok, err := rp.enforcer.E.Enforce(user.Active.Did, targetKnot, targetKnot, "repo:create")
1023
+
ok, err := rp.enforcer.E.Enforce(user.Did, targetKnot, targetKnot, "repo:create")
1024
1024
if err != nil || !ok {
1025
1025
rp.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
1026
1026
return
···
1037
1037
// in the user's account.
1038
1038
existingRepo, err := db.GetRepo(
1039
1039
rp.db,
1040
-
orm.FilterEq("did", user.Active.Did),
1040
+
orm.FilterEq("did", user.Did),
1041
1041
orm.FilterEq("name", forkName),
1042
1042
)
1043
1043
if err != nil {
···
1066
1066
// create an atproto record for this fork
1067
1067
rkey := tid.TID()
1068
1068
repo := &models.Repo{
1069
-
Did: user.Active.Did,
1069
+
Did: user.Did,
1070
1070
Name: forkName,
1071
1071
Knot: targetKnot,
1072
1072
Rkey: rkey,
···
1086
1086
1087
1087
atresp, err := comatproto.RepoPutRecord(r.Context(), atpClient, &comatproto.RepoPutRecord_Input{
1088
1088
Collection: tangled.RepoNSID,
1089
-
Repo: user.Active.Did,
1089
+
Repo: user.Did,
1090
1090
Rkey: rkey,
1091
1091
Record: &lexutil.LexiconTypeDecoder{
1092
1092
Val: &record,
···
1165
1165
}
1166
1166
1167
1167
// acls
1168
-
p, _ := securejoin.SecureJoin(user.Active.Did, forkName)
1169
-
err = rp.enforcer.AddRepo(user.Active.Did, targetKnot, p)
1168
+
p, _ := securejoin.SecureJoin(user.Did, forkName)
1169
+
err = rp.enforcer.AddRepo(user.Did, targetKnot, p)
1170
1170
if err != nil {
1171
1171
l.Error("failed to add ACLs", "err", err)
1172
1172
rp.pages.Notice(w, "repo", "Failed to set up repository permissions.")
···
1191
1191
aturi = ""
1192
1192
1193
1193
rp.notifier.NewRepo(r.Context(), repo)
1194
-
rp.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Active.Did, forkName))
1194
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Did, forkName))
1195
1195
}
1196
1196
}
1197
1197
+5
-5
appview/repo/settings.go
+5
-5
appview/repo/settings.go
···
79
79
}
80
80
81
81
func (rp *Repo) Secrets(w http.ResponseWriter, r *http.Request) {
82
-
user := rp.oauth.GetMultiAccountUser(r)
82
+
user := rp.oauth.GetUser(r)
83
83
l := rp.logger.With("handler", "Secrets")
84
-
l = l.With("did", user.Active.Did)
84
+
l = l.With("did", user.Did)
85
85
86
86
f, err := rp.repoResolver.Resolve(r)
87
87
if err != nil {
···
185
185
l := rp.logger.With("handler", "generalSettings")
186
186
187
187
f, err := rp.repoResolver.Resolve(r)
188
-
user := rp.oauth.GetMultiAccountUser(r)
188
+
user := rp.oauth.GetUser(r)
189
189
190
190
scheme := "http"
191
191
if !rp.config.Core.Dev {
···
271
271
l := rp.logger.With("handler", "accessSettings")
272
272
273
273
f, err := rp.repoResolver.Resolve(r)
274
-
user := rp.oauth.GetMultiAccountUser(r)
274
+
user := rp.oauth.GetUser(r)
275
275
276
276
collaborators, err := func(repo *models.Repo) ([]pages.Collaborator, error) {
277
277
repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(repo.DidSlashRepo(), repo.Knot)
···
318
318
l := rp.logger.With("handler", "pipelineSettings")
319
319
320
320
f, err := rp.repoResolver.Resolve(r)
321
-
user := rp.oauth.GetMultiAccountUser(r)
321
+
user := rp.oauth.GetUser(r)
322
322
323
323
// all spindles that the repo owner is a member of
324
324
spindles, err := rp.enforcer.GetSpindlesForUser(f.Did)
+1
-1
appview/repo/tree.go
+1
-1
appview/repo/tree.go
···
88
88
http.Redirect(w, r, redirectTo, http.StatusFound)
89
89
return
90
90
}
91
-
user := rp.oauth.GetMultiAccountUser(r)
91
+
user := rp.oauth.GetUser(r)
92
92
var breadcrumbs [][]string
93
93
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", ownerSlashRepo, url.PathEscape(ref))})
94
94
if treePath != "" {
+4
-4
appview/reporesolver/resolver.go
+4
-4
appview/reporesolver/resolver.go
···
55
55
// 2. [x] remove `rr`, `CurrentDir`, `Ref` fields from `ResolvedRepo`
56
56
// 3. [x] remove `ResolvedRepo`
57
57
// 4. [ ] replace reporesolver to reposervice
58
-
func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.MultiAccountUser) repoinfo.RepoInfo {
58
+
func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.User) repoinfo.RepoInfo {
59
59
ownerId, ook := r.Context().Value("resolvedId").(identity.Identity)
60
60
repo, rok := r.Context().Value("repo").(*models.Repo)
61
61
if !ook || !rok {
···
69
69
repoAt := repo.RepoAt()
70
70
isStarred := false
71
71
roles := repoinfo.RolesInRepo{}
72
-
if user != nil && user.Active != nil {
73
-
isStarred = db.GetStarStatus(rr.execer, user.Active.Did, repoAt)
74
-
roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Active.Did, repo.Knot, repo.DidSlashRepo())
72
+
if user != nil {
73
+
isStarred = db.GetStarStatus(rr.execer, user.Did, repoAt)
74
+
roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Did, repo.Knot, repo.DidSlashRepo())
75
75
}
76
76
77
77
stats := repo.RepoStats
+6
-6
appview/settings/settings.go
+6
-6
appview/settings/settings.go
···
81
81
}
82
82
83
83
func (s *Settings) profileSettings(w http.ResponseWriter, r *http.Request) {
84
-
user := s.OAuth.GetMultiAccountUser(r)
84
+
user := s.OAuth.GetUser(r)
85
85
86
86
s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{
87
87
LoggedInUser: user,
···
91
91
}
92
92
93
93
func (s *Settings) notificationsSettings(w http.ResponseWriter, r *http.Request) {
94
-
user := s.OAuth.GetMultiAccountUser(r)
94
+
user := s.OAuth.GetUser(r)
95
95
did := s.OAuth.GetDid(r)
96
96
97
97
prefs, err := db.GetNotificationPreference(s.Db, did)
···
137
137
}
138
138
139
139
func (s *Settings) keysSettings(w http.ResponseWriter, r *http.Request) {
140
-
user := s.OAuth.GetMultiAccountUser(r)
141
-
pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Active.Did)
140
+
user := s.OAuth.GetUser(r)
141
+
pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Did)
142
142
if err != nil {
143
143
log.Println(err)
144
144
}
···
152
152
}
153
153
154
154
func (s *Settings) emailsSettings(w http.ResponseWriter, r *http.Request) {
155
-
user := s.OAuth.GetMultiAccountUser(r)
156
-
emails, err := db.GetAllEmails(s.Db, user.Active.Did)
155
+
user := s.OAuth.GetUser(r)
156
+
emails, err := db.GetAllEmails(s.Db, user.Did)
157
157
if err != nil {
158
158
log.Println(err)
159
159
}
+41
-41
appview/spindles/spindles.go
+41
-41
appview/spindles/spindles.go
···
69
69
}
70
70
71
71
func (s *Spindles) spindles(w http.ResponseWriter, r *http.Request) {
72
-
user := s.OAuth.GetMultiAccountUser(r)
72
+
user := s.OAuth.GetUser(r)
73
73
all, err := db.GetSpindles(
74
74
s.Db,
75
-
orm.FilterEq("owner", user.Active.Did),
75
+
orm.FilterEq("owner", user.Did),
76
76
)
77
77
if err != nil {
78
78
s.Logger.Error("failed to fetch spindles", "err", err)
···
91
91
func (s *Spindles) dashboard(w http.ResponseWriter, r *http.Request) {
92
92
l := s.Logger.With("handler", "dashboard")
93
93
94
-
user := s.OAuth.GetMultiAccountUser(r)
95
-
l = l.With("user", user.Active.Did)
94
+
user := s.OAuth.GetUser(r)
95
+
l = l.With("user", user.Did)
96
96
97
97
instance := chi.URLParam(r, "instance")
98
98
if instance == "" {
···
103
103
spindles, err := db.GetSpindles(
104
104
s.Db,
105
105
orm.FilterEq("instance", instance),
106
-
orm.FilterEq("owner", user.Active.Did),
106
+
orm.FilterEq("owner", user.Did),
107
107
orm.FilterIsNot("verified", "null"),
108
108
)
109
109
if err != nil || len(spindles) != 1 {
···
155
155
//
156
156
// if the spindle is not up yet, the user is free to retry verification at a later point
157
157
func (s *Spindles) register(w http.ResponseWriter, r *http.Request) {
158
-
user := s.OAuth.GetMultiAccountUser(r)
158
+
user := s.OAuth.GetUser(r)
159
159
l := s.Logger.With("handler", "register")
160
160
161
161
noticeId := "register-error"
···
176
176
return
177
177
}
178
178
l = l.With("instance", instance)
179
-
l = l.With("user", user.Active.Did)
179
+
l = l.With("user", user.Did)
180
180
181
181
tx, err := s.Db.Begin()
182
182
if err != nil {
···
190
190
}()
191
191
192
192
err = db.AddSpindle(tx, models.Spindle{
193
-
Owner: syntax.DID(user.Active.Did),
193
+
Owner: syntax.DID(user.Did),
194
194
Instance: instance,
195
195
})
196
196
if err != nil {
···
214
214
return
215
215
}
216
216
217
-
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.SpindleNSID, user.Active.Did, instance)
217
+
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.SpindleNSID, user.Did, instance)
218
218
var exCid *string
219
219
if ex != nil {
220
220
exCid = ex.Cid
···
223
223
// re-announce by registering under same rkey
224
224
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
225
225
Collection: tangled.SpindleNSID,
226
-
Repo: user.Active.Did,
226
+
Repo: user.Did,
227
227
Rkey: instance,
228
228
Record: &lexutil.LexiconTypeDecoder{
229
229
Val: &tangled.Spindle{
···
254
254
}
255
255
256
256
// begin verification
257
-
err = serververify.RunVerification(r.Context(), instance, user.Active.Did, s.Config.Core.Dev)
257
+
err = serververify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
258
258
if err != nil {
259
259
l.Error("verification failed", "err", err)
260
260
s.Pages.HxRefresh(w)
261
261
return
262
262
}
263
263
264
-
_, err = serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Active.Did)
264
+
_, err = serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Did)
265
265
if err != nil {
266
266
l.Error("failed to mark verified", "err", err)
267
267
s.Pages.HxRefresh(w)
···
273
273
}
274
274
275
275
func (s *Spindles) delete(w http.ResponseWriter, r *http.Request) {
276
-
user := s.OAuth.GetMultiAccountUser(r)
276
+
user := s.OAuth.GetUser(r)
277
277
l := s.Logger.With("handler", "delete")
278
278
279
279
noticeId := "operation-error"
···
291
291
292
292
spindles, err := db.GetSpindles(
293
293
s.Db,
294
-
orm.FilterEq("owner", user.Active.Did),
294
+
orm.FilterEq("owner", user.Did),
295
295
orm.FilterEq("instance", instance),
296
296
)
297
297
if err != nil || len(spindles) != 1 {
···
300
300
return
301
301
}
302
302
303
-
if string(spindles[0].Owner) != user.Active.Did {
304
-
l.Error("unauthorized", "user", user.Active.Did, "owner", spindles[0].Owner)
303
+
if string(spindles[0].Owner) != user.Did {
304
+
l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
305
305
s.Pages.Notice(w, noticeId, "Failed to delete spindle, unauthorized deletion attempt.")
306
306
return
307
307
}
···
320
320
// remove spindle members first
321
321
err = db.RemoveSpindleMember(
322
322
tx,
323
-
orm.FilterEq("did", user.Active.Did),
323
+
orm.FilterEq("did", user.Did),
324
324
orm.FilterEq("instance", instance),
325
325
)
326
326
if err != nil {
···
331
331
332
332
err = db.DeleteSpindle(
333
333
tx,
334
-
orm.FilterEq("owner", user.Active.Did),
334
+
orm.FilterEq("owner", user.Did),
335
335
orm.FilterEq("instance", instance),
336
336
)
337
337
if err != nil {
···
359
359
360
360
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
361
361
Collection: tangled.SpindleNSID,
362
-
Repo: user.Active.Did,
362
+
Repo: user.Did,
363
363
Rkey: instance,
364
364
})
365
365
if err != nil {
···
391
391
}
392
392
393
393
func (s *Spindles) retry(w http.ResponseWriter, r *http.Request) {
394
-
user := s.OAuth.GetMultiAccountUser(r)
394
+
user := s.OAuth.GetUser(r)
395
395
l := s.Logger.With("handler", "retry")
396
396
397
397
noticeId := "operation-error"
···
407
407
return
408
408
}
409
409
l = l.With("instance", instance)
410
-
l = l.With("user", user.Active.Did)
410
+
l = l.With("user", user.Did)
411
411
412
412
spindles, err := db.GetSpindles(
413
413
s.Db,
414
-
orm.FilterEq("owner", user.Active.Did),
414
+
orm.FilterEq("owner", user.Did),
415
415
orm.FilterEq("instance", instance),
416
416
)
417
417
if err != nil || len(spindles) != 1 {
···
420
420
return
421
421
}
422
422
423
-
if string(spindles[0].Owner) != user.Active.Did {
424
-
l.Error("unauthorized", "user", user.Active.Did, "owner", spindles[0].Owner)
423
+
if string(spindles[0].Owner) != user.Did {
424
+
l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
425
425
s.Pages.Notice(w, noticeId, "Failed to verify spindle, unauthorized verification attempt.")
426
426
return
427
427
}
428
428
429
429
// begin verification
430
-
err = serververify.RunVerification(r.Context(), instance, user.Active.Did, s.Config.Core.Dev)
430
+
err = serververify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
431
431
if err != nil {
432
432
l.Error("verification failed", "err", err)
433
433
···
445
445
return
446
446
}
447
447
448
-
rowId, err := serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Active.Did)
448
+
rowId, err := serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Did)
449
449
if err != nil {
450
450
l.Error("failed to mark verified", "err", err)
451
451
s.Pages.Notice(w, noticeId, err.Error())
···
473
473
}
474
474
475
475
func (s *Spindles) addMember(w http.ResponseWriter, r *http.Request) {
476
-
user := s.OAuth.GetMultiAccountUser(r)
476
+
user := s.OAuth.GetUser(r)
477
477
l := s.Logger.With("handler", "addMember")
478
478
479
479
instance := chi.URLParam(r, "instance")
···
483
483
return
484
484
}
485
485
l = l.With("instance", instance)
486
-
l = l.With("user", user.Active.Did)
486
+
l = l.With("user", user.Did)
487
487
488
488
spindles, err := db.GetSpindles(
489
489
s.Db,
490
-
orm.FilterEq("owner", user.Active.Did),
490
+
orm.FilterEq("owner", user.Did),
491
491
orm.FilterEq("instance", instance),
492
492
)
493
493
if err != nil || len(spindles) != 1 {
···
502
502
s.Pages.Notice(w, noticeId, defaultErr)
503
503
}
504
504
505
-
if string(spindles[0].Owner) != user.Active.Did {
506
-
l.Error("unauthorized", "user", user.Active.Did, "owner", spindles[0].Owner)
505
+
if string(spindles[0].Owner) != user.Did {
506
+
l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
507
507
s.Pages.Notice(w, noticeId, "Failed to add member, unauthorized attempt.")
508
508
return
509
509
}
···
552
552
553
553
// add member to db
554
554
if err = db.AddSpindleMember(tx, models.SpindleMember{
555
-
Did: syntax.DID(user.Active.Did),
555
+
Did: syntax.DID(user.Did),
556
556
Rkey: rkey,
557
557
Instance: instance,
558
558
Subject: memberId.DID,
···
570
570
571
571
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
572
572
Collection: tangled.SpindleMemberNSID,
573
-
Repo: user.Active.Did,
573
+
Repo: user.Did,
574
574
Rkey: rkey,
575
575
Record: &lexutil.LexiconTypeDecoder{
576
576
Val: &tangled.SpindleMember{
···
603
603
}
604
604
605
605
func (s *Spindles) removeMember(w http.ResponseWriter, r *http.Request) {
606
-
user := s.OAuth.GetMultiAccountUser(r)
606
+
user := s.OAuth.GetUser(r)
607
607
l := s.Logger.With("handler", "removeMember")
608
608
609
609
noticeId := "operation-error"
···
619
619
return
620
620
}
621
621
l = l.With("instance", instance)
622
-
l = l.With("user", user.Active.Did)
622
+
l = l.With("user", user.Did)
623
623
624
624
spindles, err := db.GetSpindles(
625
625
s.Db,
626
-
orm.FilterEq("owner", user.Active.Did),
626
+
orm.FilterEq("owner", user.Did),
627
627
orm.FilterEq("instance", instance),
628
628
)
629
629
if err != nil || len(spindles) != 1 {
···
632
632
return
633
633
}
634
634
635
-
if string(spindles[0].Owner) != user.Active.Did {
636
-
l.Error("unauthorized", "user", user.Active.Did, "owner", spindles[0].Owner)
635
+
if string(spindles[0].Owner) != user.Did {
636
+
l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
637
637
s.Pages.Notice(w, noticeId, "Failed to remove member, unauthorized attempt.")
638
638
return
639
639
}
···
668
668
// get the record from the DB first:
669
669
members, err := db.GetSpindleMembers(
670
670
s.Db,
671
-
orm.FilterEq("did", user.Active.Did),
671
+
orm.FilterEq("did", user.Did),
672
672
orm.FilterEq("instance", instance),
673
673
orm.FilterEq("subject", memberId.DID),
674
674
)
···
681
681
// remove from db
682
682
if err = db.RemoveSpindleMember(
683
683
tx,
684
-
orm.FilterEq("did", user.Active.Did),
684
+
orm.FilterEq("did", user.Did),
685
685
orm.FilterEq("instance", instance),
686
686
orm.FilterEq("subject", memberId.DID),
687
687
); err != nil {
···
707
707
// remove from pds
708
708
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
709
709
Collection: tangled.SpindleMemberNSID,
710
-
Repo: user.Active.Did,
710
+
Repo: user.Did,
711
711
Rkey: members[0].Rkey,
712
712
})
713
713
if err != nil {
-83
appview/state/accounts.go
-83
appview/state/accounts.go
···
1
-
package state
2
-
3
-
import (
4
-
"net/http"
5
-
6
-
"github.com/go-chi/chi/v5"
7
-
)
8
-
9
-
func (s *State) SwitchAccount(w http.ResponseWriter, r *http.Request) {
10
-
l := s.logger.With("handler", "SwitchAccount")
11
-
12
-
if err := r.ParseForm(); err != nil {
13
-
l.Error("failed to parse form", "err", err)
14
-
http.Error(w, "invalid request", http.StatusBadRequest)
15
-
return
16
-
}
17
-
18
-
did := r.FormValue("did")
19
-
if did == "" {
20
-
http.Error(w, "missing did", http.StatusBadRequest)
21
-
return
22
-
}
23
-
24
-
if err := s.oauth.SwitchAccount(w, r, did); err != nil {
25
-
l.Error("failed to switch account", "err", err)
26
-
s.pages.HxRedirect(w, "/login?error=session")
27
-
return
28
-
}
29
-
30
-
l.Info("switched account", "did", did)
31
-
s.pages.HxRedirect(w, "/")
32
-
}
33
-
34
-
func (s *State) RemoveAccount(w http.ResponseWriter, r *http.Request) {
35
-
l := s.logger.With("handler", "RemoveAccount")
36
-
37
-
did := chi.URLParam(r, "did")
38
-
if did == "" {
39
-
http.Error(w, "missing did", http.StatusBadRequest)
40
-
return
41
-
}
42
-
43
-
currentUser := s.oauth.GetMultiAccountUser(r)
44
-
isCurrentAccount := currentUser != nil && currentUser.Active.Did == did
45
-
46
-
var remainingAccounts []string
47
-
if currentUser != nil {
48
-
for _, acc := range currentUser.Accounts {
49
-
if acc.Did != did {
50
-
remainingAccounts = append(remainingAccounts, acc.Did)
51
-
}
52
-
}
53
-
}
54
-
55
-
if err := s.oauth.RemoveAccount(w, r, did); err != nil {
56
-
l.Error("failed to remove account", "err", err)
57
-
http.Error(w, "failed to remove account", http.StatusInternalServerError)
58
-
return
59
-
}
60
-
61
-
l.Info("removed account", "did", did)
62
-
63
-
if isCurrentAccount {
64
-
if len(remainingAccounts) > 0 {
65
-
nextDid := remainingAccounts[0]
66
-
if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil {
67
-
l.Error("failed to switch to next account", "err", err)
68
-
s.pages.HxRedirect(w, "/login")
69
-
return
70
-
}
71
-
s.pages.HxRefresh(w)
72
-
return
73
-
}
74
-
75
-
if err := s.oauth.DeleteSession(w, r); err != nil {
76
-
l.Error("failed to delete session", "err", err)
77
-
}
78
-
s.pages.HxRedirect(w, "/login")
79
-
return
80
-
}
81
-
82
-
s.pages.HxRefresh(w)
83
-
}
+7
-7
appview/state/follow.go
+7
-7
appview/state/follow.go
···
15
15
)
16
16
17
17
func (s *State) Follow(w http.ResponseWriter, r *http.Request) {
18
-
currentUser := s.oauth.GetMultiAccountUser(r)
18
+
currentUser := s.oauth.GetUser(r)
19
19
20
20
subject := r.URL.Query().Get("subject")
21
21
if subject == "" {
···
29
29
return
30
30
}
31
31
32
-
if currentUser.Active.Did == subjectIdent.DID.String() {
32
+
if currentUser.Did == subjectIdent.DID.String() {
33
33
log.Println("cant follow or unfollow yourself")
34
34
return
35
35
}
···
46
46
rkey := tid.TID()
47
47
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
48
48
Collection: tangled.GraphFollowNSID,
49
-
Repo: currentUser.Active.Did,
49
+
Repo: currentUser.Did,
50
50
Rkey: rkey,
51
51
Record: &lexutil.LexiconTypeDecoder{
52
52
Val: &tangled.GraphFollow{
···
62
62
log.Println("created atproto record: ", resp.Uri)
63
63
64
64
follow := &models.Follow{
65
-
UserDid: currentUser.Active.Did,
65
+
UserDid: currentUser.Did,
66
66
SubjectDid: subjectIdent.DID.String(),
67
67
Rkey: rkey,
68
68
}
···
83
83
return
84
84
case http.MethodDelete:
85
85
// find the record in the db
86
-
follow, err := db.GetFollow(s.db, currentUser.Active.Did, subjectIdent.DID.String())
86
+
follow, err := db.GetFollow(s.db, currentUser.Did, subjectIdent.DID.String())
87
87
if err != nil {
88
88
log.Println("failed to get follow relationship")
89
89
return
···
91
91
92
92
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
93
93
Collection: tangled.GraphFollowNSID,
94
-
Repo: currentUser.Active.Did,
94
+
Repo: currentUser.Did,
95
95
Rkey: follow.Rkey,
96
96
})
97
97
···
100
100
return
101
101
}
102
102
103
-
err = db.DeleteFollowByRkey(s.db, currentUser.Active.Did, follow.Rkey)
103
+
err = db.DeleteFollowByRkey(s.db, currentUser.Did, follow.Rkey)
104
104
if err != nil {
105
105
log.Println("failed to delete follow from DB")
106
106
// this is not an issue, the firehose event might have already done this
+1
-1
appview/state/gfi.go
+1
-1
appview/state/gfi.go
+7
-57
appview/state/login.go
+7
-57
appview/state/login.go
···
5
5
"net/http"
6
6
"strings"
7
7
8
-
"tangled.org/core/appview/oauth"
9
8
"tangled.org/core/appview/pages"
10
9
)
11
10
···
16
15
case http.MethodGet:
17
16
returnURL := r.URL.Query().Get("return_url")
18
17
errorCode := r.URL.Query().Get("error")
19
-
addAccount := r.URL.Query().Get("mode") == "add_account"
20
-
21
-
user := s.oauth.GetMultiAccountUser(r)
22
-
if user == nil {
23
-
registry := s.oauth.GetAccounts(r)
24
-
if len(registry.Accounts) > 0 {
25
-
user = &oauth.MultiAccountUser{
26
-
Active: nil,
27
-
Accounts: registry.Accounts,
28
-
}
29
-
}
30
-
}
31
18
s.pages.Login(w, pages.LoginParams{
32
-
ReturnUrl: returnURL,
33
-
ErrorCode: errorCode,
34
-
AddAccount: addAccount,
35
-
LoggedInUser: user,
19
+
ReturnUrl: returnURL,
20
+
ErrorCode: errorCode,
36
21
})
37
22
case http.MethodPost:
38
23
handle := r.FormValue("handle")
39
-
returnURL := r.FormValue("return_url")
40
-
addAccount := r.FormValue("add_account") == "true"
41
24
42
25
// when users copy their handle from bsky.app, it tends to have these characters around it:
43
26
//
···
61
44
return
62
45
}
63
46
64
-
if err := s.oauth.SetAuthReturn(w, r, returnURL, addAccount); err != nil {
65
-
l.Error("failed to set auth return", "err", err)
66
-
}
67
-
68
47
redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle)
69
48
if err != nil {
70
49
l.Error("failed to start auth", "err", err)
···
79
58
func (s *State) Logout(w http.ResponseWriter, r *http.Request) {
80
59
l := s.logger.With("handler", "Logout")
81
60
82
-
currentUser := s.oauth.GetMultiAccountUser(r)
83
-
if currentUser == nil || currentUser.Active == nil {
84
-
s.pages.HxRedirect(w, "/login")
85
-
return
86
-
}
87
-
88
-
currentDid := currentUser.Active.Did
89
-
90
-
var remainingAccounts []string
91
-
for _, acc := range currentUser.Accounts {
92
-
if acc.Did != currentDid {
93
-
remainingAccounts = append(remainingAccounts, acc.Did)
94
-
}
95
-
}
96
-
97
-
if err := s.oauth.RemoveAccount(w, r, currentDid); err != nil {
98
-
l.Error("failed to remove account from registry", "err", err)
99
-
}
100
-
101
-
if err := s.oauth.DeleteSession(w, r); err != nil {
102
-
l.Error("failed to delete session", "err", err)
103
-
}
104
-
105
-
if len(remainingAccounts) > 0 {
106
-
nextDid := remainingAccounts[0]
107
-
if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil {
108
-
l.Error("failed to switch to next account", "err", err)
109
-
s.pages.HxRedirect(w, "/login")
110
-
return
111
-
}
112
-
l.Info("switched to next account after logout", "did", nextDid)
113
-
s.pages.HxRefresh(w)
114
-
return
61
+
err := s.oauth.DeleteSession(w, r)
62
+
if err != nil {
63
+
l.Error("failed to logout", "err", err)
64
+
} else {
65
+
l.Info("logged out successfully")
115
66
}
116
67
117
-
l.Info("logged out last account")
118
68
s.pages.HxRedirect(w, "/login")
119
69
}
+32
-32
appview/state/profile.go
+32
-32
appview/state/profile.go
···
77
77
return nil, fmt.Errorf("failed to get follower stats: %w", err)
78
78
}
79
79
80
-
loggedInUser := s.oauth.GetMultiAccountUser(r)
80
+
loggedInUser := s.oauth.GetUser(r)
81
81
followStatus := models.IsNotFollowing
82
82
if loggedInUser != nil {
83
-
followStatus = db.GetFollowStatus(s.db, loggedInUser.Active.Did, did)
83
+
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did)
84
84
}
85
85
86
86
now := time.Now()
···
174
174
}
175
175
176
176
s.pages.ProfileOverview(w, pages.ProfileOverviewParams{
177
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
177
+
LoggedInUser: s.oauth.GetUser(r),
178
178
Card: profile,
179
179
Repos: pinnedRepos,
180
180
CollaboratingRepos: pinnedCollaboratingRepos,
···
205
205
}
206
206
207
207
err = s.pages.ProfileRepos(w, pages.ProfileReposParams{
208
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
208
+
LoggedInUser: s.oauth.GetUser(r),
209
209
Repos: repos,
210
210
Card: profile,
211
211
})
···
234
234
}
235
235
236
236
err = s.pages.ProfileStarred(w, pages.ProfileStarredParams{
237
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
237
+
LoggedInUser: s.oauth.GetUser(r),
238
238
Repos: repos,
239
239
Card: profile,
240
240
})
···
259
259
}
260
260
261
261
err = s.pages.ProfileStrings(w, pages.ProfileStringsParams{
262
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
262
+
LoggedInUser: s.oauth.GetUser(r),
263
263
Strings: strings,
264
264
Card: profile,
265
265
})
···
283
283
}
284
284
l = l.With("profileDid", profile.UserDid)
285
285
286
-
loggedInUser := s.oauth.GetMultiAccountUser(r)
286
+
loggedInUser := s.oauth.GetUser(r)
287
287
params := FollowsPageParams{
288
288
Card: profile,
289
289
}
···
316
316
317
317
loggedInUserFollowing := make(map[string]struct{})
318
318
if loggedInUser != nil {
319
-
following, err := db.GetFollowing(s.db, loggedInUser.Active.Did)
319
+
following, err := db.GetFollowing(s.db, loggedInUser.Did)
320
320
if err != nil {
321
-
l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Active.Did)
321
+
l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Did)
322
322
return ¶ms, err
323
323
}
324
324
loggedInUserFollowing = make(map[string]struct{}, len(following))
···
333
333
followStatus := models.IsNotFollowing
334
334
if _, exists := loggedInUserFollowing[did]; exists {
335
335
followStatus = models.IsFollowing
336
-
} else if loggedInUser != nil && loggedInUser.Active.Did == did {
336
+
} else if loggedInUser != nil && loggedInUser.Did == did {
337
337
followStatus = models.IsSelf
338
338
}
339
339
···
367
367
}
368
368
369
369
s.pages.ProfileFollowers(w, pages.ProfileFollowersParams{
370
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
370
+
LoggedInUser: s.oauth.GetUser(r),
371
371
Followers: followPage.Follows,
372
372
Card: followPage.Card,
373
373
})
···
381
381
}
382
382
383
383
s.pages.ProfileFollowing(w, pages.ProfileFollowingParams{
384
-
LoggedInUser: s.oauth.GetMultiAccountUser(r),
384
+
LoggedInUser: s.oauth.GetUser(r),
385
385
Following: followPage.Follows,
386
386
Card: followPage.Card,
387
387
})
···
530
530
}
531
531
532
532
func (s *State) UpdateProfileBio(w http.ResponseWriter, r *http.Request) {
533
-
user := s.oauth.GetMultiAccountUser(r)
533
+
user := s.oauth.GetUser(r)
534
534
535
535
err := r.ParseForm()
536
536
if err != nil {
···
539
539
return
540
540
}
541
541
542
-
profile, err := db.GetProfile(s.db, user.Active.Did)
542
+
profile, err := db.GetProfile(s.db, user.Did)
543
543
if err != nil {
544
-
log.Printf("getting profile data for %s: %s", user.Active.Did, err)
544
+
log.Printf("getting profile data for %s: %s", user.Did, err)
545
545
}
546
546
547
547
profile.Description = r.FormValue("description")
···
578
578
}
579
579
580
580
func (s *State) UpdateProfilePins(w http.ResponseWriter, r *http.Request) {
581
-
user := s.oauth.GetMultiAccountUser(r)
581
+
user := s.oauth.GetUser(r)
582
582
583
583
err := r.ParseForm()
584
584
if err != nil {
···
587
587
return
588
588
}
589
589
590
-
profile, err := db.GetProfile(s.db, user.Active.Did)
590
+
profile, err := db.GetProfile(s.db, user.Did)
591
591
if err != nil {
592
-
log.Printf("getting profile data for %s: %s", user.Active.Did, err)
592
+
log.Printf("getting profile data for %s: %s", user.Did, err)
593
593
}
594
594
595
595
i := 0
···
617
617
}
618
618
619
619
func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) {
620
-
user := s.oauth.GetMultiAccountUser(r)
620
+
user := s.oauth.GetUser(r)
621
621
tx, err := s.db.BeginTx(r.Context(), nil)
622
622
if err != nil {
623
623
log.Println("failed to start transaction", err)
···
644
644
vanityStats = append(vanityStats, string(v.Kind))
645
645
}
646
646
647
-
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.ActorProfileNSID, user.Active.Did, "self")
647
+
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.ActorProfileNSID, user.Did, "self")
648
648
var cid *string
649
649
if ex != nil {
650
650
cid = ex.Cid
···
652
652
653
653
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
654
654
Collection: tangled.ActorProfileNSID,
655
-
Repo: user.Active.Did,
655
+
Repo: user.Did,
656
656
Rkey: "self",
657
657
Record: &lexutil.LexiconTypeDecoder{
658
658
Val: &tangled.ActorProfile{
···
681
681
682
682
s.notifier.UpdateProfile(r.Context(), profile)
683
683
684
-
s.pages.HxRedirect(w, "/"+user.Active.Did)
684
+
s.pages.HxRedirect(w, "/"+user.Did)
685
685
}
686
686
687
687
func (s *State) EditBioFragment(w http.ResponseWriter, r *http.Request) {
688
-
user := s.oauth.GetMultiAccountUser(r)
688
+
user := s.oauth.GetUser(r)
689
689
690
-
profile, err := db.GetProfile(s.db, user.Active.Did)
690
+
profile, err := db.GetProfile(s.db, user.Did)
691
691
if err != nil {
692
-
log.Printf("getting profile data for %s: %s", user.Active.Did, err)
692
+
log.Printf("getting profile data for %s: %s", user.Did, err)
693
693
}
694
694
695
695
s.pages.EditBioFragment(w, pages.EditBioParams{
···
699
699
}
700
700
701
701
func (s *State) EditPinsFragment(w http.ResponseWriter, r *http.Request) {
702
-
user := s.oauth.GetMultiAccountUser(r)
702
+
user := s.oauth.GetUser(r)
703
703
704
-
profile, err := db.GetProfile(s.db, user.Active.Did)
704
+
profile, err := db.GetProfile(s.db, user.Did)
705
705
if err != nil {
706
-
log.Printf("getting profile data for %s: %s", user.Active.Did, err)
706
+
log.Printf("getting profile data for %s: %s", user.Did, err)
707
707
}
708
708
709
-
repos, err := db.GetRepos(s.db, 0, orm.FilterEq("did", user.Active.Did))
709
+
repos, err := db.GetRepos(s.db, 0, orm.FilterEq("did", user.Did))
710
710
if err != nil {
711
-
log.Printf("getting repos for %s: %s", user.Active.Did, err)
711
+
log.Printf("getting repos for %s: %s", user.Did, err)
712
712
}
713
713
714
-
collaboratingRepos, err := db.CollaboratingIn(s.db, user.Active.Did)
714
+
collaboratingRepos, err := db.CollaboratingIn(s.db, user.Did)
715
715
if err != nil {
716
-
log.Printf("getting collaborating repos for %s: %s", user.Active.Did, err)
716
+
log.Printf("getting collaborating repos for %s: %s", user.Did, err)
717
717
}
718
718
719
719
allRepos := []pages.PinnedRepo{}
+7
-7
appview/state/reaction.go
+7
-7
appview/state/reaction.go
···
17
17
)
18
18
19
19
func (s *State) React(w http.ResponseWriter, r *http.Request) {
20
-
currentUser := s.oauth.GetMultiAccountUser(r)
20
+
currentUser := s.oauth.GetUser(r)
21
21
22
22
subject := r.URL.Query().Get("subject")
23
23
if subject == "" {
···
49
49
rkey := tid.TID()
50
50
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
51
51
Collection: tangled.FeedReactionNSID,
52
-
Repo: currentUser.Active.Did,
52
+
Repo: currentUser.Did,
53
53
Rkey: rkey,
54
54
Record: &lexutil.LexiconTypeDecoder{
55
55
Val: &tangled.FeedReaction{
···
64
64
return
65
65
}
66
66
67
-
err = db.AddReaction(s.db, currentUser.Active.Did, subjectUri, reactionKind, rkey)
67
+
err = db.AddReaction(s.db, currentUser.Did, subjectUri, reactionKind, rkey)
68
68
if err != nil {
69
69
log.Println("failed to react", err)
70
70
return
···
87
87
88
88
return
89
89
case http.MethodDelete:
90
-
reaction, err := db.GetReaction(s.db, currentUser.Active.Did, subjectUri, reactionKind)
90
+
reaction, err := db.GetReaction(s.db, currentUser.Did, subjectUri, reactionKind)
91
91
if err != nil {
92
-
log.Println("failed to get reaction relationship for", currentUser.Active.Did, subjectUri)
92
+
log.Println("failed to get reaction relationship for", currentUser.Did, subjectUri)
93
93
return
94
94
}
95
95
96
96
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
97
97
Collection: tangled.FeedReactionNSID,
98
-
Repo: currentUser.Active.Did,
98
+
Repo: currentUser.Did,
99
99
Rkey: reaction.Rkey,
100
100
})
101
101
···
104
104
return
105
105
}
106
106
107
-
err = db.DeleteReactionByRkey(s.db, currentUser.Active.Did, reaction.Rkey)
107
+
err = db.DeleteReactionByRkey(s.db, currentUser.Did, reaction.Rkey)
108
108
if err != nil {
109
109
log.Println("failed to delete reaction from DB")
110
110
// this is not an issue, the firehose event might have already done this
-5
appview/state/router.go
-5
appview/state/router.go
···
132
132
r.Post("/login", s.Login)
133
133
r.Post("/logout", s.Logout)
134
134
135
-
r.With(middleware.AuthMiddleware(s.oauth)).Route("/account", func(r chi.Router) {
136
-
r.Post("/switch", s.SwitchAccount)
137
-
r.Delete("/{did}", s.RemoveAccount)
138
-
})
139
-
140
135
r.Route("/repo", func(r chi.Router) {
141
136
r.Route("/new", func(r chi.Router) {
142
137
r.Use(middleware.AuthMiddleware(s.oauth))
+6
-6
appview/state/star.go
+6
-6
appview/state/star.go
···
16
16
)
17
17
18
18
func (s *State) Star(w http.ResponseWriter, r *http.Request) {
19
-
currentUser := s.oauth.GetMultiAccountUser(r)
19
+
currentUser := s.oauth.GetUser(r)
20
20
21
21
subject := r.URL.Query().Get("subject")
22
22
if subject == "" {
···
42
42
rkey := tid.TID()
43
43
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
44
44
Collection: tangled.FeedStarNSID,
45
-
Repo: currentUser.Active.Did,
45
+
Repo: currentUser.Did,
46
46
Rkey: rkey,
47
47
Record: &lexutil.LexiconTypeDecoder{
48
48
Val: &tangled.FeedStar{
···
57
57
log.Println("created atproto record: ", resp.Uri)
58
58
59
59
star := &models.Star{
60
-
Did: currentUser.Active.Did,
60
+
Did: currentUser.Did,
61
61
RepoAt: subjectUri,
62
62
Rkey: rkey,
63
63
}
···
84
84
return
85
85
case http.MethodDelete:
86
86
// find the record in the db
87
-
star, err := db.GetStar(s.db, currentUser.Active.Did, subjectUri)
87
+
star, err := db.GetStar(s.db, currentUser.Did, subjectUri)
88
88
if err != nil {
89
89
log.Println("failed to get star relationship")
90
90
return
···
92
92
93
93
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
94
94
Collection: tangled.FeedStarNSID,
95
-
Repo: currentUser.Active.Did,
95
+
Repo: currentUser.Did,
96
96
Rkey: star.Rkey,
97
97
})
98
98
···
101
101
return
102
102
}
103
103
104
-
err = db.DeleteStarByRkey(s.db, currentUser.Active.Did, star.Rkey)
104
+
err = db.DeleteStarByRkey(s.db, currentUser.Did, star.Rkey)
105
105
if err != nil {
106
106
log.Println("failed to delete star from DB")
107
107
// this is not an issue, the firehose event might have already done this
+23
-23
appview/state/state.go
+23
-23
appview/state/state.go
···
117
117
tangled.SpindleNSID,
118
118
tangled.StringNSID,
119
119
tangled.RepoIssueNSID,
120
-
tangled.RepoIssueCommentNSID,
120
+
tangled.CommentNSID,
121
121
tangled.LabelDefinitionNSID,
122
122
tangled.LabelOpNSID,
123
123
},
···
249
249
}
250
250
251
251
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
252
-
user := s.oauth.GetMultiAccountUser(r)
252
+
user := s.oauth.GetUser(r)
253
253
s.pages.TermsOfService(w, pages.TermsOfServiceParams{
254
254
LoggedInUser: user,
255
255
})
256
256
}
257
257
258
258
func (s *State) PrivacyPolicy(w http.ResponseWriter, r *http.Request) {
259
-
user := s.oauth.GetMultiAccountUser(r)
259
+
user := s.oauth.GetUser(r)
260
260
s.pages.PrivacyPolicy(w, pages.PrivacyPolicyParams{
261
261
LoggedInUser: user,
262
262
})
263
263
}
264
264
265
265
func (s *State) Brand(w http.ResponseWriter, r *http.Request) {
266
-
user := s.oauth.GetMultiAccountUser(r)
266
+
user := s.oauth.GetUser(r)
267
267
s.pages.Brand(w, pages.BrandParams{
268
268
LoggedInUser: user,
269
269
})
270
270
}
271
271
272
272
func (s *State) HomeOrTimeline(w http.ResponseWriter, r *http.Request) {
273
-
if s.oauth.GetMultiAccountUser(r) != nil {
273
+
if s.oauth.GetUser(r) != nil {
274
274
s.Timeline(w, r)
275
275
return
276
276
}
···
278
278
}
279
279
280
280
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
281
-
user := s.oauth.GetMultiAccountUser(r)
281
+
user := s.oauth.GetUser(r)
282
282
283
283
// TODO: set this flag based on the UI
284
284
filtered := false
285
285
286
286
var userDid string
287
-
if user != nil && user.Active != nil {
288
-
userDid = user.Active.Did
287
+
if user != nil {
288
+
userDid = user.Did
289
289
}
290
290
timeline, err := db.MakeTimeline(s.db, 50, userDid, filtered)
291
291
if err != nil {
···
314
314
}
315
315
316
316
func (s *State) UpgradeBanner(w http.ResponseWriter, r *http.Request) {
317
-
user := s.oauth.GetMultiAccountUser(r)
317
+
user := s.oauth.GetUser(r)
318
318
if user == nil {
319
319
return
320
320
}
321
321
322
322
l := s.logger.With("handler", "UpgradeBanner")
323
-
l = l.With("did", user.Active.Did)
323
+
l = l.With("did", user.Did)
324
324
325
325
regs, err := db.GetRegistrations(
326
326
s.db,
327
-
orm.FilterEq("did", user.Active.Did),
327
+
orm.FilterEq("did", user.Did),
328
328
orm.FilterEq("needs_upgrade", 1),
329
329
)
330
330
if err != nil {
···
333
333
334
334
spindles, err := db.GetSpindles(
335
335
s.db,
336
-
orm.FilterEq("owner", user.Active.Did),
336
+
orm.FilterEq("owner", user.Did),
337
337
orm.FilterEq("needs_upgrade", 1),
338
338
)
339
339
if err != nil {
···
447
447
func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) {
448
448
switch r.Method {
449
449
case http.MethodGet:
450
-
user := s.oauth.GetMultiAccountUser(r)
451
-
knots, err := s.enforcer.GetKnotsForUser(user.Active.Did)
450
+
user := s.oauth.GetUser(r)
451
+
knots, err := s.enforcer.GetKnotsForUser(user.Did)
452
452
if err != nil {
453
453
s.pages.Notice(w, "repo", "Invalid user account.")
454
454
return
···
462
462
case http.MethodPost:
463
463
l := s.logger.With("handler", "NewRepo")
464
464
465
-
user := s.oauth.GetMultiAccountUser(r)
466
-
l = l.With("did", user.Active.Did)
465
+
user := s.oauth.GetUser(r)
466
+
l = l.With("did", user.Did)
467
467
468
468
// form validation
469
469
domain := r.FormValue("domain")
···
495
495
description := r.FormValue("description")
496
496
497
497
// ACL validation
498
-
ok, err := s.enforcer.E.Enforce(user.Active.Did, domain, domain, "repo:create")
498
+
ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create")
499
499
if err != nil || !ok {
500
500
l.Info("unauthorized")
501
501
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
···
505
505
// Check for existing repos
506
506
existingRepo, err := db.GetRepo(
507
507
s.db,
508
-
orm.FilterEq("did", user.Active.Did),
508
+
orm.FilterEq("did", user.Did),
509
509
orm.FilterEq("name", repoName),
510
510
)
511
511
if err == nil && existingRepo != nil {
···
517
517
// create atproto record for this repo
518
518
rkey := tid.TID()
519
519
repo := &models.Repo{
520
-
Did: user.Active.Did,
520
+
Did: user.Did,
521
521
Name: repoName,
522
522
Knot: domain,
523
523
Rkey: rkey,
···
536
536
537
537
atresp, err := comatproto.RepoPutRecord(r.Context(), atpClient, &comatproto.RepoPutRecord_Input{
538
538
Collection: tangled.RepoNSID,
539
-
Repo: user.Active.Did,
539
+
Repo: user.Did,
540
540
Rkey: rkey,
541
541
Record: &lexutil.LexiconTypeDecoder{
542
542
Val: &record,
···
613
613
}
614
614
615
615
// acls
616
-
p, _ := securejoin.SecureJoin(user.Active.Did, repoName)
617
-
err = s.enforcer.AddRepo(user.Active.Did, domain, p)
616
+
p, _ := securejoin.SecureJoin(user.Did, repoName)
617
+
err = s.enforcer.AddRepo(user.Did, domain, p)
618
618
if err != nil {
619
619
l.Error("acl setup failed", "err", err)
620
620
s.pages.Notice(w, "repo", "Failed to set up repository permissions.")
···
639
639
aturi = ""
640
640
641
641
s.notifier.NewRepo(r.Context(), repo)
642
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Active.Did, repoName))
642
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Did, repoName))
643
643
}
644
644
}
645
645
+19
-19
appview/strings/strings.go
+19
-19
appview/strings/strings.go
···
82
82
}
83
83
84
84
s.Pages.StringsTimeline(w, pages.StringTimelineParams{
85
-
LoggedInUser: s.OAuth.GetMultiAccountUser(r),
85
+
LoggedInUser: s.OAuth.GetUser(r),
86
86
Strings: strings,
87
87
})
88
88
}
···
153
153
if err != nil {
154
154
l.Error("failed to get star count", "err", err)
155
155
}
156
-
user := s.OAuth.GetMultiAccountUser(r)
156
+
user := s.OAuth.GetUser(r)
157
157
isStarred := false
158
158
if user != nil {
159
-
isStarred = db.GetStarStatus(s.Db, user.Active.Did, string.AtUri())
159
+
isStarred = db.GetStarStatus(s.Db, user.Did, string.AtUri())
160
160
}
161
161
162
162
s.Pages.SingleString(w, pages.SingleStringParams{
···
178
178
func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {
179
179
l := s.Logger.With("handler", "edit")
180
180
181
-
user := s.OAuth.GetMultiAccountUser(r)
181
+
user := s.OAuth.GetUser(r)
182
182
183
183
id, ok := r.Context().Value("resolvedId").(identity.Identity)
184
184
if !ok {
···
216
216
first := all[0]
217
217
218
218
// verify that the logged in user owns this string
219
-
if user.Active.Did != id.DID.String() {
220
-
l.Error("unauthorized request", "expected", id.DID, "got", user.Active.Did)
219
+
if user.Did != id.DID.String() {
220
+
l.Error("unauthorized request", "expected", id.DID, "got", user.Did)
221
221
w.WriteHeader(http.StatusUnauthorized)
222
222
return
223
223
}
···
226
226
case http.MethodGet:
227
227
// return the form with prefilled fields
228
228
s.Pages.PutString(w, pages.PutStringParams{
229
-
LoggedInUser: s.OAuth.GetMultiAccountUser(r),
229
+
LoggedInUser: s.OAuth.GetUser(r),
230
230
Action: "edit",
231
231
String: first,
232
232
})
···
299
299
s.Notifier.EditString(r.Context(), &entry)
300
300
301
301
// if that went okay, redir to the string
302
-
s.Pages.HxRedirect(w, "/strings/"+user.Active.Did+"/"+entry.Rkey)
302
+
s.Pages.HxRedirect(w, "/strings/"+user.Did+"/"+entry.Rkey)
303
303
}
304
304
305
305
}
306
306
307
307
func (s *Strings) create(w http.ResponseWriter, r *http.Request) {
308
308
l := s.Logger.With("handler", "create")
309
-
user := s.OAuth.GetMultiAccountUser(r)
309
+
user := s.OAuth.GetUser(r)
310
310
311
311
switch r.Method {
312
312
case http.MethodGet:
313
313
s.Pages.PutString(w, pages.PutStringParams{
314
-
LoggedInUser: s.OAuth.GetMultiAccountUser(r),
314
+
LoggedInUser: s.OAuth.GetUser(r),
315
315
Action: "new",
316
316
})
317
317
case http.MethodPost:
···
335
335
description := r.FormValue("description")
336
336
337
337
string := models.String{
338
-
Did: syntax.DID(user.Active.Did),
338
+
Did: syntax.DID(user.Did),
339
339
Rkey: tid.TID(),
340
340
Filename: filename,
341
341
Description: description,
···
353
353
354
354
resp, err := comatproto.RepoPutRecord(r.Context(), client, &atproto.RepoPutRecord_Input{
355
355
Collection: tangled.StringNSID,
356
-
Repo: user.Active.Did,
356
+
Repo: user.Did,
357
357
Rkey: string.Rkey,
358
358
Record: &lexutil.LexiconTypeDecoder{
359
359
Val: &record,
···
375
375
s.Notifier.NewString(r.Context(), &string)
376
376
377
377
// successful
378
-
s.Pages.HxRedirect(w, "/strings/"+user.Active.Did+"/"+string.Rkey)
378
+
s.Pages.HxRedirect(w, "/strings/"+user.Did+"/"+string.Rkey)
379
379
}
380
380
}
381
381
382
382
func (s *Strings) delete(w http.ResponseWriter, r *http.Request) {
383
383
l := s.Logger.With("handler", "create")
384
-
user := s.OAuth.GetMultiAccountUser(r)
384
+
user := s.OAuth.GetUser(r)
385
385
fail := func(msg string, err error) {
386
386
l.Error(msg, "err", err)
387
387
s.Pages.Notice(w, "error", msg)
···
402
402
return
403
403
}
404
404
405
-
if user.Active.Did != id.DID.String() {
406
-
fail("You cannot delete this string", fmt.Errorf("unauthorized deletion, %s != %s", user.Active.Did, id.DID.String()))
405
+
if user.Did != id.DID.String() {
406
+
fail("You cannot delete this string", fmt.Errorf("unauthorized deletion, %s != %s", user.Did, id.DID.String()))
407
407
return
408
408
}
409
409
410
410
if err := db.DeleteString(
411
411
s.Db,
412
-
orm.FilterEq("did", user.Active.Did),
412
+
orm.FilterEq("did", user.Did),
413
413
orm.FilterEq("rkey", rkey),
414
414
); err != nil {
415
415
fail("Failed to delete string.", err)
416
416
return
417
417
}
418
418
419
-
s.Notifier.DeleteString(r.Context(), user.Active.Did, rkey)
419
+
s.Notifier.DeleteString(r.Context(), user.Did, rkey)
420
420
421
-
s.Pages.HxRedirect(w, "/strings/"+user.Active.Did)
421
+
s.Pages.HxRedirect(w, "/strings/"+user.Did)
422
422
}
423
423
424
424
func (s *Strings) comment(w http.ResponseWriter, r *http.Request) {
-27
appview/validator/issue.go
-27
appview/validator/issue.go
···
4
4
"fmt"
5
5
"strings"
6
6
7
-
"tangled.org/core/appview/db"
8
7
"tangled.org/core/appview/models"
9
-
"tangled.org/core/orm"
10
8
)
11
-
12
-
func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error {
13
-
// if comments have parents, only ingest ones that are 1 level deep
14
-
if comment.ReplyTo != nil {
15
-
parents, err := db.GetIssueComments(v.db, orm.FilterEq("at_uri", *comment.ReplyTo))
16
-
if err != nil {
17
-
return fmt.Errorf("failed to fetch parent comment: %w", err)
18
-
}
19
-
if len(parents) != 1 {
20
-
return fmt.Errorf("incorrect number of parent comments returned: %d", len(parents))
21
-
}
22
-
23
-
// depth check
24
-
parent := parents[0]
25
-
if parent.ReplyTo != nil {
26
-
return fmt.Errorf("incorrect depth, this comment is replying at depth >1")
27
-
}
28
-
}
29
-
30
-
if sb := strings.TrimSpace(v.sanitizer.SanitizeDefault(comment.Body)); sb == "" {
31
-
return fmt.Errorf("body is empty after HTML sanitization")
32
-
}
33
-
34
-
return nil
35
-
}
36
9
37
10
func (v *Validator) ValidateIssue(issue *models.Issue) error {
38
11
if issue.Title == "" {
+1
cmd/cborgen/cborgen.go
+1
cmd/cborgen/cborgen.go
+51
lexicons/comment/comment.json
+51
lexicons/comment/comment.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.tangled.comment",
4
+
"needsCbor": true,
5
+
"needsType": true,
6
+
"defs": {
7
+
"main": {
8
+
"type": "record",
9
+
"key": "tid",
10
+
"record": {
11
+
"type": "object",
12
+
"required": [
13
+
"subject",
14
+
"body",
15
+
"createdAt"
16
+
],
17
+
"properties": {
18
+
"subject": {
19
+
"type": "string",
20
+
"format": "at-uri"
21
+
},
22
+
"body": {
23
+
"type": "string"
24
+
},
25
+
"createdAt": {
26
+
"type": "string",
27
+
"format": "datetime"
28
+
},
29
+
"replyTo": {
30
+
"type": "string",
31
+
"format": "at-uri"
32
+
},
33
+
"mentions": {
34
+
"type": "array",
35
+
"items": {
36
+
"type": "string",
37
+
"format": "did"
38
+
}
39
+
},
40
+
"references": {
41
+
"type": "array",
42
+
"items": {
43
+
"type": "string",
44
+
"format": "at-uri"
45
+
}
46
+
}
47
+
}
48
+
}
49
+
}
50
+
}
51
+
}