74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
import { json, error } from '@sveltejs/kit';
|
|
import type { RequestHandler } from './$types';
|
|
import { db } from '$lib/server/db';
|
|
import { animals, sightings, photos } from '$lib/server/db/schema';
|
|
import { registerAnimalSchema } from '$lib/validation/sighting';
|
|
import { sql } from 'drizzle-orm';
|
|
import { env } from '$env/dynamic/private';
|
|
|
|
const MAX_DISTANCE_METERS = 30_000;
|
|
|
|
export const POST: RequestHandler = async ({ request }) => {
|
|
const body = await request.json().catch(() => null);
|
|
const parsed = registerAnimalSchema.safeParse(body);
|
|
|
|
if (!parsed.success) {
|
|
throw error(400, { message: parsed.error.issues[0]?.message ?? 'Invalid request' });
|
|
}
|
|
|
|
const data = parsed.data;
|
|
|
|
const [distRow] = await db.execute(sql`
|
|
SELECT ST_Distance(
|
|
ST_MakePoint(${data.deviceLng}, ${data.deviceLat})::geography,
|
|
ST_MakePoint(${data.lng}, ${data.lat})::geography
|
|
) AS dist
|
|
`);
|
|
|
|
const dist = Number((distRow as Record<string, unknown>).dist);
|
|
if (dist > MAX_DISTANCE_METERS) {
|
|
throw error(422, {
|
|
message: `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`
|
|
});
|
|
}
|
|
|
|
const result = await db.transaction(async (tx) => {
|
|
const [animal] = await tx
|
|
.insert(animals)
|
|
.values({
|
|
species: data.species,
|
|
breed: data.breed,
|
|
animalName: data.animalName,
|
|
description: data.description,
|
|
aiBreedSuggestion: data.aiBreedSuggestion,
|
|
aiBreedConfidence: data.aiBreedConfidence
|
|
})
|
|
.returning({ id: animals.id });
|
|
|
|
const [sighting] = await tx
|
|
.insert(sightings)
|
|
.values({
|
|
animalId: animal.id,
|
|
reporterName: data.reporterName,
|
|
seenAt: new Date(data.seenAt),
|
|
lat: data.lat,
|
|
lng: data.lng
|
|
})
|
|
.returning({ id: sightings.id });
|
|
|
|
await tx.insert(photos).values(
|
|
data.photoKeys.map((key, i) => ({
|
|
sightingId: sighting.id,
|
|
r2Key: key,
|
|
url: `${env.R2_PUBLIC_URL}/${key}`,
|
|
sortOrder: i
|
|
}))
|
|
);
|
|
|
|
return { animalId: animal.id, sightingId: sighting.id };
|
|
});
|
|
|
|
console.log(`[animals] created animal ${result.animalId}, sighting ${result.sightingId}`);
|
|
return json(result, { status: 201 });
|
|
};
|