import { useValidatedParams, z, zh } from 'h3-zod'; import prisma from '~~/lib/prisma'; import type { HuntMapData, MapPosition } from '#shared/types/map'; /** * Aggregate live-map payload for hunt organizers. * * Gated by HuntMember membership AND `hunt.trackDistance` (404 otherwise, so * non-members cannot probe the hunt). Returns plain JSON: `Date` fields as ISO * strings, Prisma `Decimal` coordinates coerced to numbers. */ export default defineEventHandler(async (event): Promise => { const user = await requireUserSession(event); const userId = user.user.id; const { huntId } = await useValidatedParams( event, z.object({ huntId: zh.numAsString }) ); const hunt = await prisma.hunt.findUnique({ where: { id: huntId, trackDistance: true, members: { some: { memberId: userId } } }, select: { id: true, teams: { orderBy: { createdAt: 'asc' }, select: { id: true, name: true, owner: { select: { id: true, name: true, email: true } }, members: { select: { member: { select: { id: true, name: true, email: true } } } } } } } }); if (!hunt) { throw createError({ status: 404, statusText: 'Not found!' }); } const teams = hunt.teams.map(({ members, ...team }) => ({ ...team, members: members.map((m) => m.member) })); // Latest TrackPoint per user for this hunt. Walk-scale volume, so fetch the // newest-first and keep the first fix seen per user. const points = await prisma.trackPoint.findMany({ where: { huntId }, orderBy: { recordedAt: 'desc' }, select: { userId: true, lat: true, lon: true, accuracy: true, recordedAt: true } }); const seen = new Set(); const positions: MapPosition[] = []; for (const p of points) { if (seen.has(p.userId)) continue; seen.add(p.userId); positions.push({ userId: p.userId, lat: Number(p.lat), lon: Number(p.lon), accuracy: p.accuracy, recordedAt: p.recordedAt.toISOString() }); } const answers = await prisma.questAnswer.findMany({ where: { quest: { huntId }, lat: { not: null }, lon: { not: null } }, select: { id: true, lat: true, lon: true, createdAt: true, memberId: true, teamId: true, picture: { select: { url: true } }, quest: { select: { id: true, title_de: true, title_en: true } } } }); return { teams, positions, answers: answers.map((a) => ({ id: a.id, lat: Number(a.lat), lon: Number(a.lon), createdAt: a.createdAt.toISOString(), memberId: a.memberId, teamId: a.teamId, pictureUrl: a.picture?.url ?? null, quest: a.quest })) }; });