package state import ( "net/http" "time" comatproto "github.com/bluesky-social/indigo/api/atproto" lexutil "github.com/bluesky-social/indigo/lex/util" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/pages" "tangled.org/core/tid" ) func (s *State) Follow(w http.ResponseWriter, r *http.Request) { l := s.logger.With("handler", "Follow") currentUser := s.oauth.GetMultiAccountUser(r) subject := r.URL.Query().Get("subject") if subject == "" { l.Warn("invalid form") return } subjectIdent, err := s.idResolver.ResolveIdent(r.Context(), subject) if err != nil { l.Error("failed to follow, invalid did", "subject", subject, "err", err) return } if currentUser.Did == subjectIdent.DID.String() { l.Warn("cant follow or unfollow yourself") return } client, err := s.oauth.AuthorizedClient(r) if err != nil { l.Error("failed to authorize client", "err", err) return } switch r.Method { case http.MethodPost: createdAt := time.Now().Format(time.RFC3339) rkey := tid.TID() resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.GraphFollowNSID, Repo: currentUser.Did, Rkey: rkey, Record: &lexutil.LexiconTypeDecoder{ Val: &tangled.GraphFollow{ Subject: subjectIdent.DID.String(), CreatedAt: createdAt, }}, }) if err != nil { l.Error("failed to create atproto record", "err", err) return } l.Info("created atproto record", "uri", resp.Uri) follow := &models.Follow{ UserDid: currentUser.Did, SubjectDid: subjectIdent.DID.String(), Rkey: rkey, } err = db.AddFollow(s.db, follow) if err != nil { l.Error("failed to follow", "err", err) return } s.notifier.NewFollow(r.Context(), follow) followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String()) if err != nil { l.Error("failed to get follow stats", "err", err) } s.pages.FollowFragment(w, pages.FollowFragmentParams{ UserDid: subjectIdent.DID.String(), FollowStatus: models.IsFollowing, FollowersCount: followStats.Followers, }) return case http.MethodDelete: // find the record in the db follow, err := db.GetFollow(s.db, currentUser.Did, subjectIdent.DID.String()) if err != nil { l.Error("failed to get follow relationship", "err", err) return } _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ Collection: tangled.GraphFollowNSID, Repo: currentUser.Did, Rkey: follow.Rkey, }) if err != nil { l.Error("failed to unfollow", "err", err) return } err = db.DeleteFollowByRkey(s.db, currentUser.Did, follow.Rkey) if err != nil { l.Warn("failed to delete follow from DB", "err", err) // this is not an issue, the firehose event might have already done this } followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String()) if err != nil { l.Error("failed to get follow stats", "err", err) } s.pages.FollowFragment(w, pages.FollowFragmentParams{ UserDid: subjectIdent.DID.String(), FollowStatus: models.IsNotFollowing, FollowersCount: followStats.Followers, }) s.notifier.DeleteFollow(r.Context(), follow) return } }