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 v0.6
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 user_old"); 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_key_old"); err != nil {
42 return err
43 }
44 }
45
46 if hasTable(tx, "collab") {
47 if _, err := tx.ExecContext(ctx, "ALTER TABLE collab RENAME TO collab_old"); err != nil {
48 return err
49 }
50 }
51
52 if hasTable(tx, "repo") {
53 if _, err := tx.ExecContext(ctx, "ALTER TABLE repo RENAME TO repo_old"); err != nil {
54 return err
55 }
56 }
57 }
58
59 if err := migrateUp(ctx, tx, createTablesVersion, createTablesName); err != nil {
60 return err
61 }
62
63 switch tx.DriverName() {
64 case "sqlite3", "sqlite":
65
66 if _, err := tx.ExecContext(ctx, "PRAGMA foreign_keys = OFF"); err != nil {
67 return err
68 }
69
70 if hasTable(tx, "user_old") {
71 sqlm := `
72 INSERT INTO users (id, username, admin, updated_at)
73 SELECT id, username, admin, updated_at FROM user_old;
74 `
75 if _, err := tx.ExecContext(ctx, sqlm); err != nil {
76 return err
77 }
78 }
79
80 if hasTable(tx, "public_key_old") {
81 // Check duplicate keys
82 pks := []struct {
83 ID string `db:"id"`
84 PublicKey string `db:"public_key"`
85 }{}
86 if err := tx.SelectContext(ctx, &pks, "SELECT id, public_key FROM public_key_old"); err != nil {
87 return err
88 }
89
90 pkss := map[string]struct{}{}
91 for _, pk := range pks {
92 if _, ok := pkss[pk.PublicKey]; ok {
93 return fmt.Errorf("duplicate public key: %q, please remove the duplicate key and try again", pk.PublicKey)
94 }
95 pkss[pk.PublicKey] = struct{}{}
96 }
97
98 sqlm := `
99 INSERT INTO public_keys (id, user_id, public_key, created_at, updated_at)
100 SELECT id, user_id, public_key, created_at, updated_at FROM public_key_old;
101 `
102 if _, err := tx.ExecContext(ctx, sqlm); err != nil {
103 return err
104 }
105 }
106
107 if hasTable(tx, "repo_old") {
108 sqlm := `
109 INSERT INTO repos (id, name, project_name, description, private,mirror, hidden, created_at, updated_at, user_id)
110 SELECT id, name, project_name, description, private, mirror, hidden, created_at, updated_at, (
111 SELECT id FROM users WHERE admin = true ORDER BY id LIMIT 1
112 ) FROM repo_old;
113 `
114 if _, err := tx.ExecContext(ctx, sqlm); err != nil {
115 return err
116 }
117 }
118
119 if hasTable(tx, "collab_old") {
120 sqlm := `
121 INSERT INTO collabs (id, user_id, repo_id, created_at, updated_at)
122 SELECT id, user_id, repo_id, created_at, updated_at FROM collab_old;
123 `
124 if _, err := tx.ExecContext(ctx, sqlm); err != nil {
125 return err
126 }
127 }
128
129 if _, err := tx.ExecContext(ctx, "PRAGMA foreign_keys = ON"); err != nil {
130 return err
131 }
132 }
133
134 // Insert default user
135 insertUser := tx.Rebind(insert + "INTO users (username, admin, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)")
136 if _, err := tx.ExecContext(ctx, insertUser, "admin", true); err != nil {
137 return err
138 }
139
140 for _, k := range cfg.AdminKeys() {
141 query := insert + "INTO public_keys (user_id, public_key, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)"
142 if tx.DriverName() == "postgres" {
143 query += " ON CONFLICT DO NOTHING"
144 }
145
146 query = tx.Rebind(query)
147 ak := sshutils.MarshalAuthorizedKey(k)
148 if _, err := tx.ExecContext(ctx, query, 1, ak); err != nil {
149 if errors.Is(db.WrapError(err), db.ErrDuplicateKey) {
150 continue
151 }
152 return err
153 }
154 }
155
156 // Insert default settings
157 insertSettings := insert + "INTO settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)"
158 insertSettings = tx.Rebind(insertSettings)
159 settings := []struct {
160 Key string
161 Value string
162 }{
163 {"allow_keyless", "true"},
164 {"anon_access", access.ReadOnlyAccess.String()},
165 {"init", "true"},
166 }
167
168 for _, s := range settings {
169 if _, err := tx.ExecContext(ctx, insertSettings, s.Key, s.Value); err != nil {
170 return fmt.Errorf("inserting default settings %q: %w", s.Key, err)
171 }
172 }
173
174 return nil
175 },
176 Rollback: func(ctx context.Context, tx *db.Tx) error {
177 return migrateDown(ctx, tx, createTablesVersion, createTablesName)
178 },
179}