A vibe coded tangled fork which supports pijul.
at sl/tap-appview 154 lines 4.1 kB view raw
1package state 2 3import ( 4 "log" 5 "net/http" 6 "time" 7 8 comatproto "github.com/bluesky-social/indigo/api/atproto" 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 lexutil "github.com/bluesky-social/indigo/lex/util" 11 12 "tangled.org/core/api/tangled" 13 "tangled.org/core/appview/db" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/pages" 16 "tangled.org/core/tid" 17) 18 19func (s *State) React(w http.ResponseWriter, r *http.Request) { 20 currentUser := s.oauth.GetMultiAccountUser(r) 21 22 subject := r.URL.Query().Get("subject") 23 if subject == "" { 24 log.Println("invalid form") 25 return 26 } 27 28 subjectUri, err := syntax.ParseATURI(subject) 29 if err != nil { 30 log.Println("invalid form") 31 return 32 } 33 34 reactionKind, ok := models.ParseReactionKind(r.URL.Query().Get("kind")) 35 if !ok { 36 log.Println("invalid reaction kind") 37 return 38 } 39 40 client, err := s.oauth.AuthorizedClient(r) 41 if err != nil { 42 log.Println("failed to authorize client", err) 43 return 44 } 45 46 switch r.Method { 47 case http.MethodPost: 48 reaction := models.Reaction{ 49 ReactedByDid: currentUser.Active.Did, 50 Rkey: tid.TID(), 51 Kind: reactionKind, 52 ThreadAt: subjectUri, 53 Created: time.Now(), 54 } 55 56 tx, err := s.db.BeginTx(r.Context(), nil) 57 if err != nil { 58 s.logger.Error("failed to start transaction", "err", err) 59 return 60 } 61 defer tx.Rollback() 62 63 if err := db.UpsertReaction(tx, reaction); err != nil { 64 log.Println("failed to react", err) 65 return 66 } 67 68 record := reaction.AsRecord() 69 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 70 Collection: tangled.FeedReactionNSID, 71 Repo: currentUser.Active.Did, 72 Rkey: reaction.Rkey, 73 Record: &lexutil.LexiconTypeDecoder{ 74 Val: &record, 75 }, 76 }) 77 if err != nil { 78 log.Println("failed to create atproto record", err) 79 return 80 } 81 log.Println("created atproto record: ", resp.Uri) 82 83 if err := tx.Commit(); err != nil { 84 s.logger.Error("failed to commit transaction", "err", err) 85 // DB op failed but record is created in PDS. Ingester will backfill the missed operation 86 } 87 88 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri) 89 if err != nil { 90 log.Println("failed to get reactions for ", subjectUri) 91 } 92 93 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{ 94 ThreadAt: subjectUri, 95 Kind: reactionKind, 96 Count: reactionMap[reactionKind].Count, 97 Users: reactionMap[reactionKind].Users, 98 IsReacted: true, 99 }) 100 101 return 102 case http.MethodDelete: 103 tx, err := s.db.BeginTx(r.Context(), nil) 104 if err != nil { 105 s.logger.Error("failed to start transaction", "err", err) 106 } 107 defer tx.Rollback() 108 109 reactions, err := db.DeleteReaction(tx, syntax.DID(currentUser.Active.Did), subjectUri, reactionKind) 110 if err != nil { 111 s.logger.Error("failed to delete reactions from db", "err", err) 112 return 113 } 114 115 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 116 for _, reactionAt := range reactions { 117 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 118 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{ 119 Collection: tangled.FeedReactionNSID, 120 Rkey: reactionAt.RecordKey().String(), 121 }, 122 }) 123 } 124 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{ 125 Repo: currentUser.Active.Did, 126 Writes: writes, 127 }) 128 if err != nil { 129 s.logger.Error("failed to delete reactions from PDS", "err", err) 130 return 131 } 132 133 if err := tx.Commit(); err != nil { 134 s.logger.Error("failed to commit transaction", "err", err) 135 // DB op failed but record is created in PDS. Ingester will backfill the missed operation 136 } 137 138 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri) 139 if err != nil { 140 log.Println("failed to get reactions for ", subjectUri) 141 return 142 } 143 144 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{ 145 ThreadAt: subjectUri, 146 Kind: reactionKind, 147 Count: reactionMap[reactionKind].Count, 148 Users: reactionMap[reactionKind].Users, 149 IsReacted: false, 150 }) 151 152 return 153 } 154}