From b9a9d687b617cba0883a3a4e172b371e461674a4 Mon Sep 17 00:00:00 2001 From: jessy-david-dev Date: Sun, 8 Feb 2026 17:24:48 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20mise=20=C3=A0=20jour=20s=C3=A9curit?= =?UTF-8?q?=C3=A9=20CVE-2025-55182=20(Next.js=2015.5.7,=20React=2019.1.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 41 + .vscode/settings.json | 3 + README.md | 83 + app/a-propos/page.tsx | 87 + app/ads/page.tsx | 11 + app/api/auth/[...nextauth]/route.ts | 3 + app/couleurs/page.tsx | 26 + app/dashboard/Sidebar.tsx | 63 + app/dashboard/StatCard.tsx | 28 + app/dashboard/actions.ts | 103 + app/dashboard/ajouter/AddProjectForm.tsx | 192 + app/dashboard/ajouter/page.tsx | 22 + app/dashboard/layout.tsx | 19 + app/dashboard/page.tsx | 48 + app/dashboard/profil/page.tsx | 79 + app/dashboard/projets/UserProjectsTable.tsx | 106 + .../projets/[id]/EditProjectForm.tsx | 165 + app/dashboard/projets/[id]/page.tsx | 41 + app/dashboard/projets/page.tsx | 41 + app/globals.css | 11 + app/layout.tsx | 33 + app/login/page.tsx | 14 + app/maps/page.tsx | 9 + app/not-found.tsx | 32 + app/outils/page.tsx | 10 + app/page.tsx | 31 + app/providers.tsx | 7 + app/user/[username]/page.tsx | 48 + auth.ts | 23 + components/AdsGalleryClient.tsx | 171 + components/AdsSideBanners.tsx | 50 + components/Banner.tsx | 29 + components/Footer.tsx | 30 + components/MapsClient.tsx | 59 + components/McSplash.tsx | 23 + components/MotionHeading.tsx | 26 + components/Navbar.tsx | 186 + components/OutilsPage.tsx | 84 + components/ProjectsTable.tsx | 255 + components/RedstoneParticles.tsx | 171 + eslint.config.mjs | 16 + lib/getAds.ts | 10 + lib/prisma.ts | 13 + lib/validators/project.ts | 15 + middleware.ts | 23 + next.config.ts | 30 + package-lock.json | 7704 +++++++++++++++++ package.json | 38 + pnpm-lock.yaml | 5019 +++++++++++ postcss.config.mjs | 6 + .../20250727013821_init/migration.sql | 82 + .../20250727025700_add_mcname/migration.sql | 11 + .../20250727031005_project/migration.sql | 14 + .../migration.sql | 15 + .../migration.sql | 23 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 95 + public/ads/riseofkinkdom.jpeg | Bin 0 -> 454654 bytes public/ads/voat.png | Bin 0 -> 1259093 bytes public/aystone-saison2.png | Bin 0 -> 80056 bytes public/aystone.png | Bin 0 -> 12806 bytes public/charte-couleurs.png | Bin 0 -> 329107 bytes tsconfig.json | 42 + types/next-auth.d.ts | 16 + 64 files changed, 15638 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 app/a-propos/page.tsx create mode 100644 app/ads/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/couleurs/page.tsx create mode 100644 app/dashboard/Sidebar.tsx create mode 100644 app/dashboard/StatCard.tsx create mode 100644 app/dashboard/actions.ts create mode 100644 app/dashboard/ajouter/AddProjectForm.tsx create mode 100644 app/dashboard/ajouter/page.tsx create mode 100644 app/dashboard/layout.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/dashboard/profil/page.tsx create mode 100644 app/dashboard/projets/UserProjectsTable.tsx create mode 100644 app/dashboard/projets/[id]/EditProjectForm.tsx create mode 100644 app/dashboard/projets/[id]/page.tsx create mode 100644 app/dashboard/projets/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/login/page.tsx create mode 100644 app/maps/page.tsx create mode 100644 app/not-found.tsx create mode 100644 app/outils/page.tsx create mode 100644 app/page.tsx create mode 100644 app/providers.tsx create mode 100644 app/user/[username]/page.tsx create mode 100644 auth.ts create mode 100644 components/AdsGalleryClient.tsx create mode 100644 components/AdsSideBanners.tsx create mode 100644 components/Banner.tsx create mode 100644 components/Footer.tsx create mode 100644 components/MapsClient.tsx create mode 100644 components/McSplash.tsx create mode 100644 components/MotionHeading.tsx create mode 100644 components/Navbar.tsx create mode 100644 components/OutilsPage.tsx create mode 100644 components/ProjectsTable.tsx create mode 100644 components/RedstoneParticles.tsx create mode 100644 eslint.config.mjs create mode 100644 lib/getAds.ts create mode 100644 lib/prisma.ts create mode 100644 lib/validators/project.ts create mode 100644 middleware.ts create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.mjs create mode 100644 prisma/migrations/20250727013821_init/migration.sql create mode 100644 prisma/migrations/20250727025700_add_mcname/migration.sql create mode 100644 prisma/migrations/20250727031005_project/migration.sql create mode 100644 prisma/migrations/20250727203115_add_availability_model/migration.sql create mode 100644 prisma/migrations/20250727203953_add_availability_model/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 public/ads/riseofkinkdom.jpeg create mode 100644 public/ads/voat.png create mode 100644 public/aystone-saison2.png create mode 100644 public/aystone.png create mode 100644 public/charte-couleurs.png create mode 100644 tsconfig.json create mode 100644 types/next-auth.d.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..32e07b4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "WillLuke.nextjs.hasPrompted": true +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4828f25 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ + +``` +aystone +├─ app +│ ├─ a-propos +│ │ └─ page.tsx +│ ├─ ads +│ │ └─ page.tsx +│ ├─ api +│ │ ├─ auth +│ │ │ └─ [...nextauth] +│ │ │ └─ route.ts +│ │ └─ planning +│ │ └─ route.ts +│ ├─ dashboard +│ │ ├─ Sidebar.tsx +│ │ ├─ StatCard.tsx +│ │ ├─ actions.ts +│ │ ├─ ajouter +│ │ │ ├─ AddProjectForm.tsx +│ │ │ └─ page.tsx +│ │ ├─ layout.tsx +│ │ ├─ page.tsx +│ │ ├─ profil +│ │ │ └─ page.tsx +│ │ └─ projets +│ │ ├─ [id] +│ │ │ ├─ EditProjectForm.tsx +│ │ │ └─ page.tsx +│ │ └─ page.tsx +│ ├─ globals.css +│ ├─ layout.tsx +│ ├─ login +│ │ └─ page.tsx +│ ├─ maps +│ │ └─ page.tsx +│ ├─ page.tsx +│ └─ providers.tsx +├─ auth.ts +├─ components +│ ├─ AdsGalleryClient.tsx +│ ├─ AdsSideBanners.tsx +│ ├─ AvailabilityPlanner.tsx +│ ├─ Banner.tsx +│ ├─ Footer.tsx +│ ├─ MapsClient.tsx +│ ├─ McSplash.tsx +│ ├─ MotionHeading.tsx +│ ├─ Navbar.tsx +│ ├─ ProjectsTable.tsx +│ └─ RedstoneParticles.tsx +├─ eslint.config.mjs +├─ lib +│ ├─ getAds.ts +│ └─ prisma.ts +├─ middleware.ts +├─ next.config.ts +├─ package-lock.json +├─ package.json +├─ postcss.config.mjs +├─ prisma +│ ├─ migrations +│ │ ├─ 20250727013821_init +│ │ │ └─ migration.sql +│ │ ├─ 20250727025700_add_mcname +│ │ │ └─ migration.sql +│ │ ├─ 20250727031005_project +│ │ │ └─ migration.sql +│ │ ├─ 20250727203115_add_availability_model +│ │ │ └─ migration.sql +│ │ └─ migration_lock.toml +│ └─ schema.prisma +├─ public +│ ├─ ads +│ │ ├─ riseofkinkdom.jpeg +│ │ └─ voat.png +│ ├─ aystone-saison2.png +│ └─ aystone.png +├─ tsconfig.json +└─ types + └─ next-auth.d.ts + +``` \ No newline at end of file diff --git a/app/a-propos/page.tsx b/app/a-propos/page.tsx new file mode 100644 index 0000000..d879b1f --- /dev/null +++ b/app/a-propos/page.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const TAGS = [ + { id: 'spawn', label: 'Spawn', cls: 'bg-purple-700/30 text-purple-300' }, + { id: 'build', label: 'Build', cls: 'bg-emerald-600/25 text-emerald-300' }, + { id: 'usine', label: 'Usine', cls: 'bg-orange-600/20 text-orange-300' }, +]; + +export default function AProposPage() { + return ( +
+
+ + À propos + + + +

+ Cette application web permet à la communauté du serveur  + Aystone de référencer, suivre + et partager ses projets Minecraft dans les différents mondes : + Overworld, + Nether, + End. +

+ +

+ Le tableau liste : joueur, état, monde, nom, description, + coordonnées et tags personnalisés. L’interface responsive et + épurée garantit une expérience fluide sur mobile comme sur desktop. +

+ +

+ Sous le capot : Next 15, React 19, + Tailwind 4 et Framer Motion pour les + animations. Aucune donnée sensible n’est stockée : on se connecte + simplement via discord . +
+ Sur le dashboard, vous pouvez ajouter votre pseudo Minecraft dans la section Infos du compte +

+ +
+

Légende des tags

+
+ {TAGS.map(t => ( + + {t.label} + + ))} +
+
+ +
+

Code source

+

+ Le projet est entièrement open-source :{" "} + + dépôt GitHub + . + Déployé sur Coolify. +

+
+
+
+
+ ); +} diff --git a/app/ads/page.tsx b/app/ads/page.tsx new file mode 100644 index 0000000..e3e45a5 --- /dev/null +++ b/app/ads/page.tsx @@ -0,0 +1,11 @@ +import AdsGalleryClient from "@/components/AdsGalleryClient"; +import { getAdFiles } from "@/lib/getAds"; + +export const metadata = { + title: "Galerie des publicités – Aystone", +}; + +export default async function AdsGalleryPage() { + const files = await getAdFiles(); + return ; +} \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..020852e --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/auth"; + +export const { GET, POST } = handlers; \ No newline at end of file diff --git a/app/couleurs/page.tsx b/app/couleurs/page.tsx new file mode 100644 index 0000000..8267121 --- /dev/null +++ b/app/couleurs/page.tsx @@ -0,0 +1,26 @@ +import { Palette } from "lucide-react"; +import Image from "next/image"; + +export const metadata = { + title: "Charte des couleurs Minecraft", +}; + +export default function CouleursPage() { + return ( +
+

+ + Charte des couleurs Minecraft +

+
+ Charte des couleurs Minecraft +
+
+ ); +} diff --git a/app/dashboard/Sidebar.tsx b/app/dashboard/Sidebar.tsx new file mode 100644 index 0000000..975843d --- /dev/null +++ b/app/dashboard/Sidebar.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { + LayoutDashboard, + PlusCircle, + TableProperties, + User +} from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +const LINKS = [ + { name: "Tableau", href: "/dashboard", icon: LayoutDashboard }, + { name: "Profil", href: "/dashboard/profil", icon: User }, + { name: "Ajouter", href: "/dashboard/ajouter", icon: PlusCircle }, + { name: "Mes Projets", href: "/dashboard/projets", icon: TableProperties }, +]; + +export default function Sidebar() { + const pathname = usePathname(); + + return ( + + ); +} \ No newline at end of file diff --git a/app/dashboard/StatCard.tsx b/app/dashboard/StatCard.tsx new file mode 100644 index 0000000..e8eedf2 --- /dev/null +++ b/app/dashboard/StatCard.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { motion } from "framer-motion"; +import { ReactNode } from "react"; + +type Props = { + title: string; + value: string | number; + icon?: ReactNode; +}; + +export default function StatCard({ title, value, icon }: Props) { + return ( + + {icon && ( +
+ {icon} +
+ )} +

{title}

+

{value}

+
+ ); +} \ No newline at end of file diff --git a/app/dashboard/actions.ts b/app/dashboard/actions.ts new file mode 100644 index 0000000..e37ef5f --- /dev/null +++ b/app/dashboard/actions.ts @@ -0,0 +1,103 @@ +"use server"; + +import { auth } from "@/auth"; +import { prisma } from "@/lib/prisma"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +export async function createProject(form: FormData) { + const session = await auth(); + if (!session) throw new Error("unauth"); + + const currentUser = await prisma.user.findUnique({ + where: { email: session.user!.email! }, + }); + + const joueur = + (form.get("joueur") as string | null)?.trim() || + currentUser?.mcName || + session.user!.name!; + + const coords = form.get("coords")?.toString().trim(); + const tagsRaw = form.get("tags")?.toString() ?? ""; + + const data = { + joueur, + etat: (form.get("etat") as string) ?? "En cours", + monde: (form.get("monde") as string) ?? "Overworld", + projet: (form.get("projet") as string).trim(), + description: (form.get("description") as string).trim(), + coords: coords && coords.length > 0 ? coords : null, + tags: tagsRaw + .split(",") + .map((t) => t.trim()) + .filter(Boolean), + }; + + await prisma.project.create({ data }); + revalidatePath("/dashboard"); + redirect("/dashboard"); +} + +export async function updateProject(projectId: string, form: FormData) { + const session = await auth(); + if (!session) throw new Error("unauth"); + + const coords = form.get("coords")?.toString().trim(); + const tagsRaw = form.get("tags")?.toString() ?? ""; + + const data = { + etat: (form.get("etat") as string), + monde: (form.get("monde") as string), + projet: (form.get("projet") as string).trim(), + description: (form.get("description") as string).trim(), + coords: coords && coords.length > 0 ? coords : null, + tags: tagsRaw + .split(",") + .map((t) => t.trim()) + .filter(Boolean), + }; + + await prisma.project.update({ + where: { id: projectId }, + data, + }); + + revalidatePath("/dashboard"); + redirect("/dashboard"); +} + +export async function updateMinecraftName(form: FormData) { + const session = await auth(); + if (!session) throw new Error("unauth"); + + const mcName = (form.get("mcName") as string).trim(); + if (!/^[A-Za-z0-9_]{3,16}$/.test(mcName)) throw new Error("invalid"); + + const user = await prisma.user.findUnique({ + where: { email: session.user!.email! }, + }); + + if (!user) { + await prisma.user.create({ + data: { + email: session.user!.email!, + name: session.user!.name, + image: session.user!.image, + mcName, + }, + }); + } else { + if (user.mcName) { + throw new Error("Le pseudo Minecraft ne peut pas être modifié après avoir été défini."); + } + + await prisma.user.update({ + where: { email: session.user!.email! }, + data: { mcName }, + }); + } + + revalidatePath("/dashboard"); + revalidatePath("/"); +} diff --git a/app/dashboard/ajouter/AddProjectForm.tsx b/app/dashboard/ajouter/AddProjectForm.tsx new file mode 100644 index 0000000..8879d57 --- /dev/null +++ b/app/dashboard/ajouter/AddProjectForm.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { motion } from "framer-motion"; +import { Layers3, MapPin, Plus, Tags, User, X } from "lucide-react"; +import { useSession } from "next-auth/react"; +import { useState } from "react"; +import { createProject } from "../actions"; + +const PREDEF = ["build", "usine", "spawn"] as const; +type TagColor = (typeof PREDEF)[number]; + +const palette: Record = { + build: "bg-emerald-600/90", + usine: "bg-orange-600/90", + spawn: "bg-fuchsia-700/90", +}; + +const isValidCoord = (value: string) => /^-?\d*$/.test(value); + +export default function AddProjectForm() { + const { data: session } = useSession(); + const [tags, setTags] = useState([]); + const [tagInput, setInput] = useState(""); + + const [x, setX] = useState(""); + const [y, setY] = useState(""); + const [z, setZ] = useState(""); + + const addTag = (t: string) => { + const val = t.trim().toLowerCase(); + if (!val || tags.includes(val) || tags.length >= 5) return; + setTags([...tags, val]); + }; + + const del = (t: string) => setTags(tags.filter((x) => x !== t)); + + const getTagClass = (t: string) => { + return palette[t as TagColor] ?? "bg-gray-600"; + }; + + return ( + + {/* nom */} +
+ + +
+ + {/* joueur */} + {session?.user?.email === "ultralionfr@gmail.com" && ( +
+ + +
+ )} + + {/* coords */} +
+ {[ + { name: "x", value: x, setValue: setX }, + { name: "y", value: y, setValue: setY }, + { name: "z", value: z, setValue: setZ }, + ].map(({ name, value, setValue }) => ( +
+ + { + if (isValidCoord(e.target.value)) setValue(e.target.value); + }} + className="w-full rounded-lg bg-gray-700 py-2 pl-9 pr-3 text-amber-50 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> +
+ ))} +
+ + {/* Tags */} +
+ + +
+ {tags.map((t) => ( + + ))} +
+ +
+ + +
+ setInput(e.target.value)} + onKeyDown={(e) => + e.key === "Enter" && (e.preventDefault(), addTag(tagInput), setInput("")) + } + placeholder="Ajouter un tag personnalisé" + className="w-full rounded-lg bg-gray-700 py-2 pl-3 pr-12 text-amber-50 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + +
+
+
+ + {/* description */} +