Refactor to vertical slice
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { assertAvailabilityRanges } from '@/lib/booking-utils';
|
||||
import { ComplexAccessError, ensureComplexAccess } from '@/lib/complex-access';
|
||||
import { Errors } from '@/lib/errors';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { type Result, err, ok } from '@/lib/result';
|
||||
import { evaluatePlanUsage, parsePlanRules } from '@/modules/plan/services/plan-rules.service';
|
||||
import type { CreateCourtInput } from '@repo/api-contract';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
import { CourtBusinessError } from '../../shared/errors';
|
||||
import { ensureActiveSport, getCourtByIdForUser } from '../../shared/guards';
|
||||
import { mapCourtResponse } from '../../shared/mappers';
|
||||
|
||||
async function enforcePlanCourtLimit(complexId: string): Promise<Result<void>> {
|
||||
const complex = await db.complex.findUnique({
|
||||
where: { id: complexId },
|
||||
select: {
|
||||
plan: {
|
||||
select: {
|
||||
rules: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!complex?.plan) {
|
||||
return ok(undefined);
|
||||
}
|
||||
|
||||
const rules = parsePlanRules(complex.plan.rules);
|
||||
const courtsCount = await db.court.count({
|
||||
where: { complexId },
|
||||
});
|
||||
|
||||
const violations = evaluatePlanUsage(rules, {
|
||||
courtsCount,
|
||||
bookingsToday: 0,
|
||||
});
|
||||
|
||||
const maxCourtViolation = violations.find((violation) => violation.code === 'MAX_COURTS_REACHED');
|
||||
|
||||
if (maxCourtViolation) {
|
||||
return err(Errors.conflict(maxCourtViolation.message));
|
||||
}
|
||||
|
||||
return ok(undefined);
|
||||
}
|
||||
|
||||
export async function createCourt(
|
||||
userId: string,
|
||||
complexId: string,
|
||||
input: CreateCourtInput
|
||||
): Promise<Result<ReturnType<typeof mapCourtResponse>>> {
|
||||
try {
|
||||
await ensureComplexAccess(complexId, userId);
|
||||
} catch (error) {
|
||||
if (error instanceof ComplexAccessError) {
|
||||
return err(Errors.forbidden(error.message));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const sportResult = await ensureActiveSport(input.sportId);
|
||||
if (!sportResult.ok) return sportResult;
|
||||
|
||||
const planResult = await enforcePlanCourtLimit(complexId);
|
||||
if (!planResult.ok) return planResult;
|
||||
|
||||
assertAvailabilityRanges(input.availability);
|
||||
|
||||
const createdCourt = await db.$transaction(async (tx) => {
|
||||
const court = await tx.court.create({
|
||||
data: {
|
||||
id: uuidv7(),
|
||||
complexId,
|
||||
sportId: input.sportId,
|
||||
name: input.name.trim(),
|
||||
slotDurationMinutes: input.slotDurationMinutes,
|
||||
basePrice: input.basePrice,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.courtAvailability.createMany({
|
||||
data: input.availability.map((availability) => ({
|
||||
id: uuidv7(),
|
||||
courtId: court.id,
|
||||
dayOfWeek: availability.dayOfWeek,
|
||||
startTime: availability.startTime,
|
||||
endTime: availability.endTime,
|
||||
})),
|
||||
});
|
||||
|
||||
return court;
|
||||
});
|
||||
|
||||
const court = await getCourtByIdForUser(createdCourt.id, userId);
|
||||
|
||||
if (!court) {
|
||||
return err(Errors.notFound('No se pudo obtener la cancha creada.'));
|
||||
}
|
||||
|
||||
return ok(mapCourtResponse(court));
|
||||
}
|
||||
|
||||
export { CourtBusinessError };
|
||||
Reference in New Issue
Block a user