From 4a519952dc40ea57d871e9e0cc061956ef0c4f51 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 15 Feb 2025 13:27:09 +0000 Subject: [PATCH] add unfollow bits --- appview/db/db.go | 1 + appview/db/follow.go | 47 +++++++++++++++++++-- appview/state/state.go | 92 +++++++++++++++++++++++++++++++++--------- 3 files changed, 117 insertions(+), 23 deletions(-) diff --git a/appview/db/db.go b/appview/db/db.go index aa5bea6e..4b1e10a9 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -43,6 +43,7 @@ func Make(dbPath string) (*DB, error) { create table if not exists follows ( user_did text not null, subject_did text not null, + at_uri text not null, followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), primary key (user_did, subject_did), check (user_did <> subject_did) diff --git a/appview/db/follow.go b/appview/db/follow.go index d02e758d..4bce8d2c 100644 --- a/appview/db/follow.go +++ b/appview/db/follow.go @@ -1,7 +1,48 @@ package db -func (d *DB) AddFollow(userDid, subjectDid string) error { - query := `insert into follows (user_did, subject_did) values (?, ?)` - _, err := d.db.Exec(query, userDid, subjectDid) +import ( + "log" + "time" +) + +type Follow struct { + UserDid string + SubjectDid string + FollowedAt *time.Time + AtUri string +} + +func (d *DB) AddFollow(userDid, subjectDid, atUri string) error { + query := `insert into follows (user_did, subject_did, at_uri) values (?, ?, ?)` + _, err := d.db.Exec(query, userDid, subjectDid, atUri) + return err +} + +// Get a follow record +func (d *DB) GetFollow(userDid, subjectDid string) (*Follow, error) { + query := `select user_did, subject_did, followed_at, at_uri from follows where user_did = ? and subject_did = ?` + row := d.db.QueryRow(query, userDid, subjectDid) + + var follow Follow + var followedAt string + err := row.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.AtUri) + if err != nil { + return nil, err + } + + followedAtTime, err := time.Parse(time.RFC3339, followedAt) + if err != nil { + log.Println("unable to determine followed at time") + follow.FollowedAt = nil + } else { + follow.FollowedAt = &followedAtTime + } + + return &follow, nil +} + +// Get a follow record +func (d *DB) DeleteFollow(userDid, subjectDid string) error { + _, err := d.db.Exec(`delete from follows where user_did = ? and subject_did = ?`, userDid, subjectDid) return err } diff --git a/appview/state/state.go b/appview/state/state.go index ab13180f..81e67cc9 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -531,38 +531,87 @@ func (s *State) ProfilePage(w http.ResponseWriter, r *http.Request) { } func (s *State) Follow(w http.ResponseWriter, r *http.Request) { - subject := r.FormValue("subject") + currentUser := s.auth.GetUser(r) + subject := r.URL.Query().Get("subject") if subject == "" { log.Println("invalid form") return } subjectIdent, err := s.resolver.ResolveIdent(r.Context(), subject) - currentUser := s.auth.GetUser(r) + if err != nil { + log.Println("failed to follow, invalid did") + } + + if currentUser.Did == subjectIdent.DID.String() { + log.Println("cant follow or unfollow yourself") + return + } client, _ := s.auth.AuthorizedClient(r) - createdAt := time.Now().Format(time.RFC3339) - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ - Collection: tangled.GraphFollowNSID, - Repo: currentUser.Did, - Rkey: s.TID(), - Record: &lexutil.LexiconTypeDecoder{ - Val: &tangled.GraphFollow{ - Subject: subjectIdent.DID.String(), - CreatedAt: createdAt, - }}, - }) - err = s.db.AddFollow(currentUser.Did, subjectIdent.DID.String()) - if err != nil { - log.Println("failed to follow", err) + switch r.Method { + case http.MethodPost: + createdAt := time.Now().Format(time.RFC3339) + resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ + Collection: tangled.GraphFollowNSID, + Repo: currentUser.Did, + Rkey: s.TID(), + Record: &lexutil.LexiconTypeDecoder{ + Val: &tangled.GraphFollow{ + Subject: subjectIdent.DID.String(), + CreatedAt: createdAt, + }}, + }) + if err != nil { + log.Println("failed to create atproto record", err) + return + } + + err = s.db.AddFollow(currentUser.Did, subjectIdent.DID.String(), resp.Uri) + if err != nil { + log.Println("failed to follow", err) + return + } + + log.Println("created atproto record: ", resp.Uri) + return - } + case http.MethodDelete: + // find the record in the db - log.Println("created atproto record: ", resp.Uri) + follow, err := s.db.GetFollow(currentUser.Did, subjectIdent.DID.String()) + if err != nil { + log.Println("failed to get follow relationship") + return + } + + existingRecordUri, _ := syntax.ParseATURI(follow.AtUri) + + resp, err := comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ + Collection: tangled.GraphFollowNSID, + Repo: currentUser.Did, + Rkey: existingRecordUri.RecordKey().String(), + }) + + log.Println(resp.Commit.Cid) + + if err != nil { + log.Println("failed to unfollow") + return + } + + err = s.db.DeleteFollow(currentUser.Did, subjectIdent.DID.String()) + if err != nil { + log.Println("failed to delete follow from DB") + // this is not an issue, the firehose event might have already done this + } + + w.WriteHeader(http.StatusNoContent) + return + } - return } func (s *State) Router() http.Handler { @@ -655,7 +704,10 @@ func (s *State) StandardRouter() http.Handler { // r.Post("/import", s.ImportRepo) }) - r.With(AuthMiddleware(s)).Put("/follow", s.Follow) + r.With(AuthMiddleware(s)).Route("/follow", func(r chi.Router) { + r.Post("/", s.Follow) + r.Delete("/", s.Follow) + }) r.Route("/settings", func(r chi.Router) { r.Use(AuthMiddleware(s)) -- 2.51.2