Add dotenv package for environment variable management and update pnpm-lock.yaml. Adjust layout in RegisterPage and LoginPage components for improved responsiveness. Enhance AdminPanel with ChallengeManagement section and update navigation links for challenges. Refactor Prisma schema to include Challenge model and related enums.

This commit is contained in:
Julien Froidefond
2025-12-15 15:16:54 +01:00
parent f2bb02406e
commit bbb0fbb9a1
34 changed files with 11414 additions and 9081 deletions

152
actions/admin/challenges.ts Normal file
View File

@@ -0,0 +1,152 @@
'use server'
import { revalidatePath } from 'next/cache'
import { auth } from '@/lib/auth'
import { challengeService } from '@/services/challenges/challenge.service'
import { Role } from '@/prisma/generated/prisma/client'
import {
ValidationError,
NotFoundError,
} from '@/services/errors'
async function checkAdminAccess() {
const session = await auth()
if (!session?.user || session.user.role !== Role.ADMIN) {
throw new Error('Accès refusé - Admin uniquement')
}
return session
}
export async function validateChallenge(
challengeId: string,
winnerId: string,
adminComment?: string
) {
try {
const session = await checkAdminAccess()
const challenge = await challengeService.validateChallenge(
challengeId,
session.user.id,
winnerId,
adminComment
)
revalidatePath('/admin')
revalidatePath('/challenges')
revalidatePath('/leaderboard')
return { success: true, message: 'Défi validé avec succès', data: challenge }
} catch (error) {
console.error('Validate challenge error:', error)
if (error instanceof ValidationError) {
return { success: false, error: error.message }
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
if (error instanceof Error && error.message.includes('Accès refusé')) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors de la validation du défi' }
}
}
export async function rejectChallenge(
challengeId: string,
adminComment?: string
) {
try {
const session = await checkAdminAccess()
const challenge = await challengeService.rejectChallenge(
challengeId,
session.user.id,
adminComment
)
revalidatePath('/admin')
revalidatePath('/challenges')
return { success: true, message: 'Défi rejeté', data: challenge }
} catch (error) {
console.error('Reject challenge error:', error)
if (error instanceof ValidationError) {
return { success: false, error: error.message }
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
if (error instanceof Error && error.message.includes('Accès refusé')) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors du rejet du défi' }
}
}
export async function updateChallenge(
challengeId: string,
data: {
title?: string
description?: string
pointsReward?: number
}
) {
try {
const session = await checkAdminAccess()
const challenge = await challengeService.updateChallenge(challengeId, {
title: data.title,
description: data.description,
pointsReward: data.pointsReward,
})
revalidatePath('/admin')
revalidatePath('/challenges')
return { success: true, message: 'Défi mis à jour avec succès', data: challenge }
} catch (error) {
console.error('Update challenge error:', error)
if (error instanceof ValidationError) {
return { success: false, error: error.message }
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
if (error instanceof Error && error.message.includes('Accès refusé')) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors de la mise à jour du défi' }
}
}
export async function deleteChallenge(challengeId: string) {
try {
const session = await checkAdminAccess()
await challengeService.deleteChallenge(challengeId)
revalidatePath('/admin')
revalidatePath('/challenges')
return { success: true, message: 'Défi supprimé avec succès' }
} catch (error) {
console.error('Delete challenge error:', error)
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
if (error instanceof Error && error.message.includes('Accès refusé')) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors de la suppression du défi' }
}
}

View File

@@ -0,0 +1,112 @@
'use server'
import { revalidatePath } from 'next/cache'
import { auth } from '@/lib/auth'
import { challengeService } from '@/services/challenges/challenge.service'
import {
ValidationError,
NotFoundError,
ConflictError,
} from '@/services/errors'
export async function createChallenge(data: {
challengedId: string
title: string
description: string
pointsReward?: number
}) {
try {
const session = await auth()
if (!session?.user?.id) {
return { success: false, error: 'Vous devez être connecté pour créer un défi' }
}
const challenge = await challengeService.createChallenge({
challengerId: session.user.id,
challengedId: data.challengedId,
title: data.title,
description: data.description,
pointsReward: data.pointsReward || 100,
})
revalidatePath('/challenges')
revalidatePath('/profile')
return { success: true, message: 'Défi créé avec succès', data: challenge }
} catch (error) {
console.error('Create challenge error:', error)
if (error instanceof ValidationError || error instanceof ConflictError) {
return { success: false, error: error.message }
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors de la création du défi' }
}
}
export async function acceptChallenge(challengeId: string) {
try {
const session = await auth()
if (!session?.user?.id) {
return { success: false, error: 'Vous devez être connecté pour accepter un défi' }
}
const challenge = await challengeService.acceptChallenge(
challengeId,
session.user.id
)
revalidatePath('/challenges')
revalidatePath('/profile')
return { success: true, message: 'Défi accepté', data: challenge }
} catch (error) {
console.error('Accept challenge error:', error)
if (error instanceof ValidationError) {
return { success: false, error: error.message }
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors de l\'acceptation du défi' }
}
}
export async function cancelChallenge(challengeId: string) {
try {
const session = await auth()
if (!session?.user?.id) {
return { success: false, error: 'Vous devez être connecté pour annuler un défi' }
}
const challenge = await challengeService.cancelChallenge(
challengeId,
session.user.id
)
revalidatePath('/challenges')
revalidatePath('/profile')
return { success: true, message: 'Défi annulé', data: challenge }
} catch (error) {
console.error('Cancel challenge error:', error)
if (error instanceof ValidationError) {
return { success: false, error: error.message }
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message }
}
return { success: false, error: 'Une erreur est survenue lors de l\'annulation du défi' }
}
}

View File

@@ -0,0 +1,30 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { challengeService } from "@/services/challenges/challenge.service";
import { Role } from "@/prisma/generated/prisma/client";
export async function GET() {
try {
const session = await auth();
if (!session?.user || session.user.role !== Role.ADMIN) {
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
}
// Récupérer tous les défis (PENDING et ACCEPTED) pour l'admin
const allChallenges = await challengeService.getAllChallenges();
// Filtrer pour ne garder que PENDING et ACCEPTED
const challenges = allChallenges.filter(
(c) => c.status === "PENDING" || c.status === "ACCEPTED"
);
return NextResponse.json(challenges);
} catch (error) {
console.error("Error fetching challenges:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des défis" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,25 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { challengeService } from "@/services/challenges/challenge.service";
export async function GET() {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Vous devez être connecté" }, { status: 401 });
}
// Récupérer tous les défis de l'utilisateur
const challenges = await challengeService.getUserChallenges(session.user.id);
return NextResponse.json(challenges);
} catch (error) {
console.error("Error fetching challenges:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des défis" },
{ status: 500 }
);
}
}

39
app/api/users/route.ts Normal file
View File

@@ -0,0 +1,39 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { userService } from "@/services/users/user.service";
export async function GET() {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Vous devez être connecté" }, { status: 401 });
}
// Récupérer tous les utilisateurs (pour sélectionner qui défier)
const users = await userService.getAllUsers({
orderBy: {
username: "asc",
},
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
});
// Filtrer l'utilisateur actuel
const otherUsers = users.filter((user) => user.id !== session.user.id);
return NextResponse.json(otherUsers);
} catch (error) {
console.error("Error fetching users:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des utilisateurs" },
{ status: 500 }
);
}
}

28
app/challenges/page.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { getBackgroundImage } from "@/lib/preferences";
import NavigationWrapper from "@/components/navigation/NavigationWrapper";
import ChallengesSection from "@/components/challenges/ChallengesSection";
export const dynamic = "force-dynamic";
export default async function ChallengesPage() {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
const backgroundImage = await getBackgroundImage(
"home",
"/got-background.jpg"
);
return (
<main className="min-h-screen bg-black relative">
<NavigationWrapper />
<ChallengesSection backgroundImage={backgroundImage} />
</main>
);
}

View File

@@ -60,7 +60,7 @@ export default function LoginPage() {
<Card variant="dark" className="p-8"> <Card variant="dark" className="p-8">
<SectionTitle <SectionTitle
variant="gradient" variant="gradient"
size="lg" size="md"
className="mb-2 text-center" className="mb-2 text-center"
> >
CONNEXION CONNEXION

View File

@@ -174,7 +174,7 @@ export default function RegisterPage() {
<Navigation /> <Navigation />
<BackgroundSection backgroundImage="/got-2.jpg" className="pt-24"> <BackgroundSection backgroundImage="/got-2.jpg" className="pt-24">
{/* Register Form */} {/* Register Form */}
<div className="w-full max-w-md mx-auto px-8"> <div className="w-full max-w-4xl mx-auto px-8">
<Card variant="dark" className="p-8"> <Card variant="dark" className="p-8">
<SectionTitle <SectionTitle
variant="gradient" variant="gradient"
@@ -397,7 +397,7 @@ export default function RegisterPage() {
<label className="block text-sm font-semibold text-gray-300 mb-3 uppercase tracking-wider"> <label className="block text-sm font-semibold text-gray-300 mb-3 uppercase tracking-wider">
Classe de Personnage (optionnel) Classe de Personnage (optionnel)
</label> </label>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{CHARACTER_CLASSES.map((cls) => ( {CHARACTER_CLASSES.map((cls) => (
<button <button
key={cls.value} key={cls.value}
@@ -411,16 +411,16 @@ export default function RegisterPage() {
: cls.value, : cls.value,
}) })
} }
className={`p-3 border-2 rounded-lg text-left transition-all ${ className={`p-4 border-2 rounded-lg text-left transition-all ${
formData.characterClass === cls.value formData.characterClass === cls.value
? "border-pixel-gold bg-pixel-gold/20" ? "border-pixel-gold bg-pixel-gold/20 shadow-lg shadow-pixel-gold/30"
: "border-pixel-gold/30 bg-black/40 hover:border-pixel-gold/50" : "border-pixel-gold/30 bg-black/40 hover:border-pixel-gold/50 hover:bg-black/60"
}`} }`}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 mb-1">
<span className="text-xl">{cls.icon}</span> <span className="text-2xl">{cls.icon}</span>
<span <span
className={`font-bold text-xs uppercase tracking-wider ${ className={`font-bold text-sm uppercase tracking-wider ${
formData.characterClass === cls.value formData.characterClass === cls.value
? "text-pixel-gold" ? "text-pixel-gold"
: "text-white" : "text-white"
@@ -429,6 +429,9 @@ export default function RegisterPage() {
{cls.name} {cls.name}
</span> </span>
</div> </div>
<p className="text-xs text-gray-400 leading-tight">
{cls.desc}
</p>
</button> </button>
))} ))}
</div> </div>

View File

@@ -4,6 +4,7 @@ import { useState } from "react";
import UserManagement from "@/components/admin/UserManagement"; import UserManagement from "@/components/admin/UserManagement";
import EventManagement from "@/components/admin/EventManagement"; import EventManagement from "@/components/admin/EventManagement";
import FeedbackManagement from "@/components/admin/FeedbackManagement"; import FeedbackManagement from "@/components/admin/FeedbackManagement";
import ChallengeManagement from "@/components/admin/ChallengeManagement";
import BackgroundPreferences from "@/components/admin/BackgroundPreferences"; import BackgroundPreferences from "@/components/admin/BackgroundPreferences";
import { Button, Card, SectionTitle } from "@/components/ui"; import { Button, Card, SectionTitle } from "@/components/ui";
@@ -18,7 +19,7 @@ interface AdminPanelProps {
initialPreferences: SitePreferences; initialPreferences: SitePreferences;
} }
type AdminSection = "preferences" | "users" | "events" | "feedbacks"; type AdminSection = "preferences" | "users" | "events" | "feedbacks" | "challenges";
export default function AdminPanel({ initialPreferences }: AdminPanelProps) { export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
const [activeSection, setActiveSection] = const [activeSection, setActiveSection] =
@@ -67,6 +68,14 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
> >
Feedbacks Feedbacks
</Button> </Button>
<Button
onClick={() => setActiveSection("challenges")}
variant={activeSection === "challenges" ? "primary" : "secondary"}
size="md"
className={activeSection === "challenges" ? "bg-pixel-gold/10" : ""}
>
Défis
</Button>
</div> </div>
{activeSection === "preferences" && ( {activeSection === "preferences" && (
@@ -108,6 +117,15 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
<FeedbackManagement /> <FeedbackManagement />
</Card> </Card>
)} )}
{activeSection === "challenges" && (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Défis
</h2>
<ChallengeManagement />
</Card>
)}
</div> </div>
</section> </section>
); );

View File

@@ -0,0 +1,504 @@
"use client";
import { useEffect, useState, useTransition } from "react";
import { validateChallenge, rejectChallenge, updateChallenge, deleteChallenge } from "@/actions/admin/challenges";
import { Button, Card, Input, Textarea, Alert } from "@/components/ui";
import { Avatar } from "@/components/ui";
interface Challenge {
id: string;
challenger: {
id: string;
username: string;
avatar: string | null;
};
challenged: {
id: string;
username: string;
avatar: string | null;
};
title: string;
description: string;
pointsReward: number;
status: string;
adminComment: string | null;
createdAt: string;
acceptedAt: string | null;
}
export default function ChallengeManagement() {
const [challenges, setChallenges] = useState<Challenge[]>([]);
const [loading, setLoading] = useState(true);
const [selectedChallenge, setSelectedChallenge] = useState<Challenge | null>(null);
const [editingChallenge, setEditingChallenge] = useState<Challenge | null>(null);
const [winnerId, setWinnerId] = useState<string>("");
const [adminComment, setAdminComment] = useState("");
const [editTitle, setEditTitle] = useState("");
const [editDescription, setEditDescription] = useState("");
const [editPointsReward, setEditPointsReward] = useState<number>(0);
const [isPending, startTransition] = useTransition();
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
fetchChallenges();
}, []);
const fetchChallenges = async () => {
try {
const response = await fetch("/api/admin/challenges");
if (response.ok) {
const data = await response.json();
setChallenges(data);
}
} catch (error) {
console.error("Error fetching challenges:", error);
} finally {
setLoading(false);
}
};
const handleValidate = async () => {
if (!selectedChallenge || !winnerId) {
setErrorMessage("Veuillez sélectionner un gagnant");
setTimeout(() => setErrorMessage(null), 5000);
return;
}
startTransition(async () => {
const result = await validateChallenge(
selectedChallenge.id,
winnerId,
adminComment || undefined
);
if (result.success) {
setSuccessMessage("Défi validé avec succès ! Les points ont été attribués.");
setSelectedChallenge(null);
setWinnerId("");
setAdminComment("");
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de la validation");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
const handleReject = async () => {
if (!selectedChallenge) return;
if (!confirm("Êtes-vous sûr de vouloir rejeter ce défi ?")) {
return;
}
startTransition(async () => {
const result = await rejectChallenge(
selectedChallenge.id,
adminComment || undefined
);
if (result.success) {
setSuccessMessage("Défi rejeté");
setSelectedChallenge(null);
setAdminComment("");
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors du rejet");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
const handleEdit = (challenge: Challenge) => {
setEditingChallenge(challenge);
setEditTitle(challenge.title);
setEditDescription(challenge.description);
setEditPointsReward(challenge.pointsReward);
};
const handleUpdate = async () => {
if (!editingChallenge) return;
startTransition(async () => {
const result = await updateChallenge(editingChallenge.id, {
title: editTitle,
description: editDescription,
pointsReward: editPointsReward,
});
if (result.success) {
setSuccessMessage("Défi mis à jour avec succès");
setEditingChallenge(null);
setEditTitle("");
setEditDescription("");
setEditPointsReward(0);
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de la mise à jour");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
const handleDelete = async (challengeId: string) => {
if (!confirm("Êtes-vous sûr de vouloir supprimer ce défi ? Cette action est irréversible.")) {
return;
}
startTransition(async () => {
const result = await deleteChallenge(challengeId);
if (result.success) {
setSuccessMessage("Défi supprimé avec succès");
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de la suppression");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
if (loading) {
return (
<div className="text-center text-pixel-gold py-8">Chargement...</div>
);
}
if (challenges.length === 0) {
return (
<div className="text-center text-gray-400 py-8">
Aucun défi en attente
</div>
);
}
const acceptedChallenges = challenges.filter((c) => c.status === "ACCEPTED");
const pendingChallenges = challenges.filter((c) => c.status === "PENDING");
return (
<div className="space-y-4">
{successMessage && (
<Alert variant="success" className="mb-4">
{successMessage}
</Alert>
)}
{errorMessage && (
<Alert variant="error" className="mb-4">
{errorMessage}
</Alert>
)}
<div className="text-sm text-gray-400 mb-4">
{acceptedChallenges.length} défi{acceptedChallenges.length > 1 ? "s" : ""} en attente de validation
{pendingChallenges.length > 0 && (
<span className="ml-2">
{pendingChallenges.length} défi{pendingChallenges.length > 1 ? "s" : ""} en attente d'acceptation
</span>
)}
</div>
{challenges.map((challenge) => (
<Card key={challenge.id} variant="dark" className="p-4">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<h3 className="text-lg font-bold text-pixel-gold mb-2">
{challenge.title}
</h3>
<p className="text-gray-300 mb-4">{challenge.description}</p>
<div className="flex items-center gap-4 mb-4">
<div className="flex items-center gap-2">
<Avatar
src={challenge.challenger.avatar}
username={challenge.challenger.username}
size="sm"
/>
<span className="text-sm text-gray-300">
{challenge.challenger.username}
</span>
<span className="text-xs text-gray-500">VS</span>
<Avatar
src={challenge.challenged.avatar}
username={challenge.challenged.username}
size="sm"
/>
<span className="text-sm text-gray-300">
{challenge.challenged.username}
</span>
</div>
</div>
<div className="text-sm text-gray-400">
Récompense: <span className="text-pixel-gold font-bold">{challenge.pointsReward} points</span>
</div>
<div className="text-xs mt-2">
<span className={`px-2 py-1 rounded ${
challenge.status === "ACCEPTED"
? "bg-green-500/20 text-green-400"
: "bg-yellow-500/20 text-yellow-400"
}`}>
{challenge.status === "ACCEPTED" ? "Accepté" : "En attente d'acceptation"}
</span>
</div>
{challenge.acceptedAt && (
<div className="text-xs text-gray-500 mt-2">
Accepté le: {new Date(challenge.acceptedAt).toLocaleDateString("fr-FR")}
</div>
)}
</div>
<div className="flex flex-col gap-2">
<Button
onClick={() => handleEdit(challenge)}
variant="secondary"
size="sm"
>
Modifier
</Button>
{challenge.status === "ACCEPTED" && (
<Button
onClick={() => setSelectedChallenge(challenge)}
variant="primary"
size="sm"
>
Valider/Rejeter
</Button>
)}
<Button
onClick={() => handleDelete(challenge.id)}
variant="secondary"
size="sm"
className="text-destructive hover:text-destructive"
style={{
color: "var(--destructive)",
borderColor: "var(--destructive)",
}}
>
Supprimer
</Button>
</div>
</div>
</Card>
))}
{/* Modal de validation */}
{selectedChallenge && (
<div
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
onClick={() => {
setSelectedChallenge(null);
setWinnerId("");
setAdminComment("");
}}
>
<Card
variant="dark"
className="max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6">
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
Valider/Rejeter le défi
</h2>
<div className="mb-6">
<h3 className="text-lg font-bold text-gray-300 mb-2">
{selectedChallenge.title}
</h3>
<p className="text-gray-400 mb-4">
{selectedChallenge.description}
</p>
<div className="flex items-center gap-4 mb-4">
<div className="flex items-center gap-2">
<Avatar
src={selectedChallenge.challenger.avatar}
username={selectedChallenge.challenger.username}
size="md"
/>
<span className="text-gray-300">
{selectedChallenge.challenger.username}
</span>
</div>
<span className="text-gray-500">VS</span>
<div className="flex items-center gap-2">
<Avatar
src={selectedChallenge.challenged.avatar}
username={selectedChallenge.challenged.username}
size="md"
/>
<span className="text-gray-300">
{selectedChallenge.challenged.username}
</span>
</div>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-bold text-pixel-gold mb-2">
Sélectionner le gagnant
</label>
<div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="winner"
value={selectedChallenge.challenger.id}
checked={winnerId === selectedChallenge.challenger.id}
onChange={(e) => setWinnerId(e.target.value)}
className="w-4 h-4"
/>
<span className="text-gray-300">
{selectedChallenge.challenger.username}
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="winner"
value={selectedChallenge.challenged.id}
checked={winnerId === selectedChallenge.challenged.id}
onChange={(e) => setWinnerId(e.target.value)}
className="w-4 h-4"
/>
<span className="text-gray-300">
{selectedChallenge.challenged.username}
</span>
</label>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-bold text-pixel-gold mb-2">
Commentaire (optionnel)
</label>
<textarea
value={adminComment}
onChange={(e) => setAdminComment(e.target.value)}
className="w-full p-2 bg-black/60 border border-pixel-gold/30 rounded text-gray-300"
rows={3}
placeholder="Commentaire pour les joueurs..."
/>
</div>
<div className="flex gap-4">
<Button
onClick={handleValidate}
variant="primary"
disabled={!winnerId || isPending}
className="flex-1"
>
{isPending ? "Validation..." : "Valider le défi"}
</Button>
<Button
onClick={handleReject}
variant="secondary"
disabled={isPending}
className="flex-1"
>
{isPending ? "Rejet..." : "Rejeter le défi"}
</Button>
<Button
onClick={() => {
setSelectedChallenge(null);
setWinnerId("");
setAdminComment("");
}}
variant="secondary"
disabled={isPending}
>
Annuler
</Button>
</div>
</div>
</Card>
</div>
)}
{/* Modal d'édition */}
{editingChallenge && (
<div
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
onClick={() => {
setEditingChallenge(null);
setEditTitle("");
setEditDescription("");
setEditPointsReward(0);
}}
>
<Card
variant="dark"
className="max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6">
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
Modifier le défi
</h2>
<div className="space-y-4">
<Input
id="edit-title"
label="Titre"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
required
placeholder="Titre du défi"
/>
<Textarea
id="edit-description"
label="Description"
value={editDescription}
onChange={(e) => setEditDescription(e.target.value)}
required
rows={4}
placeholder="Description du défi"
/>
<Input
id="edit-points"
label="Récompense (points)"
type="number"
min="1"
value={editPointsReward}
onChange={(e) => setEditPointsReward(parseInt(e.target.value) || 0)}
required
placeholder="100"
/>
<div className="flex gap-4 pt-4">
<Button
onClick={handleUpdate}
variant="primary"
disabled={isPending || !editTitle || !editDescription || editPointsReward <= 0}
className="flex-1"
>
{isPending ? "Mise à jour..." : "Enregistrer"}
</Button>
<Button
onClick={() => {
setEditingChallenge(null);
setEditTitle("");
setEditDescription("");
setEditPointsReward(0);
}}
variant="secondary"
disabled={isPending}
>
Annuler
</Button>
</div>
</div>
</div>
</Card>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,428 @@
"use client";
import { useEffect, useState, useTransition } from "react";
import { useSession } from "next-auth/react";
import { createChallenge, acceptChallenge, cancelChallenge } from "@/actions/challenges/create";
import { Button, Card, SectionTitle, Input, Textarea, Alert } from "@/components/ui";
import { Avatar } from "@/components/ui";
interface User {
id: string;
username: string;
avatar: string | null;
score: number;
level: number;
}
interface Challenge {
id: string;
challenger: {
id: string;
username: string;
avatar: string | null;
};
challenged: {
id: string;
username: string;
avatar: string | null;
};
title: string;
description: string;
pointsReward: number;
status: string;
adminComment: string | null;
winner?: {
id: string;
username: string;
} | null;
createdAt: string;
acceptedAt: string | null;
completedAt: string | null;
}
interface ChallengesSectionProps {
backgroundImage: string;
}
export default function ChallengesSection({ backgroundImage }: ChallengesSectionProps) {
const { data: session } = useSession();
const [challenges, setChallenges] = useState<Challenge[]>([]);
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [showCreateForm, setShowCreateForm] = useState(false);
const [isPending, startTransition] = useTransition();
// Form state
const [challengedId, setChallengedId] = useState("");
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [pointsReward, setPointsReward] = useState(100);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
fetchChallenges();
fetchUsers();
}, []);
const fetchChallenges = async () => {
try {
const response = await fetch("/api/challenges");
if (response.ok) {
const data = await response.json();
setChallenges(data);
}
} catch (error) {
console.error("Error fetching challenges:", error);
} finally {
setLoading(false);
}
};
const fetchUsers = async () => {
try {
const response = await fetch("/api/users");
if (response.ok) {
const data = await response.json();
setUsers(data);
}
} catch (error) {
console.error("Error fetching users:", error);
}
};
const handleCreateChallenge = () => {
if (!challengedId || !title || !description) {
setErrorMessage("Veuillez remplir tous les champs");
setTimeout(() => setErrorMessage(null), 5000);
return;
}
startTransition(async () => {
const result = await createChallenge({
challengedId,
title,
description,
pointsReward,
});
if (result.success) {
setSuccessMessage("Défi créé avec succès !");
setShowCreateForm(false);
setChallengedId("");
setTitle("");
setDescription("");
setPointsReward(100);
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de la création du défi");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
const handleAcceptChallenge = (challengeId: string) => {
startTransition(async () => {
const result = await acceptChallenge(challengeId);
if (result.success) {
setSuccessMessage("Défi accepté ! En attente de validation admin.");
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de l'acceptation");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
const handleCancelChallenge = (challengeId: string) => {
if (!confirm("Êtes-vous sûr de vouloir annuler ce défi ?")) {
return;
}
startTransition(async () => {
const result = await cancelChallenge(challengeId);
if (result.success) {
setSuccessMessage("Défi annulé");
fetchChallenges();
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de l'annulation");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
const getStatusLabel = (status: string) => {
switch (status) {
case "PENDING":
return "En attente d'acceptation";
case "ACCEPTED":
return "Accepté - En attente de validation admin";
case "COMPLETED":
return "Complété";
case "REJECTED":
return "Rejeté";
case "CANCELLED":
return "Annulé";
default:
return status;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case "PENDING":
return "text-yellow-400";
case "ACCEPTED":
return "text-blue-400";
case "COMPLETED":
return "text-green-400";
case "REJECTED":
return "text-red-400";
case "CANCELLED":
return "text-gray-400";
default:
return "text-gray-300";
}
};
return (
<section
className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16"
style={{
backgroundImage: `url(${backgroundImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}
>
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm"></div>
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
<SectionTitle variant="gradient" size="md" className="mb-8 text-center">
DÉFIS ENTRE JOUEURS
</SectionTitle>
{successMessage && (
<Alert variant="success" className="mb-4">
{successMessage}
</Alert>
)}
{errorMessage && (
<Alert variant="error" className="mb-4">
{errorMessage}
</Alert>
)}
<div className="mb-6 flex justify-center">
<Button
onClick={() => setShowCreateForm(!showCreateForm)}
variant="primary"
size="md"
>
{showCreateForm ? "Annuler" : "Créer un défi"}
</Button>
</div>
{/* Create Form */}
{showCreateForm && (
<Card variant="dark" className="p-6 mb-8">
<h2 className="text-xl font-bold text-pixel-gold mb-4">
Créer un nouveau défi
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Défier qui ?
</label>
<select
value={challengedId}
onChange={(e) => setChallengedId(e.target.value)}
className="w-full p-2 bg-black/60 border border-pixel-gold/30 rounded text-gray-300"
>
<option value="">Sélectionner un joueur</option>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.username} (Lv.{user.level} - {user.score} pts)
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Titre du défi
</label>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Ex: Qui participera à plus d'événements ce mois ?"
/>
</div>
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Description
</label>
<Textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Décrivez les règles du défi..."
rows={4}
/>
</div>
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Points à gagner (défaut: 100)
</label>
<Input
type="number"
value={pointsReward}
onChange={(e) => setPointsReward(parseInt(e.target.value) || 100)}
min={1}
max={1000}
/>
</div>
<Button
onClick={handleCreateChallenge}
variant="primary"
disabled={isPending || !challengedId || !title || !description}
className="w-full"
>
{isPending ? "Création..." : "Créer le défi"}
</Button>
</div>
</Card>
)}
{/* Challenges List */}
{loading ? (
<div className="text-center text-pixel-gold py-8">Chargement...</div>
) : challenges.length === 0 ? (
<Card variant="dark" className="p-6 text-center">
<p className="text-gray-400">
Vous n'avez aucun défi pour le moment.
</p>
</Card>
) : (
<div className="space-y-4">
{challenges.map((challenge) => {
const currentUserId = session?.user?.id;
const isChallenger = challenge.challenger.id === currentUserId;
const isChallenged = challenge.challenged.id === currentUserId;
const canAccept = challenge.status === "PENDING" && isChallenged;
const canCancel =
(challenge.status === "PENDING" || challenge.status === "ACCEPTED") &&
(isChallenger || isChallenged);
return (
<Card key={challenge.id} variant="dark" className="p-6">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="text-lg font-bold text-pixel-gold">
{challenge.title}
</h3>
<span
className={`text-xs px-2 py-1 rounded ${getStatusColor(
challenge.status
)} bg-black/40`}
>
{getStatusLabel(challenge.status)}
</span>
</div>
<p className="text-gray-300 mb-4">{challenge.description}</p>
<div className="flex items-center gap-4 mb-2">
<div className="flex items-center gap-2">
<Avatar
src={challenge.challenger.avatar}
username={challenge.challenger.username}
size="sm"
/>
<span className="text-sm text-gray-300">
{challenge.challenger.username}
</span>
</div>
<span className="text-gray-500">VS</span>
<div className="flex items-center gap-2">
<Avatar
src={challenge.challenged.avatar}
username={challenge.challenged.username}
size="sm"
/>
<span className="text-sm text-gray-300">
{challenge.challenged.username}
</span>
</div>
</div>
<div className="text-sm text-gray-400">
Récompense:{" "}
<span className="text-pixel-gold font-bold">
{challenge.pointsReward} points
</span>
</div>
{challenge.winner && (
<div className="text-sm text-green-400 mt-2">
🏆 Gagnant: {challenge.winner.username}
</div>
)}
{challenge.adminComment && (
<div className="text-xs text-gray-500 mt-2 italic">
Admin: {challenge.adminComment}
</div>
)}
<div className="text-xs text-gray-500 mt-2">
Créé le: {new Date(challenge.createdAt).toLocaleDateString("fr-FR")}
{challenge.acceptedAt &&
` • Accepté le: ${new Date(challenge.acceptedAt).toLocaleDateString("fr-FR")}`}
{challenge.completedAt &&
` • Complété le: ${new Date(challenge.completedAt).toLocaleDateString("fr-FR")}`}
</div>
</div>
<div className="flex flex-col gap-2">
{canAccept && (
<Button
onClick={() => handleAcceptChallenge(challenge.id)}
variant="primary"
size="sm"
disabled={isPending}
>
Accepter
</Button>
)}
{canCancel && (
<Button
onClick={() => handleCancelChallenge(challenge.id)}
variant="secondary"
size="sm"
disabled={isPending}
>
Annuler
</Button>
)}
</div>
</div>
</Card>
);
})}
</div>
)}
</div>
</section>
);
}

View File

@@ -113,6 +113,21 @@ export default function Navigation({
> >
LEADERBOARD LEADERBOARD
</Link> </Link>
{isAuthenticated && (
<Link
href="/challenges"
className="transition text-xs font-normal uppercase tracking-widest"
style={{ color: "var(--foreground)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.color = "var(--accent-color)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.color = "var(--foreground)")
}
>
DÉFIS
</Link>
)}
{isAdmin && ( {isAdmin && (
<Link <Link
href="/admin" href="/admin"
@@ -271,6 +286,22 @@ export default function Navigation({
> >
LEADERBOARD LEADERBOARD
</Link> </Link>
{isAuthenticated && (
<Link
href="/challenges"
onClick={() => 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)")
}
>
DÉFIS
</Link>
)}
{isAdmin && ( {isAdmin && (
<Link <Link
href="/admin" href="/admin"

View File

@@ -27,7 +27,8 @@ export default function Alert({
color: "var(--success)", color: "var(--success)",
}, },
error: { error: {
backgroundColor: "color-mix(in srgb, var(--destructive) 20%, transparent)", backgroundColor:
"color-mix(in srgb, var(--destructive) 20%, transparent)",
borderColor: "color-mix(in srgb, var(--destructive) 50%, transparent)", borderColor: "color-mix(in srgb, var(--destructive) 50%, transparent)",
color: "var(--destructive)", color: "var(--destructive)",
}, },
@@ -53,4 +54,3 @@ export default function Alert({
</div> </div>
); );
} }

View File

@@ -41,6 +41,7 @@
"@typescript-eslint/eslint-plugin": "^8.49.0", "@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0", "@typescript-eslint/parser": "^8.49.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"dotenv": "^17.2.3",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-next": "^16.0.8", "eslint-config-next": "^16.0.8",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",

9
pnpm-lock.yaml generated
View File

@@ -60,6 +60,9 @@ importers:
autoprefixer: autoprefixer:
specifier: ^10.4.19 specifier: ^10.4.19
version: 10.4.22(postcss@8.5.6) version: 10.4.22(postcss@8.5.6)
dotenv:
specifier: ^17.2.3
version: 17.2.3
eslint: eslint:
specifier: ^9.39.1 specifier: ^9.39.1
version: 9.39.1(jiti@1.21.7) version: 9.39.1(jiti@1.21.7)
@@ -1235,6 +1238,10 @@ packages:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'} engines: {node: '>=12'}
dotenv@17.2.3:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
engines: {node: '>=12'}
dunder-proto@1.0.1: dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3693,6 +3700,8 @@ snapshots:
dotenv@16.6.1: {} dotenv@16.6.1: {}
dotenv@17.2.3: {}
dunder-proto@1.0.1: dunder-proto@1.0.1:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2

View File

@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */ /* eslint-disable */
// biome-ignore-all lint: generated file // biome-ignore-all lint: generated file
@@ -12,37 +13,42 @@
* 🟢 You can import this file directly. * 🟢 You can import this file directly.
*/ */
import * as Prisma from "./internal/prismaNamespaceBrowser"; import * as Prisma from './internal/prismaNamespaceBrowser'
export { Prisma }; export { Prisma }
export * as $Enums from "./enums"; export * as $Enums from './enums'
export * from "./enums"; export * from './enums';
/** /**
* Model User * Model User
* *
*/ */
export type User = Prisma.UserModel; export type User = Prisma.UserModel
/** /**
* Model UserPreferences * Model UserPreferences
* *
*/ */
export type UserPreferences = Prisma.UserPreferencesModel; export type UserPreferences = Prisma.UserPreferencesModel
/** /**
* Model Event * Model Event
* *
*/ */
export type Event = Prisma.EventModel; export type Event = Prisma.EventModel
/** /**
* Model EventRegistration * Model EventRegistration
* *
*/ */
export type EventRegistration = Prisma.EventRegistrationModel; export type EventRegistration = Prisma.EventRegistrationModel
/** /**
* Model EventFeedback * Model EventFeedback
* *
*/ */
export type EventFeedback = Prisma.EventFeedbackModel; export type EventFeedback = Prisma.EventFeedbackModel
/** /**
* Model SitePreferences * Model SitePreferences
* *
*/ */
export type SitePreferences = Prisma.SitePreferencesModel; export type SitePreferences = Prisma.SitePreferencesModel
/**
* Model Challenge
*
*/
export type Challenge = Prisma.ChallengeModel

View File

@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */ /* eslint-disable */
// biome-ignore-all lint: generated file // biome-ignore-all lint: generated file
@@ -9,18 +10,18 @@
* 🟢 You can import this file directly. * 🟢 You can import this file directly.
*/ */
import * as process from "node:process"; import * as process from 'node:process'
import * as path from "node:path"; import * as path from 'node:path'
import { fileURLToPath } from "node:url"; import { fileURLToPath } from 'node:url'
globalThis["__dirname"] = path.dirname(fileURLToPath(import.meta.url)); globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
import * as runtime from "@prisma/client/runtime/client"; import * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums"; import * as $Enums from "./enums"
import * as $Class from "./internal/class"; import * as $Class from "./internal/class"
import * as Prisma from "./internal/prismaNamespace"; import * as Prisma from "./internal/prismaNamespace"
export * as $Enums from "./enums"; export * as $Enums from './enums'
export * from "./enums"; export * from "./enums"
/** /**
* ## Prisma Client * ## Prisma Client
* *
@@ -34,43 +35,42 @@ export * from "./enums";
* *
* Read more in our [docs](https://pris.ly/d/client). * Read more in our [docs](https://pris.ly/d/client).
*/ */
export const PrismaClient = $Class.getPrismaClientClass(); export const PrismaClient = $Class.getPrismaClientClass()
export type PrismaClient< export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
LogOpts extends Prisma.LogLevel = never, export { Prisma }
OmitOpts extends Prisma.PrismaClientOptions["omit"] =
Prisma.PrismaClientOptions["omit"],
ExtArgs extends runtime.Types.Extensions.InternalArgs =
runtime.Types.Extensions.DefaultArgs,
> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>;
export { Prisma };
/** /**
* Model User * Model User
* *
*/ */
export type User = Prisma.UserModel; export type User = Prisma.UserModel
/** /**
* Model UserPreferences * Model UserPreferences
* *
*/ */
export type UserPreferences = Prisma.UserPreferencesModel; export type UserPreferences = Prisma.UserPreferencesModel
/** /**
* Model Event * Model Event
* *
*/ */
export type Event = Prisma.EventModel; export type Event = Prisma.EventModel
/** /**
* Model EventRegistration * Model EventRegistration
* *
*/ */
export type EventRegistration = Prisma.EventRegistrationModel; export type EventRegistration = Prisma.EventRegistrationModel
/** /**
* Model EventFeedback * Model EventFeedback
* *
*/ */
export type EventFeedback = Prisma.EventFeedbackModel; export type EventFeedback = Prisma.EventFeedbackModel
/** /**
* Model SitePreferences * Model SitePreferences
* *
*/ */
export type SitePreferences = Prisma.SitePreferencesModel; export type SitePreferences = Prisma.SitePreferencesModel
/**
* Model Challenge
*
*/
export type Challenge = Prisma.ChallengeModel

View File

@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */ /* eslint-disable */
// biome-ignore-all lint: generated file // biome-ignore-all lint: generated file
@@ -8,461 +9,504 @@
* 🟢 You can import this file directly. * 🟢 You can import this file directly.
*/ */
import type * as runtime from "@prisma/client/runtime/client"; import type * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums"; import * as $Enums from "./enums"
import type * as Prisma from "./internal/prismaNamespace"; import type * as Prisma from "./internal/prismaNamespace"
export type StringFilter<$PrismaModel = never> = { export type StringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>; equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]; in?: string[]
notIn?: string[]; notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string; not?: Prisma.NestedStringFilter<$PrismaModel> | string
}; }
export type EnumRoleFilter<$PrismaModel = never> = { export type EnumRoleFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>; equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[]; in?: $Enums.Role[]
notIn?: $Enums.Role[]; notIn?: $Enums.Role[]
not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role; not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role
}; }
export type IntFilter<$PrismaModel = never> = { export type IntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>; equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[]; in?: number[]
notIn?: number[]; notIn?: number[]
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntFilter<$PrismaModel> | number; not?: Prisma.NestedIntFilter<$PrismaModel> | number
}; }
export type StringNullableFilter<$PrismaModel = never> = { export type StringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null; equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null; in?: string[] | null
notIn?: string[] | null; notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null; not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
}; }
export type EnumCharacterClassNullableFilter<$PrismaModel = never> = {
equals?:
| $Enums.CharacterClass
| Prisma.EnumCharacterClassFieldRefInput<$PrismaModel>
| null;
in?: $Enums.CharacterClass[] | null;
notIn?: $Enums.CharacterClass[] | null;
not?:
| Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
| $Enums.CharacterClass
| null;
};
export type DateTimeFilter<$PrismaModel = never> = { export type DateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]; in?: Date[] | string[]
notIn?: Date[] | string[]; notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string; not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
}; }
export type EnumCharacterClassNullableFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null
notIn?: $Enums.CharacterClass[] | null
not?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> | $Enums.CharacterClass | null
}
export type SortOrderInput = { export type SortOrderInput = {
sort: Prisma.SortOrder; sort: Prisma.SortOrder
nulls?: Prisma.NullsOrder; nulls?: Prisma.NullsOrder
}; }
export type StringWithAggregatesFilter<$PrismaModel = never> = { export type StringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>; equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]; in?: string[]
notIn?: string[]; notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string; not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>; _min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>; _max?: Prisma.NestedStringFilter<$PrismaModel>
}; }
export type EnumRoleWithAggregatesFilter<$PrismaModel = never> = { export type EnumRoleWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>; equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[]; in?: $Enums.Role[]
notIn?: $Enums.Role[]; notIn?: $Enums.Role[]
not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role; not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumRoleFilter<$PrismaModel>; _min?: Prisma.NestedEnumRoleFilter<$PrismaModel>
_max?: Prisma.NestedEnumRoleFilter<$PrismaModel>; _max?: Prisma.NestedEnumRoleFilter<$PrismaModel>
}; }
export type IntWithAggregatesFilter<$PrismaModel = never> = { export type IntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>; equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[]; in?: number[]
notIn?: number[]; notIn?: number[]
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number; not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_avg?: Prisma.NestedFloatFilter<$PrismaModel>; _avg?: Prisma.NestedFloatFilter<$PrismaModel>
_sum?: Prisma.NestedIntFilter<$PrismaModel>; _sum?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedIntFilter<$PrismaModel>; _min?: Prisma.NestedIntFilter<$PrismaModel>
_max?: Prisma.NestedIntFilter<$PrismaModel>; _max?: Prisma.NestedIntFilter<$PrismaModel>
}; }
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null; equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null; in?: string[] | null
notIn?: string[] | null; notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
| Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
| string _min?: Prisma.NestedStringNullableFilter<$PrismaModel>
| null; _max?: Prisma.NestedStringNullableFilter<$PrismaModel>
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>; }
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>;
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>;
};
export type EnumCharacterClassNullableWithAggregatesFilter<
$PrismaModel = never,
> = {
equals?:
| $Enums.CharacterClass
| Prisma.EnumCharacterClassFieldRefInput<$PrismaModel>
| null;
in?: $Enums.CharacterClass[] | null;
notIn?: $Enums.CharacterClass[] | null;
not?:
| Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel>
| $Enums.CharacterClass
| null;
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
_min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>;
_max?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>;
};
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]; in?: Date[] | string[]
notIn?: Date[] | string[]; notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string; not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>; _min?: Prisma.NestedDateTimeFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>; _max?: Prisma.NestedDateTimeFilter<$PrismaModel>
}; }
export type EnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null
notIn?: $Enums.CharacterClass[] | null
not?: Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel> | $Enums.CharacterClass | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
_max?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
}
export type EnumEventTypeFilter<$PrismaModel = never> = { export type EnumEventTypeFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>; equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[]; in?: $Enums.EventType[]
notIn?: $Enums.EventType[]; notIn?: $Enums.EventType[]
not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType; not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType
}; }
export type IntNullableFilter<$PrismaModel = never> = { export type IntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null; equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null; in?: number[] | null
notIn?: number[] | null; notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null; not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
}; }
export type EnumEventTypeWithAggregatesFilter<$PrismaModel = never> = { export type EnumEventTypeWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>; equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[]; in?: $Enums.EventType[]
notIn?: $Enums.EventType[]; notIn?: $Enums.EventType[]
not?: not?: Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> | $Enums.EventType
| Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
| $Enums.EventType; _min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>
_count?: Prisma.NestedIntFilter<$PrismaModel>; _max?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>
_min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>; }
_max?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>;
};
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = { export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null; equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null; in?: number[] | null
notIn?: number[] | null; notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
| Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
| number _avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
| null; _sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>; _min?: Prisma.NestedIntNullableFilter<$PrismaModel>
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>; _max?: Prisma.NestedIntNullableFilter<$PrismaModel>
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>; }
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>;
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>; export type EnumChallengeStatusFilter<$PrismaModel = never> = {
}; equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[]
notIn?: $Enums.ChallengeStatus[]
not?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> | $Enums.ChallengeStatus
}
export type DateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null
notIn?: Date[] | string[] | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type EnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[]
notIn?: $Enums.ChallengeStatus[]
not?: Prisma.NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel> | $Enums.ChallengeStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel>
}
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null
notIn?: Date[] | string[] | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
}
export type NestedStringFilter<$PrismaModel = never> = { export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>; equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]; in?: string[]
notIn?: string[]; notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string; not?: Prisma.NestedStringFilter<$PrismaModel> | string
}; }
export type NestedEnumRoleFilter<$PrismaModel = never> = { export type NestedEnumRoleFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>; equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[]; in?: $Enums.Role[]
notIn?: $Enums.Role[]; notIn?: $Enums.Role[]
not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role; not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role
}; }
export type NestedIntFilter<$PrismaModel = never> = { export type NestedIntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>; equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[]; in?: number[]
notIn?: number[]; notIn?: number[]
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntFilter<$PrismaModel> | number; not?: Prisma.NestedIntFilter<$PrismaModel> | number
}; }
export type NestedStringNullableFilter<$PrismaModel = never> = { export type NestedStringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null; equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null; in?: string[] | null
notIn?: string[] | null; notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null; not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
}; }
export type NestedEnumCharacterClassNullableFilter<$PrismaModel = never> = {
equals?:
| $Enums.CharacterClass
| Prisma.EnumCharacterClassFieldRefInput<$PrismaModel>
| null;
in?: $Enums.CharacterClass[] | null;
notIn?: $Enums.CharacterClass[] | null;
not?:
| Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
| $Enums.CharacterClass
| null;
};
export type NestedDateTimeFilter<$PrismaModel = never> = { export type NestedDateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]; in?: Date[] | string[]
notIn?: Date[] | string[]; notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string; not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
}; }
export type NestedEnumCharacterClassNullableFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null
notIn?: $Enums.CharacterClass[] | null
not?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> | $Enums.CharacterClass | null
}
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = { export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>; equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]; in?: string[]
notIn?: string[]; notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string; not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>; _min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>; _max?: Prisma.NestedStringFilter<$PrismaModel>
}; }
export type NestedEnumRoleWithAggregatesFilter<$PrismaModel = never> = { export type NestedEnumRoleWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>; equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[]; in?: $Enums.Role[]
notIn?: $Enums.Role[]; notIn?: $Enums.Role[]
not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role; not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumRoleFilter<$PrismaModel>; _min?: Prisma.NestedEnumRoleFilter<$PrismaModel>
_max?: Prisma.NestedEnumRoleFilter<$PrismaModel>; _max?: Prisma.NestedEnumRoleFilter<$PrismaModel>
}; }
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>; equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[]; in?: number[]
notIn?: number[]; notIn?: number[]
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number; not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_avg?: Prisma.NestedFloatFilter<$PrismaModel>; _avg?: Prisma.NestedFloatFilter<$PrismaModel>
_sum?: Prisma.NestedIntFilter<$PrismaModel>; _sum?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedIntFilter<$PrismaModel>; _min?: Prisma.NestedIntFilter<$PrismaModel>
_max?: Prisma.NestedIntFilter<$PrismaModel>; _max?: Prisma.NestedIntFilter<$PrismaModel>
}; }
export type NestedFloatFilter<$PrismaModel = never> = { export type NestedFloatFilter<$PrismaModel = never> = {
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>; equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
in?: number[]; in?: number[]
notIn?: number[]; notIn?: number[]
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>; lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>; lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>; gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>; gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
not?: Prisma.NestedFloatFilter<$PrismaModel> | number; not?: Prisma.NestedFloatFilter<$PrismaModel> | number
}; }
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null; equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null; in?: string[] | null
notIn?: string[] | null; notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>; lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>; lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>; gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>; gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>; contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>; endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
| Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
| string _min?: Prisma.NestedStringNullableFilter<$PrismaModel>
| null; _max?: Prisma.NestedStringNullableFilter<$PrismaModel>
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>; }
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>;
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>;
};
export type NestedIntNullableFilter<$PrismaModel = never> = { export type NestedIntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null; equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null; in?: number[] | null
notIn?: number[] | null; notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null; not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
}; }
export type NestedEnumCharacterClassNullableWithAggregatesFilter<
$PrismaModel = never,
> = {
equals?:
| $Enums.CharacterClass
| Prisma.EnumCharacterClassFieldRefInput<$PrismaModel>
| null;
in?: $Enums.CharacterClass[] | null;
notIn?: $Enums.CharacterClass[] | null;
not?:
| Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel>
| $Enums.CharacterClass
| null;
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
_min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>;
_max?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>;
};
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[]; in?: Date[] | string[]
notIn?: Date[] | string[]; notIn?: Date[] | string[]
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>; gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string; not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
_count?: Prisma.NestedIntFilter<$PrismaModel>; _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>; _min?: Prisma.NestedDateTimeFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>; _max?: Prisma.NestedDateTimeFilter<$PrismaModel>
}; }
export type NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null
notIn?: $Enums.CharacterClass[] | null
not?: Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel> | $Enums.CharacterClass | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
_max?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
}
export type NestedEnumEventTypeFilter<$PrismaModel = never> = { export type NestedEnumEventTypeFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>; equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[]; in?: $Enums.EventType[]
notIn?: $Enums.EventType[]; notIn?: $Enums.EventType[]
not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType; not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType
}; }
export type NestedEnumEventTypeWithAggregatesFilter<$PrismaModel = never> = { export type NestedEnumEventTypeWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>; equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[]; in?: $Enums.EventType[]
notIn?: $Enums.EventType[]; notIn?: $Enums.EventType[]
not?: not?: Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> | $Enums.EventType
| Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
| $Enums.EventType; _min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>
_count?: Prisma.NestedIntFilter<$PrismaModel>; _max?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>
_min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>; }
_max?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>;
};
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null; equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null; in?: number[] | null
notIn?: number[] | null; notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>; lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>; lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>; gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>; gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
| Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
| number _avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
| null; _sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>; _min?: Prisma.NestedIntNullableFilter<$PrismaModel>
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>; _max?: Prisma.NestedIntNullableFilter<$PrismaModel>
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>; }
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>;
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>;
};
export type NestedFloatNullableFilter<$PrismaModel = never> = { export type NestedFloatNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null; equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
in?: number[] | null; in?: number[] | null
notIn?: number[] | null; notIn?: number[] | null
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>; lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>; lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>; gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>; gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null; not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
}; }
export type NestedEnumChallengeStatusFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[]
notIn?: $Enums.ChallengeStatus[]
not?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> | $Enums.ChallengeStatus
}
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null
notIn?: Date[] | string[] | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[]
notIn?: $Enums.ChallengeStatus[]
not?: Prisma.NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel> | $Enums.ChallengeStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel>
}
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null
notIn?: Date[] | string[] | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
}

View File

@@ -1,41 +1,54 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */ /* eslint-disable */
// biome-ignore-all lint: generated file // biome-ignore-all lint: generated file
// @ts-nocheck // @ts-nocheck
/* /*
* This file exports all enum related types from the schema. * This file exports all enum related types from the schema.
* *
* 🟢 You can import this file directly. * 🟢 You can import this file directly.
*/ */
export const Role = { export const Role = {
USER: "USER", USER: 'USER',
ADMIN: "ADMIN", ADMIN: 'ADMIN'
} as const; } as const
export type Role = (typeof Role)[keyof typeof Role]
export type Role = (typeof Role)[keyof typeof Role];
export const EventType = { export const EventType = {
ATELIER: "ATELIER", ATELIER: 'ATELIER',
KATA: "KATA", KATA: 'KATA',
PRESENTATION: "PRESENTATION", PRESENTATION: 'PRESENTATION',
LEARNING_HOUR: "LEARNING_HOUR", LEARNING_HOUR: 'LEARNING_HOUR'
} as const; } as const
export type EventType = (typeof EventType)[keyof typeof EventType]
export type EventType = (typeof EventType)[keyof typeof EventType];
export const CharacterClass = { export const CharacterClass = {
WARRIOR: "WARRIOR", WARRIOR: 'WARRIOR',
MAGE: "MAGE", MAGE: 'MAGE',
ROGUE: "ROGUE", ROGUE: 'ROGUE',
RANGER: "RANGER", RANGER: 'RANGER',
PALADIN: "PALADIN", PALADIN: 'PALADIN',
ENGINEER: "ENGINEER", ENGINEER: 'ENGINEER',
MERCHANT: "MERCHANT", MERCHANT: 'MERCHANT',
SCHOLAR: "SCHOLAR", SCHOLAR: 'SCHOLAR',
BERSERKER: "BERSERKER", BERSERKER: 'BERSERKER',
NECROMANCER: "NECROMANCER", NECROMANCER: 'NECROMANCER'
} as const; } as const
export type CharacterClass = export type CharacterClass = (typeof CharacterClass)[keyof typeof CharacterClass]
(typeof CharacterClass)[keyof typeof CharacterClass];
export const ChallengeStatus = {
PENDING: 'PENDING',
ACCEPTED: 'ACCEPTED',
COMPLETED: 'COMPLETED',
REJECTED: 'REJECTED',
CANCELLED: 'CANCELLED'
} as const
export type ChallengeStatus = (typeof ChallengeStatus)[keyof typeof ChallengeStatus]

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */ /* eslint-disable */
// biome-ignore-all lint: generated file // biome-ignore-all lint: generated file
@@ -14,164 +15,184 @@
* model files in the `model` directory! * model files in the `model` directory!
*/ */
import * as runtime from "@prisma/client/runtime/index-browser"; import * as runtime from "@prisma/client/runtime/index-browser"
export type * from "../models"; export type * from '../models'
export type * from "./prismaNamespace"; export type * from './prismaNamespace'
export const Decimal = runtime.Decimal
export const Decimal = runtime.Decimal;
export const NullTypes = { export const NullTypes = {
DbNull: runtime.NullTypes.DbNull as new ( DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
secret: never JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
) => typeof runtime.DbNull, AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
JsonNull: runtime.NullTypes.JsonNull as new ( }
secret: never
) => typeof runtime.JsonNull,
AnyNull: runtime.NullTypes.AnyNull as new (
secret: never
) => typeof runtime.AnyNull,
};
/** /**
* Helper for filtering JSON entries that have `null` on the database (empty on the db) * Helper for filtering JSON entries that have `null` on the database (empty on the db)
* *
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/ */
export const DbNull = runtime.DbNull; export const DbNull = runtime.DbNull
/** /**
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db) * Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
* *
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/ */
export const JsonNull = runtime.JsonNull; export const JsonNull = runtime.JsonNull
/** /**
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull` * Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
* *
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/ */
export const AnyNull = runtime.AnyNull; export const AnyNull = runtime.AnyNull
export const ModelName = { export const ModelName = {
User: "User", User: 'User',
UserPreferences: "UserPreferences", UserPreferences: 'UserPreferences',
Event: "Event", Event: 'Event',
EventRegistration: "EventRegistration", EventRegistration: 'EventRegistration',
EventFeedback: "EventFeedback", EventFeedback: 'EventFeedback',
SitePreferences: "SitePreferences", SitePreferences: 'SitePreferences',
} as const; Challenge: 'Challenge'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]; export type ModelName = (typeof ModelName)[keyof typeof ModelName]
/* /*
* Enums * Enums
*/ */
export const TransactionIsolationLevel = { export const TransactionIsolationLevel = {
Serializable: "Serializable", Serializable: 'Serializable'
} as const; } as const
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export type TransactionIsolationLevel =
(typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel];
export const UserScalarFieldEnum = { export const UserScalarFieldEnum = {
id: "id", id: 'id',
email: "email", email: 'email',
password: "password", password: 'password',
username: "username", username: 'username',
role: "role", role: 'role',
score: "score", score: 'score',
level: "level", level: 'level',
hp: "hp", hp: 'hp',
maxHp: "maxHp", maxHp: 'maxHp',
xp: "xp", xp: 'xp',
maxXp: "maxXp", maxXp: 'maxXp',
avatar: "avatar", avatar: 'avatar',
bio: "bio", createdAt: 'createdAt',
characterClass: "characterClass", updatedAt: 'updatedAt',
createdAt: "createdAt", bio: 'bio',
updatedAt: "updatedAt", characterClass: 'characterClass'
} as const; } as const
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
export type UserScalarFieldEnum =
(typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum];
export const UserPreferencesScalarFieldEnum = { export const UserPreferencesScalarFieldEnum = {
id: "id", id: 'id',
userId: "userId", userId: 'userId',
homeBackground: "homeBackground", homeBackground: 'homeBackground',
eventsBackground: "eventsBackground", eventsBackground: 'eventsBackground',
leaderboardBackground: "leaderboardBackground", leaderboardBackground: 'leaderboardBackground',
theme: "theme", theme: 'theme',
createdAt: "createdAt", createdAt: 'createdAt',
updatedAt: "updatedAt", updatedAt: 'updatedAt'
} as const; } as const
export type UserPreferencesScalarFieldEnum = (typeof UserPreferencesScalarFieldEnum)[keyof typeof UserPreferencesScalarFieldEnum]
export type UserPreferencesScalarFieldEnum =
(typeof UserPreferencesScalarFieldEnum)[keyof typeof UserPreferencesScalarFieldEnum];
export const EventScalarFieldEnum = { export const EventScalarFieldEnum = {
id: "id", id: 'id',
date: "date", date: 'date',
name: "name", name: 'name',
description: "description", description: 'description',
type: "type", type: 'type',
room: "room", room: 'room',
time: "time", time: 'time',
maxPlaces: "maxPlaces", maxPlaces: 'maxPlaces',
createdAt: "createdAt", createdAt: 'createdAt',
updatedAt: "updatedAt", updatedAt: 'updatedAt'
} as const; } as const
export type EventScalarFieldEnum = (typeof EventScalarFieldEnum)[keyof typeof EventScalarFieldEnum]
export type EventScalarFieldEnum =
(typeof EventScalarFieldEnum)[keyof typeof EventScalarFieldEnum];
export const EventRegistrationScalarFieldEnum = { export const EventRegistrationScalarFieldEnum = {
id: "id", id: 'id',
userId: "userId", userId: 'userId',
eventId: "eventId", eventId: 'eventId',
createdAt: "createdAt", createdAt: 'createdAt'
} as const; } as const
export type EventRegistrationScalarFieldEnum = (typeof EventRegistrationScalarFieldEnum)[keyof typeof EventRegistrationScalarFieldEnum]
export type EventRegistrationScalarFieldEnum =
(typeof EventRegistrationScalarFieldEnum)[keyof typeof EventRegistrationScalarFieldEnum];
export const EventFeedbackScalarFieldEnum = { export const EventFeedbackScalarFieldEnum = {
id: "id", id: 'id',
userId: "userId", userId: 'userId',
eventId: "eventId", eventId: 'eventId',
rating: "rating", rating: 'rating',
comment: "comment", comment: 'comment',
createdAt: "createdAt", createdAt: 'createdAt',
updatedAt: "updatedAt", updatedAt: 'updatedAt'
} as const; } as const
export type EventFeedbackScalarFieldEnum = (typeof EventFeedbackScalarFieldEnum)[keyof typeof EventFeedbackScalarFieldEnum]
export type EventFeedbackScalarFieldEnum =
(typeof EventFeedbackScalarFieldEnum)[keyof typeof EventFeedbackScalarFieldEnum];
export const SitePreferencesScalarFieldEnum = { export const SitePreferencesScalarFieldEnum = {
id: "id", id: 'id',
homeBackground: "homeBackground", homeBackground: 'homeBackground',
eventsBackground: "eventsBackground", eventsBackground: 'eventsBackground',
leaderboardBackground: "leaderboardBackground", leaderboardBackground: 'leaderboardBackground',
createdAt: "createdAt", createdAt: 'createdAt',
updatedAt: "updatedAt", updatedAt: 'updatedAt',
} as const; primaryColor: 'primaryColor'
} as const
export type SitePreferencesScalarFieldEnum = (typeof SitePreferencesScalarFieldEnum)[keyof typeof SitePreferencesScalarFieldEnum]
export const ChallengeScalarFieldEnum = {
id: 'id',
challengerId: 'challengerId',
challengedId: 'challengedId',
title: 'title',
description: 'description',
pointsReward: 'pointsReward',
status: 'status',
adminId: 'adminId',
adminComment: 'adminComment',
winnerId: 'winnerId',
createdAt: 'createdAt',
acceptedAt: 'acceptedAt',
completedAt: 'completedAt',
updatedAt: 'updatedAt'
} as const
export type ChallengeScalarFieldEnum = (typeof ChallengeScalarFieldEnum)[keyof typeof ChallengeScalarFieldEnum]
export type SitePreferencesScalarFieldEnum =
(typeof SitePreferencesScalarFieldEnum)[keyof typeof SitePreferencesScalarFieldEnum];
export const SortOrder = { export const SortOrder = {
asc: "asc", asc: 'asc',
desc: "desc", desc: 'desc'
} as const; } as const
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder];
export const NullsOrder = { export const NullsOrder = {
first: "first", first: 'first',
last: "last", last: 'last'
} as const; } as const
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder];

View File

@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */ /* eslint-disable */
// biome-ignore-all lint: generated file // biome-ignore-all lint: generated file
@@ -7,10 +8,11 @@
* *
* 🟢 You can import this file directly. * 🟢 You can import this file directly.
*/ */
export type * from "./models/User"; export type * from './models/User'
export type * from "./models/UserPreferences"; export type * from './models/UserPreferences'
export type * from "./models/Event"; export type * from './models/Event'
export type * from "./models/EventRegistration"; export type * from './models/EventRegistration'
export type * from "./models/EventFeedback"; export type * from './models/EventFeedback'
export type * from "./models/SitePreferences"; export type * from './models/SitePreferences'
export type * from "./commonInputTypes"; export type * from './models/Challenge'
export type * from './commonInputTypes'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "SitePreferences" ADD COLUMN "primaryColor" TEXT DEFAULT '#0be4cc';

View File

@@ -0,0 +1,33 @@
-- CreateTable
CREATE TABLE "Challenge" (
"id" TEXT NOT NULL PRIMARY KEY,
"challengerId" TEXT NOT NULL,
"challengedId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"pointsReward" INTEGER NOT NULL DEFAULT 100,
"status" TEXT NOT NULL DEFAULT 'PENDING',
"adminId" TEXT,
"adminComment" TEXT,
"winnerId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"acceptedAt" DATETIME,
"completedAt" DATETIME,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Challenge_challengerId_fkey" FOREIGN KEY ("challengerId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Challenge_challengedId_fkey" FOREIGN KEY ("challengedId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Challenge_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Challenge_winnerId_fkey" FOREIGN KEY ("winnerId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateIndex
CREATE INDEX "Challenge_challengerId_idx" ON "Challenge"("challengerId");
-- CreateIndex
CREATE INDEX "Challenge_challengedId_idx" ON "Challenge"("challengedId");
-- CreateIndex
CREATE INDEX "Challenge_status_idx" ON "Challenge"("status");
-- CreateIndex
CREATE INDEX "Challenge_adminId_idx" ON "Challenge"("adminId");

View File

@@ -1,6 +1,3 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client { generator client {
provider = "prisma-client" provider = "prisma-client"
output = "./generated/prisma" output = "./generated/prisma"
@@ -10,6 +7,103 @@ datasource db {
provider = "sqlite" provider = "sqlite"
} }
model User {
id String @id @default(cuid())
email String @unique
password String
username String @unique
role Role @default(USER)
score Int @default(0)
level Int @default(1)
hp Int @default(1000)
maxHp Int @default(1000)
xp Int @default(0)
maxXp Int @default(5000)
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bio String?
characterClass CharacterClass?
eventFeedbacks EventFeedback[]
eventRegistrations EventRegistration[]
preferences UserPreferences?
challengesAsChallenger Challenge[] @relation("Challenger")
challengesAsChallenged Challenge[] @relation("Challenged")
challengesAsAdmin Challenge[] @relation("AdminValidator")
challengesAsWinner Challenge[] @relation("ChallengeWinner")
@@index([score])
@@index([email])
}
model UserPreferences {
id String @id @default(cuid())
userId String @unique
homeBackground String?
eventsBackground String?
leaderboardBackground String?
theme String? @default("default")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Event {
id String @id @default(cuid())
date DateTime
name String
description String
type EventType
room String?
time String?
maxPlaces Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
feedbacks EventFeedback[]
registrations EventRegistration[]
@@index([date])
}
model EventRegistration {
id String @id @default(cuid())
userId String
eventId String
createdAt DateTime @default(now())
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, eventId])
@@index([userId])
@@index([eventId])
}
model EventFeedback {
id String @id @default(cuid())
userId String
eventId String
rating Int
comment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, eventId])
@@index([userId])
@@index([eventId])
}
model SitePreferences {
id String @id @default("global")
homeBackground String?
eventsBackground String?
leaderboardBackground String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
primaryColor String? @default("#0be4cc")
}
enum Role { enum Role {
USER USER
ADMIN ADMIN
@@ -35,99 +129,36 @@ enum CharacterClass {
NECROMANCER NECROMANCER
} }
model User { enum ChallengeStatus {
id String @id @default(cuid()) PENDING
email String @unique ACCEPTED
password String COMPLETED
username String @unique REJECTED
role Role @default(USER) CANCELLED
score Int @default(0)
level Int @default(1)
hp Int @default(1000)
maxHp Int @default(1000)
xp Int @default(0)
maxXp Int @default(5000)
avatar String?
bio String?
characterClass CharacterClass?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
preferences UserPreferences?
eventRegistrations EventRegistration[]
eventFeedbacks EventFeedback[]
@@index([score])
@@index([email])
} }
model UserPreferences { model Challenge {
id String @id @default(cuid()) id String @id @default(cuid())
userId String @unique challengerId String // Joueur qui lance le défi
user User @relation(fields: [userId], references: [id], onDelete: Cascade) challengedId String // Joueur qui reçoit le défi
challenger User @relation("Challenger", fields: [challengerId], references: [id], onDelete: Cascade)
challenged User @relation("Challenged", fields: [challengedId], references: [id], onDelete: Cascade)
title String // Titre du défi
description String // Description détaillée du défi
pointsReward Int @default(100) // Points à gagner pour le gagnant
status ChallengeStatus @default(PENDING)
adminId String? // Admin qui valide le défi
admin User? @relation("AdminValidator", fields: [adminId], references: [id], onDelete: SetNull)
adminComment String? // Commentaire de l'admin lors de la validation/rejet
winnerId String? // ID du gagnant (challengerId ou challengedId)
winner User? @relation("ChallengeWinner", fields: [winnerId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
acceptedAt DateTime? // Date d'acceptation du défi
completedAt DateTime? // Date de validation par l'admin
updatedAt DateTime @updatedAt
// Background images for each page @@index([challengerId])
homeBackground String? @@index([challengedId])
eventsBackground String? @@index([status])
leaderboardBackground String? @@index([adminId])
// Other UI preferences can be added here
theme String? @default("default")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Event {
id String @id @default(cuid())
date DateTime
name String
description String
type EventType
room String?
time String?
maxPlaces Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
registrations EventRegistration[]
feedbacks EventFeedback[]
@@index([date])
}
model EventRegistration {
id String @id @default(cuid())
userId String
eventId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([userId, eventId])
@@index([userId])
@@index([eventId])
}
model EventFeedback {
id String @id @default(cuid())
userId String
eventId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
rating Int // Note de 1 à 5
comment String? // Commentaire optionnel
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, eventId])
@@index([userId])
@@index([eventId])
}
model SitePreferences {
id String @id @default("global")
homeBackground String?
eventsBackground String?
leaderboardBackground String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
} }

View File

@@ -0,0 +1,500 @@
import { prisma } from "../database";
import type {
Challenge,
ChallengeStatus,
Prisma,
} from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError, ConflictError } from "../errors";
export interface CreateChallengeInput {
challengerId: string;
challengedId: string;
title: string;
description: string;
pointsReward?: number;
}
export interface UpdateChallengeInput {
status?: ChallengeStatus;
adminId?: string;
adminComment?: string;
winnerId?: string;
title?: string;
description?: string;
pointsReward?: number;
}
export interface ChallengeWithUsers extends Challenge {
challenger: {
id: string;
username: string;
avatar: string | null;
};
challenged: {
id: string;
username: string;
avatar: string | null;
};
admin?: {
id: string;
username: string;
} | null;
winner?: {
id: string;
username: string;
} | null;
}
/**
* Service de gestion des défis entre joueurs
*/
export class ChallengeService {
/**
* Crée un nouveau défi
*/
async createChallenge(
data: CreateChallengeInput
): Promise<Challenge> {
// Vérifier que les deux joueurs existent
const [challenger, challenged] = await Promise.all([
prisma.user.findUnique({ where: { id: data.challengerId } }),
prisma.user.findUnique({ where: { id: data.challengedId } }),
]);
if (!challenger) {
throw new NotFoundError("Joueur qui lance le défi");
}
if (!challenged) {
throw new NotFoundError("Joueur qui reçoit le défi");
}
// Vérifier qu'on ne se défie pas soi-même
if (data.challengerId === data.challengedId) {
throw new ValidationError("Vous ne pouvez pas vous défier vous-même");
}
return prisma.challenge.create({
data: {
challengerId: data.challengerId,
challengedId: data.challengedId,
title: data.title,
description: data.description,
pointsReward: data.pointsReward || 100,
status: "PENDING",
},
});
}
/**
* Accepte un défi
*/
async acceptChallenge(
challengeId: string,
userId: string
): Promise<Challenge> {
const challenge = await prisma.challenge.findUnique({
where: { id: challengeId },
});
if (!challenge) {
throw new NotFoundError("Défi");
}
// Vérifier que l'utilisateur est bien celui qui reçoit le défi
if (challenge.challengedId !== userId) {
throw new ValidationError(
"Vous n'êtes pas autorisé à accepter ce défi"
);
}
// Vérifier que le défi est en attente
if (challenge.status !== "PENDING") {
throw new ValidationError(
"Ce défi ne peut plus être accepté (statut: " + challenge.status + ")"
);
}
return prisma.challenge.update({
where: { id: challengeId },
data: {
status: "ACCEPTED",
acceptedAt: new Date(),
},
});
}
/**
* Annule un défi (par le challenger ou le challenged)
*/
async cancelChallenge(
challengeId: string,
userId: string
): Promise<Challenge> {
const challenge = await prisma.challenge.findUnique({
where: { id: challengeId },
});
if (!challenge) {
throw new NotFoundError("Défi");
}
// Vérifier que l'utilisateur est bien impliqué dans le défi
if (
challenge.challengerId !== userId &&
challenge.challengedId !== userId
) {
throw new ValidationError(
"Vous n'êtes pas autorisé à annuler ce défi"
);
}
// Vérifier que le défi peut être annulé
if (challenge.status === "COMPLETED") {
throw new ValidationError("Un défi complété ne peut pas être annulé");
}
return prisma.challenge.update({
where: { id: challengeId },
data: {
status: "CANCELLED",
},
});
}
/**
* Valide un défi (admin seulement)
*/
async validateChallenge(
challengeId: string,
adminId: string,
winnerId: string,
adminComment?: string
): Promise<Challenge> {
const challenge = await prisma.challenge.findUnique({
where: { id: challengeId },
include: {
challenger: true,
challenged: true,
},
});
if (!challenge) {
throw new NotFoundError("Défi");
}
// Vérifier que le défi est accepté
if (challenge.status !== "ACCEPTED") {
throw new ValidationError(
"Seuls les défis acceptés peuvent être validés"
);
}
// Vérifier que le winner est bien l'un des deux joueurs
if (
winnerId !== challenge.challengerId &&
winnerId !== challenge.challengedId
) {
throw new ValidationError(
"Le gagnant doit être l'un des deux joueurs du défi"
);
}
// Mettre à jour le défi
const updatedChallenge = await prisma.challenge.update({
where: { id: challengeId },
data: {
status: "COMPLETED",
adminId,
adminComment: adminComment || null,
winnerId,
completedAt: new Date(),
},
include: {
challenger: true,
challenged: true,
winner: true,
},
});
// Attribuer les points au gagnant
await prisma.user.update({
where: { id: winnerId },
data: {
score: {
increment: challenge.pointsReward,
},
},
});
return updatedChallenge;
}
/**
* Rejette un défi (admin seulement)
*/
async rejectChallenge(
challengeId: string,
adminId: string,
adminComment?: string
): Promise<Challenge> {
const challenge = await prisma.challenge.findUnique({
where: { id: challengeId },
});
if (!challenge) {
throw new NotFoundError("Défi");
}
// Vérifier que le défi est accepté
if (challenge.status !== "ACCEPTED") {
throw new ValidationError(
"Seuls les défis acceptés peuvent être rejetés"
);
}
return prisma.challenge.update({
where: { id: challengeId },
data: {
status: "REJECTED",
adminId,
adminComment: adminComment || null,
},
});
}
/**
* Met à jour un défi (admin seulement)
*/
async updateChallenge(
challengeId: string,
data: UpdateChallengeInput
): Promise<Challenge> {
const challenge = await prisma.challenge.findUnique({
where: { id: challengeId },
});
if (!challenge) {
throw new NotFoundError("Défi");
}
const updateData: Prisma.ChallengeUpdateInput = {};
if (data.title !== undefined) {
updateData.title = data.title;
}
if (data.description !== undefined) {
updateData.description = data.description;
}
if (data.pointsReward !== undefined) {
updateData.pointsReward = data.pointsReward;
}
if (data.status !== undefined) {
updateData.status = data.status;
}
if (data.adminId !== undefined) {
updateData.adminId = data.adminId;
}
if (data.adminComment !== undefined) {
updateData.adminComment = data.adminComment;
}
if (data.winnerId !== undefined) {
updateData.winnerId = data.winnerId;
}
return prisma.challenge.update({
where: { id: challengeId },
data: updateData,
});
}
/**
* Supprime un défi (admin seulement)
*/
async deleteChallenge(challengeId: string): Promise<void> {
const challenge = await prisma.challenge.findUnique({
where: { id: challengeId },
});
if (!challenge) {
throw new NotFoundError("Défi");
}
await prisma.challenge.delete({
where: { id: challengeId },
});
}
/**
* Récupère un défi par son ID avec les utilisateurs
*/
async getChallengeById(id: string): Promise<ChallengeWithUsers | null> {
const challenge = await prisma.challenge.findUnique({
where: { id },
include: {
challenger: {
select: {
id: true,
username: true,
avatar: true,
},
},
challenged: {
select: {
id: true,
username: true,
avatar: true,
},
},
admin: {
select: {
id: true,
username: true,
},
},
winner: {
select: {
id: true,
username: true,
},
},
},
});
return challenge as ChallengeWithUsers | null;
}
/**
* Récupère tous les défis d'un utilisateur
*/
async getUserChallenges(userId: string): Promise<ChallengeWithUsers[]> {
return prisma.challenge.findMany({
where: {
OR: [
{ challengerId: userId },
{ challengedId: userId },
],
},
include: {
challenger: {
select: {
id: true,
username: true,
avatar: true,
},
},
challenged: {
select: {
id: true,
username: true,
avatar: true,
},
},
admin: {
select: {
id: true,
username: true,
},
},
winner: {
select: {
id: true,
username: true,
},
},
},
orderBy: {
createdAt: "desc",
},
}) as Promise<ChallengeWithUsers[]>;
}
/**
* Récupère les défis en attente de validation admin
*/
async getPendingValidationChallenges(): Promise<ChallengeWithUsers[]> {
return prisma.challenge.findMany({
where: {
status: "ACCEPTED",
},
include: {
challenger: {
select: {
id: true,
username: true,
avatar: true,
},
},
challenged: {
select: {
id: true,
username: true,
avatar: true,
},
},
admin: {
select: {
id: true,
username: true,
},
},
winner: {
select: {
id: true,
username: true,
},
},
},
orderBy: {
acceptedAt: "asc",
},
}) as Promise<ChallengeWithUsers[]>;
}
/**
* Récupère tous les défis (pour admin)
*/
async getAllChallenges(options?: {
status?: ChallengeStatus;
take?: number;
}): Promise<ChallengeWithUsers[]> {
return prisma.challenge.findMany({
where: options?.status ? { status: options.status } : undefined,
include: {
challenger: {
select: {
id: true,
username: true,
avatar: true,
},
},
challenged: {
select: {
id: true,
username: true,
avatar: true,
},
},
admin: {
select: {
id: true,
username: true,
},
},
winner: {
select: {
id: true,
username: true,
},
},
},
orderBy: {
createdAt: "desc",
},
take: options?.take,
}) as Promise<ChallengeWithUsers[]>;
}
}
export const challengeService = new ChallengeService();