Monorepo for Tangled tangled.org

add unfollow bits

Changed files
+117 -23
appview
+1
appview/db/db.go
··· 43 43 create table if not exists follows ( 44 44 user_did text not null, 45 45 subject_did text not null, 46 + at_uri text not null, 46 47 followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 47 48 primary key (user_did, subject_did), 48 49 check (user_did <> subject_did)
+44 -3
appview/db/follow.go
··· 1 1 package db 2 2 3 - func (d *DB) AddFollow(userDid, subjectDid string) error { 4 - query := `insert into follows (user_did, subject_did) values (?, ?)` 5 - _, err := d.db.Exec(query, userDid, subjectDid) 3 + import ( 4 + "log" 5 + "time" 6 + ) 7 + 8 + type Follow struct { 9 + UserDid string 10 + SubjectDid string 11 + FollowedAt *time.Time 12 + AtUri string 13 + } 14 + 15 + func (d *DB) AddFollow(userDid, subjectDid, atUri string) error { 16 + query := `insert into follows (user_did, subject_did, at_uri) values (?, ?, ?)` 17 + _, err := d.db.Exec(query, userDid, subjectDid, atUri) 18 + return err 19 + } 20 + 21 + // Get a follow record 22 + func (d *DB) GetFollow(userDid, subjectDid string) (*Follow, error) { 23 + query := `select user_did, subject_did, followed_at, at_uri from follows where user_did = ? and subject_did = ?` 24 + row := d.db.QueryRow(query, userDid, subjectDid) 25 + 26 + var follow Follow 27 + var followedAt string 28 + err := row.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.AtUri) 29 + if err != nil { 30 + return nil, err 31 + } 32 + 33 + followedAtTime, err := time.Parse(time.RFC3339, followedAt) 34 + if err != nil { 35 + log.Println("unable to determine followed at time") 36 + follow.FollowedAt = nil 37 + } else { 38 + follow.FollowedAt = &followedAtTime 39 + } 40 + 41 + return &follow, nil 42 + } 43 + 44 + // Get a follow record 45 + func (d *DB) DeleteFollow(userDid, subjectDid string) error { 46 + _, err := d.db.Exec(`delete from follows where user_did = ? and subject_did = ?`, userDid, subjectDid) 6 47 return err 7 48 }
+72 -20
appview/state/state.go
··· 531 531 } 532 532 533 533 func (s *State) Follow(w http.ResponseWriter, r *http.Request) { 534 - subject := r.FormValue("subject") 534 + currentUser := s.auth.GetUser(r) 535 535 536 + subject := r.URL.Query().Get("subject") 536 537 if subject == "" { 537 538 log.Println("invalid form") 538 539 return 539 540 } 540 541 541 542 subjectIdent, err := s.resolver.ResolveIdent(r.Context(), subject) 542 - currentUser := s.auth.GetUser(r) 543 + if err != nil { 544 + log.Println("failed to follow, invalid did") 545 + } 546 + 547 + if currentUser.Did == subjectIdent.DID.String() { 548 + log.Println("cant follow or unfollow yourself") 549 + return 550 + } 543 551 544 552 client, _ := s.auth.AuthorizedClient(r) 545 - createdAt := time.Now().Format(time.RFC3339) 546 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 547 - Collection: tangled.GraphFollowNSID, 548 - Repo: currentUser.Did, 549 - Rkey: s.TID(), 550 - Record: &lexutil.LexiconTypeDecoder{ 551 - Val: &tangled.GraphFollow{ 552 - Subject: subjectIdent.DID.String(), 553 - CreatedAt: createdAt, 554 - }}, 555 - }) 553 + 554 + switch r.Method { 555 + case http.MethodPost: 556 + createdAt := time.Now().Format(time.RFC3339) 557 + resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 558 + Collection: tangled.GraphFollowNSID, 559 + Repo: currentUser.Did, 560 + Rkey: s.TID(), 561 + Record: &lexutil.LexiconTypeDecoder{ 562 + Val: &tangled.GraphFollow{ 563 + Subject: subjectIdent.DID.String(), 564 + CreatedAt: createdAt, 565 + }}, 566 + }) 567 + if err != nil { 568 + log.Println("failed to create atproto record", err) 569 + return 570 + } 571 + 572 + err = s.db.AddFollow(currentUser.Did, subjectIdent.DID.String(), resp.Uri) 573 + if err != nil { 574 + log.Println("failed to follow", err) 575 + return 576 + } 577 + 578 + log.Println("created atproto record: ", resp.Uri) 579 + 580 + return 581 + case http.MethodDelete: 582 + // find the record in the db 583 + 584 + follow, err := s.db.GetFollow(currentUser.Did, subjectIdent.DID.String()) 585 + if err != nil { 586 + log.Println("failed to get follow relationship") 587 + return 588 + } 589 + 590 + existingRecordUri, _ := syntax.ParseATURI(follow.AtUri) 591 + 592 + resp, err := comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 593 + Collection: tangled.GraphFollowNSID, 594 + Repo: currentUser.Did, 595 + Rkey: existingRecordUri.RecordKey().String(), 596 + }) 597 + 598 + log.Println(resp.Commit.Cid) 599 + 600 + if err != nil { 601 + log.Println("failed to unfollow") 602 + return 603 + } 604 + 605 + err = s.db.DeleteFollow(currentUser.Did, subjectIdent.DID.String()) 606 + if err != nil { 607 + log.Println("failed to delete follow from DB") 608 + // this is not an issue, the firehose event might have already done this 609 + } 556 610 557 - err = s.db.AddFollow(currentUser.Did, subjectIdent.DID.String()) 558 - if err != nil { 559 - log.Println("failed to follow", err) 611 + w.WriteHeader(http.StatusNoContent) 560 612 return 561 613 } 562 614 563 - log.Println("created atproto record: ", resp.Uri) 564 - 565 - return 566 615 } 567 616 568 617 func (s *State) Router() http.Handler { ··· 655 704 // r.Post("/import", s.ImportRepo) 656 705 }) 657 706 658 - r.With(AuthMiddleware(s)).Put("/follow", s.Follow) 707 + r.With(AuthMiddleware(s)).Route("/follow", func(r chi.Router) { 708 + r.Post("/", s.Follow) 709 + r.Delete("/", s.Follow) 710 + }) 659 711 660 712 r.Route("/settings", func(r chi.Router) { 661 713 r.Use(AuthMiddleware(s))