Initial attendee management

This commit is contained in:
Jose Selesan
2026-09-18 09:42:37 -03:00
parent fa30fe2cff
commit d7ebb5b234
111 changed files with 13784 additions and 36 deletions

View File

@@ -0,0 +1,72 @@
import type { PrismaClient } from '@generated/prisma/client';
import type { AttendeeDto, CreateAttendee, ProblemDetails, Result } from '@gruperly/shared';
import { err, ok } from '@gruperly/shared';
import { conflictProblem, noGroupAccessProblem, notFoundResourceProblem } from '@/http/problem-builders';
import prisma from '@/lib/prisma';
import { type AttendeeRecord, normalizePhone, toAttendeeDto } from '../../lib/helpers';
type CreateAttendeeDeps = {
db?: Pick<PrismaClient, 'group' | 'attendee'>;
};
export class CreateAttendeeUseCase {
constructor(private readonly deps: CreateAttendeeDeps = {}) {}
async execute(
groupId: string,
userId: string,
payload: CreateAttendee,
): Promise<Result<AttendeeDto, ProblemDetails>> {
const db = this.deps.db ?? prisma;
const group = await db.group.findUnique({
where: { id: groupId },
include: {
members: { where: { userId } },
},
});
if (!group) {
return err(notFoundResourceProblem('Group', groupId));
}
const isOwner = group.createdById === userId;
const isMember = group.members.length > 0;
if (!isOwner && !isMember) {
return err(noGroupAccessProblem());
}
const phone = normalizePhone(payload.phone);
const existing = await db.attendee.findFirst({
where: {
groupId,
OR: [
{ phone },
{ phone: payload.phone.trim() },
],
},
});
if (existing) {
return err(
conflictProblem({
detail: 'Ya existe un alumno registrado con este número de teléfono en este grupo.',
code: 'attendee_already_registered',
}),
);
}
const fullName = `${payload.firstName.trim()} ${payload.lastName.trim()}`.trim();
const attendee = await db.attendee.create({
data: {
groupId,
fullName,
phone,
email: payload.email?.trim() || null,
notes: payload.notes?.trim() || null,
},
});
return ok(toAttendeeDto(attendee as unknown as AttendeeRecord));
}
}