This repository has no description
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}