import { getUserIdFromSession } from '../../../utils/auth'; import { VILLAGE_GRID_SIZE, MOVE_COST, isCropGrown } from '../../../utils/village'; // --- Handler --- export default defineEventHandler(async (event) => { const userId = await getUserIdFromSession(event); const objectId = parseInt(event.context.params?.id || '', 10); const { x, y } = await readBody(event); // 1. --- Validation --- if (isNaN(objectId)) { throw createError({ statusCode: 400, statusMessage: 'Invalid object ID.' }); } if (typeof x !== 'number' || typeof y !== 'number') { throw createError({ statusCode: 400, statusMessage: 'Invalid request body. "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: 'Cannot move object outside of village bounds.' }); } // 2. --- Fetch state and enforce rules --- const village = await prisma.village.findUnique({ where: { userId }, include: { objects: true }, }); if (!village) { throw createError({ statusCode: 404, statusMessage: 'Village not found.' }); } const objectToMove = village.objects.find(obj => obj.id === objectId); // Rule: Object must exist and belong to the user (implicit via village) if (!objectToMove) { throw createError({ statusCode: 404, statusMessage: 'Object not found.' }); } // Rule: Cannot move obstacles if (objectToMove.type === 'OBSTACLE') { throw createError({ statusCode: 400, statusMessage: 'Cannot move obstacles. They must be cleared.' }); } // Rule: Target cell must be empty (and not the same cell) if (village.objects.some(obj => obj.x === x && obj.y === y)) { throw createError({ statusCode: 409, statusMessage: 'Target cell is already occupied.' }); } // 3. --- Perform atomic transaction --- try { const [, updatedObject] = await prisma.$transaction([ prisma.user.update({ where: { id: userId, coins: { gte: MOVE_COST } }, data: { coins: { decrement: MOVE_COST } }, }), prisma.villageObject.update({ where: { id: objectId }, data: { x, y }, }), ]); return { id: updatedObject.id, type: updatedObject.type, x: updatedObject.x, y: updatedObject.y, cropType: updatedObject.cropType, isGrown: isCropGrown(updatedObject.plantedAt, updatedObject.cropType), }; } catch (e) { throw createError({ statusCode: 402, statusMessage: 'Insufficient coins to move object.' }); } });