Compare changes

Choose any two refs to compare.

+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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), &registry); err != nil { 57 - return &AccountRegistry{Accounts: []AccountInfo{}} 58 - } 59 - 60 - return &registry 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
··· 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 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 76 76 // Create the blob view 77 77 blobView := NewBlobView(resp, rp.config, f, ref, filePath, r.URL.Query()) 78 78 79 - user := rp.oauth.GetMultiAccountUser(r) 79 + user := rp.oauth.GetUser(r) 80 80 81 81 rp.pages.RepoBlob(w, pages.RepoBlobParams{ 82 82 LoggedInUser: user,
+1 -1
appview/repo/branches.go
··· 43 43 return 44 44 } 45 45 sortBranches(result.Branches) 46 - user := rp.oauth.GetMultiAccountUser(r) 46 + user := rp.oauth.GetUser(r) 47 47 rp.pages.RepoBranches(w, pages.RepoBranchesParams{ 48 48 LoggedInUser: user, 49 49 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
+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
··· 51 51 Host: host, 52 52 } 53 53 54 - user := rp.oauth.GetMultiAccountUser(r) 54 + user := rp.oauth.GetUser(r) 55 55 56 56 // Build index response from multiple XRPC calls 57 57 result, err := rp.buildIndexResponse(r.Context(), xrpcc, f, ref)
+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
··· 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
··· 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/tags.go
··· 69 69 danglingArtifacts = append(danglingArtifacts, a) 70 70 } 71 71 } 72 - user := rp.oauth.GetMultiAccountUser(r) 72 + user := rp.oauth.GetUser(r) 73 73 rp.pages.RepoTags(w, pages.RepoTagsParams{ 74 74 LoggedInUser: user, 75 75 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 15 15 ) 16 16 17 17 func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) { 18 - user := s.oauth.GetMultiAccountUser(r) 18 + user := s.oauth.GetUser(r) 19 19 20 20 page := pagination.FromContext(r.Context()) 21 21
+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
··· 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 &params, 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 15 15 "api/tangled/cbor_gen.go", 16 16 "tangled", 17 17 tangled.ActorProfile{}, 18 + tangled.Comment{}, 18 19 tangled.FeedReaction{}, 19 20 tangled.FeedStar{}, 20 21 tangled.GitRefUpdate{},
+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 + }