This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / state / follow.go
4.2 kB 159 lines
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 Rkey: tid.TID(), 50 FollowedAt: time.Now(), 51 } 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, 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: follow.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}