different approach to migration
This commit is contained in:
@@ -0,0 +1,111 @@
|
|||||||
|
package com.storycove.config;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs database migrations on application startup.
|
||||||
|
* This ensures all library databases have the required schema,
|
||||||
|
* particularly for tables like backup_jobs that were added after initial deployment.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order(1) // Run early in startup sequence
|
||||||
|
public class DatabaseMigrationRunner implements CommandLineRunner {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DatabaseMigrationRunner.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
@Value("${spring.datasource.username}")
|
||||||
|
private String dbUsername;
|
||||||
|
|
||||||
|
@Value("${spring.datasource.password}")
|
||||||
|
private String dbPassword;
|
||||||
|
|
||||||
|
// List of all library databases that need migrations
|
||||||
|
private static final List<String> LIBRARY_DATABASES = Arrays.asList(
|
||||||
|
"storycove", // default database
|
||||||
|
"storycove_afterdark",
|
||||||
|
"storycove_clas",
|
||||||
|
"storycove_secret"
|
||||||
|
);
|
||||||
|
|
||||||
|
// SQL for backup_jobs table migration (idempotent)
|
||||||
|
private static final String BACKUP_JOBS_MIGRATION = """
|
||||||
|
CREATE TABLE IF NOT EXISTS backup_jobs (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
library_id VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(50) NOT NULL CHECK (type IN ('DATABASE_ONLY', 'COMPLETE')),
|
||||||
|
status VARCHAR(50) NOT NULL CHECK (status IN ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED', 'EXPIRED')),
|
||||||
|
file_path VARCHAR(1000),
|
||||||
|
file_size_bytes BIGINT,
|
||||||
|
progress_percent INTEGER,
|
||||||
|
error_message VARCHAR(1000),
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
started_at TIMESTAMP,
|
||||||
|
completed_at TIMESTAMP,
|
||||||
|
expires_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_backup_jobs_library_id ON backup_jobs(library_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_backup_jobs_status ON backup_jobs(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_backup_jobs_expires_at ON backup_jobs(expires_at);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_backup_jobs_created_at ON backup_jobs(created_at DESC);
|
||||||
|
""";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
logger.info("🗄️ Starting database migrations...");
|
||||||
|
|
||||||
|
for (String database : LIBRARY_DATABASES) {
|
||||||
|
try {
|
||||||
|
applyMigrations(database);
|
||||||
|
logger.info("✅ Successfully applied migrations to database: {}", database);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Log error but don't fail startup if database doesn't exist yet
|
||||||
|
if (e.getMessage() != null && e.getMessage().contains("does not exist")) {
|
||||||
|
logger.warn("⚠️ Database {} does not exist yet, skipping migrations", database);
|
||||||
|
} else {
|
||||||
|
logger.error("❌ Failed to apply migrations to database: {}", database, e);
|
||||||
|
// Don't throw - allow application to start even if some migrations fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("✅ Database migrations completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyMigrations(String database) throws Exception {
|
||||||
|
// We need to connect directly to each database, not through SmartRoutingDataSource
|
||||||
|
// Build connection URL from the default datasource URL
|
||||||
|
String originalUrl = dataSource.getConnection().getMetaData().getURL();
|
||||||
|
String baseUrl = originalUrl.substring(0, originalUrl.lastIndexOf('/'));
|
||||||
|
String targetUrl = baseUrl + "/" + database;
|
||||||
|
|
||||||
|
// Connect directly to target database using credentials from application properties
|
||||||
|
try (Connection conn = java.sql.DriverManager.getConnection(
|
||||||
|
targetUrl,
|
||||||
|
dbUsername,
|
||||||
|
dbPassword
|
||||||
|
)) {
|
||||||
|
// Apply backup_jobs migration
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.execute(BACKUP_JOBS_MIGRATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Applied backup_jobs migration to {}", database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ services:
|
|||||||
- library_config:/app/config
|
- library_config:/app/config
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_started
|
condition: service_healthy
|
||||||
solr:
|
solr:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
networks:
|
networks:
|
||||||
@@ -71,35 +71,6 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
migrations:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- ./backend/create_backup_jobs_table.sql:/migrations/create_backup_jobs_table.sql:ro
|
|
||||||
networks:
|
|
||||||
- storycove-network
|
|
||||||
environment:
|
|
||||||
- PGPASSWORD=${DB_PASSWORD}
|
|
||||||
entrypoint: /bin/sh
|
|
||||||
command: >
|
|
||||||
-c "
|
|
||||||
echo '🗄️ Applying database migrations...';
|
|
||||||
for DB in storycove storycove_afterdark storycove_clas storycove_secret; do
|
|
||||||
if psql -h postgres -U storycove -lqt | cut -d '|' -f 1 | grep -qw \$$DB; then
|
|
||||||
echo \" ✓ Applying migration to \$$DB...\";
|
|
||||||
psql -h postgres -U storycove -d \$$DB -f /migrations/create_backup_jobs_table.sql > /dev/null 2>&1 || true;
|
|
||||||
fi;
|
|
||||||
done;
|
|
||||||
echo '✅ Migrations complete!';
|
|
||||||
sleep infinity;
|
|
||||||
"
|
|
||||||
deploy:
|
|
||||||
restart_policy:
|
|
||||||
condition: on-failure
|
|
||||||
max_attempts: 1
|
|
||||||
|
|
||||||
|
|
||||||
solr:
|
solr:
|
||||||
build:
|
build:
|
||||||
|
|||||||
Reference in New Issue
Block a user