habits.andr33v.ru/server/api/village/objects.post.ts

79 lines
2.9 KiB
TypeScript

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.' });
}
});