Monorepo for Tangled
tangled.org
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 createdAt := time.Now()
52 rkey := tid.TID()
53 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
54 Collection: tangled.FeedReactionNSID,
55 Repo: currentUser.Did,
56 Rkey: rkey,
57 Record: &lexutil.LexiconTypeDecoder{
58 Val: &tangled.FeedReaction{
59 Subject: subjectUri.String(),
60 Reaction: reactionKind.String(),
61 CreatedAt: createdAt.Format(time.RFC3339),
62 },
63 },
64 })
65 if err != nil {
66 l.Error("failed to create atproto record", "err", err)
67 return
68 }
69
70 err = db.AddReaction(s.db, currentUser.Did, subjectUri, reactionKind, rkey, createdAt)
71 if err != nil {
72 l.Error("failed to react", "err", err)
73 return
74 }
75
76 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
77 if err != nil {
78 l.Error("failed to get reactions", "subjectUri", subjectUri, "err", err)
79 }
80
81 l.Info("created atproto record", "uri", resp.Uri)
82
83 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
84 Kind: reactionKind,
85 Count: reactionMap[reactionKind].Count,
86 Users: reactionMap[reactionKind].Users,
87 IsReacted: true,
88 CommentRkey: subjectUri.RecordKey().String(),
89 SubjectUri: subject,
90 })
91
92 return
93 case http.MethodDelete:
94 reaction, err := db.GetReaction(s.db, currentUser.Did, subjectUri, reactionKind)
95 if err != nil {
96 l.Error("failed to get reaction relationship", "did", currentUser.Did, "subjectUri", subjectUri, "err", err)
97 return
98 }
99
100 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
101 Collection: tangled.FeedReactionNSID,
102 Repo: currentUser.Did,
103 Rkey: reaction.Rkey,
104 })
105
106 if err != nil {
107 l.Error("failed to remove reaction", "err", err)
108 return
109 }
110
111 err = db.DeleteReactionByRkey(s.db, currentUser.Did, reaction.Rkey)
112 if err != nil {
113 l.Warn("failed to delete reaction from DB", "err", err)
114 // this is not an issue, the firehose event might have already done this
115 }
116
117 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
118 if err != nil {
119 l.Error("failed to get reactions", "subjectUri", subjectUri, "err", err)
120 return
121 }
122
123 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
124 Kind: reactionKind,
125 Count: reactionMap[reactionKind].Count,
126 Users: reactionMap[reactionKind].Users,
127 IsReacted: false,
128 CommentRkey: subjectUri.RecordKey().String(),
129 SubjectUri: subject,
130 })
131
132 return
133 }
134}