fix(live-log): prevent freeze without GPS and prompt for day-start position

Harden geolocation with watchdog timeouts and permission checks so
desktop browsers without GPS no longer hang Live-Log. Show a hint to
log a position when none exists for the day.

Return 503 when crew-pool Prisma models are missing instead of crashing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-01 19:20:34 +02:00
co-authored by Cursor
parent 98c0ed81d4
commit 2304f95ac1
13 changed files with 324 additions and 36 deletions
+4 -2
View File
@@ -5,9 +5,11 @@
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"build": "prisma generate && tsc",
"postinstall": "prisma generate",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"dev": "prisma generate && tsx watch src/index.ts",
"db:push": "prisma db push",
"test": "vitest run",
"test:watch": "vitest"
},
+20
View File
@@ -506,17 +506,33 @@ router.put('/appearance-prefs', requireUser, async (req: any, res) => {
router.get('/person-pool', requireUser, async (req: any, res) => {
try {
const { hasCrewPoolPrismaModels, isMissingPrismaTable, CREW_POOL_MIGRATION_HINT } =
await import('../utils/crewPoolSchema.js')
if (!hasCrewPoolPrismaModels()) {
console.warn('Person pool Prisma models missing — run prisma generate')
return res.status(503).json({ error: CREW_POOL_MIGRATION_HINT, persons: [] })
}
const persons = await prisma.personPayload.findMany({
where: { userId: req.userId }
})
return res.json({ persons })
} catch (error: unknown) {
const { isMissingPrismaTable, CREW_POOL_MIGRATION_HINT } = await import('../utils/crewPoolSchema.js')
if (isMissingPrismaTable(error)) {
return res.status(503).json({ error: CREW_POOL_MIGRATION_HINT, persons: [] })
}
return sendInternalError(res, error, 'auth/person-pool-get')
}
})
router.post('/person-pool/push', requireUser, async (req: any, res) => {
try {
const { hasCrewPoolPrismaModels, isMissingPrismaTable, CREW_POOL_MIGRATION_HINT } =
await import('../utils/crewPoolSchema.js')
if (!hasCrewPoolPrismaModels()) {
return res.status(503).json({ error: CREW_POOL_MIGRATION_HINT })
}
const { items } = req.body
if (!items || !Array.isArray(items)) {
return res.status(400).json({ error: 'items array is required' })
@@ -569,6 +585,10 @@ router.post('/person-pool/push', requireUser, async (req: any, res) => {
return res.json({ results })
} catch (error: unknown) {
const { isMissingPrismaTable, CREW_POOL_MIGRATION_HINT } = await import('../utils/crewPoolSchema.js')
if (isMissingPrismaTable(error)) {
return res.status(503).json({ error: CREW_POOL_MIGRATION_HINT })
}
return sendInternalError(res, error, 'auth/person-pool-push')
}
})
+17 -3
View File
@@ -248,6 +248,16 @@ router.post('/push', async (req: any, res) => {
})
}
} else if (type === 'logbookCrew') {
const { hasCrewPoolPrismaModels, CREW_POOL_MIGRATION_HINT } =
await import('../utils/crewPoolSchema.js')
if (!hasCrewPoolPrismaModels()) {
results.push({
payloadId,
status: 'error',
error: CREW_POOL_MIGRATION_HINT
})
continue
}
{
const existing = await prisma.logbookCrewSelectionPayload.findUnique({ where: { logbookId } })
if (existing && new Date(existing.updatedAt) > itemUpdatedAt) {
@@ -325,9 +335,13 @@ 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 } })
const logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({
where: { logbookId }
})
let logbookCrewSelection = null
const { hasCrewPoolPrismaModels } = await import('../utils/crewPoolSchema.js')
if (hasCrewPoolPrismaModels()) {
logbookCrewSelection = await prisma.logbookCrewSelectionPayload.findUnique({
where: { logbookId }
})
}
return res.json({
yacht,
+25
View File
@@ -0,0 +1,25 @@
import { prisma } from '../db.js'
/** Prisma client includes delegates only after `npx prisma generate` on the current schema. */
export function hasCrewPoolPrismaModels(): boolean {
const client = prisma as unknown as {
personPayload?: { findMany: unknown }
logbookCrewSelectionPayload?: { findUnique: unknown }
}
return (
typeof client.personPayload?.findMany === 'function' &&
typeof client.logbookCrewSelectionPayload?.findUnique === 'function'
)
}
export const CREW_POOL_MIGRATION_HINT =
'Crew-Pool-Datenbank fehlt. Im Ordner server ausführen: npx prisma generate && npx prisma db push — danach Server neu starten.'
export function isMissingPrismaTable(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
(error as { code: string }).code === 'P2021'
)
}