From 839613d66b2186df536f2061817ddbf0f93d40b8 Mon Sep 17 00:00:00 2001 From: augustuswm Date: Tue, 19 May 2026 12:46:26 -0500 Subject: [PATCH] Migration refactor --- AGENTS.md | 1 - Cargo.lock | 9 ----- Cargo.toml | 1 - README.md | 2 +- v-api-installer/Cargo.toml | 17 -------- v-api-installer/src/lib.rs | 21 ---------- v-api-installer/src/main.rs | 15 ------- v-model/Cargo.toml | 3 +- {v-api-installer => v-model}/build.rs | 3 +- v-model/src/lib.rs | 1 + v-model/src/migrations.rs | 39 +++++++++++++++++++ .../2026-05-19-172630_sagas/down.sql | 13 +++++++ .../migrations/2026-05-19-172630_sagas/up.sql | 29 ++++++++++++++ v-model/tests/postgres.rs | 2 +- 14 files changed, 87 insertions(+), 69 deletions(-) delete mode 100644 v-api-installer/Cargo.toml delete mode 100644 v-api-installer/src/lib.rs delete mode 100644 v-api-installer/src/main.rs rename {v-api-installer => v-model}/build.rs (66%) create mode 100644 v-model/src/migrations.rs create mode 100644 v-model/src/saga/migrations/2026-05-19-172630_sagas/down.sql create mode 100644 v-model/src/saga/migrations/2026-05-19-172630_sagas/up.sql diff --git a/AGENTS.md b/AGENTS.md index 5ad7ade6..2841aba4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,6 @@ ## Crates - `v-api` - Dropshot endpoint, context, authentication, and authorization framework for the v-api service. -- `v-api-installer` - Embedded Diesel migration runner for installing the v-model Postgres schema. - `v-api-param` - Configuration parameter helpers for inline strings or file-backed secret values. - `v-api-permission-derive` - Procedural macro for deriving v-api permission traits on application enums. - `v-model` - Shared data models, Diesel schema, migrations, storage traits, and Postgres implementations. diff --git a/Cargo.lock b/Cargo.lock index 210663be..fb0ba465 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3572,14 +3572,6 @@ dependencies = [ "yup-oauth2", ] -[[package]] -name = "v-api-installer" -version = "0.4.0-alpha.0" -dependencies = [ - "diesel", - "diesel_migrations", -] - [[package]] name = "v-api-param" version = "0.4.0-alpha.0" @@ -3629,7 +3621,6 @@ dependencies = [ "tokio", "tracing", "uuid", - "v-api-installer", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e2e626eb..955781ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "v-api", - "v-api-installer", "v-api-param", "v-api-permission-derive", "v-model", diff --git a/README.md b/README.md index 662aa7b0..4771fd0e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The crate assumes that the hosting application is using a Postgres database or c connection to one. Want a different backend? Please contribute, it will be gladly welcome. The `v-model` crate contains [diesel](https://diesel.rs/) migrations for initializing the necessary -tables in a database. `v-model-installer` exposes embedded migrations via [diesel_migrations](https://docs.rs/diesel_migrations/latest/diesel_migrations/). +tables in a database. `v-model` exposes embedded migrations via [diesel_migrations](https://docs.rs/diesel_migrations/latest/diesel_migrations/). To add the endpoints in to the hosting server: diff --git a/v-api-installer/Cargo.toml b/v-api-installer/Cargo.toml deleted file mode 100644 index c4b5c377..00000000 --- a/v-api-installer/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "v-api-installer" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[lib] -name = "v_api_installer" -path = "src/lib.rs" - -[[bin]] -name = "v-api-installer" -path = "src/main.rs" - -[dependencies] -diesel = { workspace = true, features = ["postgres", "r2d2"] } -diesel_migrations = { workspace = true, features = ["postgres"] } diff --git a/v-api-installer/src/lib.rs b/v-api-installer/src/lib.rs deleted file mode 100644 index c736eb59..00000000 --- a/v-api-installer/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use diesel::{ - PgConnection, - r2d2::{ConnectionManager, ManageConnection}, -}; -use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; - -const MIGRATIONS: EmbeddedMigrations = embed_migrations!("../v-model/migrations"); - -pub fn run_migrations(url: &str) { - let mut conn = db_conn(url); - conn.run_pending_migrations(MIGRATIONS).unwrap(); -} - -fn db_conn(url: &str) -> PgConnection { - let conn: ConnectionManager = ConnectionManager::new(url); - conn.connect().unwrap() -} diff --git a/v-api-installer/src/main.rs b/v-api-installer/src/main.rs deleted file mode 100644 index a791e612..00000000 --- a/v-api-installer/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use v_api_installer::run_migrations; - -fn main() { - if let Ok(url) = std::env::var("DATABASE_URL") { - run_migrations(&url); - } else { - println!( - "DATABASE_URL environment variable must be specified to run migrations and must be in the form of a connection string" - ) - } -} diff --git a/v-model/Cargo.toml b/v-model/Cargo.toml index b4f31561..f8d85dbd 100644 --- a/v-model/Cargo.toml +++ b/v-model/Cargo.toml @@ -16,6 +16,7 @@ async-trait = { workspace = true } bb8 = { workspace = true } chrono = { workspace = true, features = ["serde"] } diesel = { workspace = true, features = ["chrono", "uuid", "serde_json"] } +diesel_migrations = { workspace = true, features = ["postgres"] } mockall = { workspace = true, optional = true } newtype-uuid = { workspace = true } partial-struct = { workspace = true } @@ -28,6 +29,4 @@ tracing = { workspace = true } uuid = { workspace = true, features = ["v4", "serde"] } [dev-dependencies] -diesel_migrations = { version = "2.3.2", features = ["postgres"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -v-api-installer = { path = "../v-api-installer" } diff --git a/v-api-installer/build.rs b/v-model/build.rs similarity index 66% rename from v-api-installer/build.rs rename to v-model/build.rs index 89650dca..e3bd6c46 100644 --- a/v-api-installer/build.rs +++ b/v-model/build.rs @@ -3,5 +3,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. fn main() { - println!("cargo:rerun-if-changed=../v-model/migrations"); + println!("cargo:rerun-if-changed=migrations"); + println!("cargo:rerun-if-changed=src/saga/migrations"); } diff --git a/v-model/src/lib.rs b/v-model/src/lib.rs index b02c4efa..e5c8282b 100644 --- a/v-model/src/lib.rs +++ b/v-model/src/lib.rs @@ -22,6 +22,7 @@ use std::{ use thiserror::Error; pub mod db; +pub mod migrations; pub mod permissions; #[cfg(feature = "sagas")] pub mod saga; diff --git a/v-model/src/migrations.rs b/v-model/src/migrations.rs new file mode 100644 index 00000000..70794e2c --- /dev/null +++ b/v-model/src/migrations.rs @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use diesel::{ + PgConnection, + r2d2::{ConnectionManager, ManageConnection}, +}; +use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; + +/// Core v-model migrations that are always applied. +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); + +/// Saga-specific migrations, only available when the `sagas` feature is enabled. +#[cfg(feature = "sagas")] +pub const SAGA_MIGRATIONS: EmbeddedMigrations = embed_migrations!("src/saga/migrations"); + +/// Returns all embedded migration sets that should be applied based on the +/// currently enabled features. +fn all_migrations() -> Vec { + let mut migrations = vec![MIGRATIONS]; + + #[cfg(feature = "sagas")] + migrations.push(SAGA_MIGRATIONS); + + migrations +} + +/// Runs all pending migrations for each enabled feature against the database +/// at the provided connection string. Migration sets are applied in dependency +/// order (core first, then feature-specific). +pub fn run_migrations(url: &str) { + let conn: ConnectionManager = ConnectionManager::new(url); + let mut conn = conn.connect().unwrap(); + + for migrations in all_migrations() { + conn.run_pending_migrations(migrations).unwrap(); + } +} diff --git a/v-model/src/saga/migrations/2026-05-19-172630_sagas/down.sql b/v-model/src/saga/migrations/2026-05-19-172630_sagas/down.sql new file mode 100644 index 00000000..4526e915 --- /dev/null +++ b/v-model/src/saga/migrations/2026-05-19-172630_sagas/down.sql @@ -0,0 +1,13 @@ +DROP INDEX IF EXISTS idx_saga_events_saga_id_id; +DROP INDEX IF EXISTS idx_saga_events_event_type; +DROP INDEX IF EXISTS idx_saga_events_saga_id; + +DROP INDEX IF EXISTS idx_sagas_node_claimed_at; +DROP INDEX IF EXISTS idx_sagas_current_node_id; +DROP INDEX IF EXISTS idx_sagas_created_at; +DROP INDEX IF EXISTS idx_sagas_name; +DROP INDEX IF EXISTS idx_sagas_state; + +-- Drop tables (saga_events first due to foreign key constraint) +DROP TABLE IF EXISTS saga_events; +DROP TABLE IF EXISTS sagas; diff --git a/v-model/src/saga/migrations/2026-05-19-172630_sagas/up.sql b/v-model/src/saga/migrations/2026-05-19-172630_sagas/up.sql new file mode 100644 index 00000000..7c33b483 --- /dev/null +++ b/v-model/src/saga/migrations/2026-05-19-172630_sagas/up.sql @@ -0,0 +1,29 @@ +CREATE TABLE sagas ( + saga_id UUID PRIMARY KEY, + name TEXT NOT NULL, + dag JSONB NOT NULL, + state TEXT NOT NULL, + current_node_id UUID, + node_claimed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_sagas_state ON sagas (state); +CREATE INDEX idx_sagas_name ON sagas (name); +CREATE INDEX idx_sagas_created_at ON sagas (created_at); +CREATE INDEX idx_sagas_current_node_id ON sagas (current_node_id); +CREATE INDEX idx_sagas_node_claimed_at ON sagas (node_claimed_at) WHERE current_node_id IS NOT NULL; + +CREATE TABLE saga_events ( + id BIGSERIAL PRIMARY KEY, + saga_id UUID NOT NULL REFERENCES sagas(saga_id) ON DELETE CASCADE, + node_id BIGINT NOT NULL, + event_type TEXT NOT NULL, + event_data JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_saga_events_saga_id ON saga_events (saga_id); +CREATE INDEX idx_saga_events_event_type ON saga_events (event_type); +CREATE INDEX idx_saga_events_saga_id_id ON saga_events (saga_id, id); diff --git a/v-model/tests/postgres.rs b/v-model/tests/postgres.rs index 3a67f212..f5e1feaa 100644 --- a/v-model/tests/postgres.rs +++ b/v-model/tests/postgres.rs @@ -15,9 +15,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::ops::{Add, Sub}; use uuid::Uuid; -use v_api_installer::run_migrations; use v_model::{ NewApiKey, NewApiUser, NewMagicLink, NewMagicLinkAttempt, UserId, + migrations::run_migrations, schema_ext::MagicLinkAttemptState, storage::{ ApiKeyFilter, ApiKeyStore, ApiUserFilter, ApiUserStore, ListPagination,