A vibe coded tangled fork which supports pijul.
at 3f6e50b7512609295303f4756793a6ff3238c847 156 lines 4.2 kB view raw
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 "tangled.org/core/api/tangled" 12 "tangled.org/core/appview/db" 13 "tangled.org/core/appview/models" 14 "tangled.org/core/appview/pages" 15 "tangled.org/core/tid" 16) 17 18func (s *State) Follow(w http.ResponseWriter, r *http.Request) { 19 currentUser := s.oauth.GetMultiAccountUser(r) 20 21 subject := r.URL.Query().Get("subject") 22 if subject == "" { 23 log.Println("invalid form") 24 return 25 } 26 27 subjectIdent, err := s.idResolver.ResolveIdent(r.Context(), subject) 28 if err != nil { 29 log.Println("failed to follow, invalid did") 30 return 31 } 32 33 if currentUser.Active.Did == subjectIdent.DID.String() { 34 log.Println("cant follow or unfollow yourself") 35 return 36 } 37 38 client, err := s.oauth.AuthorizedClient(r) 39 if err != nil { 40 log.Println("failed to authorize client") 41 return 42 } 43 44 switch r.Method { 45 case http.MethodPost: 46 follow := models.Follow{ 47 UserDid: currentUser.Active.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.Active.Did, 69 Rkey: follow.Rkey, 70 Record: &lexutil.LexiconTypeDecoder{ 71 Val: &record, 72 }, 73 }) 74 if err != nil { 75 log.Println("failed to create atproto record", err) 76 return 77 } 78 log.Println("created atproto record: ", resp.Uri) 79 80 if err := tx.Commit(); err != nil { 81 s.logger.Error("failed to commit transaction", "err", err) 82 // DB op failed but record is created in PDS. Ingester will backfill the missed operation 83 } 84 85 s.notifier.NewFollow(r.Context(), &follow) 86 87 followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String()) 88 if err != nil { 89 log.Println("failed to get follow stats", err) 90 } 91 92 s.pages.FollowFragment(w, pages.FollowFragmentParams{ 93 UserDid: subjectIdent.DID.String(), 94 FollowStatus: models.IsFollowing, 95 FollowersCount: followStats.Followers, 96 }) 97 98 return 99 case http.MethodDelete: 100 tx, err := s.db.BeginTx(r.Context(), nil) 101 if err != nil { 102 s.logger.Error("failed to start transaction", "err", err) 103 } 104 defer tx.Rollback() 105 106 follows, err := db.DeleteFollow(tx, syntax.DID(currentUser.Active.Did), subjectIdent.DID) 107 if err != nil { 108 s.logger.Error("failed to delete follows from db", "err", err) 109 return 110 } 111 112 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 113 for _, followAt := range follows { 114 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 115 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{ 116 Collection: tangled.GraphFollowNSID, 117 Rkey: followAt.RecordKey().String(), 118 }, 119 }) 120 } 121 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{ 122 Repo: currentUser.Active.Did, 123 Writes: writes, 124 }) 125 if err != nil { 126 s.logger.Error("failed to delete follows from PDS", "err", err) 127 return 128 } 129 130 if err := tx.Commit(); err != nil { 131 s.logger.Error("failed to commit transaction", "err", err) 132 // DB op failed but record is created in PDS. Ingester will backfill the missed operation 133 } 134 135 s.notifier.DeleteFollow(r.Context(), &models.Follow{ 136 UserDid: currentUser.Active.Did, 137 SubjectDid: subjectIdent.DID.String(), 138 // Rkey 139 // FollowedAt 140 }) 141 142 followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String()) 143 if err != nil { 144 log.Println("failed to get follow stats", err) 145 } 146 147 s.pages.FollowFragment(w, pages.FollowFragmentParams{ 148 UserDid: subjectIdent.DID.String(), 149 FollowStatus: models.IsNotFollowing, 150 FollowersCount: followStats.Followers, 151 }) 152 153 return 154 } 155 156}