This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

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