From f409f7c65a029828d725f639af7ac6034bc35ff0 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Tue, 17 Aug 2021 11:18:43 +0200 Subject: [PATCH] start moving some database stuff around --- internal/db/account.go | 65 ++ internal/db/admin.go | 34 + internal/db/basic.go | 64 ++ internal/db/db.go | 258 +------ internal/db/instance.go | 17 + internal/db/notification.go | 8 + internal/db/pg/account.go | 236 +++++++ internal/db/pg/admin.go | 206 ++++++ internal/db/pg/{get.go => basic.go} | 58 +- internal/db/pg/blocks.go | 2 +- internal/db/pg/delete.go | 57 -- internal/db/pg/pg.go | 641 ------------------ internal/db/pg/put.go | 33 - internal/db/pg/relationship.go | 153 +++++ internal/db/pg/status.go | 181 +++++ internal/db/pg/statuscontext.go | 104 --- internal/db/relationship.go | 27 + internal/db/status.go | 44 ++ internal/db/timeline.go | 25 + internal/federation/dereferencing/announce.go | 10 +- internal/federation/dereferencing/status.go | 4 +- internal/federation/federatingdb/followers.go | 2 +- internal/federation/federatingdb/following.go | 2 +- internal/gtsmodel/status.go | 16 +- internal/gtsmodel/statusfave.go | 10 +- internal/media/handler.go | 2 +- internal/processing/account/delete.go | 4 +- internal/processing/account/getfollowers.go | 2 +- internal/processing/account/getfollowing.go | 2 +- internal/processing/account/getstatuses.go | 2 +- internal/processing/blocks.go | 2 +- internal/processing/followrequest.go | 2 +- internal/processing/fromclientapi.go | 28 +- internal/processing/fromcommon.go | 8 +- internal/processing/status/boost.go | 2 +- internal/processing/status/fave.go | 6 +- internal/processing/status/unboost.go | 8 +- internal/typeutils/astointernal.go | 14 +- internal/typeutils/internal.go | 2 +- internal/typeutils/internaltoas.go | 52 +- internal/typeutils/internaltofrontend.go | 44 +- internal/visibility/statushometimelineable.go | 10 +- internal/visibility/util.go | 6 +- 43 files changed, 1230 insertions(+), 1223 deletions(-) create mode 100644 internal/db/account.go create mode 100644 internal/db/admin.go create mode 100644 internal/db/basic.go create mode 100644 internal/db/instance.go create mode 100644 internal/db/notification.go create mode 100644 internal/db/pg/account.go create mode 100644 internal/db/pg/admin.go rename internal/db/pg/{get.go => basic.go} (52%) delete mode 100644 internal/db/pg/delete.go delete mode 100644 internal/db/pg/put.go create mode 100644 internal/db/pg/relationship.go create mode 100644 internal/db/pg/status.go delete mode 100644 internal/db/pg/statuscontext.go create mode 100644 internal/db/relationship.go create mode 100644 internal/db/status.go create mode 100644 internal/db/timeline.go diff --git a/internal/db/account.go b/internal/db/account.go new file mode 100644 index 000000000..9584291e2 --- /dev/null +++ b/internal/db/account.go @@ -0,0 +1,65 @@ +package db + +import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + +type Account interface { + // GetAccountByUserID is a shortcut for the common action of fetching an account corresponding to a user ID. + // The given account pointer will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + GetAccountByUserID(userID string, account *gtsmodel.Account) error + + // GetLocalAccountByUsername is a shortcut for the common action of fetching an account ON THIS INSTANCE + // according to its username, which should be unique. + // The given account pointer will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + GetLocalAccountByUsername(username string, account *gtsmodel.Account) error + + // GetAccountFollowRequests is a shortcut for the common action of fetching a list of follow requests targeting the given account ID. + // The given slice 'followRequests' will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + GetAccountFollowRequests(accountID string, followRequests *[]gtsmodel.FollowRequest) error + + // GetAccountFollowing is a shortcut for the common action of fetching a list of accounts that accountID is following. + // The given slice 'following' will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + GetAccountFollowing(accountID string, following *[]gtsmodel.Follow) error + + // GetAccountFollowers is a shortcut for the common action of fetching a list of accounts that accountID is followed by. + // The given slice 'followers' will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + // + // If localOnly is set to true, then only followers from *this instance* will be returned. + GetAccountFollowers(accountID string, followers *[]gtsmodel.Follow, localOnly bool) error + + // GetAccountFaves is a shortcut for the common action of fetching a list of faves made by the given accountID. + // The given slice 'faves' will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + GetAccountFaves(accountID string, faves *[]gtsmodel.StatusFave) error + + // GetAccountStatusesCount is a shortcut for the common action of counting statuses produced by accountID. + GetAccountStatusesCount(accountID string) (int, error) + + // GetAccountStatuses is a shortcut for getting the most recent statuses. accountID is optional, if not provided + // then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can + // be very memory intensive so you probably shouldn't do this! + // In case of no entries, a 'no entries' error will be returned + GetAccountStatuses(accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, error) + + GetAccountBlocks(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, error) + + // GetAccountLastStatus simply gets the most recent status by the given account. + // The given slice 'status' pointer will be set to the result of the query, whatever it is. + // In case of no entries, a 'no entries' error will be returned + GetAccountLastStatus(accountID string, status *gtsmodel.Status) error + + // SetAccountHeaderOrAvatar sets the header or avatar for the given accountID to the given media attachment. + SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error + + // GetHeaderAvatarForAccountID gets the current avatar for the given account ID. + // The passed mediaAttachment pointer will be populated with the value of the avatar, if it exists. + GetAccountAvatar(avatar *gtsmodel.MediaAttachment, accountID string) error + + // GetAccountHeader gets the current header for the given account ID. + // The passed mediaAttachment pointer will be populated with the value of the header, if it exists. + GetAccountHeader(header *gtsmodel.MediaAttachment, accountID string) error +} diff --git a/internal/db/admin.go b/internal/db/admin.go new file mode 100644 index 000000000..84589a250 --- /dev/null +++ b/internal/db/admin.go @@ -0,0 +1,34 @@ +package db + +import ( + "net" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +type Admin interface { + // IsUsernameAvailable checks whether a given username is available on our domain. + // Returns an error if the username is already taken, or something went wrong in the db. + IsUsernameAvailable(username string) error + + // IsEmailAvailable checks whether a given email address for a new account is available to be used on our domain. + // Return an error if: + // A) the email is already associated with an account + // B) we block signups from this email domain + // C) something went wrong in the db + IsEmailAvailable(email string) error + + // NewSignup creates a new user in the database with the given parameters. + // By the time this function is called, it should be assumed that all the parameters have passed validation! + NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, error) + + // CreateInstanceAccount creates an account in the database with the same username as the instance host value. + // Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'. + // This is needed for things like serving files that belong to the instance and not an individual user/account. + CreateInstanceAccount() error + + // CreateInstanceInstance creates an instance in the database with the same domain as the instance host value. + // Ie., if the instance is hosted at 'example.org' the instance will have a domain of 'example.org'. + // This is needed for things like serving instance information through /api/v1/instance + CreateInstanceInstance() error +} diff --git a/internal/db/basic.go b/internal/db/basic.go new file mode 100644 index 000000000..d63f7034b --- /dev/null +++ b/internal/db/basic.go @@ -0,0 +1,64 @@ +package db + +import "context" + +type Basic interface { + // CreateTable creates a table for the given interface. + // For implementations that don't use tables, this can just return nil. + CreateTable(i interface{}) error + + // DropTable drops the table for the given interface. + // For implementations that don't use tables, this can just return nil. + DropTable(i interface{}) error + + // Stop should stop and close the database connection cleanly, returning an error if this is not possible. + // If the database implementation doesn't need to be stopped, this can just return nil. + Stop(ctx context.Context) error + + // IsHealthy should return nil if the database connection is healthy, or an error if not. + IsHealthy(ctx context.Context) error + + // GetByID gets one entry by its id. In a database like postgres, this might be the 'id' field of the entry, + // for other implementations (for example, in-memory) it might just be the key of a map. + // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. + // In case of no entries, a 'no entries' error will be returned + GetByID(id string, i interface{}) error + + // GetWhere gets one entry where key = value. This is similar to GetByID but allows the caller to specify the + // name of the key to select from. + // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. + // In case of no entries, a 'no entries' error will be returned + GetWhere(where []Where, i interface{}) error + + // GetAll will try to get all entries of type i. + // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. + // In case of no entries, a 'no entries' error will be returned + GetAll(i interface{}) error + + // Put simply stores i. It is up to the implementation to figure out how to store it, and using what key. + // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. + Put(i interface{}) error + + // Upsert stores or updates i based on the given conflict column, as in https://www.postgresqltutorial.com/postgresql-upsert/ + // It is up to the implementation to figure out how to store it, and using what key. + // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. + Upsert(i interface{}, conflictColumn string) error + + // UpdateByID updates i with id id. + // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. + UpdateByID(id string, i interface{}) error + + // UpdateOneByID updates interface i with database the given database id. It will update one field of key key and value value. + UpdateOneByID(id string, key string, value interface{}, i interface{}) error + + // UpdateWhere updates column key of interface i with the given value, where the given parameters apply. + UpdateWhere(where []Where, key string, value interface{}, i interface{}) error + + // DeleteByID removes i with id id. + // If i didn't exist anyway, then no error should be returned. + DeleteByID(id string, i interface{}) error + + // DeleteWhere deletes i where key = value + // If i didn't exist anyway, then no error should be returned. + DeleteWhere(where []Where, i interface{}) error +} diff --git a/internal/db/db.go b/internal/db/db.go index d0b23fbc6..bc55a2ca6 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -19,9 +19,6 @@ package db import ( - "context" - "net" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -34,253 +31,14 @@ const ( // Note that in all of the functions below, the passed interface should be a pointer or a slice, which will then be populated // by whatever is returned from the database. type DB interface { - /* - BASIC DB FUNCTIONALITY - */ - - // CreateTable creates a table for the given interface. - // For implementations that don't use tables, this can just return nil. - CreateTable(i interface{}) error - - // DropTable drops the table for the given interface. - // For implementations that don't use tables, this can just return nil. - DropTable(i interface{}) error - - // Stop should stop and close the database connection cleanly, returning an error if this is not possible. - // If the database implementation doesn't need to be stopped, this can just return nil. - Stop(ctx context.Context) error - - // IsHealthy should return nil if the database connection is healthy, or an error if not. - IsHealthy(ctx context.Context) error - - // GetByID gets one entry by its id. In a database like postgres, this might be the 'id' field of the entry, - // for other implementations (for example, in-memory) it might just be the key of a map. - // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. - // In case of no entries, a 'no entries' error will be returned - GetByID(id string, i interface{}) error - - // GetWhere gets one entry where key = value. This is similar to GetByID but allows the caller to specify the - // name of the key to select from. - // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. - // In case of no entries, a 'no entries' error will be returned - GetWhere(where []Where, i interface{}) error - - // GetAll will try to get all entries of type i. - // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. - // In case of no entries, a 'no entries' error will be returned - GetAll(i interface{}) error - - // Put simply stores i. It is up to the implementation to figure out how to store it, and using what key. - // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. - Put(i interface{}) error - - // Upsert stores or updates i based on the given conflict column, as in https://www.postgresqltutorial.com/postgresql-upsert/ - // It is up to the implementation to figure out how to store it, and using what key. - // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. - Upsert(i interface{}, conflictColumn string) error - - // UpdateByID updates i with id id. - // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. - UpdateByID(id string, i interface{}) error - - // UpdateOneByID updates interface i with database the given database id. It will update one field of key key and value value. - UpdateOneByID(id string, key string, value interface{}, i interface{}) error - - // UpdateWhere updates column key of interface i with the given value, where the given parameters apply. - UpdateWhere(where []Where, key string, value interface{}, i interface{}) error - - // DeleteByID removes i with id id. - // If i didn't exist anyway, then no error should be returned. - DeleteByID(id string, i interface{}) error - - // DeleteWhere deletes i where key = value - // If i didn't exist anyway, then no error should be returned. - DeleteWhere(where []Where, i interface{}) error - - /* - HANDY SHORTCUTS - */ - - // AcceptFollowRequest moves a follow request in the database from the follow_requests table to the follows table. - // In other words, it should create the follow, and delete the existing follow request. - // - // It will return the newly created follow for further processing. - AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, error) - - // CreateInstanceAccount creates an account in the database with the same username as the instance host value. - // Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'. - // This is needed for things like serving files that belong to the instance and not an individual user/account. - CreateInstanceAccount() error - - // CreateInstanceInstance creates an instance in the database with the same domain as the instance host value. - // Ie., if the instance is hosted at 'example.org' the instance will have a domain of 'example.org'. - // This is needed for things like serving instance information through /api/v1/instance - CreateInstanceInstance() error - - // GetAccountByUserID is a shortcut for the common action of fetching an account corresponding to a user ID. - // The given account pointer will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetAccountByUserID(userID string, account *gtsmodel.Account) error - - // GetLocalAccountByUsername is a shortcut for the common action of fetching an account ON THIS INSTANCE - // according to its username, which should be unique. - // The given account pointer will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetLocalAccountByUsername(username string, account *gtsmodel.Account) error - - // GetFollowRequestsForAccountID is a shortcut for the common action of fetching a list of follow requests targeting the given account ID. - // The given slice 'followRequests' will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error - - // GetFollowingByAccountID is a shortcut for the common action of fetching a list of accounts that accountID is following. - // The given slice 'following' will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error - - // GetFollowersByAccountID is a shortcut for the common action of fetching a list of accounts that accountID is followed by. - // The given slice 'followers' will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - // - // If localOnly is set to true, then only followers from *this instance* will be returned. - GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow, localOnly bool) error - - // GetFavesByAccountID is a shortcut for the common action of fetching a list of faves made by the given accountID. - // The given slice 'faves' will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetFavesByAccountID(accountID string, faves *[]gtsmodel.StatusFave) error - - // CountStatusesByAccountID is a shortcut for the common action of counting statuses produced by accountID. - CountStatusesByAccountID(accountID string) (int, error) - - // GetStatusesForAccount is a shortcut for getting the most recent statuses. accountID is optional, if not provided - // then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can - // be very memory intensive so you probably shouldn't do this! - // In case of no entries, a 'no entries' error will be returned - GetStatusesForAccount(accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, error) - - GetBlocksForAccount(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, error) - - // GetLastStatusForAccountID simply gets the most recent status by the given account. - // The given slice 'status' pointer will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error - - // IsUsernameAvailable checks whether a given username is available on our domain. - // Returns an error if the username is already taken, or something went wrong in the db. - IsUsernameAvailable(username string) error - - // IsEmailAvailable checks whether a given email address for a new account is available to be used on our domain. - // Return an error if: - // A) the email is already associated with an account - // B) we block signups from this email domain - // C) something went wrong in the db - IsEmailAvailable(email string) error - - // NewSignup creates a new user in the database with the given parameters. - // By the time this function is called, it should be assumed that all the parameters have passed validation! - NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, error) - - // SetHeaderOrAvatarForAccountID sets the header or avatar for the given accountID to the given media attachment. - SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error - - // GetHeaderAvatarForAccountID gets the current avatar for the given account ID. - // The passed mediaAttachment pointer will be populated with the value of the avatar, if it exists. - GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error - - // GetHeaderForAccountID gets the current header for the given account ID. - // The passed mediaAttachment pointer will be populated with the value of the header, if it exists. - GetHeaderForAccountID(header *gtsmodel.MediaAttachment, accountID string) error - - // Blocked checks whether a block exists in eiher direction between two accounts. - // That is, it returns true if account1 blocks account2, OR if account2 blocks account1. - Blocked(account1 string, account2 string) (bool, error) - - // GetRelationship retrieves the relationship of the targetAccount to the requestingAccount. - GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error) - - // Follows returns true if sourceAccount follows target account, or an error if something goes wrong while finding out. - Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) - - // FollowRequested returns true if sourceAccount has requested to follow target account, or an error if something goes wrong while finding out. - FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) - - // Mutuals returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out. - Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) - - // GetReplyCountForStatus returns the amount of replies recorded for a status, or an error if something goes wrong - GetReplyCountForStatus(status *gtsmodel.Status) (int, error) - - // GetReblogCountForStatus returns the amount of reblogs/boosts recorded for a status, or an error if something goes wrong - GetReblogCountForStatus(status *gtsmodel.Status) (int, error) - - // GetFaveCountForStatus returns the amount of faves/likes recorded for a status, or an error if something goes wrong - GetFaveCountForStatus(status *gtsmodel.Status) (int, error) - - // StatusParents get the parent statuses of a given status. - // - // If onlyDirect is true, only the immediate parent will be returned. - StatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, error) - - // StatusChildren gets the child statuses of a given status. - // - // If onlyDirect is true, only the immediate children will be returned. - StatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, error) - - // StatusFavedBy checks if a given status has been faved by a given account ID - StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, error) - - // StatusRebloggedBy checks if a given status has been reblogged/boosted by a given account ID - StatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, error) - - // StatusMutedBy checks if a given status has been muted by a given account ID - StatusMutedBy(status *gtsmodel.Status, accountID string) (bool, error) - - // StatusBookmarkedBy checks if a given status has been bookmarked by a given account ID - StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error) - - // WhoFavedStatus returns a slice of accounts who faved the given status. - // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. - WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) - - // WhoBoostedStatus returns a slice of accounts who boosted the given status. - // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. - WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) - - // GetHomeTimelineForAccount returns a slice of statuses from accounts that are followed by the given account id. - // - // Statuses should be returned in descending order of when they were created (newest first). - GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) - - // GetPublicTimelineForAccount fetches the account's PUBLIC timeline -- ie., posts and replies that are public. - // It will use the given filters and try to return as many statuses as possible up to the limit. - // - // Statuses should be returned in descending order of when they were created (newest first). - GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) - - // GetFavedTimelineForAccount fetches the account's FAVED timeline -- ie., posts and replies that the requesting account has faved. - // It will use the given filters and try to return as many statuses as possible up to the limit. - // - // Note that unlike the other GetTimeline functions, the returned statuses will be arranged by their FAVE id, not the STATUS id. - // In other words, they'll be returned in descending order of when they were faved by the requesting user, not when they were created. - // - // Also note the extra return values, which correspond to the nextMaxID and prevMinID for building Link headers. - GetFavedTimelineForAccount(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, error) - - // GetNotificationsForAccount returns a list of notifications that pertain to the given accountID. - GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) - - // GetUserCountForInstance returns the number of known accounts registered with the given domain. - GetUserCountForInstance(domain string) (int, error) - - // GetStatusCountForInstance returns the number of known statuses posted from the given domain. - GetStatusCountForInstance(domain string) (int, error) - - // GetDomainCountForInstance returns the number of known instances known that the given domain federates with. - GetDomainCountForInstance(domain string) (int, error) - - // GetAccountsForInstance returns a slice of accounts from the given instance, arranged by ID. - GetAccountsForInstance(domain string, maxID string, limit int) ([]*gtsmodel.Account, error) + Account + Admin + Basic + Instance + Notification + Relationship + Status + Timeline /* USEFUL CONVERSION FUNCTIONS diff --git a/internal/db/instance.go b/internal/db/instance.go new file mode 100644 index 000000000..dccdb9793 --- /dev/null +++ b/internal/db/instance.go @@ -0,0 +1,17 @@ +package db + +import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + +type Instance interface { + // GetUserCountForInstance returns the number of known accounts registered with the given domain. + GetUserCountForInstance(domain string) (int, error) + + // GetStatusCountForInstance returns the number of known statuses posted from the given domain. + GetStatusCountForInstance(domain string) (int, error) + + // GetDomainCountForInstance returns the number of known instances known that the given domain federates with. + GetDomainCountForInstance(domain string) (int, error) + + // GetAccountsForInstance returns a slice of accounts from the given instance, arranged by ID. + GetAccountsForInstance(domain string, maxID string, limit int) ([]*gtsmodel.Account, error) +} diff --git a/internal/db/notification.go b/internal/db/notification.go new file mode 100644 index 000000000..6a3c0c29d --- /dev/null +++ b/internal/db/notification.go @@ -0,0 +1,8 @@ +package db + +import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + +type Notification interface { + // GetNotificationsForAccount returns a list of notifications that pertain to the given accountID. + GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) +} diff --git a/internal/db/pg/account.go b/internal/db/pg/account.go new file mode 100644 index 000000000..9ecaba486 --- /dev/null +++ b/internal/db/pg/account.go @@ -0,0 +1,236 @@ +package pg + +import ( + "errors" + "fmt" + + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (ps *postgresService) GetAccountHeader(header *gtsmodel.MediaAttachment, accountID string) error { + acct := >smodel.Account{} + if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + + if acct.HeaderMediaAttachmentID == "" { + return db.ErrNoEntries{} + } + + if err := ps.conn.Model(header).Where("id = ?", acct.HeaderMediaAttachmentID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountAvatar(avatar *gtsmodel.MediaAttachment, accountID string) error { + acct := >smodel.Account{} + if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + + if acct.AvatarMediaAttachmentID == "" { + return db.ErrNoEntries{} + } + + if err := ps.conn.Model(avatar).Where("id = ?", acct.AvatarMediaAttachmentID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountLastStatus(accountID string, status *gtsmodel.Status) error { + if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + return nil + +} + +func (ps *postgresService) SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error { + if mediaAttachment.Avatar && mediaAttachment.Header { + return errors.New("one media attachment cannot be both header and avatar") + } + + var headerOrAVI string + if mediaAttachment.Avatar { + headerOrAVI = "avatar" + } else if mediaAttachment.Header { + headerOrAVI = "header" + } else { + return errors.New("given media attachment was neither a header nor an avatar") + } + + // TODO: there are probably more side effects here that need to be handled + if _, err := ps.conn.Model(mediaAttachment).OnConflict("(id) DO UPDATE").Insert(); err != nil { + return err + } + + if _, err := ps.conn.Model(>smodel.Account{}).Set(fmt.Sprintf("%s_media_attachment_id = ?", headerOrAVI), mediaAttachment.ID).Where("id = ?", accountID).Update(); err != nil { + return err + } + return nil +} + +func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.Account) error { + user := >smodel.User{ + ID: userID, + } + if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + return nil +} + +func (ps *postgresService) GetLocalAccountByUsername(username string, account *gtsmodel.Account) error { + if err := ps.conn.Model(account).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select(); err != nil { + if err == pg.ErrNoRows { + return db.ErrNoEntries{} + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountFollowRequests(accountID string, followRequests *[]gtsmodel.FollowRequest) error { + if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil { + if err == pg.ErrNoRows { + return nil + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountFollowing(accountID string, following *[]gtsmodel.Follow) error { + if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil { + if err == pg.ErrNoRows { + return nil + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountFollowers(accountID string, followers *[]gtsmodel.Follow, localOnly bool) error { + + q := ps.conn.Model(followers) + + if localOnly { + // for local accounts let's get where domain is null OR where domain is an empty string, just to be safe + whereGroup := func(q *pg.Query) (*pg.Query, error) { + q = q. + WhereOr("? IS NULL", pg.Ident("a.domain")). + WhereOr("a.domain = ?", "") + return q, nil + } + + q = q.ColumnExpr("follow.*"). + Join("JOIN accounts AS a ON follow.account_id = TEXT(a.id)"). + Where("follow.target_account_id = ?", accountID). + WhereGroup(whereGroup) + } else { + q = q.Where("target_account_id = ?", accountID) + } + + if err := q.Select(); err != nil { + if err == pg.ErrNoRows { + return nil + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountFaves(accountID string, faves *[]gtsmodel.StatusFave) error { + if err := ps.conn.Model(faves).Where("account_id = ?", accountID).Select(); err != nil { + if err == pg.ErrNoRows { + return nil + } + return err + } + return nil +} + +func (ps *postgresService) GetAccountStatusesCount(accountID string) (int, error) { + count, err := ps.conn.Model(>smodel.Status{}).Where("account_id = ?", accountID).Count() + if err != nil { + if err == pg.ErrNoRows { + return 0, nil + } + return 0, err + } + return count, nil +} + +func (ps *postgresService) GetAccountStatuses(accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, error) { + ps.log.Debugf("getting statuses for account %s", accountID) + statuses := []*gtsmodel.Status{} + + q := ps.conn.Model(&statuses).Order("id DESC") + if accountID != "" { + q = q.Where("account_id = ?", accountID) + } + + if limit != 0 { + q = q.Limit(limit) + } + + if excludeReplies { + q = q.Where("? IS NULL", pg.Ident("in_reply_to_id")) + } + + if pinnedOnly { + q = q.Where("pinned = ?", true) + } + + if mediaOnly { + q = q.WhereGroup(func(q *pg.Query) (*pg.Query, error) { + return q.Where("? IS NOT NULL", pg.Ident("attachments")).Where("attachments != '{}'"), nil + }) + } + + if maxID != "" { + q = q.Where("id < ?", maxID) + } + + if err := q.Select(); err != nil { + if err == pg.ErrNoRows { + return nil, db.ErrNoEntries{} + } + return nil, err + } + + if len(statuses) == 0 { + return nil, db.ErrNoEntries{} + } + + ps.log.Debugf("returning statuses for account %s", accountID) + return statuses, nil +} diff --git a/internal/db/pg/admin.go b/internal/db/pg/admin.go new file mode 100644 index 000000000..7bc614f14 --- /dev/null +++ b/internal/db/pg/admin.go @@ -0,0 +1,206 @@ +package pg + +import ( + "crypto/rand" + "crypto/rsa" + "fmt" + "net" + "net/mail" + "strings" + "time" + + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/util" + "golang.org/x/crypto/bcrypt" +) + +func (ps *postgresService) IsUsernameAvailable(username string) error { + // if no error we fail because it means we found something + // if error but it's not pg.ErrNoRows then we fail + // if err is pg.ErrNoRows we're good, we found nothing so continue + if err := ps.conn.Model(>smodel.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil { + return fmt.Errorf("username %s already in use", username) + } else if err != pg.ErrNoRows { + return fmt.Errorf("db error: %s", err) + } + return nil +} + +func (ps *postgresService) IsEmailAvailable(email string) error { + // parse the domain from the email + m, err := mail.ParseAddress(email) + if err != nil { + return fmt.Errorf("error parsing email address %s: %s", email, err) + } + domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @ + + // check if the email domain is blocked + if err := ps.conn.Model(>smodel.EmailDomainBlock{}).Where("domain = ?", domain).Select(); err == nil { + // fail because we found something + return fmt.Errorf("email domain %s is blocked", domain) + } else if err != pg.ErrNoRows { + // fail because we got an unexpected error + return fmt.Errorf("db error: %s", err) + } + + // check if this email is associated with a user already + if err := ps.conn.Model(>smodel.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { + // fail because we found something + return fmt.Errorf("email %s already in use", email) + } else if err != pg.ErrNoRows { + // fail because we got an unexpected error + return fmt.Errorf("db error: %s", err) + } + return nil +} + +func (ps *postgresService) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + ps.log.Errorf("error creating new rsa key: %s", err) + return nil, err + } + + // if something went wrong while creating a user, we might already have an account, so check here first... + a := >smodel.Account{} + err = ps.conn.Model(a).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select() + if err != nil { + // there's been an actual error + if err != pg.ErrNoRows { + return nil, fmt.Errorf("db error checking existence of account: %s", err) + } + + // we just don't have an account yet create one + newAccountURIs := util.GenerateURIsForAccount(username, ps.config.Protocol, ps.config.Host) + newAccountID, err := id.NewRandomULID() + if err != nil { + return nil, err + } + + a = >smodel.Account{ + ID: newAccountID, + Username: username, + DisplayName: username, + Reason: reason, + URL: newAccountURIs.UserURL, + PrivateKey: key, + PublicKey: &key.PublicKey, + PublicKeyURI: newAccountURIs.PublicKeyURI, + ActorType: gtsmodel.ActivityStreamsPerson, + URI: newAccountURIs.UserURI, + InboxURI: newAccountURIs.InboxURI, + OutboxURI: newAccountURIs.OutboxURI, + FollowersURI: newAccountURIs.FollowersURI, + FollowingURI: newAccountURIs.FollowingURI, + FeaturedCollectionURI: newAccountURIs.CollectionURI, + } + if _, err = ps.conn.Model(a).Insert(); err != nil { + return nil, err + } + } + + pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return nil, fmt.Errorf("error hashing password: %s", err) + } + + newUserID, err := id.NewRandomULID() + if err != nil { + return nil, err + } + + u := >smodel.User{ + ID: newUserID, + AccountID: a.ID, + EncryptedPassword: string(pw), + SignUpIP: signUpIP.To4(), + Locale: locale, + UnconfirmedEmail: email, + CreatedByApplicationID: appID, + Approved: !requireApproval, // if we don't require moderator approval, just pre-approve the user + } + + if emailVerified { + u.ConfirmedAt = time.Now() + u.Email = email + } + + if admin { + u.Admin = true + u.Moderator = true + } + + if _, err = ps.conn.Model(u).Insert(); err != nil { + return nil, err + } + + return u, nil +} + +func (ps *postgresService) CreateInstanceAccount() error { + username := ps.config.Host + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + ps.log.Errorf("error creating new rsa key: %s", err) + return err + } + + aID, err := id.NewRandomULID() + if err != nil { + return err + } + + newAccountURIs := util.GenerateURIsForAccount(username, ps.config.Protocol, ps.config.Host) + a := >smodel.Account{ + ID: aID, + Username: ps.config.Host, + DisplayName: username, + URL: newAccountURIs.UserURL, + PrivateKey: key, + PublicKey: &key.PublicKey, + PublicKeyURI: newAccountURIs.PublicKeyURI, + ActorType: gtsmodel.ActivityStreamsPerson, + URI: newAccountURIs.UserURI, + InboxURI: newAccountURIs.InboxURI, + OutboxURI: newAccountURIs.OutboxURI, + FollowersURI: newAccountURIs.FollowersURI, + FollowingURI: newAccountURIs.FollowingURI, + FeaturedCollectionURI: newAccountURIs.CollectionURI, + } + inserted, err := ps.conn.Model(a).Where("username = ?", username).SelectOrInsert() + if err != nil { + return err + } + if inserted { + ps.log.Infof("created instance account %s with id %s", username, a.ID) + } else { + ps.log.Infof("instance account %s already exists with id %s", username, a.ID) + } + return nil +} + +func (ps *postgresService) CreateInstanceInstance() error { + iID, err := id.NewRandomULID() + if err != nil { + return err + } + + i := >smodel.Instance{ + ID: iID, + Domain: ps.config.Host, + Title: ps.config.Host, + URI: fmt.Sprintf("%s://%s", ps.config.Protocol, ps.config.Host), + } + inserted, err := ps.conn.Model(i).Where("domain = ?", ps.config.Host).SelectOrInsert() + if err != nil { + return err + } + if inserted { + ps.log.Infof("created instance instance %s with id %s", ps.config.Host, i.ID) + } else { + ps.log.Infof("instance instance %s already exists with id %s", ps.config.Host, i.ID) + } + return nil +} diff --git a/internal/db/pg/get.go b/internal/db/pg/basic.go similarity index 52% rename from internal/db/pg/get.go rename to internal/db/pg/basic.go index d48c43520..36009120e 100644 --- a/internal/db/pg/get.go +++ b/internal/db/pg/basic.go @@ -1,30 +1,21 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - package pg import ( "errors" + "strings" "github.com/go-pg/pg/v10" "github.com/superseriousbusiness/gotosocial/internal/db" ) +func (ps *postgresService) Put(i interface{}) error { + _, err := ps.conn.Model(i).Insert(i) + if err != nil && strings.Contains(err.Error(), "duplicate key value violates unique constraint") { + return db.ErrAlreadyExists{} + } + return err +} + func (ps *postgresService) GetByID(id string, i interface{}) error { if err := ps.conn.Model(i).Where("id = ?", id).Select(); err != nil { if err == pg.ErrNoRows { @@ -73,3 +64,34 @@ func (ps *postgresService) GetAll(i interface{}) error { } return nil } + +func (ps *postgresService) DeleteByID(id string, i interface{}) error { + if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil { + // if there are no rows *anyway* then that's fine + // just return err if there's an actual error + if err != pg.ErrNoRows { + return err + } + } + return nil +} + +func (ps *postgresService) DeleteWhere(where []db.Where, i interface{}) error { + if len(where) == 0 { + return errors.New("no queries provided") + } + + q := ps.conn.Model(i) + for _, w := range where { + q = q.Where("? = ?", pg.Safe(w.Key), w.Value) + } + + if _, err := q.Delete(); err != nil { + // if there are no rows *anyway* then that's fine + // just return err if there's an actual error + if err != pg.ErrNoRows { + return err + } + } + return nil +} diff --git a/internal/db/pg/blocks.go b/internal/db/pg/blocks.go index a6fc1f859..beada4e88 100644 --- a/internal/db/pg/blocks.go +++ b/internal/db/pg/blocks.go @@ -24,7 +24,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (ps *postgresService) GetBlocksForAccount(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, error) { +func (ps *postgresService) GetAccountBlocks(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, error) { blocks := []*gtsmodel.Block{} fq := ps.conn.Model(&blocks). diff --git a/internal/db/pg/delete.go b/internal/db/pg/delete.go deleted file mode 100644 index 0f288353e..000000000 --- a/internal/db/pg/delete.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package pg - -import ( - "errors" - - "github.com/go-pg/pg/v10" - "github.com/superseriousbusiness/gotosocial/internal/db" -) - -func (ps *postgresService) DeleteByID(id string, i interface{}) error { - if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil { - // if there are no rows *anyway* then that's fine - // just return err if there's an actual error - if err != pg.ErrNoRows { - return err - } - } - return nil -} - -func (ps *postgresService) DeleteWhere(where []db.Where, i interface{}) error { - if len(where) == 0 { - return errors.New("no queries provided") - } - - q := ps.conn.Model(i) - for _, w := range where { - q = q.Where("? = ?", pg.Safe(w.Key), w.Value) - } - - if _, err := q.Delete(); err != nil { - // if there are no rows *anyway* then that's fine - // just return err if there's an actual error - if err != pg.ErrNoRows { - return err - } - } - return nil -} diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index d49c50114..0c3f7310c 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -20,15 +20,11 @@ package pg import ( "context" - "crypto/rand" - "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" - "net" - "net/mail" "os" "strings" "time" @@ -41,8 +37,6 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" - "github.com/superseriousbusiness/gotosocial/internal/util" - "golang.org/x/crypto/bcrypt" ) // postgresService satisfies the DB interface @@ -254,641 +248,6 @@ func (ps *postgresService) CreateSchema(ctx context.Context) error { HANDY SHORTCUTS */ -func (ps *postgresService) AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, error) { - // make sure the original follow request exists - fr := >smodel.FollowRequest{} - if err := ps.conn.Model(fr).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Select(); err != nil { - if err == pg.ErrMultiRows { - return nil, db.ErrNoEntries{} - } - return nil, err - } - - // create a new follow to 'replace' the request with - follow := >smodel.Follow{ - ID: fr.ID, - AccountID: originAccountID, - TargetAccountID: targetAccountID, - URI: fr.URI, - } - - // if the follow already exists, just update the URI -- we don't need to do anything else - if _, err := ps.conn.Model(follow).OnConflict("ON CONSTRAINT follows_account_id_target_account_id_key DO UPDATE set uri = ?", follow.URI).Insert(); err != nil { - return nil, err - } - - // now remove the follow request - if _, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Delete(); err != nil { - return nil, err - } - - return follow, nil -} - -func (ps *postgresService) CreateInstanceAccount() error { - username := ps.config.Host - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - ps.log.Errorf("error creating new rsa key: %s", err) - return err - } - - aID, err := id.NewRandomULID() - if err != nil { - return err - } - - newAccountURIs := util.GenerateURIsForAccount(username, ps.config.Protocol, ps.config.Host) - a := >smodel.Account{ - ID: aID, - Username: ps.config.Host, - DisplayName: username, - URL: newAccountURIs.UserURL, - PrivateKey: key, - PublicKey: &key.PublicKey, - PublicKeyURI: newAccountURIs.PublicKeyURI, - ActorType: gtsmodel.ActivityStreamsPerson, - URI: newAccountURIs.UserURI, - InboxURI: newAccountURIs.InboxURI, - OutboxURI: newAccountURIs.OutboxURI, - FollowersURI: newAccountURIs.FollowersURI, - FollowingURI: newAccountURIs.FollowingURI, - FeaturedCollectionURI: newAccountURIs.CollectionURI, - } - inserted, err := ps.conn.Model(a).Where("username = ?", username).SelectOrInsert() - if err != nil { - return err - } - if inserted { - ps.log.Infof("created instance account %s with id %s", username, a.ID) - } else { - ps.log.Infof("instance account %s already exists with id %s", username, a.ID) - } - return nil -} - -func (ps *postgresService) CreateInstanceInstance() error { - iID, err := id.NewRandomULID() - if err != nil { - return err - } - - i := >smodel.Instance{ - ID: iID, - Domain: ps.config.Host, - Title: ps.config.Host, - URI: fmt.Sprintf("%s://%s", ps.config.Protocol, ps.config.Host), - } - inserted, err := ps.conn.Model(i).Where("domain = ?", ps.config.Host).SelectOrInsert() - if err != nil { - return err - } - if inserted { - ps.log.Infof("created instance instance %s with id %s", ps.config.Host, i.ID) - } else { - ps.log.Infof("instance instance %s already exists with id %s", ps.config.Host, i.ID) - } - return nil -} - -func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.Account) error { - user := >smodel.User{ - ID: userID, - } - if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - return nil -} - -func (ps *postgresService) GetLocalAccountByUsername(username string, account *gtsmodel.Account) error { - if err := ps.conn.Model(account).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - return nil -} - -func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error { - if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return nil - } - return err - } - return nil -} - -func (ps *postgresService) GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error { - if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return nil - } - return err - } - return nil -} - -func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow, localOnly bool) error { - - q := ps.conn.Model(followers) - - if localOnly { - // for local accounts let's get where domain is null OR where domain is an empty string, just to be safe - whereGroup := func(q *pg.Query) (*pg.Query, error) { - q = q. - WhereOr("? IS NULL", pg.Ident("a.domain")). - WhereOr("a.domain = ?", "") - return q, nil - } - - q = q.ColumnExpr("follow.*"). - Join("JOIN accounts AS a ON follow.account_id = TEXT(a.id)"). - Where("follow.target_account_id = ?", accountID). - WhereGroup(whereGroup) - } else { - q = q.Where("target_account_id = ?", accountID) - } - - if err := q.Select(); err != nil { - if err == pg.ErrNoRows { - return nil - } - return err - } - return nil -} - -func (ps *postgresService) GetFavesByAccountID(accountID string, faves *[]gtsmodel.StatusFave) error { - if err := ps.conn.Model(faves).Where("account_id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return nil - } - return err - } - return nil -} - -func (ps *postgresService) CountStatusesByAccountID(accountID string) (int, error) { - count, err := ps.conn.Model(>smodel.Status{}).Where("account_id = ?", accountID).Count() - if err != nil { - if err == pg.ErrNoRows { - return 0, nil - } - return 0, err - } - return count, nil -} - -func (ps *postgresService) GetStatusesForAccount(accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, error) { - ps.log.Debugf("getting statuses for account %s", accountID) - statuses := []*gtsmodel.Status{} - - q := ps.conn.Model(&statuses).Order("id DESC") - if accountID != "" { - q = q.Where("account_id = ?", accountID) - } - - if limit != 0 { - q = q.Limit(limit) - } - - if excludeReplies { - q = q.Where("? IS NULL", pg.Ident("in_reply_to_id")) - } - - if pinnedOnly { - q = q.Where("pinned = ?", true) - } - - if mediaOnly { - q = q.WhereGroup(func(q *pg.Query) (*pg.Query, error) { - return q.Where("? IS NOT NULL", pg.Ident("attachments")).Where("attachments != '{}'"), nil - }) - } - - if maxID != "" { - q = q.Where("id < ?", maxID) - } - - if err := q.Select(); err != nil { - if err == pg.ErrNoRows { - return nil, db.ErrNoEntries{} - } - return nil, err - } - - if len(statuses) == 0 { - return nil, db.ErrNoEntries{} - } - - ps.log.Debugf("returning statuses for account %s", accountID) - return statuses, nil -} - -func (ps *postgresService) GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error { - if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - return nil - -} - -func (ps *postgresService) IsUsernameAvailable(username string) error { - // if no error we fail because it means we found something - // if error but it's not pg.ErrNoRows then we fail - // if err is pg.ErrNoRows we're good, we found nothing so continue - if err := ps.conn.Model(>smodel.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil { - return fmt.Errorf("username %s already in use", username) - } else if err != pg.ErrNoRows { - return fmt.Errorf("db error: %s", err) - } - return nil -} - -func (ps *postgresService) IsEmailAvailable(email string) error { - // parse the domain from the email - m, err := mail.ParseAddress(email) - if err != nil { - return fmt.Errorf("error parsing email address %s: %s", email, err) - } - domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @ - - // check if the email domain is blocked - if err := ps.conn.Model(>smodel.EmailDomainBlock{}).Where("domain = ?", domain).Select(); err == nil { - // fail because we found something - return fmt.Errorf("email domain %s is blocked", domain) - } else if err != pg.ErrNoRows { - // fail because we got an unexpected error - return fmt.Errorf("db error: %s", err) - } - - // check if this email is associated with a user already - if err := ps.conn.Model(>smodel.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { - // fail because we found something - return fmt.Errorf("email %s already in use", email) - } else if err != pg.ErrNoRows { - // fail because we got an unexpected error - return fmt.Errorf("db error: %s", err) - } - return nil -} - -func (ps *postgresService) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, error) { - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - ps.log.Errorf("error creating new rsa key: %s", err) - return nil, err - } - - // if something went wrong while creating a user, we might already have an account, so check here first... - a := >smodel.Account{} - err = ps.conn.Model(a).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select() - if err != nil { - // there's been an actual error - if err != pg.ErrNoRows { - return nil, fmt.Errorf("db error checking existence of account: %s", err) - } - - // we just don't have an account yet create one - newAccountURIs := util.GenerateURIsForAccount(username, ps.config.Protocol, ps.config.Host) - newAccountID, err := id.NewRandomULID() - if err != nil { - return nil, err - } - - a = >smodel.Account{ - ID: newAccountID, - Username: username, - DisplayName: username, - Reason: reason, - URL: newAccountURIs.UserURL, - PrivateKey: key, - PublicKey: &key.PublicKey, - PublicKeyURI: newAccountURIs.PublicKeyURI, - ActorType: gtsmodel.ActivityStreamsPerson, - URI: newAccountURIs.UserURI, - InboxURI: newAccountURIs.InboxURI, - OutboxURI: newAccountURIs.OutboxURI, - FollowersURI: newAccountURIs.FollowersURI, - FollowingURI: newAccountURIs.FollowingURI, - FeaturedCollectionURI: newAccountURIs.CollectionURI, - } - if _, err = ps.conn.Model(a).Insert(); err != nil { - return nil, err - } - } - - pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return nil, fmt.Errorf("error hashing password: %s", err) - } - - newUserID, err := id.NewRandomULID() - if err != nil { - return nil, err - } - - u := >smodel.User{ - ID: newUserID, - AccountID: a.ID, - EncryptedPassword: string(pw), - SignUpIP: signUpIP.To4(), - Locale: locale, - UnconfirmedEmail: email, - CreatedByApplicationID: appID, - Approved: !requireApproval, // if we don't require moderator approval, just pre-approve the user - } - - if emailVerified { - u.ConfirmedAt = time.Now() - u.Email = email - } - - if admin { - u.Admin = true - u.Moderator = true - } - - if _, err = ps.conn.Model(u).Insert(); err != nil { - return nil, err - } - - return u, nil -} - -func (ps *postgresService) SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error { - if mediaAttachment.Avatar && mediaAttachment.Header { - return errors.New("one media attachment cannot be both header and avatar") - } - - var headerOrAVI string - if mediaAttachment.Avatar { - headerOrAVI = "avatar" - } else if mediaAttachment.Header { - headerOrAVI = "header" - } else { - return errors.New("given media attachment was neither a header nor an avatar") - } - - // TODO: there are probably more side effects here that need to be handled - if _, err := ps.conn.Model(mediaAttachment).OnConflict("(id) DO UPDATE").Insert(); err != nil { - return err - } - - if _, err := ps.conn.Model(>smodel.Account{}).Set(fmt.Sprintf("%s_media_attachment_id = ?", headerOrAVI), mediaAttachment.ID).Where("id = ?", accountID).Update(); err != nil { - return err - } - return nil -} - -func (ps *postgresService) GetHeaderForAccountID(header *gtsmodel.MediaAttachment, accountID string) error { - acct := >smodel.Account{} - if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - - if acct.HeaderMediaAttachmentID == "" { - return db.ErrNoEntries{} - } - - if err := ps.conn.Model(header).Where("id = ?", acct.HeaderMediaAttachmentID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - return nil -} - -func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error { - acct := >smodel.Account{} - if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - - if acct.AvatarMediaAttachmentID == "" { - return db.ErrNoEntries{} - } - - if err := ps.conn.Model(avatar).Where("id = ?", acct.AvatarMediaAttachmentID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} - } - return err - } - return nil -} - -func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { - // TODO: check domain blocks as well - var blocked bool - if err := ps.conn.Model(>smodel.Block{}). - Where("account_id = ?", account1).Where("target_account_id = ?", account2). - WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2). - Select(); err != nil { - if err == pg.ErrNoRows { - blocked = false - return blocked, nil - } - return blocked, err - } - blocked = true - return blocked, nil -} - -func (ps *postgresService) GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error) { - r := >smodel.Relationship{ - ID: targetAccount, - } - - // check if the requesting account follows the target account - follow := >smodel.Follow{} - if err := ps.conn.Model(follow).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Select(); err != nil { - if err != pg.ErrNoRows { - // a proper error - return nil, fmt.Errorf("getrelationship: error checking follow existence: %s", err) - } - // no follow exists so these are all false - r.Following = false - r.ShowingReblogs = false - r.Notifying = false - } else { - // follow exists so we can fill these fields out... - r.Following = true - r.ShowingReblogs = follow.ShowReblogs - r.Notifying = follow.Notify - } - - // check if the target account follows the requesting account - followedBy, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists() - if err != nil { - return nil, fmt.Errorf("getrelationship: error checking followed_by existence: %s", err) - } - r.FollowedBy = followedBy - - // check if the requesting account blocks the target account - blocking, err := ps.conn.Model(>smodel.Block{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists() - if err != nil { - return nil, fmt.Errorf("getrelationship: error checking blocking existence: %s", err) - } - r.Blocking = blocking - - // check if the target account blocks the requesting account - blockedBy, err := ps.conn.Model(>smodel.Block{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists() - if err != nil { - return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err) - } - r.BlockedBy = blockedBy - - // check if there's a pending following request from requesting account to target account - requested, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists() - if err != nil { - return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err) - } - r.Requested = requested - - return r, nil -} - -func (ps *postgresService) Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { - if sourceAccount == nil || targetAccount == nil { - return false, nil - } - - return ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists() -} - -func (ps *postgresService) FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { - if sourceAccount == nil || targetAccount == nil { - return false, nil - } - - return ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists() -} - -func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) { - if account1 == nil || account2 == nil { - return false, nil - } - - // make sure account 1 follows account 2 - f1, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account1.ID).Where("target_account_id = ?", account2.ID).Exists() - if err != nil { - if err == pg.ErrNoRows { - return false, nil - } - return false, err - } - - // make sure account 2 follows account 1 - f2, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account2.ID).Where("target_account_id = ?", account1.ID).Exists() - if err != nil { - if err == pg.ErrNoRows { - return false, nil - } - return false, err - } - - return f1 && f2, nil -} - -func (ps *postgresService) GetReplyCountForStatus(status *gtsmodel.Status) (int, error) { - return ps.conn.Model(>smodel.Status{}).Where("in_reply_to_id = ?", status.ID).Count() -} - -func (ps *postgresService) GetReblogCountForStatus(status *gtsmodel.Status) (int, error) { - return ps.conn.Model(>smodel.Status{}).Where("boost_of_id = ?", status.ID).Count() -} - -func (ps *postgresService) GetFaveCountForStatus(status *gtsmodel.Status) (int, error) { - return ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Count() -} - -func (ps *postgresService) StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, error) { - return ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() -} - -func (ps *postgresService) StatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, error) { - return ps.conn.Model(>smodel.Status{}).Where("boost_of_id = ?", status.ID).Where("account_id = ?", accountID).Exists() -} - -func (ps *postgresService) StatusMutedBy(status *gtsmodel.Status, accountID string) (bool, error) { - return ps.conn.Model(>smodel.StatusMute{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() -} - -func (ps *postgresService) StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error) { - return ps.conn.Model(>smodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() -} - -func (ps *postgresService) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) { - accounts := []*gtsmodel.Account{} - - faves := []*gtsmodel.StatusFave{} - if err := ps.conn.Model(&faves).Where("status_id = ?", status.ID).Select(); err != nil { - if err == pg.ErrNoRows { - return accounts, nil // no rows just means nobody has faved this status, so that's fine - } - return nil, err // an actual error has occurred - } - - for _, f := range faves { - acc := >smodel.Account{} - if err := ps.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil { - if err == pg.ErrNoRows { - continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it - } - return nil, err // an actual error has occurred - } - accounts = append(accounts, acc) - } - return accounts, nil -} - -func (ps *postgresService) WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) { - accounts := []*gtsmodel.Account{} - - boosts := []*gtsmodel.Status{} - if err := ps.conn.Model(&boosts).Where("boost_of_id = ?", status.ID).Select(); err != nil { - if err == pg.ErrNoRows { - return accounts, nil // no rows just means nobody has boosted this status, so that's fine - } - return nil, err // an actual error has occurred - } - - for _, f := range boosts { - acc := >smodel.Account{} - if err := ps.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil { - if err == pg.ErrNoRows { - continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it - } - return nil, err // an actual error has occurred - } - accounts = append(accounts, acc) - } - return accounts, nil -} - func (ps *postgresService) GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) { notifications := []*gtsmodel.Notification{} diff --git a/internal/db/pg/put.go b/internal/db/pg/put.go deleted file mode 100644 index 09beca14b..000000000 --- a/internal/db/pg/put.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package pg - -import ( - "strings" - - "github.com/superseriousbusiness/gotosocial/internal/db" -) - -func (ps *postgresService) Put(i interface{}) error { - _, err := ps.conn.Model(i).Insert(i) - if err != nil && strings.Contains(err.Error(), "duplicate key value violates unique constraint") { - return db.ErrAlreadyExists{} - } - return err -} diff --git a/internal/db/pg/relationship.go b/internal/db/pg/relationship.go new file mode 100644 index 000000000..ac628b87d --- /dev/null +++ b/internal/db/pg/relationship.go @@ -0,0 +1,153 @@ +package pg + +import ( + "fmt" + + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { + // TODO: check domain blocks as well + var blocked bool + if err := ps.conn.Model(>smodel.Block{}). + Where("account_id = ?", account1).Where("target_account_id = ?", account2). + WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2). + Select(); err != nil { + if err == pg.ErrNoRows { + blocked = false + return blocked, nil + } + return blocked, err + } + blocked = true + return blocked, nil +} + +func (ps *postgresService) GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error) { + r := >smodel.Relationship{ + ID: targetAccount, + } + + // check if the requesting account follows the target account + follow := >smodel.Follow{} + if err := ps.conn.Model(follow).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Select(); err != nil { + if err != pg.ErrNoRows { + // a proper error + return nil, fmt.Errorf("getrelationship: error checking follow existence: %s", err) + } + // no follow exists so these are all false + r.Following = false + r.ShowingReblogs = false + r.Notifying = false + } else { + // follow exists so we can fill these fields out... + r.Following = true + r.ShowingReblogs = follow.ShowReblogs + r.Notifying = follow.Notify + } + + // check if the target account follows the requesting account + followedBy, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists() + if err != nil { + return nil, fmt.Errorf("getrelationship: error checking followed_by existence: %s", err) + } + r.FollowedBy = followedBy + + // check if the requesting account blocks the target account + blocking, err := ps.conn.Model(>smodel.Block{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists() + if err != nil { + return nil, fmt.Errorf("getrelationship: error checking blocking existence: %s", err) + } + r.Blocking = blocking + + // check if the target account blocks the requesting account + blockedBy, err := ps.conn.Model(>smodel.Block{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists() + if err != nil { + return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err) + } + r.BlockedBy = blockedBy + + // check if there's a pending following request from requesting account to target account + requested, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists() + if err != nil { + return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err) + } + r.Requested = requested + + return r, nil +} + +func (ps *postgresService) Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { + if sourceAccount == nil || targetAccount == nil { + return false, nil + } + + return ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists() +} + +func (ps *postgresService) FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { + if sourceAccount == nil || targetAccount == nil { + return false, nil + } + + return ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists() +} + +func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) { + if account1 == nil || account2 == nil { + return false, nil + } + + // make sure account 1 follows account 2 + f1, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account1.ID).Where("target_account_id = ?", account2.ID).Exists() + if err != nil { + if err == pg.ErrNoRows { + return false, nil + } + return false, err + } + + // make sure account 2 follows account 1 + f2, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account2.ID).Where("target_account_id = ?", account1.ID).Exists() + if err != nil { + if err == pg.ErrNoRows { + return false, nil + } + return false, err + } + + return f1 && f2, nil +} + +func (ps *postgresService) AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, error) { + // make sure the original follow request exists + fr := >smodel.FollowRequest{} + if err := ps.conn.Model(fr).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Select(); err != nil { + if err == pg.ErrMultiRows { + return nil, db.ErrNoEntries{} + } + return nil, err + } + + // create a new follow to 'replace' the request with + follow := >smodel.Follow{ + ID: fr.ID, + AccountID: originAccountID, + TargetAccountID: targetAccountID, + URI: fr.URI, + } + + // if the follow already exists, just update the URI -- we don't need to do anything else + if _, err := ps.conn.Model(follow).OnConflict("ON CONSTRAINT follows_account_id_target_account_id_key DO UPDATE set uri = ?", follow.URI).Insert(); err != nil { + return nil, err + } + + // now remove the follow request + if _, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Delete(); err != nil { + return nil, err + } + + return follow, nil +} diff --git a/internal/db/pg/status.go b/internal/db/pg/status.go new file mode 100644 index 000000000..ab9243a90 --- /dev/null +++ b/internal/db/pg/status.go @@ -0,0 +1,181 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package pg + +import ( + "container/list" + "errors" + + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (ps *postgresService) StatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, error) { + parents := []*gtsmodel.Status{} + ps.statusParent(status, &parents, onlyDirect) + + return parents, nil +} + +func (ps *postgresService) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status, onlyDirect bool) { + if status.InReplyToID == "" { + return + } + + parentStatus := >smodel.Status{} + if err := ps.conn.Model(parentStatus).Where("id = ?", status.InReplyToID).Select(); err == nil { + *foundStatuses = append(*foundStatuses, parentStatus) + } + + if onlyDirect { + return + } + ps.statusParent(parentStatus, foundStatuses, false) +} + +func (ps *postgresService) StatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, error) { + foundStatuses := &list.List{} + foundStatuses.PushFront(status) + ps.statusChildren(status, foundStatuses, onlyDirect, minID) + + children := []*gtsmodel.Status{} + for e := foundStatuses.Front(); e != nil; e = e.Next() { + entry, ok := e.Value.(*gtsmodel.Status) + if !ok { + panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status")) + } + + // only append children, not the overall parent status + if entry.ID != status.ID { + children = append(children, entry) + } + } + + return children, nil +} + +func (ps *postgresService) statusChildren(status *gtsmodel.Status, foundStatuses *list.List, onlyDirect bool, minID string) { + immediateChildren := []*gtsmodel.Status{} + + q := ps.conn.Model(&immediateChildren).Where("in_reply_to_id = ?", status.ID) + if minID != "" { + q = q.Where("status.id > ?", minID) + } + + if err := q.Select(); err != nil { + return + } + + for _, child := range immediateChildren { + insertLoop: + for e := foundStatuses.Front(); e != nil; e = e.Next() { + entry, ok := e.Value.(*gtsmodel.Status) + if !ok { + panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status")) + } + + if child.InReplyToAccountID != "" && entry.ID == child.InReplyToID { + foundStatuses.InsertAfter(child, e) + break insertLoop + } + } + + // only do one loop if we only want direct children + if onlyDirect { + return + } + ps.statusChildren(child, foundStatuses, false, minID) + } +} + +func (ps *postgresService) GetReplyCountForStatus(status *gtsmodel.Status) (int, error) { + return ps.conn.Model(>smodel.Status{}).Where("in_reply_to_id = ?", status.ID).Count() +} + +func (ps *postgresService) GetReblogCountForStatus(status *gtsmodel.Status) (int, error) { + return ps.conn.Model(>smodel.Status{}).Where("boost_of_id = ?", status.ID).Count() +} + +func (ps *postgresService) GetFaveCountForStatus(status *gtsmodel.Status) (int, error) { + return ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Count() +} + +func (ps *postgresService) StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, error) { + return ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() +} + +func (ps *postgresService) StatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, error) { + return ps.conn.Model(>smodel.Status{}).Where("boost_of_id = ?", status.ID).Where("account_id = ?", accountID).Exists() +} + +func (ps *postgresService) StatusMutedBy(status *gtsmodel.Status, accountID string) (bool, error) { + return ps.conn.Model(>smodel.StatusMute{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() +} + +func (ps *postgresService) StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error) { + return ps.conn.Model(>smodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() +} + +func (ps *postgresService) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) { + accounts := []*gtsmodel.Account{} + + faves := []*gtsmodel.StatusFave{} + if err := ps.conn.Model(&faves).Where("status_id = ?", status.ID).Select(); err != nil { + if err == pg.ErrNoRows { + return accounts, nil // no rows just means nobody has faved this status, so that's fine + } + return nil, err // an actual error has occurred + } + + for _, f := range faves { + acc := >smodel.Account{} + if err := ps.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil { + if err == pg.ErrNoRows { + continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it + } + return nil, err // an actual error has occurred + } + accounts = append(accounts, acc) + } + return accounts, nil +} + +func (ps *postgresService) WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) { + accounts := []*gtsmodel.Account{} + + boosts := []*gtsmodel.Status{} + if err := ps.conn.Model(&boosts).Where("boost_of_id = ?", status.ID).Select(); err != nil { + if err == pg.ErrNoRows { + return accounts, nil // no rows just means nobody has boosted this status, so that's fine + } + return nil, err // an actual error has occurred + } + + for _, f := range boosts { + acc := >smodel.Account{} + if err := ps.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil { + if err == pg.ErrNoRows { + continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it + } + return nil, err // an actual error has occurred + } + accounts = append(accounts, acc) + } + return accounts, nil +} diff --git a/internal/db/pg/statuscontext.go b/internal/db/pg/statuscontext.go deleted file mode 100644 index 2ff1a20bb..000000000 --- a/internal/db/pg/statuscontext.go +++ /dev/null @@ -1,104 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package pg - -import ( - "container/list" - "errors" - - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func (ps *postgresService) StatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, error) { - parents := []*gtsmodel.Status{} - ps.statusParent(status, &parents, onlyDirect) - - return parents, nil -} - -func (ps *postgresService) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status, onlyDirect bool) { - if status.InReplyToID == "" { - return - } - - parentStatus := >smodel.Status{} - if err := ps.conn.Model(parentStatus).Where("id = ?", status.InReplyToID).Select(); err == nil { - *foundStatuses = append(*foundStatuses, parentStatus) - } - - if onlyDirect { - return - } - ps.statusParent(parentStatus, foundStatuses, false) -} - -func (ps *postgresService) StatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, error) { - foundStatuses := &list.List{} - foundStatuses.PushFront(status) - ps.statusChildren(status, foundStatuses, onlyDirect, minID) - - children := []*gtsmodel.Status{} - for e := foundStatuses.Front(); e != nil; e = e.Next() { - entry, ok := e.Value.(*gtsmodel.Status) - if !ok { - panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status")) - } - - // only append children, not the overall parent status - if entry.ID != status.ID { - children = append(children, entry) - } - } - - return children, nil -} - -func (ps *postgresService) statusChildren(status *gtsmodel.Status, foundStatuses *list.List, onlyDirect bool, minID string) { - immediateChildren := []*gtsmodel.Status{} - - q := ps.conn.Model(&immediateChildren).Where("in_reply_to_id = ?", status.ID) - if minID != "" { - q = q.Where("status.id > ?", minID) - } - - if err := q.Select(); err != nil { - return - } - - for _, child := range immediateChildren { - insertLoop: - for e := foundStatuses.Front(); e != nil; e = e.Next() { - entry, ok := e.Value.(*gtsmodel.Status) - if !ok { - panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status")) - } - - if child.InReplyToAccountID != "" && entry.ID == child.InReplyToID { - foundStatuses.InsertAfter(child, e) - break insertLoop - } - } - - // only do one loop if we only want direct children - if onlyDirect { - return - } - ps.statusChildren(child, foundStatuses, false, minID) - } -} diff --git a/internal/db/relationship.go b/internal/db/relationship.go new file mode 100644 index 000000000..1fa532fbb --- /dev/null +++ b/internal/db/relationship.go @@ -0,0 +1,27 @@ +package db + +import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + +type Relationship interface { + // Blocked checks whether a block exists in eiher direction between two accounts. + // That is, it returns true if account1 blocks account2, OR if account2 blocks account1. + Blocked(account1 string, account2 string) (bool, error) + + // GetRelationship retrieves the relationship of the targetAccount to the requestingAccount. + GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error) + + // Follows returns true if sourceAccount follows target account, or an error if something goes wrong while finding out. + Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) + + // FollowRequested returns true if sourceAccount has requested to follow target account, or an error if something goes wrong while finding out. + FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) + + // Mutuals returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out. + Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) + + // AcceptFollowRequest moves a follow request in the database from the follow_requests table to the follows table. + // In other words, it should create the follow, and delete the existing follow request. + // + // It will return the newly created follow for further processing. + AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, error) +} diff --git a/internal/db/status.go b/internal/db/status.go new file mode 100644 index 000000000..94db51d0a --- /dev/null +++ b/internal/db/status.go @@ -0,0 +1,44 @@ +package db + +import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + +type Status interface { + // GetReplyCountForStatus returns the amount of replies recorded for a status, or an error if something goes wrong + GetReplyCountForStatus(status *gtsmodel.Status) (int, error) + + // GetReblogCountForStatus returns the amount of reblogs/boosts recorded for a status, or an error if something goes wrong + GetReblogCountForStatus(status *gtsmodel.Status) (int, error) + + // GetFaveCountForStatus returns the amount of faves/likes recorded for a status, or an error if something goes wrong + GetFaveCountForStatus(status *gtsmodel.Status) (int, error) + + // StatusParents get the parent statuses of a given status. + // + // If onlyDirect is true, only the immediate parent will be returned. + StatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, error) + + // StatusChildren gets the child statuses of a given status. + // + // If onlyDirect is true, only the immediate children will be returned. + StatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, error) + + // StatusFavedBy checks if a given status has been faved by a given account ID + StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, error) + + // StatusRebloggedBy checks if a given status has been reblogged/boosted by a given account ID + StatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, error) + + // StatusMutedBy checks if a given status has been muted by a given account ID + StatusMutedBy(status *gtsmodel.Status, accountID string) (bool, error) + + // StatusBookmarkedBy checks if a given status has been bookmarked by a given account ID + StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error) + + // WhoFavedStatus returns a slice of accounts who faved the given status. + // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. + WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) + + // WhoBoostedStatus returns a slice of accounts who boosted the given status. + // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. + WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) +} diff --git a/internal/db/timeline.go b/internal/db/timeline.go new file mode 100644 index 000000000..3ef34d0ea --- /dev/null +++ b/internal/db/timeline.go @@ -0,0 +1,25 @@ +package db + +import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + +type Timeline interface { + // GetHomeTimelineForAccount returns a slice of statuses from accounts that are followed by the given account id. + // + // Statuses should be returned in descending order of when they were created (newest first). + GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) + + // GetPublicTimelineForAccount fetches the account's PUBLIC timeline -- ie., posts and replies that are public. + // It will use the given filters and try to return as many statuses as possible up to the limit. + // + // Statuses should be returned in descending order of when they were created (newest first). + GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) + + // GetFavedTimelineForAccount fetches the account's FAVED timeline -- ie., posts and replies that the requesting account has faved. + // It will use the given filters and try to return as many statuses as possible up to the limit. + // + // Note that unlike the other GetTimeline functions, the returned statuses will be arranged by their FAVE id, not the STATUS id. + // In other words, they'll be returned in descending order of when they were faved by the requesting user, not when they were created. + // + // Also note the extra return values, which correspond to the nextMaxID and prevMinID for building Link headers. + GetFavedTimelineForAccount(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, error) +} diff --git a/internal/federation/dereferencing/announce.go b/internal/federation/dereferencing/announce.go index 2522a4034..6773db425 100644 --- a/internal/federation/dereferencing/announce.go +++ b/internal/federation/dereferencing/announce.go @@ -27,14 +27,14 @@ import ( ) func (d *deref) DereferenceAnnounce(announce *gtsmodel.Status, requestingUsername string) error { - if announce.GTSBoostedStatus == nil || announce.GTSBoostedStatus.URI == "" { + if announce.BoostOf == nil || announce.BoostOf.URI == "" { // we can't do anything unfortunately return errors.New("DereferenceAnnounce: no URI to dereference") } - boostedStatusURI, err := url.Parse(announce.GTSBoostedStatus.URI) + boostedStatusURI, err := url.Parse(announce.BoostOf.URI) if err != nil { - return fmt.Errorf("DereferenceAnnounce: couldn't parse boosted status URI %s: %s", announce.GTSBoostedStatus.URI, err) + return fmt.Errorf("DereferenceAnnounce: couldn't parse boosted status URI %s: %s", announce.BoostOf.URI, err) } if blocked, err := d.blockedDomain(boostedStatusURI.Host); blocked || err != nil { return fmt.Errorf("DereferenceAnnounce: domain %s is blocked", boostedStatusURI.Host) @@ -47,7 +47,7 @@ func (d *deref) DereferenceAnnounce(announce *gtsmodel.Status, requestingUsernam boostedStatus, _, _, err := d.GetRemoteStatus(requestingUsername, boostedStatusURI, false) if err != nil { - return fmt.Errorf("DereferenceAnnounce: error dereferencing remote status with id %s: %s", announce.GTSBoostedStatus.URI, err) + return fmt.Errorf("DereferenceAnnounce: error dereferencing remote status with id %s: %s", announce.BoostOf.URI, err) } announce.Content = boostedStatus.Content @@ -60,6 +60,6 @@ func (d *deref) DereferenceAnnounce(announce *gtsmodel.Status, requestingUsernam announce.BoostOfAccountID = boostedStatus.AccountID announce.Visibility = boostedStatus.Visibility announce.VisibilityAdvanced = boostedStatus.VisibilityAdvanced - announce.GTSBoostedStatus = boostedStatus + announce.BoostOf = boostedStatus return nil } diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index b05f6e72c..9d5a0c7ca 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -338,8 +338,8 @@ func (d *deref) populateStatusFields(status *gtsmodel.Status, requestingUsername } m.StatusID = status.ID - m.OriginAccountID = status.GTSAuthorAccount.ID - m.OriginAccountURI = status.GTSAuthorAccount.URI + m.OriginAccountID = status.Account.ID + m.OriginAccountURI = status.Account.URI targetAccount, _, err := d.GetRemoteAccount(requestingUsername, uri, false) if err != nil { diff --git a/internal/federation/federatingdb/followers.go b/internal/federation/federatingdb/followers.go index 1f111dd34..1ed74db8d 100644 --- a/internal/federation/federatingdb/followers.go +++ b/internal/federation/federatingdb/followers.go @@ -43,7 +43,7 @@ func (f *federatingDB) Followers(c context.Context, actorIRI *url.URL) (follower } acctFollowers := []gtsmodel.Follow{} - if err := f.db.GetFollowersByAccountID(acct.ID, &acctFollowers, false); err != nil { + if err := f.db.GetAccountFollowers(acct.ID, &acctFollowers, false); err != nil { return nil, fmt.Errorf("FOLLOWERS: db error getting followers for account id %s: %s", acct.ID, err) } diff --git a/internal/federation/federatingdb/following.go b/internal/federation/federatingdb/following.go index f92041e1e..f5023d1b6 100644 --- a/internal/federation/federatingdb/following.go +++ b/internal/federation/federatingdb/following.go @@ -42,7 +42,7 @@ func (f *federatingDB) Following(c context.Context, actorIRI *url.URL) (followin } acctFollowing := []gtsmodel.Follow{} - if err := f.db.GetFollowingByAccountID(acct.ID, &acctFollowing); err != nil { + if err := f.db.GetAccountFollowing(acct.ID, &acctFollowing); err != nil { return nil, fmt.Errorf("FOLLOWING: db error getting following for account id %s: %s", acct.ID, err) } diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index 106298bcd..21b8fc794 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -48,18 +48,23 @@ type Status struct { Local bool // which account posted this status? AccountID string `pg:"type:CHAR(26),notnull"` + Account *Account `pg:"rel:has-one"` // AP uri of the owner of this status AccountURI string // id of the status this status is a reply to InReplyToID string `pg:"type:CHAR(26)"` + InReplyTo *Status `pg:"-"` // AP uri of the status this status is a reply to InReplyToURI string // id of the account that this status replies to InReplyToAccountID string `pg:"type:CHAR(26)"` + InReplyToAccount *Account `pg:"-"` // id of the status this status is a boost of BoostOfID string `pg:"type:CHAR(26)"` + BoostOf *Status `pg:"-"` // id of the account that owns the boosted status BoostOfAccountID string `pg:"type:CHAR(26)"` + BoostOfAccount *Account `pg:"-"` // cw string for this status ContentWarning string // visibility entry for this status @@ -70,6 +75,7 @@ type Status struct { Language string // Which application was used to create this status? CreatedWithApplicationID string `pg:"type:CHAR(26)"` + CreatedWithApplication *Application `pg:"-"` // advanced visibility for this status VisibilityAdvanced *VisibilityAdvanced // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types @@ -88,7 +94,7 @@ type Status struct { */ // Account that created this status - GTSAuthorAccount *Account `pg:"-"` + // Mentions created in this status GTSMentions []*Mention `pg:"-"` // Hashtags used in this status @@ -98,13 +104,13 @@ type Status struct { // MediaAttachments used in this status GTSMediaAttachments []*MediaAttachment `pg:"-"` // Status being replied to - GTSReplyToStatus *Status `pg:"-"` + // Account being replied to - GTSReplyToAccount *Account `pg:"-"` + // Status being boosted - GTSBoostedStatus *Status `pg:"-"` + // Account of the boosted status - GTSBoostedAccount *Account `pg:"-"` + } // Visibility represents the visibility granularity of a status. diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index 7152db37a..012360bff 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -28,17 +28,13 @@ type StatusFave struct { CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` // id of the account that created ('did') the fave AccountID string `pg:"type:CHAR(26),notnull"` + Account *Account `pg:"rel:has-one"` // id the account owning the faved status TargetAccountID string `pg:"type:CHAR(26),notnull"` + TargetAccount *Account `pg:"rel:has-one"` // database id of the status that has been 'faved' StatusID string `pg:"type:CHAR(26),notnull"` + Status *Status `pg:"rel:has-one"` // ActivityPub URI of this fave URI string `pg:",notnull"` - - // GTSStatus is the status being interacted with. It won't be put or retrieved from the db, it's just for conveniently passing a pointer around. - GTSStatus *Status `pg:"-"` - // GTSTargetAccount is the account being interacted with. It won't be put or retrieved from the db, it's just for conveniently passing a pointer around. - GTSTargetAccount *Account `pg:"-"` - // GTSFavingAccount is the account doing the faving. It won't be put or retrieved from the db, it's just for conveniently passing a pointer around. - GTSFavingAccount *Account `pg:"-"` } diff --git a/internal/media/handler.go b/internal/media/handler.go index 0bcf46488..5879d8946 100644 --- a/internal/media/handler.go +++ b/internal/media/handler.go @@ -142,7 +142,7 @@ func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID strin } // set it in the database - if err := mh.db.SetHeaderOrAvatarForAccountID(ma, accountID); err != nil { + if err := mh.db.SetAccountHeaderOrAvatar(ma, accountID); err != nil { return nil, fmt.Errorf("error putting %s in database: %s", mediaType, err) } diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go index 65ac02291..4cadeaac6 100644 --- a/internal/processing/account/delete.go +++ b/internal/processing/account/delete.go @@ -133,7 +133,7 @@ func (p *processor) Delete(account *gtsmodel.Account, origin string) error { var maxID string selectStatusesLoop: for { - statuses, err := p.db.GetStatusesForAccount(account.ID, 20, false, maxID, false, false) + statuses, err := p.db.GetAccountStatuses(account.ID, 20, false, maxID, false, false) if err != nil { if _, ok := err.(db.ErrNoEntries); ok { // no statuses left for this instance so we're done @@ -147,7 +147,7 @@ selectStatusesLoop: for i, s := range statuses { // pass the status delete through the client api channel for processing - s.GTSAuthorAccount = account + s.Account = account l.Debug("putting status in the client api channel") p.fromClientAPI <- gtsmodel.FromClientAPI{ APObjectType: gtsmodel.ActivityStreamsNote, diff --git a/internal/processing/account/getfollowers.go b/internal/processing/account/getfollowers.go index 0806a82c0..66cd38f21 100644 --- a/internal/processing/account/getfollowers.go +++ b/internal/processing/account/getfollowers.go @@ -39,7 +39,7 @@ func (p *processor) FollowersGet(requestingAccount *gtsmodel.Account, targetAcco followers := []gtsmodel.Follow{} accounts := []apimodel.Account{} - if err := p.db.GetFollowersByAccountID(targetAccountID, &followers, false); err != nil { + if err := p.db.GetAccountFollowers(targetAccountID, &followers, false); err != nil { if _, ok := err.(db.ErrNoEntries); ok { return accounts, nil } diff --git a/internal/processing/account/getfollowing.go b/internal/processing/account/getfollowing.go index 75e89dacb..57461cf23 100644 --- a/internal/processing/account/getfollowing.go +++ b/internal/processing/account/getfollowing.go @@ -39,7 +39,7 @@ func (p *processor) FollowingGet(requestingAccount *gtsmodel.Account, targetAcco following := []gtsmodel.Follow{} accounts := []apimodel.Account{} - if err := p.db.GetFollowingByAccountID(targetAccountID, &following); err != nil { + if err := p.db.GetAccountFollowing(targetAccountID, &following); err != nil { if _, ok := err.(db.ErrNoEntries); ok { return accounts, nil } diff --git a/internal/processing/account/getstatuses.go b/internal/processing/account/getstatuses.go index b8ccbc528..27cabcc13 100644 --- a/internal/processing/account/getstatuses.go +++ b/internal/processing/account/getstatuses.go @@ -37,7 +37,7 @@ func (p *processor) StatusesGet(requestingAccount *gtsmodel.Account, targetAccou } apiStatuses := []apimodel.Status{} - statuses, err := p.db.GetStatusesForAccount(targetAccountID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly) + statuses, err := p.db.GetAccountStatuses(targetAccountID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly) if err != nil { if _, ok := err.(db.ErrNoEntries); ok { return apiStatuses, nil diff --git a/internal/processing/blocks.go b/internal/processing/blocks.go index 509600ca6..dbc2f77c7 100644 --- a/internal/processing/blocks.go +++ b/internal/processing/blocks.go @@ -29,7 +29,7 @@ import ( ) func (p *processor) BlocksGet(authed *oauth.Auth, maxID string, sinceID string, limit int) (*apimodel.BlocksResponse, gtserror.WithCode) { - accounts, nextMaxID, prevMinID, err := p.db.GetBlocksForAccount(authed.Account.ID, maxID, sinceID, limit) + accounts, nextMaxID, prevMinID, err := p.db.GetAccountBlocks(authed.Account.ID, maxID, sinceID, limit) if err != nil { if _, ok := err.(db.ErrNoEntries); ok { // there are just no entries diff --git a/internal/processing/followrequest.go b/internal/processing/followrequest.go index 5eb9fd6ad..553a953ff 100644 --- a/internal/processing/followrequest.go +++ b/internal/processing/followrequest.go @@ -28,7 +28,7 @@ import ( func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) { frs := []gtsmodel.FollowRequest{} - if err := p.db.GetFollowRequestsForAccountID(auth.Account.ID, &frs); err != nil { + if err := p.db.GetAccountFollowRequests(auth.Account.ID, &frs); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, gtserror.NewErrorInternalError(err) } diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index 6755a9d82..67db7b96a 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -187,8 +187,8 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error return errors.New("note was not parseable as *gtsmodel.Status") } - if statusToDelete.GTSAuthorAccount == nil { - statusToDelete.GTSAuthorAccount = clientMsg.OriginAccount + if statusToDelete.Account == nil { + statusToDelete.Account = clientMsg.OriginAccount } // delete all attachments for this status @@ -237,16 +237,16 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error // TODO: move all the below functions into federation.Federator func (p *processor) federateStatus(status *gtsmodel.Status) error { - if status.GTSAuthorAccount == nil { + if status.Account == nil { a := >smodel.Account{} if err := p.db.GetByID(status.AccountID, a); err != nil { return fmt.Errorf("federateStatus: error fetching status author account: %s", err) } - status.GTSAuthorAccount = a + status.Account = a } // do nothing if this isn't our status - if status.GTSAuthorAccount.Domain != "" { + if status.Account.Domain != "" { return nil } @@ -255,9 +255,9 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error { return fmt.Errorf("federateStatus: error converting status to as format: %s", err) } - outboxIRI, err := url.Parse(status.GTSAuthorAccount.OutboxURI) + outboxIRI, err := url.Parse(status.Account.OutboxURI) if err != nil { - return fmt.Errorf("federateStatus: error parsing outboxURI %s: %s", status.GTSAuthorAccount.OutboxURI, err) + return fmt.Errorf("federateStatus: error parsing outboxURI %s: %s", status.Account.OutboxURI, err) } _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, asStatus) @@ -265,16 +265,16 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error { } func (p *processor) federateStatusDelete(status *gtsmodel.Status) error { - if status.GTSAuthorAccount == nil { + if status.Account == nil { a := >smodel.Account{} if err := p.db.GetByID(status.AccountID, a); err != nil { return fmt.Errorf("federateStatus: error fetching status author account: %s", err) } - status.GTSAuthorAccount = a + status.Account = a } // do nothing if this isn't our status - if status.GTSAuthorAccount.Domain != "" { + if status.Account.Domain != "" { return nil } @@ -283,14 +283,14 @@ func (p *processor) federateStatusDelete(status *gtsmodel.Status) error { return fmt.Errorf("federateStatusDelete: error converting status to as format: %s", err) } - outboxIRI, err := url.Parse(status.GTSAuthorAccount.OutboxURI) + outboxIRI, err := url.Parse(status.Account.OutboxURI) if err != nil { - return fmt.Errorf("federateStatusDelete: error parsing outboxURI %s: %s", status.GTSAuthorAccount.OutboxURI, err) + return fmt.Errorf("federateStatusDelete: error parsing outboxURI %s: %s", status.Account.OutboxURI, err) } - actorIRI, err := url.Parse(status.GTSAuthorAccount.URI) + actorIRI, err := url.Parse(status.Account.URI) if err != nil { - return fmt.Errorf("federateStatusDelete: error parsing actorIRI %s: %s", status.GTSAuthorAccount.URI, err) + return fmt.Errorf("federateStatusDelete: error parsing actorIRI %s: %s", status.Account.URI, err) } // create a delete and set the appropriate actor on it diff --git a/internal/processing/fromcommon.go b/internal/processing/fromcommon.go index d719b7f5f..8e52db89f 100644 --- a/internal/processing/fromcommon.go +++ b/internal/processing/fromcommon.go @@ -297,22 +297,22 @@ func (p *processor) notifyAnnounce(status *gtsmodel.Status) error { func (p *processor) timelineStatus(status *gtsmodel.Status) error { // make sure the author account is pinned onto the status - if status.GTSAuthorAccount == nil { + if status.Account == nil { a := >smodel.Account{} if err := p.db.GetByID(status.AccountID, a); err != nil { return fmt.Errorf("timelineStatus: error getting author account with id %s: %s", status.AccountID, err) } - status.GTSAuthorAccount = a + status.Account = a } // get local followers of the account that posted the status followers := []gtsmodel.Follow{} - if err := p.db.GetFollowersByAccountID(status.AccountID, &followers, true); err != nil { + if err := p.db.GetAccountFollowers(status.AccountID, &followers, true); err != nil { return fmt.Errorf("timelineStatus: error getting followers for account id %s: %s", status.AccountID, err) } // if the poster is local, add a fake entry for them to the followers list so they can see their own status in their timeline - if status.GTSAuthorAccount.Domain == "" { + if status.Account.Domain == "" { followers = append(followers, gtsmodel.Follow{ AccountID: status.AccountID, }) diff --git a/internal/processing/status/boost.go b/internal/processing/status/boost.go index 93d0f19de..c21caf087 100644 --- a/internal/processing/status/boost.go +++ b/internal/processing/status/boost.go @@ -47,7 +47,7 @@ func (p *processor) Boost(account *gtsmodel.Account, application *gtsmodel.Appli } boostWrapperStatus.CreatedWithApplicationID = application.ID - boostWrapperStatus.GTSBoostedAccount = targetAccount + boostWrapperStatus.BoostOfAccount = targetAccount // put the boost in the database if err := p.db.Put(boostWrapperStatus); err != nil { diff --git a/internal/processing/status/fave.go b/internal/processing/status/fave.go index 0dfee6233..de789271a 100644 --- a/internal/processing/status/fave.go +++ b/internal/processing/status/fave.go @@ -72,9 +72,9 @@ func (p *processor) Fave(account *gtsmodel.Account, targetStatusID string) (*api TargetAccountID: targetAccount.ID, StatusID: targetStatus.ID, URI: util.GenerateURIForLike(account.Username, p.config.Protocol, p.config.Host, thisFaveID), - GTSStatus: targetStatus, - GTSTargetAccount: targetAccount, - GTSFavingAccount: account, + Status: targetStatus, + TargetAccount: targetAccount, + Account: account, } if err := p.db.Put(gtsFave); err != nil { diff --git a/internal/processing/status/unboost.go b/internal/processing/status/unboost.go index 2a1394695..3266c9de3 100644 --- a/internal/processing/status/unboost.go +++ b/internal/processing/status/unboost.go @@ -71,10 +71,10 @@ func (p *processor) Unboost(account *gtsmodel.Account, application *gtsmodel.App } // pin some stuff onto the boost while we have it out of the db - gtsBoost.GTSBoostedStatus = targetStatus - gtsBoost.GTSBoostedStatus.GTSAuthorAccount = targetAccount - gtsBoost.GTSBoostedAccount = targetAccount - gtsBoost.GTSAuthorAccount = account + gtsBoost.BoostOf = targetStatus + gtsBoost.BoostOf.Account = targetAccount + gtsBoost.BoostOfAccount = targetAccount + gtsBoost.Account = account // send it back to the processor for async processing p.fromClientAPI <- gtsmodel.FromClientAPI{ diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index f754d282a..47f8c24fc 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -231,7 +231,7 @@ func (c *converter) ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status } status.AccountID = statusOwner.ID status.AccountURI = statusOwner.URI - status.GTSAuthorAccount = statusOwner + status.Account = statusOwner // check if there's a post that this is a reply to inReplyToURI := ap.ExtractInReplyToURI(statusable) @@ -247,12 +247,12 @@ func (c *converter) ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status // so we can set these fields here and then... status.InReplyToID = inReplyToStatus.ID status.InReplyToAccountID = inReplyToStatus.AccountID - status.GTSReplyToStatus = inReplyToStatus + status.InReplyTo = inReplyToStatus // ... check if we've seen the account already inReplyToAccount := >smodel.Account{} if err := c.db.GetByID(inReplyToStatus.AccountID, inReplyToAccount); err == nil { - status.GTSReplyToAccount = inReplyToAccount + status.InReplyToAccount = inReplyToAccount } } } @@ -421,9 +421,9 @@ func (c *converter) ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, er StatusID: targetStatus.ID, AccountID: originAccount.ID, URI: uri, - GTSStatus: targetStatus, - GTSTargetAccount: targetAccount, - GTSFavingAccount: originAccount, + Status: targetStatus, + TargetAccount: targetAccount, + Account: originAccount, }, nil } @@ -487,7 +487,7 @@ func (c *converter) ASAnnounceToStatus(announceable ap.Announceable) (*gtsmodel. } // set the URI on the new status for dereferencing later - status.GTSBoostedStatus = >smodel.Status{ + status.BoostOf = >smodel.Status{ URI: boostedStatusURI.String(), } diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go index a46ad7fbd..ff9d60b8c 100644 --- a/internal/typeutils/internal.go +++ b/internal/typeutils/internal.go @@ -74,7 +74,7 @@ func (c *converter) StatusToBoost(s *gtsmodel.Status, boostingAccount *gtsmodel. // attach these here for convenience -- the boosted status/account won't go in the DB // but they're needed in the processor and for the frontend. Since we have them, we can // attach them so we don't need to fetch them again later (save some DB calls) - GTSBoostedStatus: s, + BoostOf: s, } return boostWrapperStatus, nil diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 333f131d4..6126adedd 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -330,12 +330,12 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e // check if author account is already attached to status and attach it if not // if we can't retrieve this, bail here already because we can't attribute the status to anyone - if s.GTSAuthorAccount == nil { + if s.Account == nil { a := >smodel.Account{} if err := c.db.GetByID(s.AccountID, a); err != nil { return nil, fmt.Errorf("StatusToAS: error retrieving author account from db: %s", err) } - s.GTSAuthorAccount = a + s.Account = a } // create the Note! @@ -361,16 +361,16 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e // inReplyTo if s.InReplyToID != "" { // fetch the replied status if we don't have it on hand already - if s.GTSReplyToStatus == nil { + if s.InReplyTo == nil { rs := >smodel.Status{} if err := c.db.GetByID(s.InReplyToID, rs); err != nil { return nil, fmt.Errorf("StatusToAS: error retrieving replied-to status from db: %s", err) } - s.GTSReplyToStatus = rs + s.InReplyTo = rs } - rURI, err := url.Parse(s.GTSReplyToStatus.URI) + rURI, err := url.Parse(s.InReplyTo.URI) if err != nil { - return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSReplyToStatus.URI, err) + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.InReplyTo.URI, err) } inReplyToProp := streams.NewActivityStreamsInReplyToProperty() @@ -396,9 +396,9 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e } // attributedTo - authorAccountURI, err := url.Parse(s.GTSAuthorAccount.URI) + authorAccountURI, err := url.Parse(s.Account.URI) if err != nil { - return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAuthorAccount.URI, err) + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.Account.URI, err) } attributedToProp := streams.NewActivityStreamsAttributedToProperty() attributedToProp.AppendIRI(authorAccountURI) @@ -425,9 +425,9 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e status.SetActivityStreamsTag(tagProp) // parse out some URIs we need here - authorFollowersURI, err := url.Parse(s.GTSAuthorAccount.FollowersURI) + authorFollowersURI, err := url.Parse(s.Account.FollowersURI) if err != nil { - return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.GTSAuthorAccount.FollowersURI, err) + return nil, fmt.Errorf("StatusToAS: error parsing url %s: %s", s.Account.FollowersURI, err) } publicURI, err := url.Parse(asPublicURI) @@ -648,30 +648,30 @@ func (c *converter) AttachmentToAS(a *gtsmodel.MediaAttachment) (vocab.ActivityS */ func (c *converter) FaveToAS(f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) { // check if targetStatus is already pinned to this fave, and fetch it if not - if f.GTSStatus == nil { + if f.Status == nil { s := >smodel.Status{} if err := c.db.GetByID(f.StatusID, s); err != nil { return nil, fmt.Errorf("FaveToAS: error fetching target status from database: %s", err) } - f.GTSStatus = s + f.Status = s } // check if the targetAccount is already pinned to this fave, and fetch it if not - if f.GTSTargetAccount == nil { + if f.TargetAccount == nil { a := >smodel.Account{} if err := c.db.GetByID(f.TargetAccountID, a); err != nil { return nil, fmt.Errorf("FaveToAS: error fetching target account from database: %s", err) } - f.GTSTargetAccount = a + f.TargetAccount = a } // check if the faving account is already pinned to this fave, and fetch it if not - if f.GTSFavingAccount == nil { + if f.Account == nil { a := >smodel.Account{} if err := c.db.GetByID(f.AccountID, a); err != nil { return nil, fmt.Errorf("FaveToAS: error fetching faving account from database: %s", err) } - f.GTSFavingAccount = a + f.Account = a } // create the like @@ -679,9 +679,9 @@ func (c *converter) FaveToAS(f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, // set the actor property to the fave-ing account's URI actorProp := streams.NewActivityStreamsActorProperty() - actorIRI, err := url.Parse(f.GTSFavingAccount.URI) + actorIRI, err := url.Parse(f.Account.URI) if err != nil { - return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.GTSFavingAccount.URI, err) + return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Account.URI, err) } actorProp.AppendIRI(actorIRI) like.SetActivityStreamsActor(actorProp) @@ -697,18 +697,18 @@ func (c *converter) FaveToAS(f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, // set the object property to the target status's URI objectProp := streams.NewActivityStreamsObjectProperty() - statusIRI, err := url.Parse(f.GTSStatus.URI) + statusIRI, err := url.Parse(f.Status.URI) if err != nil { - return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.GTSStatus.URI, err) + return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Status.URI, err) } objectProp.AppendIRI(statusIRI) like.SetActivityStreamsObject(objectProp) // set the TO property to the target account's IRI toProp := streams.NewActivityStreamsToProperty() - toIRI, err := url.Parse(f.GTSTargetAccount.URI) + toIRI, err := url.Parse(f.TargetAccount.URI) if err != nil { - return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.GTSTargetAccount.URI, err) + return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.TargetAccount.URI, err) } toProp.AppendIRI(toIRI) like.SetActivityStreamsTo(toProp) @@ -718,12 +718,12 @@ func (c *converter) FaveToAS(f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, func (c *converter) BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) { // the boosted status is probably pinned to the boostWrapperStatus but double check to make sure - if boostWrapperStatus.GTSBoostedStatus == nil { + if boostWrapperStatus.BoostOf == nil { b := >smodel.Status{} if err := c.db.GetByID(boostWrapperStatus.BoostOfID, b); err != nil { return nil, fmt.Errorf("BoostToAS: error getting status with ID %s from the db: %s", boostWrapperStatus.BoostOfID, err) } - boostWrapperStatus.GTSBoostedStatus = b + boostWrapperStatus.BoostOf = b } // create the announce @@ -748,9 +748,9 @@ func (c *converter) BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccou announce.SetJSONLDId(idProp) // set the object - boostedStatusURI, err := url.Parse(boostWrapperStatus.GTSBoostedStatus.URI) + boostedStatusURI, err := url.Parse(boostWrapperStatus.BoostOf.URI) if err != nil { - return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.GTSBoostedStatus.URI, err) + return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.BoostOf.URI, err) } objectProp := streams.NewActivityStreamsObjectProperty() objectProp.AppendIRI(boostedStatusURI) diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 1283e718a..18270d90e 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -39,7 +39,7 @@ func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*model.Account // check pending follow requests aimed at this account fr := []gtsmodel.FollowRequest{} - if err := c.db.GetFollowRequestsForAccountID(a.ID, &fr); err != nil { + if err := c.db.GetAccountFollowRequests(a.ID, &fr); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting follow requests: %s", err) } @@ -64,7 +64,7 @@ func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*model.Account func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, error) { // count followers followers := []gtsmodel.Follow{} - if err := c.db.GetFollowersByAccountID(a.ID, &followers, false); err != nil { + if err := c.db.GetAccountFollowers(a.ID, &followers, false); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting followers: %s", err) } @@ -76,7 +76,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e // count following following := []gtsmodel.Follow{} - if err := c.db.GetFollowingByAccountID(a.ID, &following); err != nil { + if err := c.db.GetAccountFollowing(a.ID, &following); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting following: %s", err) } @@ -87,7 +87,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e } // count statuses - statusesCount, err := c.db.CountStatusesByAccountID(a.ID) + statusesCount, err := c.db.GetAccountStatusesCount(a.ID) if err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting last statuses: %s", err) @@ -96,7 +96,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e // check when the last status was lastStatus := >smodel.Status{} - if err := c.db.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { + if err := c.db.GetAccountLastStatus(a.ID, lastStatus); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting last status: %s", err) } @@ -108,7 +108,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e // build the avatar and header URLs avi := >smodel.MediaAttachment{} - if err := c.db.GetAvatarForAccountID(avi, a.ID); err != nil { + if err := c.db.GetAccountAvatar(avi, a.ID); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting avatar: %s", err) } @@ -117,7 +117,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e aviURLStatic := avi.Thumbnail.URL header := >smodel.MediaAttachment{} - if err := c.db.GetHeaderForAccountID(header, a.ID); err != nil { + if err := c.db.GetAccountHeader(header, a.ID); err != nil { if _, ok := err.(db.ErrNoEntries); !ok { return nil, fmt.Errorf("error getting header: %s", err) } @@ -320,27 +320,27 @@ func (c *converter) StatusToMasto(s *gtsmodel.Status, requestingAccount *gtsmode var mastoRebloggedStatus *model.Status if s.BoostOfID != "" { // the boosted status might have been set on this struct already so check first before doing db calls - if s.GTSBoostedStatus == nil { + if s.BoostOf == nil { // it's not set so fetch it from the db bs := >smodel.Status{} if err := c.db.GetByID(s.BoostOfID, bs); err != nil { return nil, fmt.Errorf("error getting boosted status with id %s: %s", s.BoostOfID, err) } - s.GTSBoostedStatus = bs + s.BoostOf = bs } // the boosted account might have been set on this struct already or passed as a param so check first before doing db calls - if s.GTSBoostedAccount == nil { + if s.BoostOfAccount == nil { // it's not set so fetch it from the db ba := >smodel.Account{} - if err := c.db.GetByID(s.GTSBoostedStatus.AccountID, ba); err != nil { - return nil, fmt.Errorf("error getting boosted account %s from status with id %s: %s", s.GTSBoostedStatus.AccountID, s.BoostOfID, err) + if err := c.db.GetByID(s.BoostOf.AccountID, ba); err != nil { + return nil, fmt.Errorf("error getting boosted account %s from status with id %s: %s", s.BoostOf.AccountID, s.BoostOfID, err) } - s.GTSBoostedAccount = ba - s.GTSBoostedStatus.GTSAuthorAccount = ba + s.BoostOfAccount = ba + s.BoostOf.Account = ba } - mastoRebloggedStatus, err = c.StatusToMasto(s.GTSBoostedStatus, requestingAccount) + mastoRebloggedStatus, err = c.StatusToMasto(s.BoostOf, requestingAccount) if err != nil { return nil, fmt.Errorf("error converting boosted status to mastotype: %s", err) } @@ -358,15 +358,15 @@ func (c *converter) StatusToMasto(s *gtsmodel.Status, requestingAccount *gtsmode } } - if s.GTSAuthorAccount == nil { + if s.Account == nil { a := >smodel.Account{} if err := c.db.GetByID(s.AccountID, a); err != nil { return nil, fmt.Errorf("error getting status author: %s", err) } - s.GTSAuthorAccount = a + s.Account = a } - mastoAuthorAccount, err := c.AccountToMastoPublic(s.GTSAuthorAccount) + mastoAuthorAccount, err := c.AccountToMastoPublic(s.Account) if err != nil { return nil, fmt.Errorf("error parsing account of status author: %s", err) } @@ -589,7 +589,7 @@ func (c *converter) InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, erro if err := c.db.GetWhere([]db.Where{{Key: "username", Value: i.Domain}}, ia); err == nil { // instance account exists, get the header for the account if it exists attachment := >smodel.MediaAttachment{} - if err := c.db.GetHeaderForAccountID(attachment, ia.ID); err == nil { + if err := c.db.GetAccountHeader(attachment, ia.ID); err == nil { // header exists, set it on the api model mi.Thumbnail = attachment.URL } @@ -659,11 +659,11 @@ func (c *converter) NotificationToMasto(n *gtsmodel.Notification) (*model.Notifi n.GTSStatus = status } - if n.GTSStatus.GTSAuthorAccount == nil { + if n.GTSStatus.Account == nil { if n.GTSStatus.AccountID == n.GTSTargetAccount.ID { - n.GTSStatus.GTSAuthorAccount = n.GTSTargetAccount + n.GTSStatus.Account = n.GTSTargetAccount } else if n.GTSStatus.AccountID == n.GTSOriginAccount.ID { - n.GTSStatus.GTSAuthorAccount = n.GTSOriginAccount + n.GTSStatus.Account = n.GTSOriginAccount } } diff --git a/internal/visibility/statushometimelineable.go b/internal/visibility/statushometimelineable.go index bc5f7bcb8..ecb1d6857 100644 --- a/internal/visibility/statushometimelineable.go +++ b/internal/visibility/statushometimelineable.go @@ -37,21 +37,21 @@ func (f *filter) StatusHometimelineable(targetStatus *gtsmodel.Status, timelineO // if a status replies to an ID we know in the database, we need to make sure we also follow the replied-to status owner account if targetStatus.InReplyToID != "" { // pin the reply to status on to this status if it hasn't been done already - if targetStatus.GTSReplyToStatus == nil { + if targetStatus.InReplyTo == nil { rs := >smodel.Status{} if err := f.db.GetByID(targetStatus.InReplyToID, rs); err != nil { return false, fmt.Errorf("StatusHometimelineable: error getting replied to status with id %s: %s", targetStatus.InReplyToID, err) } - targetStatus.GTSReplyToStatus = rs + targetStatus.InReplyTo = rs } // pin the reply to account on to this status if it hasn't been done already - if targetStatus.GTSReplyToAccount == nil { + if targetStatus.InReplyToAccount == nil { ra := >smodel.Account{} if err := f.db.GetByID(targetStatus.InReplyToAccountID, ra); err != nil { return false, fmt.Errorf("StatusHometimelineable: error getting replied to account with id %s: %s", targetStatus.InReplyToAccountID, err) } - targetStatus.GTSReplyToAccount = ra + targetStatus.InReplyToAccount = ra } // if it's a reply to the timelineOwnerAccount, we don't need to check if the timelineOwnerAccount follows itself, just return true, they can see it @@ -60,7 +60,7 @@ func (f *filter) StatusHometimelineable(targetStatus *gtsmodel.Status, timelineO } // the replied-to account != timelineOwnerAccount, so make sure the timelineOwnerAccount follows the replied-to account - follows, err := f.db.Follows(timelineOwnerAccount, targetStatus.GTSReplyToAccount) + follows, err := f.db.Follows(timelineOwnerAccount, targetStatus.InReplyToAccount) if err != nil { return false, fmt.Errorf("StatusHometimelineable: error checking follow from account %s to account %s: %s", timelineOwnerAccount.ID, targetStatus.InReplyToAccountID, err) } diff --git a/internal/visibility/util.go b/internal/visibility/util.go index a12dd555f..e7d5b4378 100644 --- a/internal/visibility/util.go +++ b/internal/visibility/util.go @@ -14,14 +14,14 @@ func (f *filter) pullRelevantAccountsFromStatus(targetStatus *gtsmodel.Status) ( } // get the author account - if targetStatus.GTSAuthorAccount == nil { + if targetStatus.Account == nil { statusAuthor := >smodel.Account{} if err := f.db.GetByID(targetStatus.AccountID, statusAuthor); err != nil { return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting statusAuthor with id %s: %s", targetStatus.AccountID, err) } - targetStatus.GTSAuthorAccount = statusAuthor + targetStatus.Account = statusAuthor } - accounts.StatusAuthor = targetStatus.GTSAuthorAccount + accounts.StatusAuthor = targetStatus.Account // get the replied to account from the status and add it to the pile if targetStatus.InReplyToAccountID != "" {