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 createdAt := time.Now().Format(time.RFC3339)
49 rkey := tid.TID()
50 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
51 Collection: tangled.FeedReactionNSID,
52 Repo: currentUser.Active.Did,
53 Rkey: rkey,
54 Record: &lexutil.LexiconTypeDecoder{
55 Val: &tangled.FeedReaction{
56 Subject: subjectUri.String(),
57 Reaction: reactionKind.String(),
58 CreatedAt: createdAt,
59 },
60 },
61 })
62 if err != nil {
63 log.Println("failed to create atproto record", err)
64 return
65 }
66
67 err = db.AddReaction(s.db, currentUser.Active.Did, subjectUri, reactionKind, rkey)
68 if err != nil {
69 log.Println("failed to react", err)
70 return
71 }
72
73 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
74 if err != nil {
75 log.Println("failed to get reactions for ", subjectUri)
76 }
77
78 log.Println("created atproto record: ", resp.Uri)
79
80 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
81 ThreadAt: subjectUri,
82 Kind: reactionKind,
83 Count: reactionMap[reactionKind].Count,
84 Users: reactionMap[reactionKind].Users,
85 IsReacted: true,
86 })
87
88 return
89 case http.MethodDelete:
90 tx, err := s.db.BeginTx(r.Context(), nil)
91 if err != nil {
92 s.logger.Error("failed to start transaction", "err", err)
93 }
94 defer tx.Rollback()
95
96 reactions, err := db.DeleteReaction(tx, syntax.DID(currentUser.Active.Did), subjectUri, reactionKind)
97 if err != nil {
98 s.logger.Error("failed to delete reactions from db", "err", err)
99 return
100 }
101
102 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
103 for _, reactionAt := range reactions {
104 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
105 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
106 Collection: tangled.FeedReactionNSID,
107 Rkey: reactionAt.RecordKey().String(),
108 },
109 })
110 }
111 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
112 Repo: currentUser.Active.Did,
113 Writes: writes,
114 })
115 if err != nil {
116 s.logger.Error("failed to delete reactions from PDS", "err", err)
117 return
118 }
119
120 if err := tx.Commit(); err != nil {
121 s.logger.Error("failed to commit transaction", "err", err)
122 // DB op failed but record is created in PDS. Ingester will backfill the missed operation
123 }
124
125 reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
126 if err != nil {
127 log.Println("failed to get reactions for ", subjectUri)
128 return
129 }
130
131 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{
132 ThreadAt: subjectUri,
133 Kind: reactionKind,
134 Count: reactionMap[reactionKind].Count,
135 Users: reactionMap[reactionKind].Users,
136 IsReacted: false,
137 })
138
139 return
140 }
141}