From 5e4e08c2551632f1ba7338e43f8970af36dc9e43 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sat, 29 Nov 2025 20:32:32 +0000 Subject: [PATCH] fix: migration code not working - read database config from config file - rename migration file to expected file name format --- apps/backend/cmd/migration/main.go | 28 +++++++++++++++-- apps/backend/internal/database/migrate.go | 21 ++++++++++--- .../{initial.sql => 001_initial.up.sql} | 31 +++---------------- 3 files changed, 46 insertions(+), 34 deletions(-) rename apps/backend/internal/database/migrations/{initial.sql => 001_initial.up.sql} (79%) diff --git a/apps/backend/cmd/migration/main.go b/apps/backend/cmd/migration/main.go index c5aa925..d4325a1 100644 --- a/apps/backend/cmd/migration/main.go +++ b/apps/backend/cmd/migration/main.go @@ -1,13 +1,37 @@ package main import ( + "context" + "flag" + "fmt" "log" + "os" "github.com/get-drexa/drexa/internal/database" + "github.com/get-drexa/drexa/internal/drexa" ) func main() { - if err := database.RunMigrations(); err != nil { - log.Fatalf("Failed to run migrations: %v", err) + configPath := flag.String("config", "", "path to config file (required)") + flag.Parse() + + if *configPath == "" { + fmt.Fprintln(os.Stderr, "error: --config is required") + flag.Usage() + os.Exit(1) } + + config, err := drexa.ConfigFromFile(*configPath) + if err != nil { + log.Fatalf("failed to load config: %v", err) + } + + db := database.NewFromPostgres(config.Database.PostgresURL) + defer db.Close() + + log.Println("running migrations...") + if err := database.RunMigrations(context.Background(), db); err != nil { + log.Fatalf("failed to run migrations: %v", err) + } + log.Println("migrations completed successfully") } diff --git a/apps/backend/internal/database/migrate.go b/apps/backend/internal/database/migrate.go index 5ecf76d..f70ee89 100644 --- a/apps/backend/internal/database/migrate.go +++ b/apps/backend/internal/database/migrate.go @@ -1,17 +1,28 @@ package database import ( + "context" "embed" + "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" ) //go:embed migrations/*.sql var sqlMigrations embed.FS -// RunMigrations discovers and runs all migrations in the migrations directory. -// Currently, the migrations directory is in internal/db/migrations. -func RunMigrations() error { - m := migrate.NewMigrations() - return m.Discover(sqlMigrations) +// RunMigrations discovers and runs all migrations against the database. +func RunMigrations(ctx context.Context, db *bun.DB) error { + migrations := migrate.NewMigrations() + if err := migrations.Discover(sqlMigrations); err != nil { + return err + } + + migrator := migrate.NewMigrator(db, migrations) + if err := migrator.Init(ctx); err != nil { + return err + } + + _, err := migrator.Migrate(ctx) + return err } diff --git a/apps/backend/internal/database/migrations/initial.sql b/apps/backend/internal/database/migrations/001_initial.up.sql similarity index 79% rename from apps/backend/internal/database/migrations/initial.sql rename to apps/backend/internal/database/migrations/001_initial.up.sql index 12f769c..5bda205 100644 --- a/apps/backend/internal/database/migrations/initial.sql +++ b/apps/backend/internal/database/migrations/001_initial.up.sql @@ -1,32 +1,9 @@ --- Enable UUID extension for UUIDv7 support -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- UUIDv7 generation function (timestamp-ordered UUIDs) --- Based on the draft RFC: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format -CREATE OR REPLACE FUNCTION uuid_generate_v7() -RETURNS UUID -AS $$ -DECLARE - unix_ts_ms BIGINT; - uuid_bytes BYTEA; -BEGIN - unix_ts_ms = (EXTRACT(EPOCH FROM CLOCK_TIMESTAMP()) * 1000)::BIGINT; - uuid_bytes = OVERLAY(gen_random_bytes(16) PLACING - SUBSTRING(INT8SEND(unix_ts_ms) FROM 3) FROM 1 FOR 6 - ); - -- Set version (7) and variant bits - uuid_bytes = SET_BYTE(uuid_bytes, 6, (GET_BYTE(uuid_bytes, 6) & 15) | 112); - uuid_bytes = SET_BYTE(uuid_bytes, 8, (GET_BYTE(uuid_bytes, 8) & 63) | 128); - RETURN ENCODE(uuid_bytes, 'hex')::UUID; -END; -$$ LANGUAGE plpgsql VOLATILE; - -- ============================================================================ -- Application Tables -- ============================================================================ CREATE TABLE IF NOT EXISTS users ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), + id UUID PRIMARY KEY, display_name TEXT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, @@ -39,7 +16,7 @@ CREATE TABLE IF NOT EXISTS users ( CREATE INDEX idx_users_email ON users(email); CREATE TABLE IF NOT EXISTS refresh_tokens ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), + id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, @@ -52,7 +29,7 @@ CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at); -- Virtual filesystem nodes (unified files + directories) CREATE TABLE IF NOT EXISTS vfs_nodes ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), + id UUID PRIMARY KEY, public_id TEXT NOT NULL UNIQUE, -- opaque ID for external API (no timestamp leak) user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, parent_id UUID REFERENCES vfs_nodes(id) ON DELETE CASCADE, -- NULL = root directory @@ -83,7 +60,7 @@ CREATE UNIQUE INDEX idx_vfs_nodes_user_root ON vfs_nodes(user_id) WHERE parent_i CREATE INDEX idx_vfs_nodes_pending ON vfs_nodes(created_at) WHERE status = 'pending'; -- for cleanup job CREATE TABLE IF NOT EXISTS node_shares ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), + id UUID PRIMARY KEY, node_id UUID NOT NULL REFERENCES vfs_nodes(id) ON DELETE CASCADE, share_token TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ,