diff --git a/README.md b/README.md index e4f2a66..b2dada1 100644 --- a/README.md +++ b/README.md @@ -317,11 +317,15 @@ If the job passes the frontend check but the backend never becomes healthy: 1. Open the failed job log and read **Backend logs** (printed before rollback). 2. Match the error to a fix — do not guess: + - **`Detected applied migration not resolved locally: 6`** (or 2, 4) — prod DB still lists + dev seed migrations removed from `db/migration`. Fixed in app via `ProdFlywayConfig` + (repair before migrate); redeploy after that commit is on `master`. - **`password authentication failed`** — DB credentials in the running stack do not match what Postgres was initialized with; fix credentials or Postgres password to match (only wipe the volume if you accept losing prod data). - **`Production requires ADMIN_EMAIL and ADMIN_PASSWORD`** — add those Forgejo secrets. - - **Flyway / migration errors** — fix schema or migration history before redeploying. + - **Other Flyway / migration errors** — read the stack trace; do not wipe the volume unless + the log clearly requires it. 3. **DBeaver from your laptop** — prod Postgres binds to `127.0.0.1:5433` on the server only. Use an SSH tunnel, then host `localhost` port `5433` (not `192.168.0.59` directly). diff --git a/backend/src/main/java/se/bilhalsning/config/ProdFlywayConfig.java b/backend/src/main/java/se/bilhalsning/config/ProdFlywayConfig.java new file mode 100644 index 0000000..bdf38b6 --- /dev/null +++ b/backend/src/main/java/se/bilhalsning/config/ProdFlywayConfig.java @@ -0,0 +1,24 @@ +package se.bilhalsning.config; + +import org.flywaydb.core.Flyway; +import org.springframework.boot.flyway.autoconfigure.FlywayMigrationStrategy; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("prod") +public class ProdFlywayConfig { + + /** + * Prod databases may record dev seed migrations (V2, V4, V6) from before they moved to + * db/dev-migration/. Repair marks those missing scripts as deleted so validate passes. + */ + @Bean + public FlywayMigrationStrategy prodFlywayMigrationStrategy() { + return (Flyway flyway) -> { + flyway.repair(); + flyway.migrate(); + }; + } +} diff --git a/backend/src/test/java/se/bilhalsning/config/ProdFlywayConfigTest.java b/backend/src/test/java/se/bilhalsning/config/ProdFlywayConfigTest.java new file mode 100644 index 0000000..e7909b1 --- /dev/null +++ b/backend/src/test/java/se/bilhalsning/config/ProdFlywayConfigTest.java @@ -0,0 +1,33 @@ +package se.bilhalsning.config; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.flyway.autoconfigure.FlywayMigrationStrategy; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.mockito.InOrder; + +@SpringBootTest( + properties = { + "app.admin.email=admin@test.se", + "app.admin.password=test-password", + }) +@ActiveProfiles("prod") +class ProdFlywayConfigTest { + + @Autowired + private FlywayMigrationStrategy flywayMigrationStrategy; + + @Test + void shouldRepairBeforeMigrateOnProd() { + Flyway flyway = mock(Flyway.class); + flywayMigrationStrategy.migrate(flyway); + InOrder order = inOrder(flyway); + order.verify(flyway).repair(); + order.verify(flyway).migrate(); + } +}