This repository has no description
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 "tangled.org/core/api/tangled"
11 "tangled.org/core/appview/db"
12 "tangled.org/core/appview/models"
13 "tangled.org/core/appview/pages"
14 "tangled.org/core/tid"
15)
16
17func (s *State) Follow(w http.ResponseWriter, r *http.Request) {
18 l := s.logger.With("handler", "Follow")
19 currentUser := s.oauth.GetMultiAccountUser(r)
20
21 subject := r.URL.Query().Get("subject")
22 if subject == "" {
23 l.Warn("invalid form")
24 return
25 }
26
27 subjectIdent, err := s.idResolver.ResolveIdent(r.Context(), subject)
28 if err != nil {
29 l.Error("failed to follow, invalid did", "subject", subject, "err", err)
30 return
31 }
32
33 if currentUser.Did == subjectIdent.DID.String() {
34 l.Warn("cant follow or unfollow yourself")
35 return
36 }
37
38 client, err := s.oauth.AuthorizedClient(r)
39 if err != nil {
40 l.Error("failed to authorize client", "err", err)
41 return
42 }
43
44 switch r.Method {
45 case http.MethodPost:
46 follow := models.Follow{
47 UserDid: currentUser.Did,
48 SubjectDid: subjectIdent.DID.String(),
49 FollowedAt: time.Now(),
50 }
51 rkey := tid.TID()
52
53 tx, err := s.db.BeginTx(r.Context(), nil)
54 if err != nil {
55 s.logger.Error("failed to start transaction", "err", err)
56 return
57 }
58 defer tx.Rollback()
59
60 if err := db.UpsertFollow(tx, rkey, follow); err != nil {
61 s.logger.Error("failed to follow", "err", err)
62 return
63 }
64
65 record := follow.AsRecord()
66 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
67 Collection: tangled.GraphFollowNSID,
68 Repo: currentUser.Did,
69 Rkey: rkey,
70 Record: &lexutil.LexiconTypeDecoder{
71 Val: &record,
72 },
73 })
74 if err != nil {
75 l.Error("failed to create atproto record", "err", err)
76 return
77 }
78
79 l.Info("created atproto record", "uri", resp.Uri)
80
81 if err := tx.Commit(); err != nil {
82 s.logger.Error("failed to commit transaction", "err", err)
83 // DB op failed but record is created in PDS. Ingester will backfill the missed operation
84 }
85
86 s.notifier.NewFollow(r.Context(), &follow)
87
88 followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
89 if err != nil {
90 l.Error("failed to get follow stats", "err", err)
91 }
92
93 s.pages.FollowFragment(w, pages.FollowFragmentParams{
94 UserDid: subjectIdent.DID.String(),
95 FollowStatus: models.IsFollowing,
96 FollowersCount: followStats.Followers,
97 })
98
99 return
100 case http.MethodDelete:
101 tx, err := s.db.BeginTx(r.Context(), nil)
102 if err != nil {
103 l.Error("failed to start transaction", "err", err)
104 return
105 }
106 defer tx.Rollback()
107
108 follows, err := db.DeleteFollow(tx, syntax.DID(currentUser.Did), subjectIdent.DID)
109 if err != nil {
110 l.Error("failed to delete follows from db", "err", err)
111 return
112 }
113
114 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
115 for _, followAt := range follows {
116 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
117 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
118 Collection: tangled.GraphFollowNSID,
119 Rkey: followAt.RecordKey().String(),
120 },
121 })
122 }
123 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
124 Repo: currentUser.Did,
125 Writes: writes,
126 })
127 if err != nil {
128 l.Error("failed to delete follows from PDS", "err", err)
129 return
130 }
131
132 if err := tx.Commit(); err != nil {
133 l.Error("failed to commit transaction", "err", err)
134 // The record was deleted from the PDS but the local rollback kept it.
135 // Ingester will backfill the missed operation
136 }
137
138 s.notifier.DeleteFollow(r.Context(), &models.Follow{
139 UserDid: currentUser.Did,
140 SubjectDid: subjectIdent.DID.String(),
141 // Rkey
142 // FollowedAt
143 })
144
145 followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
146 if err != nil {
147 l.Error("failed to get follow stats", "err", err)
148 }
149
150 s.pages.FollowFragment(w, pages.FollowFragmentParams{
151 UserDid: subjectIdent.DID.String(),
152 FollowStatus: models.IsNotFollowing,
153 FollowersCount: followStats.Followers,
154 })
155
156 return
157 }
158
159}