From 0b9fb00218716f94fc98786e19cd7005bf9375fb Mon Sep 17 00:00:00 2001 From: KodekaTeam Date: Wed, 3 Jun 2026 02:05:59 +0700 Subject: [PATCH] update: add validation for migration process --- docker-entrypoint.sh | 206 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 1 deletion(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 40552d3..1c787b1 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -43,7 +43,211 @@ check(); NODE if [ -d /app/prisma/migrations ] && [ "$(find /app/prisma/migrations -mindepth 1 -maxdepth 1 | wc -l)" -gt 0 ]; then - npx prisma migrate deploy + db_action="$(node <<'NODE' +const { PrismaClient } = require("@prisma/client"); + +const prisma = new PrismaClient(); + async function main() { + const tableRows = await prisma.$queryRaw` + SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'public' + AND table_type = 'BASE TABLE' + `; + + const enumRows = await prisma.$queryRaw` + SELECT typname + FROM pg_type t + JOIN pg_namespace n ON n.oid = t.typnamespace + WHERE n.nspname = 'public' + AND t.typtype = 'e' + `; + + const tables = new Set(tableRows.map((row) => row.table_name)); + + const hasMigrationTable = tables.has("_prisma_migrations"); + + const appTables = [...tables].filter( + (table) => table !== "_prisma_migrations" + ); + + const hasEnums = enumRows.length > 0; + + let failedMigrations = []; + let migrationCount = 0; + + if (hasMigrationTable) { + failedMigrations = await prisma.$queryRaw` + SELECT migration_name + FROM "_prisma_migrations" + WHERE finished_at IS NULL + AND rolled_back_at IS NULL + `; + + const migrationCountResult = await prisma.$queryRaw` + SELECT COUNT(*)::int AS count + FROM "_prisma_migrations" + `; + + migrationCount = migrationCountResult[0].count; + } + + /** + * STATE #1 + * Fresh database + */ + if ( + appTables.length === 0 && + !hasMigrationTable && + !hasEnums + ) { + console.error( + "Fresh database detected. Applying migrations." + ); + + console.log("migrate"); + return; + } + + /** + * STATE #2 + * Existing schema without Prisma migration history + */ + if ( + appTables.length > 0 && + !hasMigrationTable + ) { + console.error( + "Application tables exist but Prisma migration history table is missing." + ); + + console.error( + "Database state is inconsistent." + ); + + console.log("blocked"); + return; + } + + /** + * STATE #3 + * Prisma migration history exists + */ + if (hasMigrationTable) { + if ( + migrationCount === 0 && + ( + appTables.length > 0 || + hasEnums + ) + ) { + console.error( + "Database contains schema objects but Prisma migration history is empty." + ); + + console.error( + "Database state is inconsistent." + ); + + console.error( + "Do not delete records from _prisma_migrations manually." + ); + + console.log("blocked"); + return; + } + + if (failedMigrations.length > 0) { + console.error( + `Failed Prisma migrations detected: ${failedMigrations + .map((row) => row.migration_name) + .join(", ")}` + ); + + console.error( + "Resolve the failed migration before starting Doktainer." + ); + + console.log("blocked"); + return; + } + + console.error( + "Prisma migration history detected. Applying pending migrations." + ); + + console.log("migrate"); + return; + } + + /** + * STATE #4 + * Partial schema / inconsistent state + */ + console.error(""); + console.error( + "Database appears to be partially initialized." + ); + + console.error( + "Found Prisma objects without a valid schema state." + ); + + console.error(""); + console.error( + "This may happen when:" + ); + + console.error( + "- a migration failed previously" + ); + + console.error( + "- the database was partially restored" + ); + + console.error( + "- schema objects were created manually" + ); + + console.error(""); + console.error( + "Doktainer will not automatically modify the database." + ); + + console.error( + "Manual intervention is required." + ); + + console.log("blocked"); + } + +main() + .catch((error) => { + console.error(error); + console.log("blocked"); + }) + .finally(async () => { + await prisma.$disconnect(); + }); +NODE +)" + + case "$db_action" in + migrate) + npx prisma migrate deploy + ;; + push) + npx prisma db push + ;; + blocked) + exit 1 + ;; + *) + echo "Unknown database setup action: $db_action" >&2 + exit 1 + ;; + esac else npx prisma db push fi