A vibe coded tangled fork which supports pijul.
1package state
2
3import (
4 "log"
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 currentUser := s.oauth.GetMultiAccountUser(r)
21
22 subject := r.URL.Query().Get("subject")
23 if subject == "" {
24 log.Println("invalid form")
25 return
26 }
27
28 subjectUri, err := syntax.ParseATURI(subject)
29 if err != nil {
30 log.Println("invalid form")
31 return
32 }
33
34 reactionKind, ok := models.ParseReactionKind(r.URL.Query().Get("kind"))
35 if !ok {
36 log.Println("invalid reaction kind")
37 return
38 }
39
40 client, err := s.oauth.AuthorizedClient(r)
41 if err != nil {
42 log.Println("failed to authorize client", err)
43 return
44 }
45
46 switch r.Method {
47 case http.MethodPost:
48 reaction := models.Reaction{
49 ReactedByDid: currentUser.Active.Did,
50 Rkey: tid.TID(),
51 Kind: reactionKind,
52 ThreadAt: subjectUri,
53 Created: time.Now(),
54 }
55
56 tx, err := s.db.BeginTx(r.Context(), nil)
57 if err != nil {
58 s.logger.Error("failed to start transaction", "err", err)
59 return
60 }
61 defer tx.Rollback()
62
63 if err := db.UpsertReaction(tx, reaction); err != nil {
64 log.Println("failed to react", err)
65 return
66 }
67
68 record := reaction.AsRecord()
69 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
70 Collection: tangled.FeedReactionNSID,
71 Repo: currentUser.Active.Did,
72 Rkey: reaction.Rkey,
73 Record: &lexutil.LexiconTypeDecoder{
74 Val: &record,
75 },
76 })
77 if err != nil {
78 log.Println("failed to create atproto record", err)
79 return
80 }
81 log.Println("created atproto record: ", resp.Uri)
82
83 if err := tx.Commit(); err != nil {
84 s.logger.Error("failed to commit transaction", "err", err)
85 // DB op failed but record is created in PDS. Ingester will backfill the missed operation
86 }
87
88 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
89 if err != nil {
90 log.Println("failed to get reactions for ", subjectUri)
91 }
92
93 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
94 ThreadAt: subjectUri,
95 Kind: reactionKind,
96 Count: reactionMap[reactionKind].Count,
97 Users: reactionMap[reactionKind].Users,
98 IsReacted: true,
99 })
100
101 return
102 case http.MethodDelete:
103 tx, err := s.db.BeginTx(r.Context(), nil)
104 if err != nil {
105 s.logger.Error("failed to start transaction", "err", err)
106 }
107 defer tx.Rollback()
108
109 reactions, err := db.DeleteReaction(tx, syntax.DID(currentUser.Active.Did), subjectUri, reactionKind)
110 if err != nil {
111 s.logger.Error("failed to delete reactions from db", "err", err)
112 return
113 }
114
115 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
116 for _, reactionAt := range reactions {
117 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
118 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
119 Collection: tangled.FeedReactionNSID,
120 Rkey: reactionAt.RecordKey().String(),
121 },
122 })
123 }
124 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
125 Repo: currentUser.Active.Did,
126 Writes: writes,
127 })
128 if err != nil {
129 s.logger.Error("failed to delete reactions from PDS", "err", err)
130 return
131 }
132
133 if err := tx.Commit(); err != nil {
134 s.logger.Error("failed to commit transaction", "err", err)
135 // DB op failed but record is created in PDS. Ingester will backfill the missed operation
136 }
137
138 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
139 if err != nil {
140 log.Println("failed to get reactions for ", subjectUri)
141 return
142 }
143
144 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
145 ThreadAt: subjectUri,
146 Kind: reactionKind,
147 Count: reactionMap[reactionKind].Count,
148 Users: reactionMap[reactionKind].Users,
149 IsReacted: false,
150 })
151
152 return
153 }
154}