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 })
89
90 return
91 case http.MethodDelete:
92 reaction, err := db.GetReaction(s.db, currentUser.Did, subjectUri, reactionKind)
93 if err != nil {
94 l.Error("failed to get reaction relationship", "did", currentUser.Did, "subjectUri", subjectUri, "err", err)
95 return
96 }
97
98 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
99 Collection: tangled.FeedReactionNSID,
100 Repo: currentUser.Did,
101 Rkey: reaction.Rkey,
102 })
103
104 if err != nil {
105 l.Error("failed to remove reaction", "err", err)
106 return
107 }
108
109 err = db.DeleteReactionByRkey(s.db, currentUser.Did, reaction.Rkey)
110 if err != nil {
111 l.Warn("failed to delete reaction from DB", "err", err)
112 // this is not an issue, the firehose event might have already done this
113 }
114
115 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
116 if err != nil {
117 l.Error("failed to get reactions", "subjectUri", subjectUri, "err", err)
118 return
119 }
120
121 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
122 Kind: reactionKind,
123 Count: reactionMap[reactionKind].Count,
124 Users: reactionMap[reactionKind].Users,
125 IsReacted: false,
126 })
127
128 return
129 }
130}