From 373b9ce16e98298a9c9bcb0dcd30760b9b222156 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sat, 26 Feb 2022 15:58:30 -0800 Subject: [PATCH] Add IsFollowed field on Users --- persistence/schema.sql | 1 + persistence/user_queries.go | 24 ++++++++++++++--- persistence/user_queries_test.go | 46 ++++++++++++++++++++++++++++++++ persistence/versions.go | 3 ++- scraper/user.go | 1 + 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/persistence/schema.sql b/persistence/schema.sql index 12bde6d..130f76f 100644 --- a/persistence/schema.sql +++ b/persistence/schema.sql @@ -19,6 +19,7 @@ create table users (rowid integer primary key, banner_image_local_path text, pinned_tweet_id integer check(typeof(pinned_tweet_id) = 'integer' or pinned_tweet_id = ''), + is_followed boolean default 0, is_content_downloaded boolean default 0 ); diff --git a/persistence/user_queries.go b/persistence/user_queries.go index e5208b1..37c8562 100644 --- a/persistence/user_queries.go +++ b/persistence/user_queries.go @@ -1,6 +1,7 @@ package persistence import ( + "fmt" "database/sql" "time" "offline_twitter/scraper" @@ -78,7 +79,7 @@ func parse_user_from_row(row *sql.Row) (scraper.User, error) { var u scraper.User var joinDate int64 - err := row.Scan(&u.ID, &u.DisplayName, &u.Handle, &u.Bio, &u.FollowingCount, &u.FollowersCount, &u.Location, &u.Website, &joinDate, &u.IsPrivate, &u.IsVerified, &u.IsBanned, &u.ProfileImageUrl, &u.ProfileImageLocalPath, &u.BannerImageUrl, &u.BannerImageLocalPath, &u.PinnedTweetID, &u.IsContentDownloaded) + err := row.Scan(&u.ID, &u.DisplayName, &u.Handle, &u.Bio, &u.FollowingCount, &u.FollowersCount, &u.Location, &u.Website, &joinDate, &u.IsPrivate, &u.IsVerified, &u.IsBanned, &u.ProfileImageUrl, &u.ProfileImageLocalPath, &u.BannerImageUrl, &u.BannerImageLocalPath, &u.PinnedTweetID, &u.IsContentDownloaded, &u.IsFollowed) if err != nil { return u, err } @@ -101,7 +102,7 @@ func (p Profile) GetUserByHandle(handle scraper.UserHandle) (scraper.User, error db := p.DB stmt, err := db.Prepare(` - select id, display_name, handle, bio, following_count, followers_count, location, website, join_date, is_private, is_verified, is_banned, profile_image_url, profile_image_local_path, banner_image_url, banner_image_local_path, pinned_tweet_id, is_content_downloaded + select id, display_name, handle, bio, following_count, followers_count, location, website, join_date, is_private, is_verified, is_banned, profile_image_url, profile_image_local_path, banner_image_url, banner_image_local_path, pinned_tweet_id, is_content_downloaded, is_followed from users where lower(handle) = lower(?) `) @@ -132,7 +133,7 @@ func (p Profile) GetUserByID(id scraper.UserID) (scraper.User, error) { db := p.DB stmt, err := db.Prepare(` - select id, display_name, handle, bio, following_count, followers_count, location, website, join_date, is_private, is_verified, is_banned, profile_image_url, profile_image_local_path, banner_image_url, banner_image_local_path, pinned_tweet_id, is_content_downloaded + select id, display_name, handle, bio, following_count, followers_count, location, website, join_date, is_private, is_verified, is_banned, profile_image_url, profile_image_local_path, banner_image_url, banner_image_local_path, pinned_tweet_id, is_content_downloaded, is_followed from users where id = ? `) @@ -189,3 +190,20 @@ func (p Profile) CheckUserContentDownloadNeeded(user scraper.User) bool { } return false } + +/** + * Follow / unfollow a user. Update the given User object's IsFollowed field. + */ +func (p Profile) SetUserFollowed(user *scraper.User, is_followed bool) { + result, err := p.DB.Exec("update users set is_followed = ? where id = ?", is_followed, user.ID) + if err != nil { + panic("Unknown error: " + err.Error()) + } + if count, _ := result.RowsAffected(); count != 1 { + panic(fmt.Sprintf("User with handle %q not found", user.Handle)) + } + if err != nil { + panic(fmt.Sprintf("Error inserting user with handle %q: %s", user.Handle, err.Error())) + } + user.IsFollowed = is_followed +} diff --git a/persistence/user_queries_test.go b/persistence/user_queries_test.go index 411016b..0df88ce 100644 --- a/persistence/user_queries_test.go +++ b/persistence/user_queries_test.go @@ -4,6 +4,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/go-test/deep" ) @@ -205,3 +208,46 @@ func TestCheckUserContentDownloadNeeded(t *testing.T) { t.Errorf("If banner image changed, user should require another download") } } + +/** + * Make sure following works + * + * - users are unfollowed by default + * - following a user makes it save as is_followed + * - using regular save method doesn't un-follow + * - unfollowing a user makes it save as no longer is_followed + */ +func TestFollowUnfollowUser(t *testing.T) { + assert := assert.New(t) + + profile_path := "test_profiles/TestUserQueries" + profile := create_or_load_profile(profile_path) + + user := create_dummy_user() + assert.False(user.IsFollowed) + profile.SaveUser(user) + + profile.SetUserFollowed(&user, true) + assert.True(user.IsFollowed) + + // Ensure the change was persisted + user_reloaded, err := profile.GetUserByHandle(user.Handle) + require.NoError(t, err) + assert.Equal(user.ID, user_reloaded.ID) // Verify it's the same user + assert.True(user_reloaded.IsFollowed) + + profile.SaveUser(user) // should NOT un-set is_followed + user_reloaded, err = profile.GetUserByHandle(user.Handle) + require.NoError(t, err) + assert.Equal(user.ID, user_reloaded.ID) // Verify it's the same user + assert.True(user_reloaded.IsFollowed) + + profile.SetUserFollowed(&user, false) + assert.False(user.IsFollowed) + + // Ensure the change was persisted + user_reloaded, err = profile.GetUserByHandle(user.Handle) + require.NoError(t, err) + assert.Equal(user.ID, user_reloaded.ID) // Verify it's the same user + assert.False(user_reloaded.IsFollowed) +} diff --git a/persistence/versions.go b/persistence/versions.go index daa9d71..49ac958 100644 --- a/persistence/versions.go +++ b/persistence/versions.go @@ -8,7 +8,7 @@ import ( ) -const ENGINE_DATABASE_VERSION = 8 +const ENGINE_DATABASE_VERSION = 9 type VersionMismatchError struct { @@ -61,6 +61,7 @@ var MIGRATIONS = []string{ `alter table users add column is_banned boolean default 0`, `alter table urls add column short_text text not null default ""`, `insert into tombstone_types (rowid, short_name, tombstone_text) values (7, 'age-restricted', 'Age-restricted adult content. This content might not be appropriate for people under 18 years old. To view this media, you’ll need to log in to Twitter')`, +`alter table users add column is_followed boolean default 0`, } /** diff --git a/scraper/user.go b/scraper/user.go index 8cf37c6..33ac599 100644 --- a/scraper/user.go +++ b/scraper/user.go @@ -44,6 +44,7 @@ type User struct { PinnedTweetID TweetID PinnedTweet *Tweet + IsFollowed bool IsContentDownloaded bool }