From 847c73fda90105f53dfb49dca844a25044dc21a7 Mon Sep 17 00:00:00 2001 From: elpatron Date: Mon, 1 Jun 2026 21:33:41 +0200 Subject: [PATCH] fix(dev): Prisma db push beim Start und sichere Vessel-Pool-Sync-Abfragen start-dev.sh synchronisiert Schema vor dem Backend; Sync/Collaboration liefern bei fehlenden Tabellen null statt 500. Co-authored-by: Cursor --- scripts/start-dev.sh | 23 +++++++++++++++++++++++ server/src/routes/collaboration.ts | 14 ++++---------- server/src/routes/sync.ts | 27 ++++++++++++--------------- server/src/utils/crewPoolSchema.ts | 22 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh index f7fc147..5aa8129 100755 --- a/scripts/start-dev.sh +++ b/scripts/start-dev.sh @@ -159,6 +159,29 @@ else echo "Warning: Docker command not found. Skipping PostgreSQL container management." fi +# Sync Prisma client and database schema (dev) +sync_prisma_schema() { + local server_dir="$REPO_ROOT/server" + if [ ! -f "$server_dir/prisma/schema.prisma" ]; then + return 0 + fi + if [ ! -d "$server_dir/node_modules" ]; then + echo "Warning: server/node_modules missing — skipping Prisma sync. Run: cd server && npm ci" + return 0 + fi + echo "Syncing Prisma client and database schema..." + ( + cd "$server_dir" || exit 1 + npx prisma generate && npx prisma db push + ) || { + echo "Error: Prisma generate/db push failed. Check DATABASE_URL in .env and PostgreSQL." + exit 1 + } + echo "Prisma schema is in sync." +} + +sync_prisma_schema + # Start backend server echo "Starting backend API server..." cd "$REPO_ROOT/server" || exit 1 diff --git a/server/src/routes/collaboration.ts b/server/src/routes/collaboration.ts index af98cb0..e6177e8 100644 --- a/server/src/routes/collaboration.ts +++ b/server/src/routes/collaboration.ts @@ -77,16 +77,10 @@ router.get('/share-pull', async (req: any, res) => { const yacht = await prisma.yachtPayload.findUnique({ where: { logbookId } }) const deviation = await prisma.deviationPayload.findUnique({ where: { logbookId } }) const crews = await prisma.crewPayload.findMany({ where: { logbookId } }) - const logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({ - where: { logbookId } - }) - let logbookVesselSelection = null - const { hasVesselPoolPrismaModels } = await import('../utils/crewPoolSchema.js') - if (hasVesselPoolPrismaModels()) { - logbookVesselSelection = await prisma.logbookVesselSelectionPayload.findUnique({ - where: { logbookId } - }) - } + const { findLogbookCrewSelectionSafe, findLogbookVesselSelectionSafe } = + await import('../utils/crewPoolSchema.js') + const logbookCrewSelection = await findLogbookCrewSelectionSafe(logbookId) + const logbookVesselSelection = await findLogbookVesselSelectionSafe(logbookId) const entries = await prisma.entryPayload.findMany({ where: { logbookId } }) const photos = await prisma.photoPayload.findMany({ where: { logbookId } }) const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } }) diff --git a/server/src/routes/sync.ts b/server/src/routes/sync.ts index ee7ae72..df3a979 100644 --- a/server/src/routes/sync.ts +++ b/server/src/routes/sync.ts @@ -273,7 +273,7 @@ router.post('/push', async (req: any, res) => { }) } } else if (type === 'logbookVessel') { - const { hasVesselPoolPrismaModels, VESSEL_POOL_MIGRATION_HINT } = + const { hasVesselPoolPrismaModels, isMissingPrismaTable, VESSEL_POOL_MIGRATION_HINT } = await import('../utils/crewPoolSchema.js') if (!hasVesselPoolPrismaModels()) { results.push({ @@ -283,7 +283,7 @@ router.post('/push', async (req: any, res) => { }) continue } - { + try { const existing = await prisma.logbookVesselSelectionPayload.findUnique({ where: { logbookId } }) if (existing && new Date(existing.updatedAt) > itemUpdatedAt) { results.push({ payloadId, status: 'conflict', reason: 'Server version is newer' }) @@ -294,6 +294,12 @@ router.post('/push', async (req: any, res) => { create: { logbookId, encryptedData, iv, tag, updatedAt: itemUpdatedAt }, update: { encryptedData, iv, tag, updatedAt: itemUpdatedAt } }) + } catch (err: unknown) { + if (isMissingPrismaTable(err)) { + results.push({ payloadId, status: 'error', error: VESSEL_POOL_MIGRATION_HINT }) + continue + } + throw err } } @@ -360,19 +366,10 @@ router.get('/pull', async (req: any, res) => { const entries = await prisma.entryPayload.findMany({ where: { logbookId } }) const photos = await prisma.photoPayload.findMany({ where: { logbookId } }) const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } }) - let logbookCrewSelection = null - let logbookVesselSelection = null - const { hasCrewPoolPrismaModels, hasVesselPoolPrismaModels } = await import('../utils/crewPoolSchema.js') - if (hasCrewPoolPrismaModels()) { - logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({ - where: { logbookId } - }) - } - if (hasVesselPoolPrismaModels()) { - logbookVesselSelection = await prisma.logbookVesselSelectionPayload.findUnique({ - where: { logbookId } - }) - } + const { findLogbookCrewSelectionSafe, findLogbookVesselSelectionSafe } = + await import('../utils/crewPoolSchema.js') + const logbookCrewSelection = await findLogbookCrewSelectionSafe(logbookId) + const logbookVesselSelection = await findLogbookVesselSelectionSafe(logbookId) return res.json({ yacht, diff --git a/server/src/utils/crewPoolSchema.ts b/server/src/utils/crewPoolSchema.ts index 09007fc..89e67b9 100644 --- a/server/src/utils/crewPoolSchema.ts +++ b/server/src/utils/crewPoolSchema.ts @@ -37,3 +37,25 @@ export function isMissingPrismaTable(error: unknown): boolean { (error as { code: string }).code === 'P2021' ) } + +/** Pull-safe: returns null when models or DB tables are missing (P2021). */ +export async function findLogbookCrewSelectionSafe(logbookId: string) { + if (!hasCrewPoolPrismaModels()) return null + try { + return await prisma.logbookCrewSelectionPayload.findUnique({ where: { logbookId } }) + } catch (error) { + if (isMissingPrismaTable(error)) return null + throw error + } +} + +/** Pull-safe: returns null when models or DB tables are missing (P2021). */ +export async function findLogbookVesselSelectionSafe(logbookId: string) { + if (!hasVesselPoolPrismaModels()) return null + try { + return await prisma.logbookVesselSelectionPayload.findUnique({ where: { logbookId } }) + } catch (error) { + if (isMissingPrismaTable(error)) return null + throw error + } +}