Monorepo for Tangled
tangled.org
1package state
2
3import (
4 "fmt"
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 l := s.logger.With("handler", "React")
21 currentUser := s.oauth.GetMultiAccountUser(r)
22
23 subject := r.FormValue("subject-uri")
24 if subject == "" {
25 l.Warn("invalid form")
26 return
27 }
28
29 subjectUri, err := syntax.ParseATURI(subject)
30 if err != nil {
31 l.Warn("invalid form", "subject", subject, "err", err)
32 return
33 }
34
35 // override collection NSID to new one
36 switch subjectUri.Collection() {
37 case tangled.RepoIssueCommentNSID, tangled.RepoPullCommentNSID:
38 subjectUri = syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", subjectUri.Authority(), tangled.FeedCommentNSID, subjectUri.RecordKey()))
39 }
40
41 reactionKind, ok := models.ParseReactionKind(r.URL.Query().Get("kind"))
42 if !ok {
43 l.Warn("invalid reaction kind", "kind", r.URL.Query().Get("kind"))
44 return
45 }
46
47 client, err := s.oauth.AuthorizedClient(r)
48 if err != nil {
49 l.Error("failed to authorize client", "err", err)
50 return
51 }
52
53 switch r.Method {
54 case http.MethodPost:
55 createdAt := time.Now().Format(time.RFC3339)
56 rkey := tid.TID()
57 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
58 Collection: tangled.FeedReactionNSID,
59 Repo: currentUser.Did,
60 Rkey: rkey,
61 Record: &lexutil.LexiconTypeDecoder{
62 Val: &tangled.FeedReaction{
63 Subject: subjectUri.String(),
64 Reaction: reactionKind.String(),
65 CreatedAt: createdAt,
66 },
67 },
68 })
69 if err != nil {
70 l.Error("failed to create atproto record", "err", err)
71 return
72 }
73
74 err = db.AddReaction(s.db, currentUser.Did, subjectUri, reactionKind, rkey)
75 if err != nil {
76 l.Error("failed to react", "err", err)
77 return
78 }
79
80 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
81 if err != nil {
82 l.Error("failed to get reactions", "subjectUri", subjectUri, "err", err)
83 }
84
85 l.Info("created atproto record", "uri", resp.Uri)
86
87 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
88 Kind: reactionKind,
89 Count: reactionMap[reactionKind].Count,
90 Users: reactionMap[reactionKind].Users,
91 IsReacted: true,
92 })
93
94 return
95 case http.MethodDelete:
96 reaction, err := db.GetReaction(s.db, currentUser.Did, subjectUri, reactionKind)
97 if err != nil {
98 l.Error("failed to get reaction relationship", "did", currentUser.Did, "subjectUri", subjectUri, "err", err)
99 return
100 }
101
102 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
103 Collection: tangled.FeedReactionNSID,
104 Repo: currentUser.Did,
105 Rkey: reaction.Rkey,
106 })
107
108 if err != nil {
109 l.Error("failed to remove reaction", "err", err)
110 return
111 }
112
113 err = db.DeleteReactionByRkey(s.db, currentUser.Did, reaction.Rkey)
114 if err != nil {
115 l.Warn("failed to delete reaction from DB", "err", err)
116 // this is not an issue, the firehose event might have already done this
117 }
118
119 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
120 if err != nil {
121 l.Error("failed to get reactions", "subjectUri", subjectUri, "err", err)
122 return
123 }
124
125 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
126 Kind: reactionKind,
127 Count: reactionMap[reactionKind].Count,
128 Users: reactionMap[reactionKind].Users,
129 IsReacted: false,
130 })
131
132 return
133 }
134}