1package migrate
  2
  3import (
  4	"context"
  5	"errors"
  6	"fmt"
  7
  8	"github.com/charmbracelet/soft-serve/server/access"
  9	"github.com/charmbracelet/soft-serve/server/config"
 10	"github.com/charmbracelet/soft-serve/server/db"
 11	"github.com/charmbracelet/soft-serve/server/sshutils"
 12)
 13
 14const (
 15	createTablesName    = "create tables"
 16	createTablesVersion = 1
 17)
 18
 19var createTables = Migration{
 20	Version: createTablesVersion,
 21	Name:    createTablesName,
 22	Migrate: func(ctx context.Context, tx *db.Tx) error {
 23		cfg := config.FromContext(ctx)
 24
 25		insert := "INSERT "
 26
 27		// Alter old tables (if exist)
 28		// This is to support prior versions of Soft Serve
 29		switch tx.DriverName() {
 30		case "sqlite3", "sqlite":
 31			insert += "OR IGNORE "
 32
 33			hasUserTable := hasTable(tx, "user")
 34			if hasUserTable {
 35				if _, err := tx.ExecContext(ctx, "ALTER TABLE user RENAME TO users"); err != nil {
 36					return err
 37				}
 38			}
 39
 40			if hasTable(tx, "public_key") {
 41				if _, err := tx.ExecContext(ctx, "ALTER TABLE public_key RENAME TO public_keys"); err != nil {
 42					return err
 43				}
 44			}
 45
 46			if hasTable(tx, "collab") {
 47				if _, err := tx.ExecContext(ctx, "ALTER TABLE collab RENAME TO collabs"); err != nil {
 48					return err
 49				}
 50			}
 51
 52			if hasTable(tx, "repo") {
 53				if _, err := tx.ExecContext(ctx, "ALTER TABLE repo RENAME TO repos"); err != nil {
 54					return err
 55				}
 56			}
 57
 58			// Fix username being nullable
 59			if hasUserTable {
 60				sqlm := `
 61				PRAGMA foreign_keys = OFF;
 62
 63				CREATE TABLE users_new (
 64					id INTEGER PRIMARY KEY AUTOINCREMENT,
 65					username TEXT NOT NULL UNIQUE,
 66					admin BOOLEAN NOT NULL,
 67					created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 68					updated_at DATETIME NOT NULL
 69				);
 70
 71				INSERT INTO users_new (username, admin, updated_at)
 72					SELECT username, admin, updated_at FROM users;
 73
 74				DROP TABLE users;
 75				ALTER TABLE users_new RENAME TO users;
 76
 77				PRAGMA foreign_keys = ON;
 78				`
 79				if _, err := tx.ExecContext(ctx, sqlm); err != nil {
 80					return err
 81				}
 82			}
 83		}
 84
 85		if err := migrateUp(ctx, tx, createTablesVersion, createTablesName); err != nil {
 86			return err
 87		}
 88
 89		// Insert default user
 90		insertUser := tx.Rebind(insert + "INTO users (username, admin, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)")
 91		if _, err := tx.ExecContext(ctx, insertUser, "admin", true); err != nil {
 92			return err
 93		}
 94
 95		for _, k := range cfg.AdminKeys() {
 96			query := insert + "INTO public_keys (user_id, public_key, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)"
 97			if tx.DriverName() == "postgres" {
 98				query += " ON CONFLICT DO NOTHING"
 99			}
100
101			query = tx.Rebind(query)
102			ak := sshutils.MarshalAuthorizedKey(k)
103			if _, err := tx.ExecContext(ctx, query, 1, ak); err != nil {
104				if errors.Is(db.WrapError(err), db.ErrDuplicateKey) {
105					continue
106				}
107				return err
108			}
109		}
110
111		// Insert default settings
112		insertSettings := insert + "INTO settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)"
113		insertSettings = tx.Rebind(insertSettings)
114		settings := []struct {
115			Key   string
116			Value string
117		}{
118			{"allow_keyless", "true"},
119			{"anon_access", access.ReadOnlyAccess.String()},
120			{"init", "true"},
121		}
122
123		for _, s := range settings {
124			if _, err := tx.ExecContext(ctx, insertSettings, s.Key, s.Value); err != nil {
125				return fmt.Errorf("inserting default settings %q: %w", s.Key, err)
126			}
127		}
128
129		return nil
130	},
131	Rollback: func(ctx context.Context, tx *db.Tx) error {
132		return migrateDown(ctx, tx, createTablesVersion, createTablesName)
133	},
134}