| 
									
										
										
										
											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-03-26 19:02:20 +01:00
										 |  |  | 	"net" | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/go-fed/activity/pub" | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	"github.com/gotosocial/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2021-03-23 22:13:01 +01:00
										 |  |  | 	"github.com/gotosocial/gotosocial/internal/db/model" | 
					
						
							|  |  |  | 	"github.com/gotosocial/gotosocial/pkg/mastotypes" | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 	"github.com/sirupsen/logrus" | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const dbTypePostgres string = "POSTGRES" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-25 20:08:23 +01:00
										 |  |  | // ErrNoEntries is to be returned from the DB interface when no entries are found for a given query. | 
					
						
							| 
									
										
										
										
											2021-03-23 22:13:01 +01:00
										 |  |  | 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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01: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-03-23 22:13:01 +01:00
										 |  |  | 	// 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-03-23 22:13:01 +01: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-03-23 13:17:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-23 22:13:01 +01: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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-25 23:33:26 +01:00
										 |  |  | 	// 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-25 23:49:29 +01:00
										 |  |  | 	// 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 | 
					
						
							| 
									
										
										
										
											2021-03-25 23:33:26 +01:00
										 |  |  | 	IsEmailAvailable(email string) error | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-26 19:02:20 +01:00
										 |  |  | 	// 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) (*model.User, error) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-23 22:13:01 +01:00
										 |  |  | 	/* | 
					
						
							|  |  |  | 		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) | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 	} | 
					
						
							|  |  |  | } |