Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

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

add followers/following to UI

Akshay a37dc67c ee81ad33

+140 -29
+1 -1
appview/db/db.go
··· 59 59 create table if not exists follows ( 60 60 user_did text not null, 61 61 subject_did text not null, 62 - at_uri text not null, 62 + rkey text not null, 63 63 followed_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 64 64 primary key (user_did, subject_did), 65 65 check (user_did <> subject_did)
+46 -2
appview/db/follow.go
··· 20 20 21 21 // Get a follow record 22 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 = ?` 23 + query := `select user_did, subject_did, followed_at, rkey from follows where user_did = ? and subject_did = ?` 24 24 row := d.db.QueryRow(query, userDid, subjectDid) 25 25 26 26 var follow Follow ··· 47 47 return err 48 48 } 49 49 50 + func (d *DB) GetFollowerFollowing(did string) (int, int, error) { 51 + followers, following := 0, 0 52 + err := d.db.QueryRow( 53 + `SELECT 54 + COUNT(CASE WHEN subject_did = ? THEN 1 END) AS followers, 55 + COUNT(CASE WHEN user_did = ? THEN 1 END) AS following 56 + FROM follows;`, did, did).Scan(&followers, &following) 57 + if err != nil { 58 + return 0, 0, err 59 + } 60 + return followers, following, nil 61 + } 62 + 63 + type FollowStatus int 64 + 65 + const ( 66 + IsNotFollowing FollowStatus = iota 67 + IsFollowing 68 + IsSelf 69 + ) 70 + 71 + func (s FollowStatus) String() string { 72 + switch s { 73 + case IsNotFollowing: 74 + return "IsNotFollowing" 75 + case IsFollowing: 76 + return "IsFollowing" 77 + case IsSelf: 78 + return "IsSelf" 79 + default: 80 + return "IsNotFollowing" 81 + } 82 + } 83 + 84 + func (d *DB) GetFollowStatus(userDid, subjectDid string) FollowStatus { 85 + if userDid == subjectDid { 86 + return IsSelf 87 + } else if _, err := d.GetFollow(userDid, subjectDid); err != nil { 88 + return IsNotFollowing 89 + } else { 90 + return IsFollowing 91 + } 92 + } 93 + 50 94 func (d *DB) GetAllFollows() ([]Follow, error) { 51 95 var follows []Follow 52 96 53 - rows, err := d.db.Query(`select user_did, subject_did, followed_at, at_uri from follows`) 97 + rows, err := d.db.Query(`select user_did, subject_did, followed_at, rkey from follows`) 54 98 if err != nil { 55 99 return nil, err 56 100 }
+19 -17
appview/db/repos.go
··· 9 9 Did string 10 10 Name string 11 11 Knot string 12 - Created *time.Time 13 12 Rkey string 13 + Created *time.Time 14 14 } 15 15 16 16 func (d *DB) GetAllRepos() ([]Repo, error) { 17 17 var repos []Repo 18 18 19 - rows, err := d.db.Query(`select did, name, knot, created from repos`) 19 + rows, err := d.db.Query(`select * from repos`) 20 20 if err != nil { 21 21 return nil, err 22 22 } 23 23 defer rows.Close() 24 24 25 25 for rows.Next() { 26 - repo, err := scanRepo(rows) 26 + var repo Repo 27 + err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, repo.Created) 27 28 if err != nil { 28 29 return nil, err 29 30 } 30 - repos = append(repos, *repo) 31 + repos = append(repos, repo) 31 32 } 32 33 33 34 if err := rows.Err(); err != nil { ··· 48 47 defer rows.Close() 49 48 50 49 for rows.Next() { 51 - repo, err := scanRepo(rows) 50 + var repo Repo 51 + err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, repo.Created) 52 52 if err != nil { 53 53 return nil, err 54 54 } 55 - repos = append(repos, *repo) 55 + repos = append(repos, repo) 56 56 } 57 57 58 58 if err := rows.Err(); err != nil { ··· 99 97 func (d *DB) CollaboratingIn(collaborator string) ([]Repo, error) { 100 98 var repos []Repo 101 99 102 - rows, err := d.db.Query(`select r.* from repos r join collaborators c on r.id = c.repo where c.did = ?;`, collaborator) 100 + rows, err := d.db.Query(`select r.did, r.name, r.knot, r.rkey, r.created from repos r join collaborators c on r.id = c.repo where c.did = ?;`, collaborator) 103 101 if err != nil { 104 102 return nil, err 105 103 } 106 104 defer rows.Close() 107 105 108 106 for rows.Next() { 109 - repo, err := scanRepo(rows) 107 + var repo Repo 108 + err := scanRepo(rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, repo.Created) 110 109 if err != nil { 111 110 return nil, err 112 111 } 113 - repos = append(repos, *repo) 112 + repos = append(repos, repo) 114 113 } 115 114 116 115 if err := rows.Err(); err != nil { ··· 121 118 return repos, nil 122 119 } 123 120 124 - func scanRepo(rows *sql.Rows) (*Repo, error) { 125 - var repo Repo 121 + func scanRepo(rows *sql.Rows, did, name, knot, rkey *string, created *time.Time) error { 126 122 var createdAt string 127 - if err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt); err != nil { 128 - return nil, err 123 + if err := rows.Scan(did, name, knot, rkey, &createdAt); err != nil { 124 + return err 129 125 } 130 126 131 127 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 132 128 if err != nil { 133 129 now := time.Now() 134 - repo.Created = &now 130 + created = &now 131 + } else { 132 + created = &createdAtTime 135 133 } 136 134 137 - repo.Created = &createdAtTime 138 - 139 - return &repo, nil 135 + return nil 140 136 }
+7
appview/pages/pages.go
··· 226 226 UserHandle string 227 227 Repos []db.Repo 228 228 CollaboratingRepos []db.Repo 229 + ProfileStats ProfileStats 230 + FollowStatus db.FollowStatus 231 + } 232 + 233 + type ProfileStats struct { 234 + Followers int 235 + Following int 229 236 } 230 237 231 238 func (p *Pages) ProfilePage(w io.Writer, params ProfilePageParams) error {
+27 -3
appview/pages/templates/user/profile.html
··· 1 1 {{ define "title" }}{{ or .UserHandle .UserDid }}{{ end }} 2 2 3 3 {{ define "content" }} 4 - <h1>{{ didOrHandle .UserDid .UserHandle }}</h1> 4 + <div class="flex "> 5 + <h1 class="pb-1"> 6 + {{ didOrHandle .UserDid .UserHandle }} 7 + </h1> 8 + {{ if ne .FollowStatus.String "IsSelf" }} 9 + <button id="followBtn" 10 + class="btn mt-2" 11 + {{ if eq .FollowStatus.String "IsNotFollowing" }} 12 + hx-post="/follow?subject={{.UserDid}}" 13 + {{ else }} 14 + hx-delete="/follow?subject={{.UserDid}}" 15 + {{ end }} 16 + hx-trigger="click" 17 + hx-target="#followBtn" 18 + hx-swap="outerHTML" 19 + > 20 + {{ if eq .FollowStatus.String "IsNotFollowing" }}Follow{{ else }}Unfollow{{ end }} 21 + </button> 22 + {{ end }} 23 + </div> 24 + <div class="text-sm mb-4"> 25 + <span>{{ .ProfileStats.Followers }} followers</span> 26 + <div class="inline-block px-1 select-none after:content-['·']"></div> 27 + <span>following {{ .ProfileStats.Following }}</span> 28 + </div> 5 29 <p class="text-xs font-bold py-2">REPOS</p> 6 30 <div id="repos" class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> 7 31 {{ range .Repos }} ··· 57 33 class="border border-black p-4 shadow-sm bg-white" 58 34 > 59 35 <div id="repo-card-name" class="font-medium"> 60 - <a href="/@{{ or $.UserHandle $.UserDid }}/{{ .Name }}"> 61 - @{{ or $.UserHandle $.UserDid }}/{{ .Name }} 36 + <a href="/{{ .Did }}/{{ .Name }}"> 37 + @{{ .Did }}/{{ .Name }} 62 38 </a> 63 39 </div> 64 40 <div
+40 -6
appview/state/state.go
··· 78 78 record := tangled.GraphFollow{} 79 79 err := json.Unmarshal(raw, &record) 80 80 if err != nil { 81 + log.Println("invalid record") 81 82 return err 82 83 } 83 84 err = db.AddFollow(did, record.Subject, e.Commit.RKey) ··· 620 619 log.Printf("getting collaborating repos for %s: %s", ident.DID.String(), err) 621 620 } 622 621 622 + followers, following, err := s.db.GetFollowerFollowing(ident.DID.String()) 623 + if err != nil { 624 + log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err) 625 + } 626 + 627 + loggedInUser := s.auth.GetUser(r) 628 + followStatus := db.IsNotFollowing 629 + if loggedInUser != nil { 630 + followStatus = s.db.GetFollowStatus(loggedInUser.Did, ident.DID.String()) 631 + } 632 + 623 633 s.pages.ProfilePage(w, pages.ProfilePageParams{ 624 - LoggedInUser: s.auth.GetUser(r), 634 + LoggedInUser: loggedInUser, 625 635 UserDid: ident.DID.String(), 626 636 UserHandle: ident.Handle.String(), 627 637 Repos: repos, 628 638 CollaboratingRepos: collaboratingRepos, 639 + ProfileStats: pages.ProfileStats{ 640 + Followers: followers, 641 + Following: following, 642 + }, 643 + FollowStatus: db.FollowStatus(followStatus), 629 644 }) 630 645 } 631 646 ··· 693 676 694 677 log.Println("created atproto record: ", resp.Uri) 695 678 679 + w.Write([]byte(fmt.Sprintf(` 680 + <button id="followBtn" 681 + class="btn mt-2" 682 + hx-delete="/follow?subject=%s" 683 + hx-trigger="click" 684 + hx-target="#followBtn" 685 + hx-swap="outerHTML"> 686 + Unfollow 687 + </button> 688 + `, subjectIdent.DID.String()))) 689 + 696 690 return 697 691 case http.MethodDelete: 698 692 // find the record in the db 699 - 700 693 follow, err := s.db.GetFollow(currentUser.Did, subjectIdent.DID.String()) 701 694 if err != nil { 702 695 log.Println("failed to get follow relationship") 703 696 return 704 697 } 705 698 706 - resp, err := comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 699 + _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 707 700 Collection: tangled.GraphFollowNSID, 708 701 Repo: currentUser.Did, 709 702 Rkey: follow.RKey, 710 703 }) 711 - 712 - log.Println(resp.Commit.Cid) 713 704 714 705 if err != nil { 715 706 log.Println("failed to unfollow") ··· 730 705 // this is not an issue, the firehose event might have already done this 731 706 } 732 707 733 - w.WriteHeader(http.StatusNoContent) 708 + w.Write([]byte(fmt.Sprintf(` 709 + <button id="followBtn" 710 + class="btn mt-2" 711 + hx-post="/follow?subject=%s" 712 + hx-trigger="click" 713 + hx-target="#followBtn" 714 + hx-swap="outerHTML"> 715 + Follow 716 + </button> 717 + `, subjectIdent.DID.String()))) 734 718 return 735 719 } 736 720