Api/v1/statuses (#11)
This PR adds:
Statuses
New status creation.
View existing status
Delete a status
Fave a status
Unfave a status
See who's faved a status
Media
Upload media attachment and store/retrieve it
Upload custom emoji and store/retrieve it
Fileserver
Serve files from storage
Testing
Test models, testrig -- run a GTS test instance and play around with it.
This commit is contained in:
@ -27,8 +27,7 @@ import (
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/model"
|
||||
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
|
||||
)
|
||||
|
||||
const dbTypePostgres string = "POSTGRES"
|
||||
@ -79,6 +78,11 @@ type DB interface {
|
||||
// In case of no entries, a 'no entries' error will be returned
|
||||
GetWhere(key string, value interface{}, i interface{}) error
|
||||
|
||||
// // GetWhereMany gets one entry where key = value for *ALL* parameters passed as "where".
|
||||
// // That is, if you pass 2 'where' entries, with 1 being Key username and Value test, and the second
|
||||
// // being Key domain and Value example.org, only entries will be returned where BOTH conditions are true.
|
||||
// GetWhereMany(i interface{}, where ...model.Where) 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
|
||||
@ -88,6 +92,11 @@ type DB interface {
|
||||
// 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
|
||||
@ -107,41 +116,46 @@ type DB interface {
|
||||
HANDY SHORTCUTS
|
||||
*/
|
||||
|
||||
// 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
|
||||
|
||||
// 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 *model.Account) error
|
||||
GetAccountByUserID(userID 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 *[]model.FollowRequest) error
|
||||
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 *[]model.Follow) error
|
||||
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
|
||||
GetFollowersByAccountID(accountID string, followers *[]model.Follow) error
|
||||
GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error
|
||||
|
||||
// GetStatusesByAccountID is a shortcut for the common action of fetching a list of statuses produced by accountID.
|
||||
// The given slice 'statuses' will be set to the result of the query, whatever it is.
|
||||
// In case of no entries, a 'no entries' error will be returned
|
||||
GetStatusesByAccountID(accountID string, statuses *[]model.Status) error
|
||||
GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error
|
||||
|
||||
// GetStatusesByTimeDescending 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
|
||||
GetStatusesByTimeDescending(accountID string, statuses *[]model.Status, limit int) error
|
||||
GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) 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 *model.Status) error
|
||||
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.
|
||||
@ -156,32 +170,112 @@ type DB interface {
|
||||
|
||||
// NewSignup creates a new user in the database with the given parameters, with an *unconfirmed* email address.
|
||||
// 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) (*model.User, error)
|
||||
NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*gtsmodel.User, error)
|
||||
|
||||
// SetHeaderOrAvatarForAccountID sets the header or avatar for the given accountID to the given media attachment.
|
||||
SetHeaderOrAvatarForAccountID(mediaAttachment *model.MediaAttachment, accountID string) error
|
||||
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 *model.MediaAttachment, accountID string) error
|
||||
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 *model.MediaAttachment, accountID string) error
|
||||
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)
|
||||
|
||||
// StatusVisible returns true if targetStatus is visible to requestingAccount, based on the
|
||||
// privacy settings of the status, and any blocks/mutes that might exist between the two accounts
|
||||
// or account domains.
|
||||
//
|
||||
// StatusVisible will also check through the given slice of 'otherRelevantAccounts', which should include:
|
||||
//
|
||||
// 1. Accounts mentioned in the targetStatus
|
||||
//
|
||||
// 2. Accounts replied to by the target status
|
||||
//
|
||||
// 3. Accounts boosted by the target status
|
||||
//
|
||||
// Will return an error if something goes wrong while pulling stuff out of the database.
|
||||
StatusVisible(targetStatus *gtsmodel.Status, targetAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// PullRelevantAccountsFromStatus returns all accounts mentioned in a status, replied to by a status, or boosted by a status
|
||||
PullRelevantAccountsFromStatus(status *gtsmodel.Status) (*gtsmodel.RelevantAccounts, 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// StatusPinnedBy checks if a given status has been pinned by a given account ID
|
||||
StatusPinnedBy(status *gtsmodel.Status, accountID string) (bool, error)
|
||||
|
||||
// FaveStatus faves the given status, using accountID as the faver.
|
||||
// The returned fave will be nil if the status was already faved.
|
||||
FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error)
|
||||
|
||||
// UnfaveStatus unfaves the given status, using accountID as the unfaver (sure, that's a word).
|
||||
// The returned fave will be nil if the status was already not faved.
|
||||
UnfaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, 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)
|
||||
|
||||
/*
|
||||
USEFUL CONVERSION FUNCTIONS
|
||||
*/
|
||||
|
||||
// AccountToMastoSensitive takes a db model account as a param, and returns a populated mastotype account, or an error
|
||||
// if something goes wrong. The returned account should be ready to serialize on an API level, and may have sensitive fields,
|
||||
// so serve it only to an authorized user who should have permission to see it.
|
||||
AccountToMastoSensitive(account *model.Account) (*mastotypes.Account, error)
|
||||
// MentionStringsToMentions takes a slice of deduplicated, lowercase account names in the form "@test@whatever.example.org" for a remote account,
|
||||
// or @test for a local account, which have been mentioned in a status.
|
||||
// It takes the id of the account that wrote the status, and the id of the status itself, and then
|
||||
// checks in the database for the mentioned accounts, and returns a slice of mentions generated based on the given parameters.
|
||||
//
|
||||
// Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking
|
||||
// if they exist in the db and conveniently returning them if they do.
|
||||
MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error)
|
||||
|
||||
// AccountToMastoPublic takes a db model account as a param, and returns a populated mastotype account, or an error
|
||||
// if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields.
|
||||
// In other words, this is the public record that the server has of an account.
|
||||
AccountToMastoPublic(account *model.Account) (*mastotypes.Account, error)
|
||||
// TagStringsToTags takes a slice of deduplicated, lowercase tags in the form "somehashtag", which have been
|
||||
// used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
|
||||
// returns a slice of *model.Tag corresponding to the given tags. If the tag already exists in database, that tag
|
||||
// will be returned. Otherwise a pointer to a new tag struct will be created and returned.
|
||||
//
|
||||
// Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking
|
||||
// if they exist in the db already, and conveniently returning them, or creating new tag structs.
|
||||
TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error)
|
||||
|
||||
// EmojiStringsToEmojis takes a slice of deduplicated, lowercase emojis in the form ":emojiname:", which have been
|
||||
// used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
|
||||
// returns a slice of *model.Emoji corresponding to the given emojis.
|
||||
//
|
||||
// Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking
|
||||
// if they exist in the db and conveniently returning them if they do.
|
||||
EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error)
|
||||
}
|
||||
|
||||
// New returns a new database service that satisfies the DB interface and, by extension,
|
||||
|
||||
Reference in New Issue
Block a user