| 
									
										
										
										
											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 ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	"crypto/rand" | 
					
						
							|  |  |  | 	"crypto/rsa" | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/mail" | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	"regexp" | 
					
						
							| 
									
										
										
										
											2021-03-03 18:12:02 +01:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | 	"github.com/go-fed/activity/pub" | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	"github.com/go-pg/pg/extra/pgdebug" | 
					
						
							|  |  |  | 	"github.com/go-pg/pg/v10" | 
					
						
							|  |  |  | 	"github.com/go-pg/pg/v10/orm" | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	"github.com/google/uuid" | 
					
						
							| 
									
										
										
										
											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" | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | 
					
						
							|  |  |  | 	"golang.org/x/crypto/bcrypt" | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | // postgresService satisfies the DB interface | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | type postgresService struct { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	config       *config.Config | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | 	conn         *pg.DB | 
					
						
							|  |  |  | 	log          *logrus.Entry | 
					
						
							|  |  |  | 	cancel       context.CancelFunc | 
					
						
							|  |  |  | 	federationDB pub.Database | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface. | 
					
						
							|  |  |  | // Under the hood, it uses https://github.com/go-pg/pg to create and maintain a database connection. | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | func newPostgresService(ctx context.Context, c *config.Config, log *logrus.Entry) (DB, error) { | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	opts, err := derivePGOptions(c) | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("could not create postgres service: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	log.Debugf("using pg options: %+v", opts) | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// create a connection | 
					
						
							|  |  |  | 	pgCtx, cancel := context.WithCancel(ctx) | 
					
						
							|  |  |  | 	conn := pg.Connect(opts).WithContext(pgCtx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	// this will break the logfmt format we normally log in, | 
					
						
							|  |  |  | 	// since we can't choose where pg outputs to and it defaults to | 
					
						
							|  |  |  | 	// stdout. So use this option with care! | 
					
						
							|  |  |  | 	if log.Logger.GetLevel() >= logrus.TraceLevel { | 
					
						
							|  |  |  | 		conn.AddQueryHook(pgdebug.DebugHook{ | 
					
						
							|  |  |  | 			// Print all queries. | 
					
						
							|  |  |  | 			Verbose: true, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// actually *begin* the connection so that we can tell if the db is there and listening | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	if err := conn.Ping(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 		cancel() | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("db connection error: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	// print out discovered postgres version | 
					
						
							|  |  |  | 	var version string | 
					
						
							|  |  |  | 	if _, err = conn.QueryOneContext(ctx, pg.Scan(&version), "SELECT version()"); err != nil { | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 		cancel() | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("db connection error: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	log.Infof("connected to postgres version: %s", version) | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	ps := &postgresService{ | 
					
						
							|  |  |  | 		config: c, | 
					
						
							|  |  |  | 		conn:   conn, | 
					
						
							|  |  |  | 		log:    log, | 
					
						
							|  |  |  | 		cancel: cancel, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 11:19:18 +02:00
										 |  |  | 	federatingDB := newFederatingDB(ps, c, log) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	ps.federationDB = federatingDB | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// we can confidently return this useable postgres service now | 
					
						
							|  |  |  | 	return ps, nil | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | /* | 
					
						
							|  |  |  | 	HANDY STUFF | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // derivePGOptions takes an application config and returns either a ready-to-use *pg.Options | 
					
						
							|  |  |  | // with sensible defaults, or an error if it's not satisfied by the provided config. | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | func derivePGOptions(c *config.Config) (*pg.Options, error) { | 
					
						
							|  |  |  | 	if strings.ToUpper(c.DBConfig.Type) != dbTypePostgres { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("expected db type of %s but got %s", dbTypePostgres, c.DBConfig.Type) | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-04 12:07:24 +01:00
										 |  |  | 	// validate port | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	if c.DBConfig.Port == 0 { | 
					
						
							| 
									
										
										
										
											2021-03-04 12:07:24 +01:00
										 |  |  | 		return nil, errors.New("no port set") | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// validate address | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	if c.DBConfig.Address == "" { | 
					
						
							| 
									
										
										
										
											2021-03-04 12:07:24 +01:00
										 |  |  | 		return nil, errors.New("no address set") | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ipv4Regex := regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`) | 
					
						
							|  |  |  | 	hostnameRegex := regexp.MustCompile(`^(?:[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]{2,}$`) | 
					
						
							|  |  |  | 	if !hostnameRegex.MatchString(c.DBConfig.Address) && !ipv4Regex.MatchString(c.DBConfig.Address) && c.DBConfig.Address != "localhost" { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("address %s was neither an ipv4 address nor a valid hostname", c.DBConfig.Address) | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// validate username | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	if c.DBConfig.User == "" { | 
					
						
							| 
									
										
										
										
											2021-03-04 12:07:24 +01:00
										 |  |  | 		return nil, errors.New("no user set") | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// validate that there's a password | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	if c.DBConfig.Password == "" { | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 		return nil, errors.New("no password set") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// validate database | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 	if c.DBConfig.Database == "" { | 
					
						
							| 
									
										
										
										
											2021-03-04 12:07:24 +01:00
										 |  |  | 		return nil, errors.New("no database set") | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 	// We can rely on the pg library we're using to set | 
					
						
							|  |  |  | 	// sensible defaults for everything we don't set here. | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	options := &pg.Options{ | 
					
						
							| 
									
										
										
										
											2021-03-04 14:38:18 +01:00
										 |  |  | 		Addr:            fmt.Sprintf("%s:%d", c.DBConfig.Address, c.DBConfig.Port), | 
					
						
							|  |  |  | 		User:            c.DBConfig.User, | 
					
						
							|  |  |  | 		Password:        c.DBConfig.Password, | 
					
						
							|  |  |  | 		Database:        c.DBConfig.Database, | 
					
						
							|  |  |  | 		ApplicationName: c.ApplicationName, | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return options, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	FEDERATION FUNCTIONALITY | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | func (ps *postgresService) Federation() pub.Database { | 
					
						
							|  |  |  | 	return ps.federationDB | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | 	BASIC DB FUNCTIONALITY | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) CreateTable(i interface{}) error { | 
					
						
							|  |  |  | 	return ps.conn.Model(i).CreateTable(&orm.CreateTableOptions{ | 
					
						
							|  |  |  | 		IfNotExists: true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) DropTable(i interface{}) error { | 
					
						
							|  |  |  | 	return ps.conn.Model(i).DropTable(&orm.DropTableOptions{ | 
					
						
							|  |  |  | 		IfExists: true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | func (ps *postgresService) Stop(ctx context.Context) error { | 
					
						
							|  |  |  | 	ps.log.Info("closing db connection") | 
					
						
							|  |  |  | 	if err := ps.conn.Close(); err != nil { | 
					
						
							|  |  |  | 		// only cancel if there's a problem closing the db | 
					
						
							|  |  |  | 		ps.cancel() | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | func (ps *postgresService) IsHealthy(ctx context.Context) error { | 
					
						
							|  |  |  | 	return ps.conn.Ping(ctx) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | func (ps *postgresService) CreateSchema(ctx context.Context) error { | 
					
						
							|  |  |  | 	models := []interface{}{ | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		(*gtsmodel.Account)(nil), | 
					
						
							|  |  |  | 		(*gtsmodel.Status)(nil), | 
					
						
							|  |  |  | 		(*gtsmodel.User)(nil), | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	ps.log.Info("creating db schema") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, model := range models { | 
					
						
							|  |  |  | 		err := ps.conn.Model(model).CreateTable(&orm.CreateTableOptions{ | 
					
						
							|  |  |  | 			IfNotExists: true, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ps.log.Info("db schema created") | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | func (ps *postgresService) GetByID(id string, i interface{}) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(i).Where("id = ?", id).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) GetWhere(key string, value interface{}, i interface{}) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | // func (ps *postgresService) GetWhereMany(i interface{}, where ...model.Where) error { | 
					
						
							|  |  |  | // 	return nil | 
					
						
							|  |  |  | // } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | func (ps *postgresService) GetAll(i interface{}) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(i).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) Put(i interface{}) error { | 
					
						
							|  |  |  | 	_, err := ps.conn.Model(i).Insert(i) | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) Upsert(i interface{}, conflictColumn string) error { | 
					
						
							|  |  |  | 	if _, err := ps.conn.Model(i).OnConflict(fmt.Sprintf("(%s) DO UPDATE", conflictColumn)).Insert(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | func (ps *postgresService) UpdateByID(id string, i interface{}) error { | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	if _, err := ps.conn.Model(i).Where("id = ?", id).OnConflict("(id) DO UPDATE").Insert(); err != nil { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) UpdateOneByID(id string, key string, value interface{}, i interface{}) error { | 
					
						
							|  |  |  | 	_, err := ps.conn.Model(i).Set("? = ?", pg.Safe(key), value).Where("id = ?", id).Update() | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) DeleteByID(id string, i interface{}) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) DeleteWhere(key string, value interface{}, i interface{}) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if _, err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Delete(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | 	HANDY SHORTCUTS | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) CreateInstanceAccount() error { | 
					
						
							|  |  |  | 	username := ps.config.Host | 
					
						
							|  |  |  | 	instanceAccount := >smodel.Account{ | 
					
						
							|  |  |  | 		Username: username, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	inserted, err := ps.conn.Model(instanceAccount).Where("username = ?", username).SelectOrInsert() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if inserted { | 
					
						
							|  |  |  | 		ps.log.Infof("created instance account %s with id %s", username, instanceAccount.ID) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ps.log.Infof("instance account %s already exists with id %s", username, instanceAccount.ID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.Account) error { | 
					
						
							|  |  |  | 	user := >smodel.User{ | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		ID: userID, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(followers).Where("target_account_id = ?", accountID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(statuses).Where("account_id = ?", accountID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	q := ps.conn.Model(statuses).Order("created_at DESC") | 
					
						
							|  |  |  | 	if limit != 0 { | 
					
						
							|  |  |  | 		q = q.Limit(limit) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if accountID != "" { | 
					
						
							|  |  |  | 		q = q.Where("account_id = ?", accountID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := q.Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil { | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return 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 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	if err := ps.conn.Model(>smodel.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		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 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	if err := ps.conn.Model(>smodel.EmailDomainBlock{}).Where("domain = ?", domain).Select(); err == nil { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		// 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 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	if err := ps.conn.Model(>smodel.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		// 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*gtsmodel.User, error) { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	key, err := rsa.GenerateKey(rand.Reader, 2048) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		ps.log.Errorf("error creating new rsa key: %s", err) | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 18:51:49 +02:00
										 |  |  | 	newAccountURIs := util.GenerateURIsForAccount(username, ps.config.Protocol, ps.config.Host) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	a := >smodel.Account{ | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		Username:              username, | 
					
						
							|  |  |  | 		DisplayName:           username, | 
					
						
							|  |  |  | 		Reason:                reason, | 
					
						
							| 
									
										
										
										
											2021-04-22 18:51:49 +02:00
										 |  |  | 		URL:                   newAccountURIs.UserURL, | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		PrivateKey:            key, | 
					
						
							|  |  |  | 		PublicKey:             &key.PublicKey, | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		ActorType:             gtsmodel.ActivityStreamsPerson, | 
					
						
							| 
									
										
										
										
											2021-04-22 18:51:49 +02:00
										 |  |  | 		URI:                   newAccountURIs.UserURI, | 
					
						
							|  |  |  | 		InboxURL:              newAccountURIs.InboxURI, | 
					
						
							|  |  |  | 		OutboxURL:             newAccountURIs.OutboxURI, | 
					
						
							|  |  |  | 		FollowersURL:          newAccountURIs.FollowersURI, | 
					
						
							|  |  |  | 		FeaturedCollectionURL: newAccountURIs.CollectionURI, | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	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) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	u := >smodel.User{ | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		AccountID:              a.ID, | 
					
						
							|  |  |  | 		EncryptedPassword:      string(pw), | 
					
						
							|  |  |  | 		SignUpIP:               signUpIP, | 
					
						
							|  |  |  | 		Locale:                 locale, | 
					
						
							|  |  |  | 		UnconfirmedEmail:       email, | 
					
						
							|  |  |  | 		CreatedByApplicationID: appID, | 
					
						
							|  |  |  | 		Approved:               !requireApproval, // if we don't require moderator approval, just pre-approve the user | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err = ps.conn.Model(u).Insert(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return u, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 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 | 
					
						
							| 
									
										
										
										
											2021-03-14 17:56:16 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 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 ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if acct.HeaderMediaAttachmentID == "" { | 
					
						
							|  |  |  | 		return ErrNoEntries{} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := ps.conn.Model(header).Where("id = ?", acct.HeaderMediaAttachmentID).Select(); err != nil { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 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 ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if acct.AvatarMediaAttachmentID == "" { | 
					
						
							|  |  |  | 		return ErrNoEntries{} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := ps.conn.Model(avatar).Where("id = ?", acct.AvatarMediaAttachmentID).Select(); err != nil { | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { | 
					
						
							|  |  |  | 	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 | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-20 18:14:23 +02:00
										 |  |  | 		return blocked, err | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	blocked = true | 
					
						
							|  |  |  | 	return blocked, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) { | 
					
						
							|  |  |  | 	l := ps.log.WithField("func", "StatusVisible") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if target account is suspended then don't show the status | 
					
						
							|  |  |  | 	if !targetAccount.SuspendedAt.IsZero() { | 
					
						
							|  |  |  | 		l.Debug("target account suspended at is not zero") | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// if the target user doesn't exist (anymore) then the status also shouldn't be visible | 
					
						
							|  |  |  | 	targetUser := >smodel.User{} | 
					
						
							|  |  |  | 	if err := ps.conn.Model(targetUser).Where("account_id = ?", targetAccount.ID).Select(); err != nil { | 
					
						
							|  |  |  | 		l.Debug("target user could not be selected") | 
					
						
							|  |  |  | 		if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 			return false, ErrNoEntries{} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-20 18:14:23 +02:00
										 |  |  | 		return false, err | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// if target user is disabled, not yet approved, or not confirmed then don't show the status | 
					
						
							|  |  |  | 	// (although in the latter two cases it's unlikely they posted a status yet anyway, but you never know!) | 
					
						
							|  |  |  | 	if targetUser.Disabled || !targetUser.Approved || targetUser.ConfirmedAt.IsZero() { | 
					
						
							|  |  |  | 		l.Debug("target user is disabled, not approved, or not confirmed") | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If requesting account is nil, that means whoever requested the status didn't auth, or their auth failed. | 
					
						
							|  |  |  | 	// In this case, we can still serve the status if it's public, otherwise we definitely shouldn't. | 
					
						
							|  |  |  | 	if requestingAccount == nil { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if targetStatus.Visibility == gtsmodel.VisibilityPublic { | 
					
						
							|  |  |  | 			return true, nil | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		l.Debug("requesting account is nil but the target status isn't public") | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// if requesting account is suspended then don't show the status -- although they probably shouldn't have gotten | 
					
						
							|  |  |  | 	// this far (ie., been authed) in the first place: this is just for safety. | 
					
						
							|  |  |  | 	if !requestingAccount.SuspendedAt.IsZero() { | 
					
						
							|  |  |  | 		l.Debug("requesting account is suspended") | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// check if we have a local account -- if so we can check the user for that account in the DB | 
					
						
							|  |  |  | 	if requestingAccount.Domain == "" { | 
					
						
							|  |  |  | 		requestingUser := >smodel.User{} | 
					
						
							|  |  |  | 		if err := ps.conn.Model(requestingUser).Where("account_id = ?", requestingAccount.ID).Select(); err != nil { | 
					
						
							|  |  |  | 			// if the requesting account is local but doesn't have a corresponding user in the db this is a problem | 
					
						
							|  |  |  | 			if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 				l.Debug("requesting account is local but there's no corresponding user") | 
					
						
							|  |  |  | 				return false, nil | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-04-20 18:14:23 +02:00
										 |  |  | 			l.Debugf("requesting account is local but there was an error getting the corresponding user: %s", err) | 
					
						
							|  |  |  | 			return false, err | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		// okay, user exists, so make sure it has full privileges/is confirmed/approved | 
					
						
							|  |  |  | 		if requestingUser.Disabled || !requestingUser.Approved || requestingUser.ConfirmedAt.IsZero() { | 
					
						
							|  |  |  | 			l.Debug("requesting account is local but corresponding user is either disabled, not approved, or not confirmed") | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// if the target status belongs to the requesting account, they should always be able to view it at this point | 
					
						
							|  |  |  | 	if targetStatus.AccountID == requestingAccount.ID { | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// At this point we have a populated targetAccount, targetStatus, and requestingAccount, so we can check for blocks and whathaveyou | 
					
						
							|  |  |  | 	// First check if a block exists directly between the target account (which authored the status) and the requesting account. | 
					
						
							|  |  |  | 	if blocked, err := ps.Blocked(targetAccount.ID, requestingAccount.ID); err != nil { | 
					
						
							|  |  |  | 		l.Debugf("something went wrong figuring out if the accounts have a block: %s", err) | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} else if blocked { | 
					
						
							|  |  |  | 		// don't allow the status to be viewed if a block exists in *either* direction between these two accounts, no creepy stalking please | 
					
						
							|  |  |  | 		l.Debug("a block exists between requesting account and target account") | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check other accounts mentioned/boosted by/replied to by the status, if they exist | 
					
						
							|  |  |  | 	if relevantAccounts != nil { | 
					
						
							|  |  |  | 		// status replies to account id | 
					
						
							|  |  |  | 		if relevantAccounts.ReplyToAccount != nil { | 
					
						
							|  |  |  | 			if blocked, err := ps.Blocked(relevantAccounts.ReplyToAccount.ID, requestingAccount.ID); err != nil { | 
					
						
							|  |  |  | 				return false, err | 
					
						
							|  |  |  | 			} else if blocked { | 
					
						
							|  |  |  | 				return false, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		// status boosts accounts id | 
					
						
							|  |  |  | 		if relevantAccounts.BoostedAccount != nil { | 
					
						
							|  |  |  | 			if blocked, err := ps.Blocked(relevantAccounts.BoostedAccount.ID, requestingAccount.ID); err != nil { | 
					
						
							|  |  |  | 				return false, err | 
					
						
							|  |  |  | 			} else if blocked { | 
					
						
							|  |  |  | 				return false, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// status boosts a reply to account id | 
					
						
							|  |  |  | 		if relevantAccounts.BoostedReplyToAccount != nil { | 
					
						
							|  |  |  | 			if blocked, err := ps.Blocked(relevantAccounts.BoostedReplyToAccount.ID, requestingAccount.ID); err != nil { | 
					
						
							|  |  |  | 				return false, err | 
					
						
							|  |  |  | 			} else if blocked { | 
					
						
							|  |  |  | 				return false, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// status mentions accounts | 
					
						
							|  |  |  | 		for _, a := range relevantAccounts.MentionedAccounts { | 
					
						
							|  |  |  | 			if blocked, err := ps.Blocked(a.ID, requestingAccount.ID); err != nil { | 
					
						
							|  |  |  | 				return false, err | 
					
						
							|  |  |  | 			} else if blocked { | 
					
						
							|  |  |  | 				return false, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// at this point we know neither account blocks the other, or another account mentioned or otherwise referred to in the status | 
					
						
							|  |  |  | 	// that means it's now just a matter of checking the visibility settings of the status itself | 
					
						
							|  |  |  | 	switch targetStatus.Visibility { | 
					
						
							|  |  |  | 	case gtsmodel.VisibilityPublic, gtsmodel.VisibilityUnlocked: | 
					
						
							|  |  |  | 		// no problem here, just return OK | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 	case gtsmodel.VisibilityFollowersOnly: | 
					
						
							|  |  |  | 		// check one-way follow | 
					
						
							|  |  |  | 		follows, err := ps.Follows(requestingAccount, targetAccount) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return false, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !follows { | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 	case gtsmodel.VisibilityMutualsOnly: | 
					
						
							|  |  |  | 		// check mutual follow | 
					
						
							|  |  |  | 		mutuals, err := ps.Mutuals(requestingAccount, targetAccount) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return false, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !mutuals { | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							|  |  |  | 	case gtsmodel.VisibilityDirect: | 
					
						
							|  |  |  | 		// make sure the requesting account is mentioned in the status | 
					
						
							|  |  |  | 		for _, menchie := range targetStatus.Mentions { | 
					
						
							|  |  |  | 			if menchie == requestingAccount.ID { | 
					
						
							|  |  |  | 				return true, nil // yep it's mentioned! | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		return false, nil // it's not mentioned -_- | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return false, errors.New("reached the end of StatusVisible with no result") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { | 
					
						
							|  |  |  | 	return ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) { | 
					
						
							|  |  |  | 	// 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 | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-20 18:14:23 +02:00
										 |  |  | 		return false, err | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// 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 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-20 18:14:23 +02:00
										 |  |  | 		return false, err | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return f1 && f2, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) PullRelevantAccountsFromStatus(targetStatus *gtsmodel.Status) (*gtsmodel.RelevantAccounts, error) { | 
					
						
							|  |  |  | 	accounts := >smodel.RelevantAccounts{ | 
					
						
							|  |  |  | 		MentionedAccounts: []*gtsmodel.Account{}, | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// get the replied to account from the status and add it to the pile | 
					
						
							|  |  |  | 	if targetStatus.InReplyToAccountID != "" { | 
					
						
							|  |  |  | 		repliedToAccount := >smodel.Account{} | 
					
						
							|  |  |  | 		if err := ps.conn.Model(repliedToAccount).Where("id = ?", targetStatus.InReplyToAccountID).Select(); err != nil { | 
					
						
							|  |  |  | 			return accounts, err | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		accounts.ReplyToAccount = repliedToAccount | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// get the boosted account from the status and add it to the pile | 
					
						
							|  |  |  | 	if targetStatus.BoostOfID != "" { | 
					
						
							|  |  |  | 		// retrieve the boosted status first | 
					
						
							|  |  |  | 		boostedStatus := >smodel.Status{} | 
					
						
							|  |  |  | 		if err := ps.conn.Model(boostedStatus).Where("id = ?", targetStatus.BoostOfID).Select(); err != nil { | 
					
						
							|  |  |  | 			return accounts, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		boostedAccount := >smodel.Account{} | 
					
						
							|  |  |  | 		if err := ps.conn.Model(boostedAccount).Where("id = ?", boostedStatus.AccountID).Select(); err != nil { | 
					
						
							|  |  |  | 			return accounts, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		accounts.BoostedAccount = boostedAccount | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// the boosted status might be a reply to another account so we should get that too | 
					
						
							|  |  |  | 		if boostedStatus.InReplyToAccountID != "" { | 
					
						
							|  |  |  | 			boostedStatusRepliedToAccount := >smodel.Account{} | 
					
						
							|  |  |  | 			if err := ps.conn.Model(boostedStatusRepliedToAccount).Where("id = ?", boostedStatus.InReplyToAccountID).Select(); err != nil { | 
					
						
							|  |  |  | 				return accounts, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			accounts.BoostedReplyToAccount = boostedStatusRepliedToAccount | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	// now get all accounts with IDs that are mentioned in the status | 
					
						
							|  |  |  | 	for _, mentionedAccountID := range targetStatus.Mentions { | 
					
						
							|  |  |  | 		mentionedAccount := >smodel.Account{} | 
					
						
							|  |  |  | 		if err := ps.conn.Model(mentionedAccount).Where("id = ?", mentionedAccountID).Select(); err != nil { | 
					
						
							|  |  |  | 			return accounts, err | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		accounts.MentionedAccounts = append(accounts.MentionedAccounts, mentionedAccount) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return accounts, 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) StatusPinnedBy(status *gtsmodel.Status, accountID string) (bool, error) { | 
					
						
							|  |  |  | 	return ps.conn.Model(>smodel.StatusPin{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) { | 
					
						
							|  |  |  | 	// first check if a fave already exists, we can just return if so | 
					
						
							|  |  |  | 	existingFave := >smodel.StatusFave{} | 
					
						
							|  |  |  | 	err := ps.conn.Model(existingFave).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Select() | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		// fave already exists so just return nothing at all | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// an error occurred so it might exist or not, we don't know | 
					
						
							|  |  |  | 	if err != pg.ErrNoRows { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// it doesn't exist so create it | 
					
						
							|  |  |  | 	newFave := >smodel.StatusFave{ | 
					
						
							|  |  |  | 		AccountID:       accountID, | 
					
						
							|  |  |  | 		TargetAccountID: status.AccountID, | 
					
						
							|  |  |  | 		StatusID:        status.ID, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err = ps.conn.Model(newFave).Insert(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return newFave, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) UnfaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) { | 
					
						
							|  |  |  | 	// if a fave doesn't exist, we don't need to do anything | 
					
						
							|  |  |  | 	existingFave := >smodel.StatusFave{} | 
					
						
							|  |  |  | 	err := ps.conn.Model(existingFave).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Select() | 
					
						
							|  |  |  | 	// the fave doesn't exist so return nothing at all | 
					
						
							|  |  |  | 	if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// an error occurred so it might exist or not, we don't know | 
					
						
							|  |  |  | 	if err != nil && err != pg.ErrNoRows { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// the fave exists so remove it | 
					
						
							|  |  |  | 	if _, err = ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Delete(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	return existingFave, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		return nil, err // an actual error has occurred | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	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 | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 		accounts = append(accounts, acc) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	return accounts, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | 	CONVERSION FUNCTIONS | 
					
						
							|  |  |  | */ | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) { | 
					
						
							|  |  |  | 	menchies := []*gtsmodel.Mention{} | 
					
						
							|  |  |  | 	for _, a := range targetAccounts { | 
					
						
							|  |  |  | 		// A mentioned account looks like "@test@example.org" or just "@test" for a local account | 
					
						
							|  |  |  | 		// -- we can guarantee this from the regex that targetAccounts should have been derived from. | 
					
						
							|  |  |  | 		// But we still need to do a bit of fiddling to get what we need here -- the username and domain (if given). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 1.  trim off the first @ | 
					
						
							|  |  |  | 		t := strings.TrimPrefix(a, "@") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 2. split the username and domain | 
					
						
							|  |  |  | 		s := strings.Split(t, "@") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 3. if it's length 1 it's a local account, length 2 means remote, anything else means something is wrong | 
					
						
							|  |  |  | 		var local bool | 
					
						
							|  |  |  | 		switch len(s) { | 
					
						
							|  |  |  | 		case 1: | 
					
						
							|  |  |  | 			local = true | 
					
						
							|  |  |  | 		case 2: | 
					
						
							|  |  |  | 			local = false | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("mentioned account format '%s' was not valid", a) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		var username, domain string | 
					
						
							|  |  |  | 		username = s[0] | 
					
						
							|  |  |  | 		if !local { | 
					
						
							|  |  |  | 			domain = s[1] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 4. check we now have a proper username and domain | 
					
						
							|  |  |  | 		if username == "" || (!local && domain == "") { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("username or domain for '%s' was nil", a) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// okay we're good now, we can start pulling accounts out of the database | 
					
						
							|  |  |  | 		mentionedAccount := >smodel.Account{} | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		if local { | 
					
						
							|  |  |  | 			// local user -- should have a null domain | 
					
						
							|  |  |  | 			err = ps.conn.Model(mentionedAccount).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// remote user -- should have domain defined | 
					
						
							|  |  |  | 			err = ps.conn.Model(mentionedAccount).Where("username = ?", username).Where("? = ?", pg.Ident("domain"), domain).Select() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 				// no result found for this username/domain so just don't include it as a mencho and carry on about our business | 
					
						
							|  |  |  | 				ps.log.Debugf("no account found with username '%s' and domain '%s', skipping it", username, domain) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// a serious error has happened so bail | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("error getting account with username '%s' and domain '%s': %s", username, domain, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// id, createdAt and updatedAt will be populated by the db, so we have everything we need! | 
					
						
							|  |  |  | 		menchies = append(menchies, >smodel.Mention{ | 
					
						
							|  |  |  | 			StatusID:        statusID, | 
					
						
							|  |  |  | 			OriginAccountID: originAccountID, | 
					
						
							|  |  |  | 			TargetAccountID: mentionedAccount.ID, | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	return menchies, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) { | 
					
						
							|  |  |  | 	newTags := []*gtsmodel.Tag{} | 
					
						
							|  |  |  | 	for _, t := range tags { | 
					
						
							|  |  |  | 		tag := >smodel.Tag{} | 
					
						
							|  |  |  | 		// we can use selectorinsert here to create the new tag if it doesn't exist already | 
					
						
							|  |  |  | 		// inserted will be true if this is a new tag we just created | 
					
						
							|  |  |  | 		if err := ps.conn.Model(tag).Where("name = ?", t).Select(); err != nil { | 
					
						
							|  |  |  | 			if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 				// tag doesn't exist yet so populate it | 
					
						
							|  |  |  | 				tag.ID = uuid.NewString() | 
					
						
							|  |  |  | 				tag.Name = t | 
					
						
							|  |  |  | 				tag.FirstSeenFromAccountID = originAccountID | 
					
						
							|  |  |  | 				tag.CreatedAt = time.Now() | 
					
						
							|  |  |  | 				tag.UpdatedAt = time.Now() | 
					
						
							|  |  |  | 				tag.Useable = true | 
					
						
							|  |  |  | 				tag.Listable = true | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				return nil, fmt.Errorf("error getting tag with name %s: %s", t, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// bail already if the tag isn't useable | 
					
						
							|  |  |  | 		if !tag.Useable { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		tag.LastStatusAt = time.Now() | 
					
						
							|  |  |  | 		newTags = append(newTags, tag) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return newTags, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) { | 
					
						
							|  |  |  | 	newEmojis := []*gtsmodel.Emoji{} | 
					
						
							|  |  |  | 	for _, e := range emojis { | 
					
						
							|  |  |  | 		emoji := >smodel.Emoji{} | 
					
						
							|  |  |  | 		err := ps.conn.Model(emoji).Where("shortcode = ?", e).Where("visible_in_picker = true").Where("disabled = false").Select() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if err == pg.ErrNoRows { | 
					
						
							|  |  |  | 				// no result found for this username/domain so just don't include it as an emoji and carry on about our business | 
					
						
							|  |  |  | 				ps.log.Debugf("no emoji found with shortcode %s, skipping it", e) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// a serious error has happened so bail | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("error getting emoji with shortcode %s: %s", e, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		newEmojis = append(newEmojis, emoji) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return newEmojis, nil | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | } |