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 <cursoragent@cursor.com>
This commit is contained in:
2026-06-01 21:33:41 +02:00
parent ec11dd8d2b
commit 847c73fda9
4 changed files with 61 additions and 25 deletions
+23
View File
@@ -159,6 +159,29 @@ else
echo "Warning: Docker command not found. Skipping PostgreSQL container management." echo "Warning: Docker command not found. Skipping PostgreSQL container management."
fi 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 # Start backend server
echo "Starting backend API server..." echo "Starting backend API server..."
cd "$REPO_ROOT/server" || exit 1 cd "$REPO_ROOT/server" || exit 1
+4 -10
View File
@@ -77,16 +77,10 @@ router.get('/share-pull', async (req: any, res) => {
const yacht = await prisma.yachtPayload.findUnique({ where: { logbookId } }) const yacht = await prisma.yachtPayload.findUnique({ where: { logbookId } })
const deviation = await prisma.deviationPayload.findUnique({ where: { logbookId } }) const deviation = await prisma.deviationPayload.findUnique({ where: { logbookId } })
const crews = await prisma.crewPayload.findMany({ where: { logbookId } }) const crews = await prisma.crewPayload.findMany({ where: { logbookId } })
const logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({ const { findLogbookCrewSelectionSafe, findLogbookVesselSelectionSafe } =
where: { logbookId } await import('../utils/crewPoolSchema.js')
}) const logbookCrewSelection = await findLogbookCrewSelectionSafe(logbookId)
let logbookVesselSelection = null const logbookVesselSelection = await findLogbookVesselSelectionSafe(logbookId)
const { hasVesselPoolPrismaModels } = await import('../utils/crewPoolSchema.js')
if (hasVesselPoolPrismaModels()) {
logbookVesselSelection = await prisma.logbookVesselSelectionPayload.findUnique({
where: { logbookId }
})
}
const entries = await prisma.entryPayload.findMany({ where: { logbookId } }) const entries = await prisma.entryPayload.findMany({ where: { logbookId } })
const photos = await prisma.photoPayload.findMany({ where: { logbookId } }) const photos = await prisma.photoPayload.findMany({ where: { logbookId } })
const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } }) const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } })
+12 -15
View File
@@ -273,7 +273,7 @@ router.post('/push', async (req: any, res) => {
}) })
} }
} else if (type === 'logbookVessel') { } else if (type === 'logbookVessel') {
const { hasVesselPoolPrismaModels, VESSEL_POOL_MIGRATION_HINT } = const { hasVesselPoolPrismaModels, isMissingPrismaTable, VESSEL_POOL_MIGRATION_HINT } =
await import('../utils/crewPoolSchema.js') await import('../utils/crewPoolSchema.js')
if (!hasVesselPoolPrismaModels()) { if (!hasVesselPoolPrismaModels()) {
results.push({ results.push({
@@ -283,7 +283,7 @@ router.post('/push', async (req: any, res) => {
}) })
continue continue
} }
{ try {
const existing = await prisma.logbookVesselSelectionPayload.findUnique({ where: { logbookId } }) const existing = await prisma.logbookVesselSelectionPayload.findUnique({ where: { logbookId } })
if (existing && new Date(existing.updatedAt) > itemUpdatedAt) { if (existing && new Date(existing.updatedAt) > itemUpdatedAt) {
results.push({ payloadId, status: 'conflict', reason: 'Server version is newer' }) 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 }, create: { logbookId, encryptedData, iv, tag, updatedAt: itemUpdatedAt },
update: { 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 entries = await prisma.entryPayload.findMany({ where: { logbookId } })
const photos = await prisma.photoPayload.findMany({ where: { logbookId } }) const photos = await prisma.photoPayload.findMany({ where: { logbookId } })
const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } }) const gpsTracks = await prisma.gpsTrackPayload.findMany({ where: { logbookId } })
let logbookCrewSelection = null const { findLogbookCrewSelectionSafe, findLogbookVesselSelectionSafe } =
let logbookVesselSelection = null await import('../utils/crewPoolSchema.js')
const { hasCrewPoolPrismaModels, hasVesselPoolPrismaModels } = await import('../utils/crewPoolSchema.js') const logbookCrewSelection = await findLogbookCrewSelectionSafe(logbookId)
if (hasCrewPoolPrismaModels()) { const logbookVesselSelection = await findLogbookVesselSelectionSafe(logbookId)
logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({
where: { logbookId }
})
}
if (hasVesselPoolPrismaModels()) {
logbookVesselSelection = await prisma.logbookVesselSelectionPayload.findUnique({
where: { logbookId }
})
}
return res.json({ return res.json({
yacht, yacht,
+22
View File
@@ -37,3 +37,25 @@ export function isMissingPrismaTable(error: unknown): boolean {
(error as { code: string }).code === 'P2021' (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
}
}