import { getUserIdFromSession } from '../../utils/auth'; import { VILLAGE_GRID_SIZE, ITEM_COSTS } from '../../utils/village'; import { VillageObjectType } from '@prisma/client'; // --- Handler --- export default defineEventHandler(async (event) => { const userId = await getUserIdFromSession(event); const { type, x, y } = await readBody(event); // 1. --- Validation --- if (!type || typeof x !== 'number' || typeof y !== 'number') { throw createError({ statusCode: 400, statusMessage: 'Invalid request body. "type", "x", and "y" are required.' }); } if (x < 0 || x >= VILLAGE_GRID_SIZE.width || y < 0 || y >= VILLAGE_GRID_SIZE.height) { throw createError({ statusCode: 400, statusMessage: 'Object placed outside of village bounds.' }); } const cost = ITEM_COSTS[type as VillageObjectType]; if (cost === undefined) { throw createError({ statusCode: 400, statusMessage: 'Cannot place objects of this type.' }); } // 2. --- Fetch current state and enforce rules --- const village = await prisma.village.findUnique({ where: { userId }, include: { objects: true }, }); if (!village) { // This should not happen if GET /village is called first, but as a safeguard: throw createError({ statusCode: 404, statusMessage: 'Village not found.' }); } // Rule: Cell must be empty if (village.objects.some(obj => obj.x === x && obj.y === y)) { throw createError({ statusCode: 409, statusMessage: 'A building already exists on this cell.' }); } // Rule: Fields require available workers if (type === 'FIELD') { const houseCount = village.objects.filter(obj => obj.type === 'HOUSE').length; const fieldCount = village.objects.filter(obj => obj.type === 'FIELD').length; if (fieldCount >= houseCount) { throw createError({ statusCode: 400, statusMessage: 'Not enough available workers to build a new field. Build more houses first.' }); } } // 3. --- Perform atomic transaction --- try { const [, newObject] = await prisma.$transaction([ prisma.user.update({ where: { id: userId, coins: { gte: cost } }, data: { coins: { decrement: cost } }, }), prisma.villageObject.create({ data: { villageId: village.id, type, x, y, }, }), ]); setResponseStatus(event, 201); return { id: newObject.id, type: newObject.type, x: newObject.x, y: newObject.y, cropType: null, isGrown: null, }; } catch (e) { // Catches failed transactions, likely from the user.update 'where' clause (insufficient funds) throw createError({ statusCode: 402, statusMessage: 'Insufficient coins to build.' }); } });