import { prisma } from "../database"; import type { House, HouseMembership, HouseInvitation, HouseRequest, HouseRole, InvitationStatus, RequestStatus, Prisma, SitePreferences, } from "@/prisma/generated/prisma/client"; import { ValidationError, NotFoundError, ConflictError, ForbiddenError, } from "../errors"; import { sitePreferencesService } from "../preferences/site-preferences.service"; // Type étendu pour les préférences avec les nouveaux champs de points des maisons type SitePreferencesWithHousePoints = SitePreferences & { houseJoinPoints?: number; houseLeavePoints?: number; houseCreatePoints?: number; }; const HOUSE_NAME_MIN_LENGTH = 3; const HOUSE_NAME_MAX_LENGTH = 50; const HOUSE_DESCRIPTION_MAX_LENGTH = 500; export interface CreateHouseInput { name: string; description?: string | null; creatorId: string; } export interface UpdateHouseInput { name?: string; description?: string | null; } export interface InviteUserInput { houseId: string; inviterId: string; inviteeId: string; } export interface RequestToJoinInput { houseId: string; requesterId: string; } /** * Service de gestion des maisons */ export class HouseService { /** * Récupère une maison par son ID */ async getHouseById( id: string, include?: Prisma.HouseInclude ): Promise { return prisma.house.findUnique({ where: { id }, include, }); } /** * Récupère toutes les maisons avec pagination */ async getAllHouses(options?: { skip?: number; take?: number; include?: Prisma.HouseInclude; orderBy?: Prisma.HouseOrderByWithRelationInput; }): Promise { return prisma.house.findMany({ skip: options?.skip, take: options?.take, include: options?.include, orderBy: options?.orderBy || { createdAt: "desc" }, }); } /** * Recherche des maisons par nom */ async searchHouses( searchTerm: string, options?: { skip?: number; take?: number; include?: Prisma.HouseInclude; } ): Promise { return prisma.house.findMany({ where: { name: { contains: searchTerm, mode: "insensitive", }, }, skip: options?.skip, take: options?.take, include: options?.include, orderBy: { createdAt: "desc" }, }); } /** * Récupère les maisons d'un utilisateur */ async getUserHouses( userId: string, include?: Prisma.HouseInclude ): Promise { const memberships = await prisma.houseMembership.findMany({ where: { userId }, include: { house: { include: include, }, }, }); return memberships.map((m) => m.house); } /** * Récupère la maison d'un utilisateur (s'il en a une) */ async getUserHouse( userId: string, include?: Prisma.HouseInclude ): Promise { const membership = await prisma.houseMembership.findFirst({ where: { userId }, include: { house: { include: include, }, }, orderBy: { joinedAt: "asc" }, // Première maison jointe }); return membership?.house || null; } /** * Vérifie si un utilisateur est membre d'une maison */ async isUserMemberOfHouse(userId: string, houseId: string): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId, }, }, }); return !!membership; } /** * Vérifie si un utilisateur est propriétaire ou admin d'une maison */ async isUserOwnerOrAdmin(userId: string, houseId: string): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId, }, }, }); return membership?.role === "OWNER" || membership?.role === "ADMIN"; } /** * Vérifie si un utilisateur est propriétaire d'une maison */ async isUserOwner(userId: string, houseId: string): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId, }, }, }); return membership?.role === "OWNER"; } /** * Récupère le rôle d'un utilisateur dans une maison */ async getUserRole( userId: string, houseId: string ): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId, }, }, }); return membership?.role || null; } /** * Récupère les membres d'une maison */ async getHouseMembers( houseId: string, include?: Prisma.HouseMembershipInclude ): Promise { return prisma.houseMembership.findMany({ where: { houseId }, include, orderBy: [ { role: "asc" }, // OWNER, ADMIN, MEMBER { joinedAt: "asc" }, ], }); } /** * Crée une nouvelle maison */ async createHouse(data: CreateHouseInput): Promise { // Validation if (!data.name || data.name.trim().length === 0) { throw new ValidationError("Le nom de la maison est requis", "name"); } if ( data.name.length < HOUSE_NAME_MIN_LENGTH || data.name.length > HOUSE_NAME_MAX_LENGTH ) { throw new ValidationError( `Le nom de la maison doit contenir entre ${HOUSE_NAME_MIN_LENGTH} et ${HOUSE_NAME_MAX_LENGTH} caractères`, "name" ); } if ( data.description && data.description.length > HOUSE_DESCRIPTION_MAX_LENGTH ) { throw new ValidationError( `La description ne peut pas dépasser ${HOUSE_DESCRIPTION_MAX_LENGTH} caractères`, "description" ); } // Vérifier si l'utilisateur est déjà dans une maison const existingMembership = await prisma.houseMembership.findFirst({ where: { userId: data.creatorId }, }); if (existingMembership) { throw new ConflictError( "Vous êtes déjà membre d'une maison. Vous devez quitter votre maison actuelle avant d'en créer une nouvelle." ); } // Vérifier si le nom est déjà pris const existingHouse = await prisma.house.findFirst({ where: { name: { equals: data.name.trim(), mode: "insensitive", }, }, }); if (existingHouse) { throw new ConflictError("Ce nom de maison est déjà utilisé"); } // Récupérer les points à attribuer depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToAward = (sitePreferences as SitePreferencesWithHousePoints).houseCreatePoints ?? 100; console.log( "[HouseService] Creating house - points to award:", pointsToAward, "preferences:", sitePreferences ); // Créer la maison et ajouter le créateur comme OWNER, puis attribuer les points return prisma.$transaction(async (tx) => { const house = await tx.house.create({ data: { name: data.name.trim(), description: data.description?.trim() || null, creatorId: data.creatorId, memberships: { create: { userId: data.creatorId, role: "OWNER", }, }, }, }); // Attribuer les points au créateur await tx.user.update({ where: { id: data.creatorId }, data: { score: { increment: pointsToAward, }, }, }); return house; }); } /** * Met à jour une maison */ async updateHouse( houseId: string, userId: string, data: UpdateHouseInput ): Promise { // Vérifier que l'utilisateur est propriétaire ou admin const isAuthorized = await this.isUserOwnerOrAdmin(userId, houseId); if (!isAuthorized) { throw new ForbiddenError( "Vous n'avez pas les permissions pour modifier cette maison" ); } const updateData: Prisma.HouseUpdateInput = {}; if (data.name !== undefined) { if (!data.name || data.name.trim().length === 0) { throw new ValidationError("Le nom de la maison est requis", "name"); } if ( data.name.length < HOUSE_NAME_MIN_LENGTH || data.name.length > HOUSE_NAME_MAX_LENGTH ) { throw new ValidationError( `Le nom de la maison doit contenir entre ${HOUSE_NAME_MIN_LENGTH} et ${HOUSE_NAME_MAX_LENGTH} caractères`, "name" ); } // Vérifier si le nom est déjà pris par une autre maison const existingHouse = await prisma.house.findFirst({ where: { name: { equals: data.name.trim(), mode: "insensitive", }, NOT: { id: houseId }, }, }); if (existingHouse) { throw new ConflictError("Ce nom de maison est déjà utilisé"); } updateData.name = data.name.trim(); } if (data.description !== undefined) { if ( data.description && data.description.length > HOUSE_DESCRIPTION_MAX_LENGTH ) { throw new ValidationError( `La description ne peut pas dépasser ${HOUSE_DESCRIPTION_MAX_LENGTH} caractères`, "description" ); } updateData.description = data.description?.trim() || null; } return prisma.house.update({ where: { id: houseId }, data: updateData, }); } /** * Supprime une maison */ async deleteHouse(houseId: string, userId: string): Promise { // Vérifier que l'utilisateur est propriétaire const isOwner = await this.isUserOwner(userId, houseId); if (!isOwner) { throw new ForbiddenError("Seul le propriétaire peut supprimer la maison"); } // Récupérer la maison pour obtenir le créateur const house = await prisma.house.findUnique({ where: { id: houseId }, select: { creatorId: true }, }); if (!house) { throw new NotFoundError("Maison"); } // Récupérer les points à enlever depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToDeduct = (sitePreferences as SitePreferencesWithHousePoints).houseCreatePoints ?? 100; console.log( "[HouseService] Deleting house - points to deduct:", pointsToDeduct, "creatorId:", house.creatorId ); // Supprimer la maison et enlever les points au créateur await prisma.$transaction(async (tx) => { // Enlever les points au créateur await tx.user.update({ where: { id: house.creatorId }, data: { score: { decrement: pointsToDeduct, }, }, }); // Supprimer la maison (cela supprimera automatiquement les membreships, invitations, etc. grâce aux CASCADE) await tx.house.delete({ where: { id: houseId }, }); }); } /** * Invite un utilisateur à rejoindre une maison */ async inviteUser(data: InviteUserInput): Promise { // Vérifier que l'inviteur est membre de la maison const isMember = await this.isUserMemberOfHouse( data.inviterId, data.houseId ); if (!isMember) { throw new ForbiddenError( "Vous devez être membre de la maison pour inviter quelqu'un" ); } // Vérifier que l'invité n'est pas déjà membre const isAlreadyMember = await this.isUserMemberOfHouse( data.inviteeId, data.houseId ); if (isAlreadyMember) { throw new ConflictError("Cet utilisateur est déjà membre de la maison"); } // Vérifier que l'invité n'est pas déjà dans une autre maison const existingMembership = await prisma.houseMembership.findFirst({ where: { userId: data.inviteeId }, }); if (existingMembership) { throw new ConflictError( "Cet utilisateur est déjà membre d'une autre maison" ); } // Vérifier s'il existe déjà une invitation (peu importe le statut) const existingInvitation = await prisma.houseInvitation.findUnique({ where: { houseId_inviteeId: { houseId: data.houseId, inviteeId: data.inviteeId, }, }, }); if (existingInvitation) { if (existingInvitation.status === "PENDING") { throw new ConflictError( "Une invitation est déjà en attente pour cet utilisateur" ); } // Si l'invitation existe avec un autre statut, on la réinitialise return prisma.houseInvitation.update({ where: { houseId_inviteeId: { houseId: data.houseId, inviteeId: data.inviteeId, }, }, data: { inviterId: data.inviterId, status: "PENDING", }, }); } // Créer l'invitation return prisma.houseInvitation.create({ data: { houseId: data.houseId, inviterId: data.inviterId, inviteeId: data.inviteeId, status: "PENDING", }, }); } /** * Accepte une invitation */ async acceptInvitation( invitationId: string, userId: string ): Promise { const invitation = await prisma.houseInvitation.findUnique({ where: { id: invitationId }, }); if (!invitation) { throw new NotFoundError("Invitation"); } if (invitation.inviteeId !== userId) { throw new ForbiddenError("Cette invitation ne vous est pas destinée"); } if (invitation.status !== "PENDING") { throw new ConflictError("Cette invitation n'est plus valide"); } // Vérifier que l'utilisateur n'est pas déjà dans une maison const existingMembership = await prisma.houseMembership.findFirst({ where: { userId }, }); if (existingMembership) { throw new ConflictError( "Vous êtes déjà membre d'une maison. Vous devez quitter votre maison actuelle avant d'accepter cette invitation." ); } // Récupérer les points à attribuer depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToAward = (sitePreferences as SitePreferencesWithHousePoints).houseJoinPoints ?? 100; console.log( "[HouseService] Accepting invitation - points to award:", pointsToAward, "userId:", userId ); // Créer le membership et mettre à jour l'invitation return prisma.$transaction(async (tx) => { const membership = await tx.houseMembership.create({ data: { houseId: invitation.houseId, userId: invitation.inviteeId, role: "MEMBER", }, }); await tx.houseInvitation.update({ where: { id: invitationId }, data: { status: "ACCEPTED" }, }); // Annuler toutes les autres invitations en attente pour cet utilisateur await tx.houseInvitation.updateMany({ where: { inviteeId: userId, status: "PENDING", id: { not: invitationId }, }, data: { status: "CANCELLED" }, }); // Annuler toutes les demandes en attente pour cet utilisateur await tx.houseRequest.updateMany({ where: { requesterId: userId, status: "PENDING", }, data: { status: "CANCELLED" }, }); // Attribuer les points à l'utilisateur qui rejoint await tx.user.update({ where: { id: userId }, data: { score: { increment: pointsToAward, }, }, }); return membership; }); } /** * Refuse une invitation */ async rejectInvitation(invitationId: string, userId: string): Promise { const invitation = await prisma.houseInvitation.findUnique({ where: { id: invitationId }, }); if (!invitation) { throw new NotFoundError("Invitation"); } if (invitation.inviteeId !== userId) { throw new ForbiddenError("Cette invitation ne vous est pas destinée"); } if (invitation.status !== "PENDING") { throw new ConflictError("Cette invitation n'est plus valide"); } await prisma.houseInvitation.update({ where: { id: invitationId }, data: { status: "REJECTED" }, }); } /** * Annule une invitation (par l'inviteur) */ async cancelInvitation(invitationId: string, userId: string): Promise { const invitation = await prisma.houseInvitation.findUnique({ where: { id: invitationId }, }); if (!invitation) { throw new NotFoundError("Invitation"); } if (invitation.inviterId !== userId) { throw new ForbiddenError( "Vous ne pouvez annuler que vos propres invitations" ); } if (invitation.status !== "PENDING") { throw new ConflictError("Cette invitation n'est plus valide"); } await prisma.houseInvitation.update({ where: { id: invitationId }, data: { status: "CANCELLED" }, }); } /** * Demande à rejoindre une maison */ async requestToJoin(data: RequestToJoinInput): Promise { // Vérifier que l'utilisateur n'est pas déjà membre const isMember = await this.isUserMemberOfHouse( data.requesterId, data.houseId ); if (isMember) { throw new ConflictError("Vous êtes déjà membre de cette maison"); } // Vérifier que l'utilisateur n'est pas déjà dans une autre maison const existingMembership = await prisma.houseMembership.findFirst({ where: { userId: data.requesterId }, }); if (existingMembership) { throw new ConflictError( "Vous êtes déjà membre d'une maison. Vous devez quitter votre maison actuelle avant de faire une demande." ); } // Vérifier s'il existe déjà une demande const existingRequest = await prisma.houseRequest.findUnique({ where: { houseId_requesterId: { houseId: data.houseId, requesterId: data.requesterId, }, }, }); if (existingRequest) { if (existingRequest.status === "PENDING") { throw new ConflictError( "Une demande est déjà en attente pour cette maison" ); } // Si la demande existe mais n'est pas PENDING (REJECTED, CANCELLED), on la réactive return prisma.houseRequest.update({ where: { houseId_requesterId: { houseId: data.houseId, requesterId: data.requesterId, }, }, data: { status: "PENDING", }, }); } // Créer une nouvelle demande return prisma.houseRequest.create({ data: { houseId: data.houseId, requesterId: data.requesterId, status: "PENDING", }, }); } /** * Accepte une demande d'adhésion */ async acceptRequest( requestId: string, userId: string ): Promise { const request = await prisma.houseRequest.findUnique({ where: { id: requestId }, include: { house: true }, }); if (!request) { throw new NotFoundError("Demande"); } // Vérifier que l'utilisateur est propriétaire ou admin de la maison const isAuthorized = await this.isUserOwnerOrAdmin(userId, request.houseId); if (!isAuthorized) { throw new ForbiddenError( "Vous n'avez pas les permissions pour accepter cette demande" ); } if (request.status !== "PENDING") { throw new ConflictError("Cette demande n'est plus valide"); } // Vérifier que le demandeur n'est pas déjà dans une maison const existingMembership = await prisma.houseMembership.findFirst({ where: { userId: request.requesterId }, }); if (existingMembership) { throw new ConflictError( "Cet utilisateur est déjà membre d'une autre maison" ); } // Récupérer les points à attribuer depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToAward = (sitePreferences as SitePreferencesWithHousePoints).houseJoinPoints ?? 100; console.log( "[HouseService] Accepting request - points to award:", pointsToAward, "requesterId:", request.requesterId ); // Créer le membership et mettre à jour la demande return prisma.$transaction(async (tx) => { const membership = await tx.houseMembership.create({ data: { houseId: request.houseId, userId: request.requesterId, role: "MEMBER", }, }); await tx.houseRequest.update({ where: { id: requestId }, data: { status: "ACCEPTED" }, }); // Annuler toutes les autres demandes en attente pour cet utilisateur await tx.houseRequest.updateMany({ where: { requesterId: request.requesterId, status: "PENDING", id: { not: requestId }, }, data: { status: "CANCELLED" }, }); // Annuler toutes les invitations en attente pour cet utilisateur await tx.houseInvitation.updateMany({ where: { inviteeId: request.requesterId, status: "PENDING", }, data: { status: "CANCELLED" }, }); // Attribuer les points à l'utilisateur qui rejoint await tx.user.update({ where: { id: request.requesterId }, data: { score: { increment: pointsToAward, }, }, }); return membership; }); } /** * Refuse une demande d'adhésion */ async rejectRequest(requestId: string, userId: string): Promise { const request = await prisma.houseRequest.findUnique({ where: { id: requestId }, }); if (!request) { throw new NotFoundError("Demande"); } // Vérifier que l'utilisateur est propriétaire ou admin de la maison const isAuthorized = await this.isUserOwnerOrAdmin(userId, request.houseId); if (!isAuthorized) { throw new ForbiddenError( "Vous n'avez pas les permissions pour refuser cette demande" ); } if (request.status !== "PENDING") { throw new ConflictError("Cette demande n'est plus valide"); } await prisma.houseRequest.update({ where: { id: requestId }, data: { status: "REJECTED" }, }); } /** * Annule une demande (par le demandeur) */ async cancelRequest(requestId: string, userId: string): Promise { const request = await prisma.houseRequest.findUnique({ where: { id: requestId }, }); if (!request) { throw new NotFoundError("Demande"); } if (request.requesterId !== userId) { throw new ForbiddenError( "Vous ne pouvez annuler que vos propres demandes" ); } if (request.status !== "PENDING") { throw new ConflictError("Cette demande n'est plus valide"); } await prisma.houseRequest.update({ where: { id: requestId }, data: { status: "CANCELLED" }, }); } /** * Retire un membre d'une maison (par un OWNER ou ADMIN) */ async removeMember( houseId: string, memberIdToRemove: string, removerId: string ): Promise { // Vérifier que celui qui retire est OWNER ou ADMIN const isAuthorized = await this.isUserOwnerOrAdmin(removerId, houseId); if (!isAuthorized) { throw new ForbiddenError( "Vous n'avez pas les permissions pour retirer un membre" ); } // Récupérer les membreships const removerMembership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId: removerId, }, }, }); const memberToRemoveMembership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId: memberIdToRemove, }, }, }); if (!memberToRemoveMembership) { throw new NotFoundError("Membre"); } // Un OWNER ne peut pas être retiré if (memberToRemoveMembership.role === "OWNER") { throw new ForbiddenError("Le propriétaire ne peut pas être retiré"); } // Un ADMIN ne peut retirer que des MEMBER (pas d'autres ADMIN) if ( removerMembership?.role === "ADMIN" && memberToRemoveMembership.role === "ADMIN" ) { throw new ForbiddenError("Un admin ne peut pas retirer un autre admin"); } // Récupérer les points à enlever depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToDeduct = (sitePreferences as SitePreferencesWithHousePoints).houseLeavePoints ?? 100; // Supprimer le membership et enlever les points await prisma.$transaction(async (tx) => { await tx.houseMembership.delete({ where: { houseId_userId: { houseId, userId: memberIdToRemove, }, }, }); // Enlever les points à l'utilisateur retiré await tx.user.update({ where: { id: memberIdToRemove }, data: { score: { decrement: pointsToDeduct, }, }, }); }); } /** * Retire un membre d'une maison (par un admin du site) * Bypass les vérifications normales de permissions */ async removeMemberAsAdmin( houseId: string, memberIdToRemove: string ): Promise { const memberToRemoveMembership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId: memberIdToRemove, }, }, }); if (!memberToRemoveMembership) { throw new NotFoundError("Membre"); } // Un OWNER ne peut pas être retiré même par un admin if (memberToRemoveMembership.role === "OWNER") { throw new ForbiddenError("Le propriétaire ne peut pas être retiré"); } // Récupérer les points à enlever depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToDeduct = (sitePreferences as SitePreferencesWithHousePoints).houseLeavePoints ?? 100; // Supprimer le membership et enlever les points await prisma.$transaction(async (tx) => { await tx.houseMembership.delete({ where: { houseId_userId: { houseId, userId: memberIdToRemove, }, }, }); // Enlever les points à l'utilisateur retiré await tx.user.update({ where: { id: memberIdToRemove }, data: { score: { decrement: pointsToDeduct, }, }, }); }); } /** * Quitte une maison */ async leaveHouse(houseId: string, userId: string): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { houseId, userId, }, }, }); if (!membership) { throw new NotFoundError("Membre"); } // Le propriétaire ne peut pas quitter sa maison if (membership.role === "OWNER") { throw new ForbiddenError( "Le propriétaire ne peut pas quitter sa maison. Vous devez d'abord transférer la propriété ou supprimer la maison." ); } // Récupérer les points à enlever depuis les préférences du site const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences(); const pointsToDeduct = (sitePreferences as SitePreferencesWithHousePoints).houseLeavePoints ?? 100; console.log( "[HouseService] Leaving house - points to deduct:", pointsToDeduct, "userId:", userId ); // Supprimer le membership et enlever les points await prisma.$transaction(async (tx) => { await tx.houseMembership.delete({ where: { houseId_userId: { houseId, userId, }, }, }); // Enlever les points à l'utilisateur qui quitte await tx.user.update({ where: { id: userId }, data: { score: { decrement: pointsToDeduct, }, }, }); }); } /** * Récupère les invitations reçues par un utilisateur */ async getUserInvitations( userId: string, status?: InvitationStatus ): Promise { return prisma.houseInvitation.findMany({ where: { inviteeId: userId, ...(status && { status }), }, include: { house: true, inviter: { select: { id: true, username: true, avatar: true, }, }, }, orderBy: { createdAt: "desc" }, }); } /** * Compte les invitations en attente pour un utilisateur */ async getPendingInvitationsCount(userId: string): Promise { return prisma.houseInvitation.count({ where: { inviteeId: userId, status: "PENDING", }, }); } /** * Compte les demandes d'adhésion en attente pour un utilisateur * (demandes reçues pour les maisons dont l'utilisateur est propriétaire ou admin) */ async getPendingRequestsCount(userId: string): Promise { // Trouver toutes les maisons où l'utilisateur est OWNER ou ADMIN const userHouses = await prisma.houseMembership.findMany({ where: { userId, role: { in: ["OWNER", "ADMIN"], }, }, select: { houseId: true, }, }); const houseIds = userHouses.map((m) => m.houseId); if (houseIds.length === 0) { return 0; } // Compter les demandes PENDING pour ces maisons return prisma.houseRequest.count({ where: { houseId: { in: houseIds, }, status: "PENDING", }, }); } /** * Compte le total des invitations et demandes en attente pour un utilisateur * - Invitations : invitations reçues par l'utilisateur (inviteeId) * - Demandes : demandes reçues pour les maisons dont l'utilisateur est OWNER ou ADMIN */ async getPendingHouseActionsCount(userId: string): Promise { const [invitationsCount, requestsCount] = await Promise.all([ this.getPendingInvitationsCount(userId), this.getPendingRequestsCount(userId), ]); return invitationsCount + requestsCount; } /** * Récupère les demandes d'une maison */ async getHouseRequests( houseId: string, status?: RequestStatus ): Promise { return prisma.houseRequest.findMany({ where: { houseId, ...(status && { status }), }, include: { requester: { select: { id: true, username: true, avatar: true, }, }, }, orderBy: { createdAt: "desc" }, }); } /** * Récupère les invitations envoyées par une maison */ async getHouseInvitations( houseId: string, status?: InvitationStatus ): Promise { return prisma.houseInvitation.findMany({ where: { houseId, ...(status && { status }), }, include: { invitee: { select: { id: true, username: true, avatar: true, }, }, inviter: { select: { id: true, username: true, avatar: true, }, }, }, orderBy: { createdAt: "desc" }, }); } /** * Récupère une invitation par son ID (avec seulement houseId) */ async getInvitationById(id: string): Promise<{ houseId: string } | null> { return prisma.houseInvitation.findUnique({ where: { id }, select: { houseId: true }, }); } } export const houseService = new HouseService();