| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | /* | 
					
						
							|  |  |  |    GoToSocial | 
					
						
							| 
									
										
										
										
											2023-01-05 12:43:00 +01:00
										 |  |  |    Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |    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/>. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | package bundb | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 	"crypto/tls" | 
					
						
							|  |  |  | 	"crypto/x509" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"database/sql" | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 	"encoding/pem" | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2021-09-20 18:20:21 +02:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	"codeberg.org/gruf/go-bytesize" | 
					
						
							| 
									
										
										
										
											2022-08-22 11:21:36 +02:00
										 |  |  | 	"github.com/google/uuid" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"github.com/jackc/pgx/v4" | 
					
						
							|  |  |  | 	"github.com/jackc/pgx/v4/stdlib" | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2021-05-15 11:58:11 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/state" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"github.com/uptrace/bun" | 
					
						
							|  |  |  | 	"github.com/uptrace/bun/dialect/pgdialect" | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 	"github.com/uptrace/bun/dialect/sqlitedialect" | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 	"github.com/uptrace/bun/migrate" | 
					
						
							| 
									
										
										
										
											2021-09-30 11:16:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	"modernc.org/sqlite" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | var registerTables = []interface{}{ | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 	>smodel.AccountToEmoji{}, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	>smodel.StatusToEmoji{}, | 
					
						
							|  |  |  | 	>smodel.StatusToTag{}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | // DBService satisfies the DB interface | 
					
						
							|  |  |  | type DBService struct { | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	db.Account | 
					
						
							|  |  |  | 	db.Admin | 
					
						
							|  |  |  | 	db.Basic | 
					
						
							|  |  |  | 	db.Domain | 
					
						
							| 
									
										
										
										
											2022-05-20 04:34:36 -04:00
										 |  |  | 	db.Emoji | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	db.Instance | 
					
						
							|  |  |  | 	db.Media | 
					
						
							|  |  |  | 	db.Mention | 
					
						
							|  |  |  | 	db.Notification | 
					
						
							|  |  |  | 	db.Relationship | 
					
						
							| 
									
										
										
										
											2023-01-10 15:19:05 +01:00
										 |  |  | 	db.Report | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	db.Session | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	db.Status | 
					
						
							|  |  |  | 	db.Timeline | 
					
						
							| 
									
										
										
										
											2022-10-03 10:46:11 +02:00
										 |  |  | 	db.User | 
					
						
							| 
									
										
										
										
											2022-11-11 12:18:38 +01:00
										 |  |  | 	db.Tombstone | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	conn *DBConn | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | // GetConn returns the underlying bun connection. | 
					
						
							|  |  |  | // Should only be used in testing + exceptional circumstance. | 
					
						
							|  |  |  | func (dbService *DBService) GetConn() *DBConn { | 
					
						
							|  |  |  | 	return dbService.conn | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | func doMigration(ctx context.Context, db *bun.DB) error { | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 	migrator := migrate.NewMigrator(db, migrations.Migrations) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := migrator.Init(ctx); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	group, err := migrator.Migrate(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-09-01 18:29:25 +02:00
										 |  |  | 		if err.Error() == "migrate: there are no any migrations" { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if group.ID == 0 { | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 		log.Info("there are no new migrations to run") | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	log.Infof("MIGRATED DATABASE TO %s", group) | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | // NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface. | 
					
						
							|  |  |  | // Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection. | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 	var conn *DBConn | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	var err error | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	t := strings.ToLower(config.GetDbType()) | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	switch t { | 
					
						
							|  |  |  | 	case "postgres": | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 		conn, err = pgConn(ctx) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	case "sqlite": | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 		conn, err = sqliteConn(ctx) | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 			return nil, err | 
					
						
							| 
									
										
										
										
											2021-08-29 15:41:41 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 		return nil, fmt.Errorf("database type %s not supported for bundb", t) | 
					
						
							| 
									
										
										
										
											2021-03-05 18:31:12 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-10 16:18:21 +01:00
										 |  |  | 	// Add database query hook | 
					
						
							|  |  |  | 	conn.DB.AddQueryHook(queryHook{}) | 
					
						
							| 
									
										
										
										
											2021-09-11 13:19:06 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	// execute sqlite pragmas *after* adding database hook; | 
					
						
							|  |  |  | 	// this allows the pragma queries to be logged | 
					
						
							|  |  |  | 	if t == "sqlite" { | 
					
						
							|  |  |  | 		if err := sqlitePragmas(ctx, conn); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	// table registration is needed for many-to-many, see: | 
					
						
							|  |  |  | 	// https://bun.uptrace.dev/orm/many-to-many-relation/ | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	for _, t := range registerTables { | 
					
						
							|  |  |  | 		conn.RegisterModel(t) | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	// perform any pending database migrations: this includes | 
					
						
							|  |  |  | 	// the very first 'migration' on startup which just creates | 
					
						
							|  |  |  | 	// necessary tables | 
					
						
							| 
									
										
										
										
											2021-10-11 05:37:33 -07:00
										 |  |  | 	if err := doMigration(ctx, conn.DB); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-31 19:27:02 +02:00
										 |  |  | 		return nil, fmt.Errorf("db migration error: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-01 10:08:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 	ps := &DBService{ | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 		Account: &accountDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		Admin: &adminDB{ | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		Basic: &basicDB{ | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 			conn: conn, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 		Domain: &domainDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Emoji: &emojiDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		Instance: &instanceDB{ | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 			conn: conn, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		Media: &mediaDB{ | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 			conn: conn, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 		Mention: &mentionDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Notification: ¬ificationDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Relationship: &relationshipDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-01-10 15:19:05 +01:00
										 |  |  | 		Report: &reportDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 		Session: &sessionDB{ | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 			conn: conn, | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-12-08 17:35:14 +00:00
										 |  |  | 		Status: &statusDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Timeline: &timelineDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		User: &userDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Tombstone: &tombstoneDB{ | 
					
						
							|  |  |  | 			conn:  conn, | 
					
						
							|  |  |  | 			state: state, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		conn: conn, | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	// we can confidently return this useable service now | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 	return ps, nil | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 15:12:48 +01:00
										 |  |  | func pgConn(ctx context.Context) (*DBConn, error) { | 
					
						
							|  |  |  | 	opts, err := deriveBunDBPGOptions() //nolint:contextcheck | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("could not create bundb postgres options: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sqldb := stdlib.OpenDB(*opts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Tune db connections for postgres, see: | 
					
						
							|  |  |  | 	// - https://bun.uptrace.dev/guide/running-bun-in-production.html#database-sql | 
					
						
							|  |  |  | 	// - https://www.alexedwards.net/blog/configuring-sqldb | 
					
						
							|  |  |  | 	sqldb.SetMaxOpenConns(maxOpenConns())     // x number of conns per CPU | 
					
						
							|  |  |  | 	sqldb.SetMaxIdleConns(2)                  // assume default 2; if max idle is less than max open, it will be automatically adjusted | 
					
						
							|  |  |  | 	sqldb.SetConnMaxLifetime(5 * time.Minute) // fine to kill old connections | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	conn := WrapDBConn(bun.NewDB(sqldb, pgdialect.New())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ping to check the db is there and listening | 
					
						
							|  |  |  | 	if err := conn.PingContext(ctx); err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("postgres ping: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	log.Info("connected to POSTGRES database") | 
					
						
							|  |  |  | 	return conn, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | func sqliteConn(ctx context.Context) (*DBConn, error) { | 
					
						
							| 
									
										
										
										
											2022-01-30 17:06:28 +01:00
										 |  |  | 	// validate db address has actually been set | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	address := config.GetDbAddress() | 
					
						
							|  |  |  | 	if address == "" { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 		return nil, fmt.Errorf("'%s' was not set when attempting to start sqlite", config.DbAddressFlag()) | 
					
						
							| 
									
										
										
										
											2022-01-30 17:06:28 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	// Drop anything fancy from DB address | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | 	address = strings.Split(address, "?")[0]       // drop any provided query strings | 
					
						
							|  |  |  | 	address = strings.TrimPrefix(address, "file:") // we'll prepend this later ourselves | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// build our own SQLite preferences | 
					
						
							|  |  |  | 	prefs := []string{ | 
					
						
							|  |  |  | 		// use immediate transaction lock mode to fail quickly if tx can't lock | 
					
						
							|  |  |  | 		// see https://pkg.go.dev/modernc.org/sqlite#Driver.Open | 
					
						
							|  |  |  | 		"_txlock=immediate", | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | 	if address == ":memory:" { | 
					
						
							|  |  |  | 		log.Warn("using sqlite in-memory mode; all data will be deleted when gts shuts down; this mode should only be used for debugging or running tests") | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | 		// Use random name for in-memory instead of ':memory:', so | 
					
						
							|  |  |  | 		// multiple in-mem databases can be created without conflict. | 
					
						
							|  |  |  | 		address = uuid.NewString() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// in-mem-specific preferences | 
					
						
							|  |  |  | 		prefs = append(prefs, []string{ | 
					
						
							|  |  |  | 			"mode=memory",  // indicate in-memory mode using query | 
					
						
							|  |  |  | 			"cache=shared", // shared cache so that tests don't fail | 
					
						
							|  |  |  | 		}...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// rebuild address string with our derived preferences | 
					
						
							|  |  |  | 	address = "file:" + address | 
					
						
							|  |  |  | 	for i, q := range prefs { | 
					
						
							|  |  |  | 		var prefix string | 
					
						
							|  |  |  | 		if i == 0 { | 
					
						
							|  |  |  | 			prefix = "?" | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			prefix = "&" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		address += prefix + q | 
					
						
							| 
									
										
										
										
											2022-08-22 11:21:36 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	// Open new DB instance | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	sqldb, err := sql.Open("sqlite", address) | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if errWithCode, ok := err.(*sqlite.Error); ok { | 
					
						
							|  |  |  | 			err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()]) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | 		return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err) | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 15:12:48 +01:00
										 |  |  | 	// Tune db connections for sqlite, see: | 
					
						
							|  |  |  | 	// - https://bun.uptrace.dev/guide/running-bun-in-production.html#database-sql | 
					
						
							|  |  |  | 	// - https://www.alexedwards.net/blog/configuring-sqldb | 
					
						
							|  |  |  | 	sqldb.SetMaxOpenConns(maxOpenConns()) // x number of conns per cpu | 
					
						
							|  |  |  | 	sqldb.SetMaxIdleConns(1)              // only keep max 1 idle connection around | 
					
						
							|  |  |  | 	sqldb.SetConnMaxLifetime(0)           // don't kill connections due to age | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	// Wrap Bun database conn in our own wrapper | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	conn := WrapDBConn(bun.NewDB(sqldb, sqlitedialect.New())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ping to check the db is there and listening | 
					
						
							|  |  |  | 	if err := conn.PingContext(ctx); err != nil { | 
					
						
							|  |  |  | 		if errWithCode, ok := err.(*sqlite.Error); ok { | 
					
						
							|  |  |  | 			err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()]) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("sqlite ping: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | 	log.Infof("connected to SQLITE database with address %s", address) | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-21 17:41:51 +01:00
										 |  |  | 	return conn, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | /* | 
					
						
							|  |  |  | 	HANDY STUFF | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 15:12:48 +01:00
										 |  |  | // maxOpenConns returns multiplier * GOMAXPROCS, | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | // returning just 1 instead if multiplier < 1. | 
					
						
							| 
									
										
										
										
											2023-01-26 15:12:48 +01:00
										 |  |  | func maxOpenConns() int { | 
					
						
							|  |  |  | 	multiplier := config.GetDbMaxOpenConnsMultiplier() | 
					
						
							|  |  |  | 	if multiplier < 1 { | 
					
						
							| 
									
										
										
										
											2023-01-31 13:46:45 +01:00
										 |  |  | 		return 1 | 
					
						
							| 
									
										
										
										
											2023-01-26 15:12:48 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return multiplier * runtime.GOMAXPROCS(0) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | // deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | // with sensible defaults, or an error if it's not satisfied by the provided config. | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | func deriveBunDBPGOptions() (*pgx.ConnConfig, error) { | 
					
						
							| 
									
										
										
										
											2021-12-21 12:08:27 +01:00
										 |  |  | 	// these are all optional, the db adapter figures out defaults | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	address := config.GetDbAddress() | 
					
						
							| 
									
										
										
										
											2021-03-02 22:52:31 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// validate database | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	database := config.GetDbDatabase() | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	if 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-07-19 18:03:07 +02:00
										 |  |  | 	var tlsConfig *tls.Config | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	switch config.GetDbTLSMode() { | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	case "", "disable": | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		break // nothing to do | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	case "enable": | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 		/* #nosec G402 */ | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		tlsConfig = &tls.Config{ | 
					
						
							|  |  |  | 			InsecureSkipVerify: true, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-01-17 13:29:44 +01:00
										 |  |  | 	case "require": | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		tlsConfig = &tls.Config{ | 
					
						
							|  |  |  | 			InsecureSkipVerify: false, | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 			ServerName:         address, | 
					
						
							| 
									
										
										
										
											2021-11-22 08:46:19 +01:00
										 |  |  | 			MinVersion:         tls.VersionTLS12, | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	if certPath := config.GetDbTLSCACert(); tlsConfig != nil && certPath != "" { | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		// load the system cert pool first -- we'll append the given CA cert to this | 
					
						
							|  |  |  | 		certPool, err := x509.SystemCertPool() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("error fetching system CA cert pool: %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// open the file itself and make sure there's something in it | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 		caCertBytes, err := os.ReadFile(certPath) | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 			return nil, fmt.Errorf("error opening CA certificate at %s: %s", certPath, err) | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if len(caCertBytes) == 0 { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 			return nil, fmt.Errorf("ca cert at %s was empty", certPath) | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// make sure we have a PEM block | 
					
						
							|  |  |  | 		caPem, _ := pem.Decode(caCertBytes) | 
					
						
							|  |  |  | 		if caPem == nil { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 			return nil, fmt.Errorf("could not parse cert at %s into PEM", certPath) | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// parse the PEM block into the certificate | 
					
						
							|  |  |  | 		caCert, err := x509.ParseCertificate(caPem.Bytes) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 			return nil, fmt.Errorf("could not parse cert at %s into x509 certificate: %s", certPath, err) | 
					
						
							| 
									
										
										
										
											2021-07-19 18:03:07 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// we're happy, add it to the existing pool and then use this pool in our tls config | 
					
						
							|  |  |  | 		certPool.AddCert(caCert) | 
					
						
							|  |  |  | 		tlsConfig.RootCAs = certPool | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	cfg, _ := pgx.ParseConfig("") | 
					
						
							| 
									
										
										
										
											2021-12-21 12:08:27 +01:00
										 |  |  | 	if address != "" { | 
					
						
							|  |  |  | 		cfg.Host = address | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-06-03 15:40:38 +02:00
										 |  |  | 	if port := config.GetDbPort(); port > 0 { | 
					
						
							| 
									
										
										
										
											2021-12-21 12:08:27 +01:00
										 |  |  | 		cfg.Port = uint16(port) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	if u := config.GetDbUser(); u != "" { | 
					
						
							|  |  |  | 		cfg.User = u | 
					
						
							| 
									
										
										
										
											2021-12-21 12:08:27 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	if p := config.GetDbPassword(); p != "" { | 
					
						
							|  |  |  | 		cfg.Password = p | 
					
						
							| 
									
										
										
										
											2021-12-21 12:08:27 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if tlsConfig != nil { | 
					
						
							|  |  |  | 		cfg.TLSConfig = tlsConfig | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	cfg.Database = database | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	cfg.PreferSimpleProtocol = true | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	cfg.RuntimeParams["application_name"] = config.GetApplicationName() | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	return cfg, nil | 
					
						
							| 
									
										
										
										
											2021-03-02 18:26:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-26 15:12:48 +01:00
										 |  |  | // sqlitePragmas sets desired sqlite pragmas based on configured values, and | 
					
						
							|  |  |  | // logs the results of the pragma queries. Errors if something goes wrong. | 
					
						
							|  |  |  | func sqlitePragmas(ctx context.Context, conn *DBConn) error { | 
					
						
							|  |  |  | 	var pragmas [][]string | 
					
						
							|  |  |  | 	if mode := config.GetDbSqliteJournalMode(); mode != "" { | 
					
						
							|  |  |  | 		// Set the user provided SQLite journal mode | 
					
						
							|  |  |  | 		pragmas = append(pragmas, []string{"journal_mode", mode}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if mode := config.GetDbSqliteSynchronous(); mode != "" { | 
					
						
							|  |  |  | 		// Set the user provided SQLite synchronous mode | 
					
						
							|  |  |  | 		pragmas = append(pragmas, []string{"synchronous", mode}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if size := config.GetDbSqliteCacheSize(); size > 0 { | 
					
						
							|  |  |  | 		// Set the user provided SQLite cache size (in kibibytes) | 
					
						
							|  |  |  | 		// Prepend a '-' character to this to indicate to sqlite | 
					
						
							|  |  |  | 		// that we're giving kibibytes rather than num pages. | 
					
						
							|  |  |  | 		// https://www.sqlite.org/pragma.html#pragma_cache_size | 
					
						
							|  |  |  | 		s := "-" + strconv.FormatUint(uint64(size/bytesize.KiB), 10) | 
					
						
							|  |  |  | 		pragmas = append(pragmas, []string{"cache_size", s}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if timeout := config.GetDbSqliteBusyTimeout(); timeout > 0 { | 
					
						
							|  |  |  | 		t := strconv.FormatInt(timeout.Milliseconds(), 10) | 
					
						
							|  |  |  | 		pragmas = append(pragmas, []string{"busy_timeout", t}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, p := range pragmas { | 
					
						
							|  |  |  | 		pk := p[0] | 
					
						
							|  |  |  | 		pv := p[1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if _, err := conn.DB.ExecContext(ctx, "PRAGMA ?=?", bun.Ident(pk), bun.Safe(pv)); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("error executing sqlite pragma %s: %w", pk, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var res string | 
					
						
							|  |  |  | 		if err := conn.DB.NewRaw("PRAGMA ?", bun.Ident(pk)).Scan(ctx, &res); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("error scanning sqlite pragma %s: %w", pv, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		log.Infof("sqlite pragma %s set to %s", pk, res) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | /* | 
					
						
							|  |  |  | 	CONVERSION FUNCTIONS | 
					
						
							|  |  |  | */ | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | func (dbService *DBService) TagStringsToTags(ctx context.Context, tags []string, originAccountID string) ([]*gtsmodel.Tag, error) { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 	protocol := config.GetProtocol() | 
					
						
							|  |  |  | 	host := config.GetHost() | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 	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 | 
					
						
							| 
									
										
										
										
											2022-09-26 11:56:01 +02:00
										 |  |  | 		if err := dbService.conn.NewSelect().Model(tag).Where("LOWER(?) = LOWER(?)", bun.Ident("name"), t).Scan(ctx); err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 			if err == sql.ErrNoRows { | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 				// tag doesn't exist yet so populate it | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 				newID, err := id.NewRandomULID() | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					return nil, err | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				tag.ID = newID | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 				tag.URL = fmt.Sprintf("%s://%s/tags/%s", protocol, host, t) | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 				tag.Name = t | 
					
						
							|  |  |  | 				tag.FirstSeenFromAccountID = originAccountID | 
					
						
							|  |  |  | 				tag.CreatedAt = time.Now() | 
					
						
							|  |  |  | 				tag.UpdatedAt = time.Now() | 
					
						
							| 
									
										
										
										
											2022-08-15 12:35:05 +02:00
										 |  |  | 				useable := true | 
					
						
							|  |  |  | 				tag.Useable = &useable | 
					
						
							|  |  |  | 				listable := true | 
					
						
							|  |  |  | 				tag.Listable = &listable | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				return nil, fmt.Errorf("error getting tag with name %s: %s", t, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// bail already if the tag isn't useable | 
					
						
							| 
									
										
										
										
											2022-08-15 12:35:05 +02:00
										 |  |  | 		if !*tag.Useable { | 
					
						
							| 
									
										
										
										
											2021-04-19 19:42:19 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		tag.LastStatusAt = time.Now() | 
					
						
							|  |  |  | 		newTags = append(newTags, tag) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return newTags, nil | 
					
						
							|  |  |  | } |