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}