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."
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
+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 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 } })
+12 -15
View File
@@ -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,
+22
View File
@@ -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
}
}