Files
pichunt/server/api/admin/hunt/[huntId]/map.get.ts
T
Pascal 91d9bc4cbb
Docker Image CI / test (push) Successful in 1m2s
Tests / unit (push) Successful in 1m2s
Tests / integration (push) Successful in 1m39s
Docker Image CI / deploy (push) Successful in 1m24s
style(prettier): Fix formatting
2026-09-27 21:00:09 +02:00

127 lines
2.9 KiB
TypeScript

import type { HuntMapData, MapPosition } from '#shared/types/map';
import { useValidatedParams, z, zh } from 'h3-zod';
import prisma from '~~/lib/prisma';
/**
* 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<HuntMapData> => {
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<number>();
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
}))
};
});