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 != "" {