mod v001_backdrop_cache_keys; mod v002_backdrop_position_direction_cache; mod v003_badge_shape_background_cache; mod v004_image_meta_api_key_id; mod v005_api_key_settings_updated_at; mod v006_cache_persistence; use sea_orm::{ConnectionTrait, DatabaseConnection}; use crate::error::AppError; /// Run all pending data upgrades. Each upgrade is tracked in the `upgrades` /// table so it executes only once. New upgrades are added as modules and /// registered below. pub async fn run( db: &DatabaseConnection, cache_dir: &str, external_cache_only: bool, ) -> Result<(), AppError> { ensure_table(db).await?; run_once(db, "v001_backdrop_cache_keys", || { v001_backdrop_cache_keys::run(db, cache_dir, external_cache_only) }) .await?; run_once(db, "v002_backdrop_position_direction_cache", || { v002_backdrop_position_direction_cache::run(db, cache_dir, external_cache_only) }) .await?; run_once(db, "v003_badge_shape_background_cache", || { v003_badge_shape_background_cache::run(db, cache_dir, external_cache_only) }) .await?; run_once(db, "v004_image_meta_api_key_id", || { v004_image_meta_api_key_id::run(db, cache_dir, external_cache_only) }) .await?; run_once(db, "v005_api_key_settings_updated_at", || { v005_api_key_settings_updated_at::run(db, cache_dir, external_cache_only) }) .await?; run_once(db, "v006_cache_persistence", || { v006_cache_persistence::run(db, cache_dir, external_cache_only) }) .await?; Ok(()) } async fn ensure_table(db: &DatabaseConnection) -> Result<(), AppError> { db.execute_unprepared( "CREATE TABLE IF NOT EXISTS upgrades ( name TEXT PRIMARY KEY, completed_at BIGINT NOT NULL )", ) .await?; Ok(()) } async fn is_completed(db: &DatabaseConnection, name: &str) -> Result { let row = db .query_one(sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, "SELECT 1 FROM upgrades WHERE name = $1", [name.into()], )) .await?; Ok(row.is_some()) } async fn mark_completed(db: &DatabaseConnection, name: &str) -> Result<(), AppError> { let now = chrono::Utc::now().timestamp(); db.execute(sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, "INSERT INTO upgrades (name, completed_at) VALUES ($1, $2)", [name.into(), now.into()], )) .await?; Ok(()) } async fn run_once(db: &DatabaseConnection, name: &str, f: F) -> Result<(), AppError> where F: FnOnce() -> Fut, Fut: std::future::Future>, { if is_completed(db, name).await? { return Ok(()); } tracing::info!(name, "running data upgrade"); f().await?; mark_completed(db, name).await?; tracing::info!(name, "data upgrade complete"); Ok(()) }