diff --git a/appview/db/follow.go b/appview/db/follow.go --- a/appview/db/follow.go +++ b/appview/db/follow.go @@ -5,27 +5,22 @@ "fmt" "log" "strings" "time" + + "tangled.org/core/appview/models" ) -type Follow struct { - UserDid string - SubjectDid string - FollowedAt time.Time - Rkey string -} - -func AddFollow(e Execer, follow *Follow) error { +func AddFollow(e Execer, follow *models.Follow) error { query := `insert or ignore into follows (user_did, subject_did, rkey) values (?, ?, ?)` _, err := e.Exec(query, follow.UserDid, follow.SubjectDid, follow.Rkey) return err } // Get a follow record -func GetFollow(e Execer, userDid, subjectDid string) (*Follow, error) { +func GetFollow(e Execer, userDid, subjectDid string) (*models.Follow, error) { query := `select user_did, subject_did, followed_at, rkey from follows where user_did = ? and subject_did = ?` row := e.QueryRow(query, userDid, subjectDid) - var follow Follow + var follow models.Follow var followedAt string err := row.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.Rkey) if err != nil { @@ -55,12 +50,7 @@ _, err := e.Exec(`delete from follows where user_did = ? and rkey = ?`, userDid, rkey) return err } -type FollowStats struct { - Followers int64 - Following int64 -} - -func GetFollowerFollowingCount(e Execer, did string) (FollowStats, error) { +func GetFollowerFollowingCount(e Execer, did string) (models.FollowStats, error) { var followers, following int64 err := e.QueryRow( `SELECT @@ -68,15 +58,15 @@ COUNT(CASE WHEN subject_did = ? THEN 1 END) AS followers, COUNT(CASE WHEN user_did = ? THEN 1 END) AS following FROM follows;`, did, did).Scan(&followers, &following) if err != nil { - return FollowStats{}, err + return models.FollowStats{}, err } - return FollowStats{ + return models.FollowStats{ Followers: followers, Following: following, }, nil } -func GetFollowerFollowingCounts(e Execer, dids []string) (map[string]FollowStats, error) { +func GetFollowerFollowingCounts(e Execer, dids []string) (map[string]models.FollowStats, error) { if len(dids) == 0 { return nil, nil } @@ -112,7 +102,7 @@ group by user_did ) g on f.did = g.did`, placeholderStr, placeholderStr) - result := make(map[string]FollowStats) + result := make(map[string]models.FollowStats) rows, err := e.Query(query, args...) if err != nil { @@ -126,7 +116,7 @@ var followers, following int64 if err := rows.Scan(&did, &followers, &following); err != nil { return nil, err } - result[did] = FollowStats{ + result[did] = models.FollowStats{ Followers: followers, Following: following, } @@ -134,7 +124,7 @@ } for _, did := range dids { if _, exists := result[did]; !exists { - result[did] = FollowStats{ + result[did] = models.FollowStats{ Followers: 0, Following: 0, } @@ -144,8 +134,8 @@ return result, nil } -func GetFollows(e Execer, limit int, filters ...filter) ([]Follow, error) { - var follows []Follow +func GetFollows(e Execer, limit int, filters ...filter) ([]models.Follow, error) { + var follows []models.Follow var conditions []string var args []any @@ -177,7 +167,7 @@ if err != nil { return nil, err } for rows.Next() { - var follow Follow + var follow models.Follow var followedAt string err := rows.Scan( &follow.UserDid, @@ -200,47 +190,26 @@ } return follows, nil } -func GetFollowers(e Execer, did string) ([]Follow, error) { +func GetFollowers(e Execer, did string) ([]models.Follow, error) { return GetFollows(e, 0, FilterEq("subject_did", did)) } -func GetFollowing(e Execer, did string) ([]Follow, error) { +func GetFollowing(e Execer, did string) ([]models.Follow, error) { return GetFollows(e, 0, FilterEq("user_did", did)) } -type FollowStatus int - -const ( - IsNotFollowing FollowStatus = iota - IsFollowing - IsSelf -) - -func (s FollowStatus) String() string { - switch s { - case IsNotFollowing: - return "IsNotFollowing" - case IsFollowing: - return "IsFollowing" - case IsSelf: - return "IsSelf" - default: - return "IsNotFollowing" - } -} - -func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) { +func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) { if len(subjectDids) == 0 || userDid == "" { - return make(map[string]FollowStatus), nil + return make(map[string]models.FollowStatus), nil } - result := make(map[string]FollowStatus) + result := make(map[string]models.FollowStatus) for _, subjectDid := range subjectDids { if userDid == subjectDid { - result[subjectDid] = IsSelf + result[subjectDid] = models.IsSelf } else { - result[subjectDid] = IsNotFollowing + result[subjectDid] = models.IsNotFollowing } } @@ -281,20 +250,20 @@ var subjectDid string if err := rows.Scan(&subjectDid); err != nil { return nil, err } - result[subjectDid] = IsFollowing + result[subjectDid] = models.IsFollowing } return result, nil } -func GetFollowStatus(e Execer, userDid, subjectDid string) FollowStatus { +func GetFollowStatus(e Execer, userDid, subjectDid string) models.FollowStatus { statuses, err := getFollowStatuses(e, userDid, []string{subjectDid}) if err != nil { - return IsNotFollowing + return models.IsNotFollowing } return statuses[subjectDid] } -func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) { +func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) { return getFollowStatuses(e, userDid, subjectDids) } diff --git a/appview/db/timeline.go b/appview/db/timeline.go --- a/appview/db/timeline.go +++ b/appview/db/timeline.go @@ -5,11 +5,12 @@ "sort" "time" "github.com/bluesky-social/indigo/atproto/syntax" + "tangled.org/core/appview/models" ) type TimelineEvent struct { *Repo - *Follow + *models.Follow *Star EventAt time.Time @@ -19,8 +20,8 @@ Source *Repo // optional: populate only if event is Follow *Profile - *FollowStats - *FollowStatus + *models.FollowStats + *models.FollowStatus // optional: populate only if event is Repo IsStarred bool @@ -211,7 +212,7 @@ if err != nil { return nil, err } - var followStatuses map[string]FollowStatus + var followStatuses map[string]models.FollowStatus if loggedInUserDid != "" { followStatuses, err = GetFollowStatuses(e, loggedInUserDid, subjects) if err != nil { @@ -224,7 +225,7 @@ for _, f := range follows { profile, _ := profiles[f.SubjectDid] followStatMap, _ := followStatMap[f.SubjectDid] - followStatus := IsNotFollowing + followStatus := models.IsNotFollowing if followStatuses != nil { followStatus = followStatuses[f.SubjectDid] } diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -149,7 +149,7 @@ l.Error("invalid record", "err", err) return err } - err = db.AddFollow(i.Db, &db.Follow{ + err = db.AddFollow(i.Db, &models.Follow{ UserDid: did, SubjectDid: record.Subject, Rkey: e.Commit.RKey, diff --git a/appview/models/follow.go b/appview/models/follow.go new file mode 100644 --- /dev/null +++ b/appview/models/follow.go @@ -0,0 +1,38 @@ +package models + +import ( + "time" +) + +type Follow struct { + UserDid string + SubjectDid string + FollowedAt time.Time + Rkey string +} + +type FollowStats struct { + Followers int64 + Following int64 +} + +type FollowStatus int + +const ( + IsNotFollowing FollowStatus = iota + IsFollowing + IsSelf +) + +func (s FollowStatus) String() string { + switch s { + case IsNotFollowing: + return "IsNotFollowing" + case IsFollowing: + return "IsFollowing" + case IsSelf: + return "IsSelf" + default: + return "IsNotFollowing" + } +} diff --git a/appview/notify/merged_notifier.go b/appview/notify/merged_notifier.go --- a/appview/notify/merged_notifier.go +++ b/appview/notify/merged_notifier.go @@ -4,6 +4,7 @@ import ( "context" "tangled.org/core/appview/db" + "tangled.org/core/appview/models" ) type mergedNotifier struct { @@ -39,12 +40,12 @@ notifier.NewIssue(ctx, issue) } } -func (m *mergedNotifier) NewFollow(ctx context.Context, follow *db.Follow) { +func (m *mergedNotifier) NewFollow(ctx context.Context, follow *models.Follow) { for _, notifier := range m.notifiers { notifier.NewFollow(ctx, follow) } } -func (m *mergedNotifier) DeleteFollow(ctx context.Context, follow *db.Follow) { +func (m *mergedNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) { for _, notifier := range m.notifiers { notifier.DeleteFollow(ctx, follow) } diff --git a/appview/notify/notifier.go b/appview/notify/notifier.go --- a/appview/notify/notifier.go +++ b/appview/notify/notifier.go @@ -4,6 +4,7 @@ import ( "context" "tangled.org/core/appview/db" + "tangled.org/core/appview/models" ) type Notifier interface { @@ -14,8 +15,8 @@ DeleteStar(ctx context.Context, star *db.Star) NewIssue(ctx context.Context, issue *db.Issue) - NewFollow(ctx context.Context, follow *db.Follow) - DeleteFollow(ctx context.Context, follow *db.Follow) + NewFollow(ctx context.Context, follow *models.Follow) + DeleteFollow(ctx context.Context, follow *models.Follow) NewPull(ctx context.Context, pull *db.Pull) NewPullComment(ctx context.Context, comment *db.PullComment) @@ -39,8 +40,8 @@ func (m *BaseNotifier) DeleteStar(ctx context.Context, star *db.Star) {} func (m *BaseNotifier) NewIssue(ctx context.Context, issue *db.Issue) {} -func (m *BaseNotifier) NewFollow(ctx context.Context, follow *db.Follow) {} -func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *db.Follow) {} +func (m *BaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {} +func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {} func (m *BaseNotifier) NewPull(ctx context.Context, pull *db.Pull) {} func (m *BaseNotifier) NewPullComment(ctx context.Context, comment *db.PullComment) {} diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -411,7 +411,7 @@ type ProfileCard struct { UserDid string UserHandle string - FollowStatus db.FollowStatus + FollowStatus models.FollowStatus Punchcard *db.Punchcard Profile *db.Profile Stats ProfileStats @@ -489,7 +489,7 @@ } type FollowCard struct { UserDid string - FollowStatus db.FollowStatus + FollowStatus models.FollowStatus FollowersCount int64 FollowingCount int64 Profile *db.Profile @@ -521,7 +521,7 @@ } type FollowFragmentParams struct { UserDid string - FollowStatus db.FollowStatus + FollowStatus models.FollowStatus } func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error { diff --git a/appview/posthog/notifier.go b/appview/posthog/notifier.go --- a/appview/posthog/notifier.go +++ b/appview/posthog/notifier.go @@ -6,6 +6,7 @@ "log" "github.com/posthog/posthog-go" "tangled.org/core/appview/db" + "tangled.org/core/appview/models" "tangled.org/core/appview/notify" ) @@ -98,7 +99,7 @@ log.Println("failed to enqueue posthog event:", err) } } -func (n *posthogNotifier) NewFollow(ctx context.Context, follow *db.Follow) { +func (n *posthogNotifier) NewFollow(ctx context.Context, follow *models.Follow) { err := n.client.Enqueue(posthog.Capture{ DistinctId: follow.UserDid, Event: "follow", @@ -109,7 +110,7 @@ log.Println("failed to enqueue posthog event:", err) } } -func (n *posthogNotifier) DeleteFollow(ctx context.Context, follow *db.Follow) { +func (n *posthogNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) { err := n.client.Enqueue(posthog.Capture{ DistinctId: follow.UserDid, Event: "unfollow", diff --git a/appview/state/follow.go b/appview/state/follow.go --- a/appview/state/follow.go +++ b/appview/state/follow.go @@ -9,6 +9,7 @@ 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" ) @@ -59,7 +60,7 @@ } log.Println("created atproto record: ", resp.Uri) - follow := &db.Follow{ + follow := &models.Follow{ UserDid: currentUser.Did, SubjectDid: subjectIdent.DID.String(), Rkey: rkey, @@ -75,7 +76,7 @@ s.notifier.NewFollow(r.Context(), follow) s.pages.FollowFragment(w, pages.FollowFragmentParams{ UserDid: subjectIdent.DID.String(), - FollowStatus: db.IsFollowing, + FollowStatus: models.IsFollowing, }) return @@ -106,7 +107,7 @@ } s.pages.FollowFragment(w, pages.FollowFragmentParams{ UserDid: subjectIdent.DID.String(), - FollowStatus: db.IsNotFollowing, + FollowStatus: models.IsNotFollowing, }) s.notifier.DeleteFollow(r.Context(), follow) diff --git a/appview/state/profile.go b/appview/state/profile.go --- a/appview/state/profile.go +++ b/appview/state/profile.go @@ -17,6 +17,7 @@ "github.com/go-chi/chi/v5" "github.com/gorilla/feeds" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" + "tangled.org/core/appview/models" "tangled.org/core/appview/pages" ) @@ -76,7 +77,7 @@ return nil, fmt.Errorf("failed to get follower stats: %w", err) } loggedInUser := s.oauth.GetUser(r) - followStatus := db.IsNotFollowing + followStatus := models.IsNotFollowing if loggedInUser != nil { followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did) } @@ -271,8 +272,8 @@ } func (s *State) followPage( r *http.Request, - fetchFollows func(db.Execer, string) ([]db.Follow, error), - extractDid func(db.Follow) string, + fetchFollows func(db.Execer, string) ([]models.Follow, error), + extractDid func(models.Follow) string, ) (*FollowsPageParams, error) { l := s.logger.With("handler", "reposPage") @@ -329,11 +330,11 @@ followCards := make([]pages.FollowCard, len(follows)) for i, did := range followDids { followStats := followStatsMap[did] - followStatus := db.IsNotFollowing + followStatus := models.IsNotFollowing if _, exists := loggedInUserFollowing[did]; exists { - followStatus = db.IsFollowing + followStatus = models.IsFollowing } else if loggedInUser != nil && loggedInUser.Did == did { - followStatus = db.IsSelf + followStatus = models.IsSelf } var profile *db.Profile @@ -358,7 +359,7 @@ return ¶ms, nil } func (s *State) followersPage(w http.ResponseWriter, r *http.Request) { - followPage, err := s.followPage(r, db.GetFollowers, func(f db.Follow) string { return f.UserDid }) + followPage, err := s.followPage(r, db.GetFollowers, func(f models.Follow) string { return f.UserDid }) if err != nil { s.pages.Notice(w, "all-followers", "Failed to load followers") return @@ -372,7 +373,7 @@ }) } func (s *State) followingPage(w http.ResponseWriter, r *http.Request) { - followPage, err := s.followPage(r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid }) + followPage, err := s.followPage(r, db.GetFollowing, func(f models.Follow) string { return f.SubjectDid }) if err != nil { s.pages.Notice(w, "all-following", "Failed to load following") return