Yōten: A social tracker for your language learning journey built on the atproto.

feat(db): add migration logic

Signed-off-by: brookjeynes <me@brookjeynes.dev>

authored by brookjeynes.dev and committed by Tangled 056d2db7 c7b4f265

Changed files
+48 -2
internal
db
server
+47 -1
internal/db/db.go
··· 8 8 "strings" 9 9 10 10 _ "github.com/mattn/go-sqlite3" 11 + "yoten.app/internal/server/log" 11 12 ) 12 13 13 14 type DB struct { ··· 26 27 PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) 27 28 } 28 29 29 - func Make(ctx context.Context, dbPath string, logger *slog.Logger) (*DB, error) { 30 + func Make(ctx context.Context, dbPath string) (*DB, error) { 30 31 opts := []string{ 31 32 "_foreign_keys=1", 32 33 "_journal_mode=WAL", 33 34 "_synchronous=NORMAL", 34 35 "_auto_vacuum=incremental", 35 36 } 37 + 38 + logger := log.FromContext(ctx) 39 + logger = log.SubLogger(logger, "db") 36 40 37 41 db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&")) 38 42 if err != nil { ··· 236 240 logger, 237 241 }, nil 238 242 } 243 + 244 + type migrationFn = func(*sql.Tx) error 245 + 246 + func runMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error { 247 + logger = logger.With("migration", name) 248 + 249 + tx, err := c.BeginTx(context.Background(), nil) 250 + if err != nil { 251 + return err 252 + } 253 + defer tx.Rollback() 254 + 255 + var exists bool 256 + err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists) 257 + if err != nil { 258 + return err 259 + } 260 + 261 + if !exists { 262 + err = migrationFn(tx) 263 + if err != nil { 264 + logger.Error("failed to run migration", "err", err) 265 + return err 266 + } 267 + 268 + _, err = tx.Exec("insert into migrations (name) values (?)", name) 269 + if err != nil { 270 + logger.Error("failed to mark migration as complete", "err", err) 271 + return err 272 + } 273 + 274 + if err := tx.Commit(); err != nil { 275 + return err 276 + } 277 + 278 + logger.Info("migration applied successfully") 279 + } else { 280 + logger.Warn("skipped migration, already applied") 281 + } 282 + 283 + return nil 284 + }
+1 -1
internal/server/app.go
··· 51 51 func Make(ctx context.Context, config *config.Config) (*Server, error) { 52 52 logger := log.FromContext(ctx) 53 53 54 - d, err := db.Make(ctx, config.Core.DbPath, log.SubLogger(logger, "db")) 54 + d, err := db.Make(ctx, config.Core.DbPath) 55 55 if err != nil { 56 56 return nil, err 57 57 }