From 1b82bd9ee6de5f81f3871a17a5ffd731751fd720 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 18 Dec 2025 08:48:31 +0100 Subject: [PATCH] Implement house points system: Add houseJoinPoints, houseLeavePoints, and houseCreatePoints to SitePreferences model and update related services. Enhance house management features to award and deduct points for house creation, membership removal, and leaving a house. Update environment configuration for PostgreSQL and adjust UI components to reflect new functionalities. --- .env | 2 +- actions/admin/preferences.ts | 6 + actions/houses/update.ts | 35 ++ app/api/invitations/pending-count/route.ts | 23 + app/houses/page.tsx | 14 +- components/admin/AdminPanel.tsx | 5 + components/admin/HousePointsPreferences.tsx | 269 +++++++++++ components/houses/HouseCard.tsx | 2 + components/houses/HouseForm.tsx | 4 + components/houses/HouseManagement.tsx | 57 ++- components/houses/InvitationList.tsx | 6 + components/houses/RequestList.tsx | 6 + components/navigation/InvitationBadge.tsx | 73 +++ components/navigation/Navigation.tsx | 35 +- components/navigation/NavigationWrapper.tsx | 9 +- prisma/generated/prisma/internal/class.ts | 4 +- .../prisma/internal/prismaNamespace.ts | 3 + .../prisma/internal/prismaNamespaceBrowser.ts | 3 + .../prisma/models/SitePreferences.ts | 104 ++++- .../migration.sql | 9 + prisma/schema.prisma | 3 + services/houses/house.service.ts | 435 +++++++++++++++--- .../preferences/site-preferences.service.ts | 32 ++ 23 files changed, 1026 insertions(+), 113 deletions(-) create mode 100644 app/api/invitations/pending-count/route.ts create mode 100644 components/admin/HousePointsPreferences.tsx create mode 100644 components/navigation/InvitationBadge.tsx create mode 100644 prisma/migrations/20250115120000_add_house_points_preferences/migration.sql diff --git a/.env b/.env index fbb4685..5fe017d 100644 --- a/.env +++ b/.env @@ -25,7 +25,7 @@ POSTGRES_PORT=5433 # Database URL (construite automatiquement si non définie) # Si vous définissez cette variable, elle sera utilisée telle quelle # Sinon, elle sera construite à partir des variables POSTGRES_* ci-dessus -# DATABASE_URL=postgresql://gotgaming:change-this-in-production@got-postgres:5432/gotgaming?schema=public +DATABASE_URL=postgresql://gotgaming:change-this-in-production@localhost:5433/gotgaming?schema=public # Docker Volumes (optionnel) POSTGRES_DATA_PATH=./data/postgres diff --git a/actions/admin/preferences.ts b/actions/admin/preferences.ts index 0dd64f5..8bd71bf 100644 --- a/actions/admin/preferences.ts +++ b/actions/admin/preferences.ts @@ -22,6 +22,9 @@ export async function updateSitePreferences(data: { challengesBackground?: string | null; eventRegistrationPoints?: number; eventFeedbackPoints?: number; + houseJoinPoints?: number; + houseLeavePoints?: number; + houseCreatePoints?: number; }) { try { await checkAdminAccess()(); @@ -33,6 +36,9 @@ export async function updateSitePreferences(data: { challengesBackground: data.challengesBackground, eventRegistrationPoints: data.eventRegistrationPoints, eventFeedbackPoints: data.eventFeedbackPoints, + houseJoinPoints: data.houseJoinPoints, + houseLeavePoints: data.houseLeavePoints, + houseCreatePoints: data.houseCreatePoints, }); revalidatePath("/admin"); diff --git a/actions/houses/update.ts b/actions/houses/update.ts index f1d3f27..e1f6757 100644 --- a/actions/houses/update.ts +++ b/actions/houses/update.ts @@ -7,6 +7,7 @@ import { ValidationError, ConflictError, ForbiddenError, + NotFoundError, } from "@/services/errors"; export async function updateHouse( @@ -112,3 +113,37 @@ export async function leaveHouse(houseId: string) { } } +export async function removeMember(houseId: string, memberId: string) { + try { + const session = await auth(); + + if (!session?.user?.id) { + return { + success: false, + error: "Vous devez être connecté", + }; + } + + await houseService.removeMember(houseId, memberId, session.user.id); + + revalidatePath("/houses"); + revalidatePath("/profile"); + + return { success: true, message: "Membre retiré de la maison" }; + } catch (error) { + console.error("Remove member error:", error); + + if ( + error instanceof ForbiddenError || + error instanceof NotFoundError + ) { + return { success: false, error: error.message }; + } + + return { + success: false, + error: "Une erreur est survenue lors du retrait du membre", + }; + } +} + diff --git a/app/api/invitations/pending-count/route.ts b/app/api/invitations/pending-count/route.ts new file mode 100644 index 0000000..c00e3be --- /dev/null +++ b/app/api/invitations/pending-count/route.ts @@ -0,0 +1,23 @@ +import { NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { houseService } from "@/services/houses/house.service"; + +export async function GET() { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ count: 0 }); + } + + // Compter les invitations ET les demandes d'adhésion en attente + const count = await houseService.getPendingHouseActionsCount( + session.user.id + ); + + return NextResponse.json({ count }); + } catch (error) { + console.error("Error fetching pending house actions count:", error); + return NextResponse.json({ count: 0 }); + } +} diff --git a/app/houses/page.tsx b/app/houses/page.tsx index bb70d27..9e1d464 100644 --- a/app/houses/page.tsx +++ b/app/houses/page.tsx @@ -4,7 +4,7 @@ import { getBackgroundImage } from "@/lib/preferences"; import NavigationWrapper from "@/components/navigation/NavigationWrapper"; import HousesSection from "@/components/houses/HousesSection"; import { houseService } from "@/services/houses/house.service"; -import { userService } from "@/services/users/user.service"; +import { prisma } from "@/services/database"; export const dynamic = "force-dynamic"; @@ -70,13 +70,21 @@ export default async function HousesPage() { }), // Récupérer les invitations de l'utilisateur houseService.getUserInvitations(session.user.id, "PENDING"), - // Récupérer tous les utilisateurs pour les invitations - userService.getAllUsers({ + // Récupérer tous les utilisateurs sans maison pour les invitations + prisma.user.findMany({ + where: { + houseMemberships: { + none: {}, + }, + }, select: { id: true, username: true, avatar: true, }, + orderBy: { + username: "asc", + }, }), getBackgroundImage("challenges", "/got-2.jpg"), ]); diff --git a/components/admin/AdminPanel.tsx b/components/admin/AdminPanel.tsx index 86f5fc5..a59eea9 100644 --- a/components/admin/AdminPanel.tsx +++ b/components/admin/AdminPanel.tsx @@ -8,6 +8,7 @@ import ChallengeManagement from "@/components/admin/ChallengeManagement"; import BackgroundPreferences from "@/components/admin/BackgroundPreferences"; import EventPointsPreferences from "@/components/admin/EventPointsPreferences"; import EventFeedbackPointsPreferences from "@/components/admin/EventFeedbackPointsPreferences"; +import HousePointsPreferences from "@/components/admin/HousePointsPreferences"; import { Button, Card, SectionTitle } from "@/components/ui"; interface SitePreferences { @@ -18,6 +19,9 @@ interface SitePreferences { challengesBackground: string | null; eventRegistrationPoints: number; eventFeedbackPoints: number; + houseJoinPoints: number; + houseLeavePoints: number; + houseCreatePoints: number; } interface AdminPanelProps { @@ -99,6 +103,7 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) { + )} diff --git a/components/admin/HousePointsPreferences.tsx b/components/admin/HousePointsPreferences.tsx new file mode 100644 index 0000000..ab0a661 --- /dev/null +++ b/components/admin/HousePointsPreferences.tsx @@ -0,0 +1,269 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { updateSitePreferences } from "@/actions/admin/preferences"; +import { Button, Card, Input } from "@/components/ui"; + +interface SitePreferences { + id: string; + houseJoinPoints: number; + houseLeavePoints: number; + houseCreatePoints: number; +} + +interface HousePointsPreferencesProps { + initialPreferences: SitePreferences; +} + +export default function HousePointsPreferences({ + initialPreferences, +}: HousePointsPreferencesProps) { + const [preferences, setPreferences] = useState( + initialPreferences + ); + const [isEditing, setIsEditing] = useState(false); + const [formData, setFormData] = useState({ + houseJoinPoints: initialPreferences.houseJoinPoints.toString(), + houseLeavePoints: initialPreferences.houseLeavePoints.toString(), + houseCreatePoints: initialPreferences.houseCreatePoints.toString(), + }); + const [isSaving, setIsSaving] = useState(false); + + // Synchroniser les préférences quand initialPreferences change + useEffect(() => { + setPreferences(initialPreferences); + setFormData({ + houseJoinPoints: initialPreferences.houseJoinPoints.toString(), + houseLeavePoints: initialPreferences.houseLeavePoints.toString(), + houseCreatePoints: initialPreferences.houseCreatePoints.toString(), + }); + }, [initialPreferences]); + + const handleEdit = () => { + setIsEditing(true); + }; + + const handleSave = async () => { + const joinPoints = parseInt(formData.houseJoinPoints, 10); + const leavePoints = parseInt(formData.houseLeavePoints, 10); + const createPoints = parseInt(formData.houseCreatePoints, 10); + + if (isNaN(joinPoints) || joinPoints < 0) { + alert("Le nombre de points pour rejoindre une maison doit être un nombre positif"); + return; + } + + if (isNaN(leavePoints) || leavePoints < 0) { + alert("Le nombre de points pour quitter une maison doit être un nombre positif"); + return; + } + + if (isNaN(createPoints) || createPoints < 0) { + alert("Le nombre de points pour créer une maison doit être un nombre positif"); + return; + } + + setIsSaving(true); + try { + const result = await updateSitePreferences({ + houseJoinPoints: joinPoints, + houseLeavePoints: leavePoints, + houseCreatePoints: createPoints, + }); + + if (result.success && result.data) { + setPreferences(result.data); + setFormData({ + houseJoinPoints: result.data.houseJoinPoints.toString(), + houseLeavePoints: result.data.houseLeavePoints.toString(), + houseCreatePoints: result.data.houseCreatePoints.toString(), + }); + setIsEditing(false); + } else { + console.error("Error updating preferences:", result.error); + alert(result.error || "Erreur lors de la mise à jour"); + } + } catch (error) { + console.error("Error updating preferences:", error); + alert("Erreur lors de la mise à jour"); + } finally { + setIsSaving(false); + } + }; + + const handleCancel = () => { + setIsEditing(false); + if (preferences) { + setFormData({ + houseJoinPoints: preferences.houseJoinPoints.toString(), + houseLeavePoints: preferences.houseLeavePoints.toString(), + houseCreatePoints: preferences.houseCreatePoints.toString(), + }); + } + }; + + return ( + +
+
+

+ Points des Maisons +

+

+ Nombre de points attribués ou retirés pour les actions liées aux maisons +

+
+ {!isEditing && ( + + )} +
+ + {isEditing ? ( +
+
+ + + setFormData({ + ...formData, + houseJoinPoints: e.target.value, + }) + } + placeholder="100" + className="w-full" + /> +

+ Les utilisateurs gagneront ce nombre de points lorsqu'ils rejoignent une maison +

+
+ +
+ + + setFormData({ + ...formData, + houseLeavePoints: e.target.value, + }) + } + placeholder="100" + className="w-full" + /> +

+ Les utilisateurs perdront ce nombre de points lorsqu'ils quittent une maison +

+
+ +
+ + + setFormData({ + ...formData, + houseCreatePoints: e.target.value, + }) + } + placeholder="100" + className="w-full" + /> +

+ Les utilisateurs gagneront ce nombre de points lorsqu'ils créent une maison +

+
+ +
+ + +
+
+ ) : ( +
+
+ + Points pour rejoindre: + +
+ + {preferences?.houseJoinPoints ?? 100} + + points +
+
+ +
+ + Points retirés en quittant: + +
+ + {preferences?.houseLeavePoints ?? 100} + + points +
+
+ +
+ + Points pour créer: + +
+ + {preferences?.houseCreatePoints ?? 100} + + points +
+
+
+ )} +
+ ); +} + diff --git a/components/houses/HouseCard.tsx b/components/houses/HouseCard.tsx index 5767d62..ea3415f 100644 --- a/components/houses/HouseCard.tsx +++ b/components/houses/HouseCard.tsx @@ -60,6 +60,8 @@ export default function HouseCard({ house, onRequestSent }: HouseCardProps) { const result = await requestToJoin(house.id); if (result.success) { + // Rafraîchir le badge d'invitations/demandes dans le header + window.dispatchEvent(new Event("refreshInvitations")); setSuccess("Demande envoyée avec succès"); onRequestSent?.(); } else { diff --git a/components/houses/HouseForm.tsx b/components/houses/HouseForm.tsx index 785780e..4dfc5a1 100644 --- a/components/houses/HouseForm.tsx +++ b/components/houses/HouseForm.tsx @@ -38,6 +38,10 @@ export default function HouseForm({ : await createHouse({ name, description: description || null }); if (result.success) { + // Rafraîchir le score dans le header si on crée une maison (pas si on met à jour) + if (!house) { + window.dispatchEvent(new Event("refreshUserScore")); + } onSuccess?.(); } else { setError(result.error || "Une erreur est survenue"); diff --git a/components/houses/HouseManagement.tsx b/components/houses/HouseManagement.tsx index ae75afb..5a9be41 100644 --- a/components/houses/HouseManagement.tsx +++ b/components/houses/HouseManagement.tsx @@ -7,7 +7,7 @@ import Button from "@/components/ui/Button"; import HouseForm from "./HouseForm"; import RequestList from "./RequestList"; import Alert from "@/components/ui/Alert"; -import { deleteHouse, leaveHouse } from "@/actions/houses/update"; +import { deleteHouse, leaveHouse, removeMember } from "@/actions/houses/update"; import { inviteUser } from "@/actions/houses/invitations"; interface House { @@ -112,6 +112,8 @@ export default function HouseManagement({ startTransition(async () => { const result = await deleteHouse(house.id); if (result.success) { + // Rafraîchir le score dans le header (le créateur perd des points) + window.dispatchEvent(new Event("refreshUserScore")); onUpdate?.(); } else { setError(result.error || "Erreur lors de la suppression"); @@ -128,6 +130,7 @@ export default function HouseManagement({ startTransition(async () => { const result = await leaveHouse(house.id); if (result.success) { + window.dispatchEvent(new Event("refreshUserScore")); onUpdate?.(); } else { setError(result.error || "Erreur lors de la sortie"); @@ -144,6 +147,8 @@ export default function HouseManagement({ startTransition(async () => { const result = await inviteUser(house.id, selectedUserId); if (result.success) { + // Rafraîchir le badge d'invitations/demandes dans le header (pour l'invité) + window.dispatchEvent(new Event("refreshInvitations")); setSuccess("Invitation envoyée"); setShowInviteForm(false); setSelectedUserId(""); @@ -303,17 +308,45 @@ export default function HouseManagement({ - - {membership.role === "OWNER" && "👑 "} - {membership.role} - +
+ + {membership.role === "OWNER" && "👑 "} + {membership.role} + + {isAdmin && + !isCurrentUser && + (isOwner || membership.role === "MEMBER") && + membership.role !== "OWNER" && ( + + )} +
); })} diff --git a/components/houses/InvitationList.tsx b/components/houses/InvitationList.tsx index 9a6a012..1950a46 100644 --- a/components/houses/InvitationList.tsx +++ b/components/houses/InvitationList.tsx @@ -41,6 +41,10 @@ export default function InvitationList({ startTransition(async () => { const result = await acceptInvitation(invitationId); if (result.success) { + // Rafraîchir le score dans le header (l'utilisateur reçoit des points) + window.dispatchEvent(new Event("refreshUserScore")); + // Rafraîchir le badge d'invitations dans le header + window.dispatchEvent(new Event("refreshInvitations")); onUpdate?.(); } else { setError(result.error || "Erreur lors de l'acceptation"); @@ -53,6 +57,8 @@ export default function InvitationList({ startTransition(async () => { const result = await rejectInvitation(invitationId); if (result.success) { + // Rafraîchir le badge d'invitations dans le header + window.dispatchEvent(new Event("refreshInvitations")); onUpdate?.(); } else { setError(result.error || "Erreur lors du refus"); diff --git a/components/houses/RequestList.tsx b/components/houses/RequestList.tsx index eb89fc4..9ceef36 100644 --- a/components/houses/RequestList.tsx +++ b/components/houses/RequestList.tsx @@ -37,6 +37,10 @@ export default function RequestList({ startTransition(async () => { const result = await acceptRequest(requestId); if (result.success) { + // Rafraîchir le score dans le header (le requester reçoit des points) + window.dispatchEvent(new Event("refreshUserScore")); + // Rafraîchir le badge d'invitations/demandes dans le header (le requester n'a plus de demande en attente) + window.dispatchEvent(new Event("refreshInvitations")); onUpdate?.(); } else { setError(result.error || "Erreur lors de l'acceptation"); @@ -49,6 +53,8 @@ export default function RequestList({ startTransition(async () => { const result = await rejectRequest(requestId); if (result.success) { + // Rafraîchir le badge d'invitations/demandes dans le header (le requester n'a plus de demande en attente) + window.dispatchEvent(new Event("refreshInvitations")); onUpdate?.(); } else { setError(result.error || "Erreur lors du refus"); diff --git a/components/navigation/InvitationBadge.tsx b/components/navigation/InvitationBadge.tsx new file mode 100644 index 0000000..be79ad1 --- /dev/null +++ b/components/navigation/InvitationBadge.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Link from "next/link"; + +interface InvitationBadgeProps { + initialCount?: number; + onNavigate?: () => void; +} + +export default function InvitationBadge({ + initialCount = 0, + onNavigate, +}: InvitationBadgeProps) { + const [count, setCount] = useState(initialCount); + + // Utiliser le count initial (déjà récupéré côté serveur) + useEffect(() => { + setCount(initialCount); + }, [initialCount]); + + // Écouter les événements de refresh des invitations (déclenché après acceptation/refus) + useEffect(() => { + const handleRefreshInvitations = async () => { + try { + const response = await fetch("/api/invitations/pending-count"); + const data = await response.json(); + setCount(data.count || 0); + } catch (error) { + console.error("Error fetching pending invitations count:", error); + } + }; + + window.addEventListener("refreshInvitations", handleRefreshInvitations); + return () => { + window.removeEventListener("refreshInvitations", handleRefreshInvitations); + }; + }, []); + + return ( + + (e.currentTarget.style.color = "var(--accent-color)") + } + onMouseLeave={(e) => (e.currentTarget.style.color = "var(--foreground)")} + title={ + count > 0 + ? `${count} action${count > 1 ? "s" : ""} en attente (invitations et demandes)` + : "Maisons" + } + > + MAISONS + {count > 0 && ( + + {count > 9 ? "9+" : count} + + )} + + ); +} + diff --git a/components/navigation/Navigation.tsx b/components/navigation/Navigation.tsx index a685ac6..364df6b 100644 --- a/components/navigation/Navigation.tsx +++ b/components/navigation/Navigation.tsx @@ -7,6 +7,7 @@ import { usePathname } from "next/navigation"; import PlayerStats from "@/components/profile/PlayerStats"; import { Button, ThemeToggle } from "@/components/ui"; import ChallengeBadge from "./ChallengeBadge"; +import InvitationBadge from "./InvitationBadge"; interface UserData { username: string; @@ -23,12 +24,14 @@ interface NavigationProps { initialUserData?: UserData | null; initialIsAdmin?: boolean; initialActiveChallengesCount?: number; + initialPendingInvitationsCount?: number; } export default function Navigation({ initialUserData, initialIsAdmin, initialActiveChallengesCount = 0, + initialPendingInvitationsCount = 0, }: NavigationProps) { const { data: session } = useSession(); const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -119,19 +122,7 @@ export default function Navigation({ {isAuthenticated && ( <> - - (e.currentTarget.style.color = "var(--accent-color)") - } - onMouseLeave={(e) => - (e.currentTarget.style.color = "var(--foreground)") - } - > - MAISONS - + )} @@ -295,20 +286,10 @@ export default function Navigation({ {isAuthenticated && ( <> - setIsMenuOpen(false)} - className="transition text-xs font-normal uppercase tracking-widest py-2" - style={{ color: "var(--foreground)" }} - onMouseEnter={(e) => - (e.currentTarget.style.color = "var(--accent-color)") - } - onMouseLeave={(e) => - (e.currentTarget.style.color = "var(--foreground)") - } - > - MAISONS - + setIsMenuOpen(false)} + /> setIsMenuOpen(false)} diff --git a/components/navigation/NavigationWrapper.tsx b/components/navigation/NavigationWrapper.tsx index 51c0f63..d691700 100644 --- a/components/navigation/NavigationWrapper.tsx +++ b/components/navigation/NavigationWrapper.tsx @@ -1,6 +1,7 @@ import { auth } from "@/lib/auth"; import { userService } from "@/services/users/user.service"; import { challengeService } from "@/services/challenges/challenge.service"; +import { houseService } from "@/services/houses/house.service"; import Navigation from "./Navigation"; interface UserData { @@ -20,10 +21,11 @@ export default async function NavigationWrapper() { let userData: UserData | null = null; const isAdmin = session?.user?.role === "ADMIN"; let activeChallengesCount = 0; + let pendingHouseActionsCount = 0; if (session?.user?.id) { // Paralléliser les appels DB - const [user, count] = await Promise.all([ + const [user, challengesCount, houseActionsCount] = await Promise.all([ userService.getUserById(session.user.id, { username: true, avatar: true, @@ -35,13 +37,15 @@ export default async function NavigationWrapper() { score: true, }), challengeService.getActiveChallengesCount(session.user.id), + houseService.getPendingHouseActionsCount(session.user.id), ]); if (user) { userData = user; } - activeChallengesCount = count; + activeChallengesCount = challengesCount; + pendingHouseActionsCount = houseActionsCount; } return ( @@ -49,6 +53,7 @@ export default async function NavigationWrapper() { initialUserData={userData} initialIsAdmin={isAdmin} initialActiveChallengesCount={activeChallengesCount} + initialPendingInvitationsCount={pendingHouseActionsCount} /> ); } diff --git a/prisma/generated/prisma/internal/class.ts b/prisma/generated/prisma/internal/class.ts index 3e9f18c..d663e7a 100644 --- a/prisma/generated/prisma/internal/class.ts +++ b/prisma/generated/prisma/internal/class.ts @@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = { "clientVersion": "7.1.0", "engineVersion": "ab635e6b9d606fa5c8fb8b1a7f909c3c3c1c98ba", "activeProvider": "postgresql", - "inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"./generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel User {\n id String @id @default(cuid())\n email String @unique\n password String\n username String @unique\n role Role @default(USER)\n score Int @default(0)\n level Int @default(1)\n hp Int @default(1000)\n maxHp Int @default(1000)\n xp Int @default(0)\n maxXp Int @default(5000)\n avatar String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n bio String?\n characterClass CharacterClass?\n eventFeedbacks EventFeedback[]\n eventRegistrations EventRegistration[]\n preferences UserPreferences?\n challengesAsChallenger Challenge[] @relation(\"Challenger\")\n challengesAsChallenged Challenge[] @relation(\"Challenged\")\n challengesAsAdmin Challenge[] @relation(\"AdminValidator\")\n challengesAsWinner Challenge[] @relation(\"ChallengeWinner\")\n houseMemberships HouseMembership[]\n houseInvitationsSent HouseInvitation[] @relation(\"Inviter\")\n houseInvitationsReceived HouseInvitation[] @relation(\"Invitee\")\n houseRequestsSent HouseRequest[] @relation(\"Requester\")\n housesCreated House[] @relation(\"HouseCreator\")\n\n @@index([score])\n @@index([email])\n}\n\nmodel UserPreferences {\n id String @id @default(cuid())\n userId String @unique\n homeBackground String?\n eventsBackground String?\n leaderboardBackground String?\n theme String? @default(\"default\")\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Event {\n id String @id @default(cuid())\n date DateTime\n name String\n description String\n type EventType\n room String?\n time String?\n maxPlaces Int?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n feedbacks EventFeedback[]\n registrations EventRegistration[]\n\n @@index([date])\n}\n\nmodel EventRegistration {\n id String @id @default(cuid())\n userId String\n eventId String\n createdAt DateTime @default(now())\n event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([userId, eventId])\n @@index([userId])\n @@index([eventId])\n}\n\nmodel EventFeedback {\n id String @id @default(cuid())\n userId String\n eventId String\n rating Int\n comment String?\n isRead Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([userId, eventId])\n @@index([userId])\n @@index([eventId])\n @@index([isRead])\n}\n\nmodel SitePreferences {\n id String @id @default(\"global\")\n homeBackground String?\n eventsBackground String?\n leaderboardBackground String?\n challengesBackground String?\n eventRegistrationPoints Int @default(100)\n eventFeedbackPoints Int @default(100)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nenum Role {\n USER\n ADMIN\n}\n\nenum EventType {\n ATELIER\n KATA\n PRESENTATION\n LEARNING_HOUR\n}\n\nenum CharacterClass {\n WARRIOR\n MAGE\n ROGUE\n RANGER\n PALADIN\n ENGINEER\n MERCHANT\n SCHOLAR\n BERSERKER\n NECROMANCER\n}\n\nenum ChallengeStatus {\n PENDING\n ACCEPTED\n COMPLETED\n REJECTED\n CANCELLED\n}\n\nmodel Challenge {\n id String @id @default(cuid())\n challengerId String // Joueur qui lance le défi\n challengedId String // Joueur qui reçoit le défi\n challenger User @relation(\"Challenger\", fields: [challengerId], references: [id], onDelete: Cascade)\n challenged User @relation(\"Challenged\", fields: [challengedId], references: [id], onDelete: Cascade)\n title String // Titre du défi\n description String // Description détaillée du défi\n pointsReward Int @default(100) // Points à gagner pour le gagnant\n status ChallengeStatus @default(PENDING)\n adminId String? // Admin qui valide le défi\n admin User? @relation(\"AdminValidator\", fields: [adminId], references: [id], onDelete: SetNull)\n adminComment String? // Commentaire de l'admin lors de la validation/rejet\n winnerId String? // ID du gagnant (challengerId ou challengedId)\n winner User? @relation(\"ChallengeWinner\", fields: [winnerId], references: [id], onDelete: SetNull)\n createdAt DateTime @default(now())\n acceptedAt DateTime? // Date d'acceptation du défi\n completedAt DateTime? // Date de validation par l'admin\n updatedAt DateTime @updatedAt\n\n @@index([challengerId])\n @@index([challengedId])\n @@index([status])\n @@index([adminId])\n}\n\nmodel House {\n id String @id @default(cuid())\n name String\n description String?\n creatorId String\n creator User @relation(\"HouseCreator\", fields: [creatorId], references: [id], onDelete: Cascade)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n memberships HouseMembership[]\n invitations HouseInvitation[]\n requests HouseRequest[]\n\n @@index([creatorId])\n @@index([name])\n}\n\nmodel HouseMembership {\n id String @id @default(cuid())\n houseId String\n userId String\n role HouseRole @default(MEMBER)\n joinedAt DateTime @default(now())\n house House @relation(fields: [houseId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([houseId, userId])\n @@index([houseId])\n @@index([userId])\n}\n\nmodel HouseInvitation {\n id String @id @default(cuid())\n houseId String\n inviterId String // Utilisateur qui envoie l'invitation\n inviteeId String // Utilisateur invité\n status InvitationStatus @default(PENDING)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n house House @relation(fields: [houseId], references: [id], onDelete: Cascade)\n inviter User @relation(\"Inviter\", fields: [inviterId], references: [id], onDelete: Cascade)\n invitee User @relation(\"Invitee\", fields: [inviteeId], references: [id], onDelete: Cascade)\n\n @@unique([houseId, inviteeId])\n @@index([houseId])\n @@index([inviteeId])\n @@index([status])\n}\n\nmodel HouseRequest {\n id String @id @default(cuid())\n houseId String\n requesterId String // Utilisateur qui demande à rejoindre\n status RequestStatus @default(PENDING)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n house House @relation(fields: [houseId], references: [id], onDelete: Cascade)\n requester User @relation(\"Requester\", fields: [requesterId], references: [id], onDelete: Cascade)\n\n @@unique([houseId, requesterId])\n @@index([houseId])\n @@index([requesterId])\n @@index([status])\n}\n\nenum HouseRole {\n OWNER\n ADMIN\n MEMBER\n}\n\nenum InvitationStatus {\n PENDING\n ACCEPTED\n REJECTED\n CANCELLED\n}\n\nenum RequestStatus {\n PENDING\n ACCEPTED\n REJECTED\n CANCELLED\n}\n", + "inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"./generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel User {\n id String @id @default(cuid())\n email String @unique\n password String\n username String @unique\n role Role @default(USER)\n score Int @default(0)\n level Int @default(1)\n hp Int @default(1000)\n maxHp Int @default(1000)\n xp Int @default(0)\n maxXp Int @default(5000)\n avatar String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n bio String?\n characterClass CharacterClass?\n eventFeedbacks EventFeedback[]\n eventRegistrations EventRegistration[]\n preferences UserPreferences?\n challengesAsChallenger Challenge[] @relation(\"Challenger\")\n challengesAsChallenged Challenge[] @relation(\"Challenged\")\n challengesAsAdmin Challenge[] @relation(\"AdminValidator\")\n challengesAsWinner Challenge[] @relation(\"ChallengeWinner\")\n houseMemberships HouseMembership[]\n houseInvitationsSent HouseInvitation[] @relation(\"Inviter\")\n houseInvitationsReceived HouseInvitation[] @relation(\"Invitee\")\n houseRequestsSent HouseRequest[] @relation(\"Requester\")\n housesCreated House[] @relation(\"HouseCreator\")\n\n @@index([score])\n @@index([email])\n}\n\nmodel UserPreferences {\n id String @id @default(cuid())\n userId String @unique\n homeBackground String?\n eventsBackground String?\n leaderboardBackground String?\n theme String? @default(\"default\")\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Event {\n id String @id @default(cuid())\n date DateTime\n name String\n description String\n type EventType\n room String?\n time String?\n maxPlaces Int?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n feedbacks EventFeedback[]\n registrations EventRegistration[]\n\n @@index([date])\n}\n\nmodel EventRegistration {\n id String @id @default(cuid())\n userId String\n eventId String\n createdAt DateTime @default(now())\n event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([userId, eventId])\n @@index([userId])\n @@index([eventId])\n}\n\nmodel EventFeedback {\n id String @id @default(cuid())\n userId String\n eventId String\n rating Int\n comment String?\n isRead Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([userId, eventId])\n @@index([userId])\n @@index([eventId])\n @@index([isRead])\n}\n\nmodel SitePreferences {\n id String @id @default(\"global\")\n homeBackground String?\n eventsBackground String?\n leaderboardBackground String?\n challengesBackground String?\n eventRegistrationPoints Int @default(100)\n eventFeedbackPoints Int @default(100)\n houseJoinPoints Int @default(100)\n houseLeavePoints Int @default(100)\n houseCreatePoints Int @default(100)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nenum Role {\n USER\n ADMIN\n}\n\nenum EventType {\n ATELIER\n KATA\n PRESENTATION\n LEARNING_HOUR\n}\n\nenum CharacterClass {\n WARRIOR\n MAGE\n ROGUE\n RANGER\n PALADIN\n ENGINEER\n MERCHANT\n SCHOLAR\n BERSERKER\n NECROMANCER\n}\n\nenum ChallengeStatus {\n PENDING\n ACCEPTED\n COMPLETED\n REJECTED\n CANCELLED\n}\n\nmodel Challenge {\n id String @id @default(cuid())\n challengerId String // Joueur qui lance le défi\n challengedId String // Joueur qui reçoit le défi\n challenger User @relation(\"Challenger\", fields: [challengerId], references: [id], onDelete: Cascade)\n challenged User @relation(\"Challenged\", fields: [challengedId], references: [id], onDelete: Cascade)\n title String // Titre du défi\n description String // Description détaillée du défi\n pointsReward Int @default(100) // Points à gagner pour le gagnant\n status ChallengeStatus @default(PENDING)\n adminId String? // Admin qui valide le défi\n admin User? @relation(\"AdminValidator\", fields: [adminId], references: [id], onDelete: SetNull)\n adminComment String? // Commentaire de l'admin lors de la validation/rejet\n winnerId String? // ID du gagnant (challengerId ou challengedId)\n winner User? @relation(\"ChallengeWinner\", fields: [winnerId], references: [id], onDelete: SetNull)\n createdAt DateTime @default(now())\n acceptedAt DateTime? // Date d'acceptation du défi\n completedAt DateTime? // Date de validation par l'admin\n updatedAt DateTime @updatedAt\n\n @@index([challengerId])\n @@index([challengedId])\n @@index([status])\n @@index([adminId])\n}\n\nmodel House {\n id String @id @default(cuid())\n name String\n description String?\n creatorId String\n creator User @relation(\"HouseCreator\", fields: [creatorId], references: [id], onDelete: Cascade)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n memberships HouseMembership[]\n invitations HouseInvitation[]\n requests HouseRequest[]\n\n @@index([creatorId])\n @@index([name])\n}\n\nmodel HouseMembership {\n id String @id @default(cuid())\n houseId String\n userId String\n role HouseRole @default(MEMBER)\n joinedAt DateTime @default(now())\n house House @relation(fields: [houseId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([houseId, userId])\n @@index([houseId])\n @@index([userId])\n}\n\nmodel HouseInvitation {\n id String @id @default(cuid())\n houseId String\n inviterId String // Utilisateur qui envoie l'invitation\n inviteeId String // Utilisateur invité\n status InvitationStatus @default(PENDING)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n house House @relation(fields: [houseId], references: [id], onDelete: Cascade)\n inviter User @relation(\"Inviter\", fields: [inviterId], references: [id], onDelete: Cascade)\n invitee User @relation(\"Invitee\", fields: [inviteeId], references: [id], onDelete: Cascade)\n\n @@unique([houseId, inviteeId])\n @@index([houseId])\n @@index([inviteeId])\n @@index([status])\n}\n\nmodel HouseRequest {\n id String @id @default(cuid())\n houseId String\n requesterId String // Utilisateur qui demande à rejoindre\n status RequestStatus @default(PENDING)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n house House @relation(fields: [houseId], references: [id], onDelete: Cascade)\n requester User @relation(\"Requester\", fields: [requesterId], references: [id], onDelete: Cascade)\n\n @@unique([houseId, requesterId])\n @@index([houseId])\n @@index([requesterId])\n @@index([status])\n}\n\nenum HouseRole {\n OWNER\n ADMIN\n MEMBER\n}\n\nenum InvitationStatus {\n PENDING\n ACCEPTED\n REJECTED\n CANCELLED\n}\n\nenum RequestStatus {\n PENDING\n ACCEPTED\n REJECTED\n CANCELLED\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -28,7 +28,7 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"Role\"},{\"name\":\"score\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"level\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"hp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"maxHp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"xp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"maxXp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"avatar\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"bio\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"characterClass\",\"kind\":\"enum\",\"type\":\"CharacterClass\"},{\"name\":\"eventFeedbacks\",\"kind\":\"object\",\"type\":\"EventFeedback\",\"relationName\":\"EventFeedbackToUser\"},{\"name\":\"eventRegistrations\",\"kind\":\"object\",\"type\":\"EventRegistration\",\"relationName\":\"EventRegistrationToUser\"},{\"name\":\"preferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"challengesAsChallenger\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"Challenger\"},{\"name\":\"challengesAsChallenged\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"Challenged\"},{\"name\":\"challengesAsAdmin\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"AdminValidator\"},{\"name\":\"challengesAsWinner\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"ChallengeWinner\"},{\"name\":\"houseMemberships\",\"kind\":\"object\",\"type\":\"HouseMembership\",\"relationName\":\"HouseMembershipToUser\"},{\"name\":\"houseInvitationsSent\",\"kind\":\"object\",\"type\":\"HouseInvitation\",\"relationName\":\"Inviter\"},{\"name\":\"houseInvitationsReceived\",\"kind\":\"object\",\"type\":\"HouseInvitation\",\"relationName\":\"Invitee\"},{\"name\":\"houseRequestsSent\",\"kind\":\"object\",\"type\":\"HouseRequest\",\"relationName\":\"Requester\"},{\"name\":\"housesCreated\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseCreator\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventsBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"leaderboardBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"theme\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Event\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"enum\",\"type\":\"EventType\"},{\"name\":\"room\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"time\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxPlaces\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"feedbacks\",\"kind\":\"object\",\"type\":\"EventFeedback\",\"relationName\":\"EventToEventFeedback\"},{\"name\":\"registrations\",\"kind\":\"object\",\"type\":\"EventRegistration\",\"relationName\":\"EventToEventRegistration\"}],\"dbName\":null},\"EventRegistration\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"event\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToEventRegistration\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventRegistrationToUser\"}],\"dbName\":null},\"EventFeedback\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rating\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"comment\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isRead\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"event\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToEventFeedback\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventFeedbackToUser\"}],\"dbName\":null},\"SitePreferences\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventsBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"leaderboardBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengesBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventRegistrationPoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"eventFeedbackPoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Challenge\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengedId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challenger\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Challenger\"},{\"name\":\"challenged\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Challenged\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"pointsReward\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"ChallengeStatus\"},{\"name\":\"adminId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"admin\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AdminValidator\"},{\"name\":\"adminComment\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"winnerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"winner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChallengeWinner\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"acceptedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"completedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"House\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"creatorId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"creator\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HouseCreator\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"memberships\",\"kind\":\"object\",\"type\":\"HouseMembership\",\"relationName\":\"HouseToHouseMembership\"},{\"name\":\"invitations\",\"kind\":\"object\",\"type\":\"HouseInvitation\",\"relationName\":\"HouseToHouseInvitation\"},{\"name\":\"requests\",\"kind\":\"object\",\"type\":\"HouseRequest\",\"relationName\":\"HouseToHouseRequest\"}],\"dbName\":null},\"HouseMembership\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"houseId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"HouseRole\"},{\"name\":\"joinedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"house\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseToHouseMembership\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HouseMembershipToUser\"}],\"dbName\":null},\"HouseInvitation\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"houseId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inviterId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inviteeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"InvitationStatus\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"house\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseToHouseInvitation\"},{\"name\":\"inviter\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Inviter\"},{\"name\":\"invitee\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Invitee\"}],\"dbName\":null},\"HouseRequest\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"houseId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"requesterId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"RequestStatus\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"house\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseToHouseRequest\"},{\"name\":\"requester\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Requester\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"Role\"},{\"name\":\"score\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"level\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"hp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"maxHp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"xp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"maxXp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"avatar\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"bio\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"characterClass\",\"kind\":\"enum\",\"type\":\"CharacterClass\"},{\"name\":\"eventFeedbacks\",\"kind\":\"object\",\"type\":\"EventFeedback\",\"relationName\":\"EventFeedbackToUser\"},{\"name\":\"eventRegistrations\",\"kind\":\"object\",\"type\":\"EventRegistration\",\"relationName\":\"EventRegistrationToUser\"},{\"name\":\"preferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"challengesAsChallenger\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"Challenger\"},{\"name\":\"challengesAsChallenged\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"Challenged\"},{\"name\":\"challengesAsAdmin\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"AdminValidator\"},{\"name\":\"challengesAsWinner\",\"kind\":\"object\",\"type\":\"Challenge\",\"relationName\":\"ChallengeWinner\"},{\"name\":\"houseMemberships\",\"kind\":\"object\",\"type\":\"HouseMembership\",\"relationName\":\"HouseMembershipToUser\"},{\"name\":\"houseInvitationsSent\",\"kind\":\"object\",\"type\":\"HouseInvitation\",\"relationName\":\"Inviter\"},{\"name\":\"houseInvitationsReceived\",\"kind\":\"object\",\"type\":\"HouseInvitation\",\"relationName\":\"Invitee\"},{\"name\":\"houseRequestsSent\",\"kind\":\"object\",\"type\":\"HouseRequest\",\"relationName\":\"Requester\"},{\"name\":\"housesCreated\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseCreator\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventsBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"leaderboardBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"theme\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Event\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"enum\",\"type\":\"EventType\"},{\"name\":\"room\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"time\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxPlaces\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"feedbacks\",\"kind\":\"object\",\"type\":\"EventFeedback\",\"relationName\":\"EventToEventFeedback\"},{\"name\":\"registrations\",\"kind\":\"object\",\"type\":\"EventRegistration\",\"relationName\":\"EventToEventRegistration\"}],\"dbName\":null},\"EventRegistration\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"event\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToEventRegistration\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventRegistrationToUser\"}],\"dbName\":null},\"EventFeedback\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rating\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"comment\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isRead\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"event\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToEventFeedback\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventFeedbackToUser\"}],\"dbName\":null},\"SitePreferences\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventsBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"leaderboardBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengesBackground\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"eventRegistrationPoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"eventFeedbackPoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"houseJoinPoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"houseLeavePoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"houseCreatePoints\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Challenge\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengedId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challenger\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Challenger\"},{\"name\":\"challenged\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Challenged\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"pointsReward\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"ChallengeStatus\"},{\"name\":\"adminId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"admin\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AdminValidator\"},{\"name\":\"adminComment\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"winnerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"winner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChallengeWinner\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"acceptedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"completedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"House\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"creatorId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"creator\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HouseCreator\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"memberships\",\"kind\":\"object\",\"type\":\"HouseMembership\",\"relationName\":\"HouseToHouseMembership\"},{\"name\":\"invitations\",\"kind\":\"object\",\"type\":\"HouseInvitation\",\"relationName\":\"HouseToHouseInvitation\"},{\"name\":\"requests\",\"kind\":\"object\",\"type\":\"HouseRequest\",\"relationName\":\"HouseToHouseRequest\"}],\"dbName\":null},\"HouseMembership\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"houseId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"HouseRole\"},{\"name\":\"joinedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"house\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseToHouseMembership\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HouseMembershipToUser\"}],\"dbName\":null},\"HouseInvitation\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"houseId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inviterId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inviteeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"InvitationStatus\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"house\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseToHouseInvitation\"},{\"name\":\"inviter\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Inviter\"},{\"name\":\"invitee\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Invitee\"}],\"dbName\":null},\"HouseRequest\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"houseId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"requesterId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"RequestStatus\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"house\",\"kind\":\"object\",\"type\":\"House\",\"relationName\":\"HouseToHouseRequest\"},{\"name\":\"requester\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"Requester\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") async function decodeBase64AsWasm(wasmBase64: string): Promise { const { Buffer } = await import('node:buffer') diff --git a/prisma/generated/prisma/internal/prismaNamespace.ts b/prisma/generated/prisma/internal/prismaNamespace.ts index a11cfd7..3500c72 100644 --- a/prisma/generated/prisma/internal/prismaNamespace.ts +++ b/prisma/generated/prisma/internal/prismaNamespace.ts @@ -1351,6 +1351,9 @@ export const SitePreferencesScalarFieldEnum = { challengesBackground: 'challengesBackground', eventRegistrationPoints: 'eventRegistrationPoints', eventFeedbackPoints: 'eventFeedbackPoints', + houseJoinPoints: 'houseJoinPoints', + houseLeavePoints: 'houseLeavePoints', + houseCreatePoints: 'houseCreatePoints', createdAt: 'createdAt', updatedAt: 'updatedAt' } as const diff --git a/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts b/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts index 7f65992..f6127ef 100644 --- a/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts +++ b/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts @@ -164,6 +164,9 @@ export const SitePreferencesScalarFieldEnum = { challengesBackground: 'challengesBackground', eventRegistrationPoints: 'eventRegistrationPoints', eventFeedbackPoints: 'eventFeedbackPoints', + houseJoinPoints: 'houseJoinPoints', + houseLeavePoints: 'houseLeavePoints', + houseCreatePoints: 'houseCreatePoints', createdAt: 'createdAt', updatedAt: 'updatedAt' } as const diff --git a/prisma/generated/prisma/models/SitePreferences.ts b/prisma/generated/prisma/models/SitePreferences.ts index 3aa5e16..008078d 100644 --- a/prisma/generated/prisma/models/SitePreferences.ts +++ b/prisma/generated/prisma/models/SitePreferences.ts @@ -29,11 +29,17 @@ export type AggregateSitePreferences = { export type SitePreferencesAvgAggregateOutputType = { eventRegistrationPoints: number | null eventFeedbackPoints: number | null + houseJoinPoints: number | null + houseLeavePoints: number | null + houseCreatePoints: number | null } export type SitePreferencesSumAggregateOutputType = { eventRegistrationPoints: number | null eventFeedbackPoints: number | null + houseJoinPoints: number | null + houseLeavePoints: number | null + houseCreatePoints: number | null } export type SitePreferencesMinAggregateOutputType = { @@ -44,6 +50,9 @@ export type SitePreferencesMinAggregateOutputType = { challengesBackground: string | null eventRegistrationPoints: number | null eventFeedbackPoints: number | null + houseJoinPoints: number | null + houseLeavePoints: number | null + houseCreatePoints: number | null createdAt: Date | null updatedAt: Date | null } @@ -56,6 +65,9 @@ export type SitePreferencesMaxAggregateOutputType = { challengesBackground: string | null eventRegistrationPoints: number | null eventFeedbackPoints: number | null + houseJoinPoints: number | null + houseLeavePoints: number | null + houseCreatePoints: number | null createdAt: Date | null updatedAt: Date | null } @@ -68,6 +80,9 @@ export type SitePreferencesCountAggregateOutputType = { challengesBackground: number eventRegistrationPoints: number eventFeedbackPoints: number + houseJoinPoints: number + houseLeavePoints: number + houseCreatePoints: number createdAt: number updatedAt: number _all: number @@ -77,11 +92,17 @@ export type SitePreferencesCountAggregateOutputType = { export type SitePreferencesAvgAggregateInputType = { eventRegistrationPoints?: true eventFeedbackPoints?: true + houseJoinPoints?: true + houseLeavePoints?: true + houseCreatePoints?: true } export type SitePreferencesSumAggregateInputType = { eventRegistrationPoints?: true eventFeedbackPoints?: true + houseJoinPoints?: true + houseLeavePoints?: true + houseCreatePoints?: true } export type SitePreferencesMinAggregateInputType = { @@ -92,6 +113,9 @@ export type SitePreferencesMinAggregateInputType = { challengesBackground?: true eventRegistrationPoints?: true eventFeedbackPoints?: true + houseJoinPoints?: true + houseLeavePoints?: true + houseCreatePoints?: true createdAt?: true updatedAt?: true } @@ -104,6 +128,9 @@ export type SitePreferencesMaxAggregateInputType = { challengesBackground?: true eventRegistrationPoints?: true eventFeedbackPoints?: true + houseJoinPoints?: true + houseLeavePoints?: true + houseCreatePoints?: true createdAt?: true updatedAt?: true } @@ -116,6 +143,9 @@ export type SitePreferencesCountAggregateInputType = { challengesBackground?: true eventRegistrationPoints?: true eventFeedbackPoints?: true + houseJoinPoints?: true + houseLeavePoints?: true + houseCreatePoints?: true createdAt?: true updatedAt?: true _all?: true @@ -215,6 +245,9 @@ export type SitePreferencesGroupByOutputType = { challengesBackground: string | null eventRegistrationPoints: number eventFeedbackPoints: number + houseJoinPoints: number + houseLeavePoints: number + houseCreatePoints: number createdAt: Date updatedAt: Date _count: SitePreferencesCountAggregateOutputType | null @@ -250,6 +283,9 @@ export type SitePreferencesWhereInput = { challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number + houseJoinPoints?: Prisma.IntFilter<"SitePreferences"> | number + houseLeavePoints?: Prisma.IntFilter<"SitePreferences"> | number + houseCreatePoints?: Prisma.IntFilter<"SitePreferences"> | number createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string } @@ -262,6 +298,9 @@ export type SitePreferencesOrderByWithRelationInput = { challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder } @@ -277,6 +316,9 @@ export type SitePreferencesWhereUniqueInput = Prisma.AtLeast<{ challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number + houseJoinPoints?: Prisma.IntFilter<"SitePreferences"> | number + houseLeavePoints?: Prisma.IntFilter<"SitePreferences"> | number + houseCreatePoints?: Prisma.IntFilter<"SitePreferences"> | number createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string }, "id"> @@ -289,6 +331,9 @@ export type SitePreferencesOrderByWithAggregationInput = { challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder _count?: Prisma.SitePreferencesCountOrderByAggregateInput @@ -309,6 +354,9 @@ export type SitePreferencesScalarWhereWithAggregatesInput = { challengesBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null eventRegistrationPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number eventFeedbackPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number + houseJoinPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number + houseLeavePoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number + houseCreatePoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number createdAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string } @@ -321,6 +369,9 @@ export type SitePreferencesCreateInput = { challengesBackground?: string | null eventRegistrationPoints?: number eventFeedbackPoints?: number + houseJoinPoints?: number + houseLeavePoints?: number + houseCreatePoints?: number createdAt?: Date | string updatedAt?: Date | string } @@ -333,6 +384,9 @@ export type SitePreferencesUncheckedCreateInput = { challengesBackground?: string | null eventRegistrationPoints?: number eventFeedbackPoints?: number + houseJoinPoints?: number + houseLeavePoints?: number + houseCreatePoints?: number createdAt?: Date | string updatedAt?: Date | string } @@ -345,6 +399,9 @@ export type SitePreferencesUpdateInput = { challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number + houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } @@ -357,6 +414,9 @@ export type SitePreferencesUncheckedUpdateInput = { challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number + houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } @@ -369,6 +429,9 @@ export type SitePreferencesCreateManyInput = { challengesBackground?: string | null eventRegistrationPoints?: number eventFeedbackPoints?: number + houseJoinPoints?: number + houseLeavePoints?: number + houseCreatePoints?: number createdAt?: Date | string updatedAt?: Date | string } @@ -381,6 +444,9 @@ export type SitePreferencesUpdateManyMutationInput = { challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number + houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } @@ -393,6 +459,9 @@ export type SitePreferencesUncheckedUpdateManyInput = { challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number + houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number + houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } @@ -405,6 +474,9 @@ export type SitePreferencesCountOrderByAggregateInput = { challengesBackground?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder } @@ -412,6 +484,9 @@ export type SitePreferencesCountOrderByAggregateInput = { export type SitePreferencesAvgOrderByAggregateInput = { eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder } export type SitePreferencesMaxOrderByAggregateInput = { @@ -422,6 +497,9 @@ export type SitePreferencesMaxOrderByAggregateInput = { challengesBackground?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder } @@ -434,6 +512,9 @@ export type SitePreferencesMinOrderByAggregateInput = { challengesBackground?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder } @@ -441,6 +522,9 @@ export type SitePreferencesMinOrderByAggregateInput = { export type SitePreferencesSumOrderByAggregateInput = { eventRegistrationPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder + houseJoinPoints?: Prisma.SortOrder + houseLeavePoints?: Prisma.SortOrder + houseCreatePoints?: Prisma.SortOrder } @@ -453,6 +537,9 @@ export type SitePreferencesSelect @@ -465,6 +552,9 @@ export type SitePreferencesSelectCreateManyAndReturn @@ -477,6 +567,9 @@ export type SitePreferencesSelectUpdateManyAndReturn @@ -489,11 +582,14 @@ export type SitePreferencesSelectScalar = { challengesBackground?: boolean eventRegistrationPoints?: boolean eventFeedbackPoints?: boolean + houseJoinPoints?: boolean + houseLeavePoints?: boolean + houseCreatePoints?: boolean createdAt?: boolean updatedAt?: boolean } -export type SitePreferencesOmit = runtime.Types.Extensions.GetOmit<"id" | "homeBackground" | "eventsBackground" | "leaderboardBackground" | "challengesBackground" | "eventRegistrationPoints" | "eventFeedbackPoints" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]> +export type SitePreferencesOmit = runtime.Types.Extensions.GetOmit<"id" | "homeBackground" | "eventsBackground" | "leaderboardBackground" | "challengesBackground" | "eventRegistrationPoints" | "eventFeedbackPoints" | "houseJoinPoints" | "houseLeavePoints" | "houseCreatePoints" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]> export type $SitePreferencesPayload = { name: "SitePreferences" @@ -506,6 +602,9 @@ export type $SitePreferencesPayload @@ -938,6 +1037,9 @@ export interface SitePreferencesFieldRefs { readonly challengesBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly eventRegistrationPoints: Prisma.FieldRef<"SitePreferences", 'Int'> readonly eventFeedbackPoints: Prisma.FieldRef<"SitePreferences", 'Int'> + readonly houseJoinPoints: Prisma.FieldRef<"SitePreferences", 'Int'> + readonly houseLeavePoints: Prisma.FieldRef<"SitePreferences", 'Int'> + readonly houseCreatePoints: Prisma.FieldRef<"SitePreferences", 'Int'> readonly createdAt: Prisma.FieldRef<"SitePreferences", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"SitePreferences", 'DateTime'> } diff --git a/prisma/migrations/20250115120000_add_house_points_preferences/migration.sql b/prisma/migrations/20250115120000_add_house_points_preferences/migration.sql new file mode 100644 index 0000000..5427415 --- /dev/null +++ b/prisma/migrations/20250115120000_add_house_points_preferences/migration.sql @@ -0,0 +1,9 @@ +-- AlterTable +ALTER TABLE "SitePreferences" ADD COLUMN "houseJoinPoints" INTEGER NOT NULL DEFAULT 100; + +-- AlterTable +ALTER TABLE "SitePreferences" ADD COLUMN "houseLeavePoints" INTEGER NOT NULL DEFAULT 100; + +-- AlterTable +ALTER TABLE "SitePreferences" ADD COLUMN "houseCreatePoints" INTEGER NOT NULL DEFAULT 100; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3937fff..052a4e7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -109,6 +109,9 @@ model SitePreferences { challengesBackground String? eventRegistrationPoints Int @default(100) eventFeedbackPoints Int @default(100) + houseJoinPoints Int @default(100) + houseLeavePoints Int @default(100) + houseCreatePoints Int @default(100) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/services/houses/house.service.ts b/services/houses/house.service.ts index e52d095..7abd83e 100644 --- a/services/houses/house.service.ts +++ b/services/houses/house.service.ts @@ -8,6 +8,7 @@ import type { InvitationStatus, RequestStatus, Prisma, + SitePreferences, } from "@/prisma/generated/prisma/client"; import { ValidationError, @@ -15,6 +16,14 @@ import { 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; @@ -143,10 +152,7 @@ export class HouseService { /** * Vérifie si un utilisateur est membre d'une maison */ - async isUserMemberOfHouse( - userId: string, - houseId: string - ): Promise { + async isUserMemberOfHouse(userId: string, houseId: string): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { @@ -161,10 +167,7 @@ export class HouseService { /** * Vérifie si un utilisateur est propriétaire ou admin d'une maison */ - async isUserOwnerOrAdmin( - userId: string, - houseId: string - ): Promise { + async isUserOwnerOrAdmin(userId: string, houseId: string): Promise { const membership = await prisma.houseMembership.findUnique({ where: { houseId_userId: { @@ -248,7 +251,10 @@ export class HouseService { ); } - if (data.description && data.description.length > HOUSE_DESCRIPTION_MAX_LENGTH) { + 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" @@ -280,19 +286,46 @@ export class HouseService { throw new ConflictError("Ce nom de maison est déjà utilisé"); } - // Créer la maison et ajouter le créateur comme OWNER - return prisma.house.create({ - data: { - name: data.name.trim(), - description: data.description?.trim() || null, - creatorId: data.creatorId, - memberships: { - create: { - userId: data.creatorId, - role: "OWNER", + // 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; }); } @@ -348,7 +381,10 @@ export class HouseService { } if (data.description !== undefined) { - if (data.description && data.description.length > HOUSE_DESCRIPTION_MAX_LENGTH) { + 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" @@ -370,13 +406,48 @@ export class HouseService { // 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" - ); + throw new ForbiddenError("Seul le propriétaire peut supprimer la maison"); } - await prisma.house.delete({ + // 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 }, + }); }); } @@ -404,22 +475,6 @@ export class HouseService { throw new ConflictError("Cet utilisateur est déjà membre de la maison"); } - // Vérifier qu'il n'y a pas déjà une invitation en attente - const existingInvitation = await prisma.houseInvitation.findUnique({ - where: { - houseId_inviteeId: { - houseId: data.houseId, - inviteeId: data.inviteeId, - }, - }, - }); - - if (existingInvitation && existingInvitation.status === "PENDING") { - throw new ConflictError( - "Une invitation est déjà en attente pour cet utilisateur" - ); - } - // Vérifier que l'invité n'est pas déjà dans une autre maison const existingMembership = await prisma.houseMembership.findFirst({ where: { userId: data.inviteeId }, @@ -431,6 +486,37 @@ export class HouseService { ); } + // 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: { @@ -476,6 +562,19 @@ export class HouseService { ); } + // 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({ @@ -510,6 +609,16 @@ export class HouseService { data: { status: "CANCELLED" }, }); + // Attribuer les points à l'utilisateur qui rejoint + await tx.user.update({ + where: { id: userId }, + data: { + score: { + increment: pointsToAward, + }, + }, + }); + return membership; }); } @@ -592,7 +701,7 @@ export class HouseService { ); } - // Vérifier qu'il n'y a pas déjà une demande en attente + // Vérifier s'il existe déjà une demande const existingRequest = await prisma.houseRequest.findUnique({ where: { houseId_requesterId: { @@ -602,13 +711,27 @@ export class HouseService { }, }); - if (existingRequest && existingRequest.status === "PENDING") { - throw new ConflictError( - "Une demande est déjà en attente pour cette maison" - ); + 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 la demande + // Créer une nouvelle demande return prisma.houseRequest.create({ data: { houseId: data.houseId, @@ -635,10 +758,7 @@ export class HouseService { } // Vérifier que l'utilisateur est propriétaire ou admin de la maison - const isAuthorized = await this.isUserOwnerOrAdmin( - userId, - request.houseId - ); + const isAuthorized = await this.isUserOwnerOrAdmin(userId, request.houseId); if (!isAuthorized) { throw new ForbiddenError( "Vous n'avez pas les permissions pour accepter cette demande" @@ -660,6 +780,19 @@ export class HouseService { ); } + // 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({ @@ -694,6 +827,16 @@ export class HouseService { data: { status: "CANCELLED" }, }); + // Attribuer les points à l'utilisateur qui rejoint + await tx.user.update({ + where: { id: request.requesterId }, + data: { + score: { + increment: pointsToAward, + }, + }, + }); + return membership; }); } @@ -711,10 +854,7 @@ export class HouseService { } // Vérifier que l'utilisateur est propriétaire ou admin de la maison - const isAuthorized = await this.isUserOwnerOrAdmin( - userId, - request.houseId - ); + const isAuthorized = await this.isUserOwnerOrAdmin(userId, request.houseId); if (!isAuthorized) { throw new ForbiddenError( "Vous n'avez pas les permissions pour refuser cette demande" @@ -759,6 +899,88 @@ export class HouseService { }); } + /** + * 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, + }, + }, + }); + }); + } + /** * Quitte une maison */ @@ -783,13 +1005,39 @@ export class HouseService { ); } - await prisma.houseMembership.delete({ - where: { - houseId_userId: { - houseId, - userId, + // 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, + }, + }, + }); }); } @@ -819,6 +1067,66 @@ export class HouseService { }); } + /** + * 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 */ @@ -879,9 +1187,7 @@ export class HouseService { /** * Récupère une invitation par son ID (avec seulement houseId) */ - async getInvitationById( - id: string - ): Promise<{ houseId: string } | null> { + async getInvitationById(id: string): Promise<{ houseId: string } | null> { return prisma.houseInvitation.findUnique({ where: { id }, select: { houseId: true }, @@ -890,4 +1196,3 @@ export class HouseService { } export const houseService = new HouseService(); - diff --git a/services/preferences/site-preferences.service.ts b/services/preferences/site-preferences.service.ts index 531e2fb..d72c56a 100644 --- a/services/preferences/site-preferences.service.ts +++ b/services/preferences/site-preferences.service.ts @@ -2,6 +2,13 @@ import { prisma } from "../database"; import { normalizeBackgroundUrl } from "@/lib/avatars"; import type { SitePreferences } from "@/prisma/generated/prisma/client"; +// Type étendu pour les préférences avec les nouveaux champs de points des maisons +type SitePreferencesWithHousePoints = SitePreferences & { + houseJoinPoints?: number; + houseLeavePoints?: number; + houseCreatePoints?: number; +}; + export interface UpdateSitePreferencesInput { homeBackground?: string | null; eventsBackground?: string | null; @@ -9,6 +16,9 @@ export interface UpdateSitePreferencesInput { challengesBackground?: string | null; eventRegistrationPoints?: number; eventFeedbackPoints?: number; + houseJoinPoints?: number; + houseLeavePoints?: number; + houseCreatePoints?: number; } /** @@ -42,10 +52,19 @@ export class SitePreferencesService { challengesBackground: null, eventRegistrationPoints: 100, eventFeedbackPoints: 100, + houseJoinPoints: 100, + houseLeavePoints: 100, + houseCreatePoints: 100, }, }); } + // S'assurer que les valeurs par défaut sont présentes même si les colonnes n'existent pas encore + const prefs = sitePreferences as SitePreferencesWithHousePoints; + if (prefs.houseJoinPoints == null) prefs.houseJoinPoints = 100; + if (prefs.houseLeavePoints == null) prefs.houseLeavePoints = 100; + if (prefs.houseCreatePoints == null) prefs.houseCreatePoints = 100; + return sitePreferences; } @@ -82,6 +101,16 @@ export class SitePreferencesService { data.eventFeedbackPoints !== undefined ? data.eventFeedbackPoints : undefined, + houseJoinPoints: + data.houseJoinPoints !== undefined ? data.houseJoinPoints : undefined, + houseLeavePoints: + data.houseLeavePoints !== undefined + ? data.houseLeavePoints + : undefined, + houseCreatePoints: + data.houseCreatePoints !== undefined + ? data.houseCreatePoints + : undefined, }, create: { id: "global", @@ -99,6 +128,9 @@ export class SitePreferencesService { : (data.challengesBackground ?? null), eventRegistrationPoints: data.eventRegistrationPoints ?? 100, eventFeedbackPoints: data.eventFeedbackPoints ?? 100, + houseJoinPoints: data.houseJoinPoints ?? 100, + houseLeavePoints: data.houseLeavePoints ?? 100, + houseCreatePoints: data.houseCreatePoints ?? 100, }, }); }