2021-03-02 18:26:30 +01:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package db
|
|
|
|
|
|
|
|
import (
|
2021-03-02 22:52:31 +01:00
|
|
|
"context"
|
2021-03-02 18:26:30 +01:00
|
|
|
"fmt"
|
2021-04-01 20:46:45 +02:00
|
|
|
"net"
|
2021-03-02 18:26:30 +01:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/go-fed/activity/pub"
|
2021-03-02 22:52:31 +01:00
|
|
|
"github.com/sirupsen/logrus"
|
2021-04-01 20:46:45 +02:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/db/model"
|
|
|
|
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
|
2021-03-02 18:26:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const dbTypePostgres string = "POSTGRES"
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// ErrNoEntries is to be returned from the DB interface when no entries are found for a given query.
|
|
|
|
type ErrNoEntries struct{}
|
|
|
|
|
|
|
|
func (e ErrNoEntries) Error() string {
|
|
|
|
return "no entries"
|
|
|
|
}
|
|
|
|
|
2021-03-22 22:26:54 +01:00
|
|
|
// DB provides methods for interacting with an underlying database or other storage mechanism (for now, just postgres).
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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.
|
2021-03-04 14:38:18 +01:00
|
|
|
type DB interface {
|
2021-03-22 22:26:54 +01:00
|
|
|
// Federation returns an interface that's compatible with go-fed, for performing federation storage/retrieval functions.
|
|
|
|
// See: https://pkg.go.dev/github.com/go-fed/activity@v1.0.0/pub?utm_source=gopls#Database
|
|
|
|
Federation() pub.Database
|
2021-03-02 18:26:30 +01:00
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
/*
|
|
|
|
BASIC DB FUNCTIONALITY
|
|
|
|
*/
|
|
|
|
|
|
|
|
// CreateTable creates a table for the given interface.
|
|
|
|
// For implementations that don't use tables, this can just return nil.
|
2021-03-22 22:26:54 +01:00
|
|
|
CreateTable(i interface{}) error
|
2021-03-05 18:31:12 +01:00
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// DropTable drops the table for the given interface.
|
|
|
|
// For implementations that don't use tables, this can just return nil.
|
2021-03-22 22:26:54 +01:00
|
|
|
DropTable(i interface{}) error
|
2021-03-05 18:31:12 +01:00
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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.
|
2021-03-22 22:26:54 +01:00
|
|
|
Stop(ctx context.Context) error
|
2021-03-05 18:31:12 +01:00
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// IsHealthy should return nil if the database connection is healthy, or an error if not.
|
2021-03-22 22:26:54 +01:00
|
|
|
IsHealthy(ctx context.Context) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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
|
2021-03-22 22:26:54 +01:00
|
|
|
GetByID(id string, i interface{}) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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
|
2021-03-22 22:26:54 +01:00
|
|
|
GetWhere(key string, value interface{}, i interface{}) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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
|
2021-03-22 22:26:54 +01:00
|
|
|
GetAll(i interface{}) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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.
|
2021-03-22 22:26:54 +01:00
|
|
|
Put(i interface{}) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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.
|
2021-03-22 22:26:54 +01:00
|
|
|
UpdateByID(id string, i interface{}) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// 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
|
|
|
|
|
|
|
|
// DeleteByID removes i with id id.
|
|
|
|
// If i didn't exist anyway, then no error should be returned.
|
2021-03-22 22:26:54 +01:00
|
|
|
DeleteByID(id string, i interface{}) error
|
|
|
|
|
2021-04-01 20:46:45 +02:00
|
|
|
// DeleteWhere deletes i where key = value
|
|
|
|
// If i didn't exist anyway, then no error should be returned.
|
2021-03-22 22:26:54 +01:00
|
|
|
DeleteWhere(key string, value interface{}, i interface{}) error
|
2021-04-01 20:46:45 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
HANDY SHORTCUTS
|
|
|
|
*/
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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, 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)
|
|
|
|
|
|
|
|
// SetHeaderOrAvatarForAccountID sets the header or avatar for the given accountID to the given media attachment.
|
|
|
|
SetHeaderOrAvatarForAccountID(mediaAttachment *model.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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
/*
|
|
|
|
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)
|
|
|
|
|
|
|
|
// 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)
|
2021-03-02 18:26:30 +01:00
|
|
|
}
|
|
|
|
|
2021-03-22 22:26:54 +01:00
|
|
|
// New returns a new database service that satisfies the DB interface and, by extension,
|
2021-03-02 18:26:30 +01:00
|
|
|
// the go-fed database interface described here: https://github.com/go-fed/activity/blob/master/pub/database.go
|
2021-03-04 14:38:18 +01:00
|
|
|
func New(ctx context.Context, c *config.Config, log *logrus.Logger) (DB, error) {
|
|
|
|
switch strings.ToUpper(c.DBConfig.Type) {
|
2021-03-02 18:26:30 +01:00
|
|
|
case dbTypePostgres:
|
2021-03-04 14:38:18 +01:00
|
|
|
return newPostgresService(ctx, c, log.WithField("service", "db"))
|
2021-03-02 18:26:30 +01:00
|
|
|
default:
|
2021-03-04 14:38:18 +01:00
|
|
|
return nil, fmt.Errorf("database type %s not supported", c.DBConfig.Type)
|
2021-03-02 18:26:30 +01:00
|
|
|
}
|
|
|
|
}
|