From 0f3104fc03552263dcc1af36918a2c054f35eebf Mon Sep 17 00:00:00 2001 From: moustillon Date: Mon, 15 Jun 2026 13:54:43 +0200 Subject: [PATCH] feat: Subjects now accepts any format of image --- backend/src/index.ts | 14 +++++++++++--- backend/src/services/subject.ts | 15 +++++++++------ frontend/src/index.ts | 2 +- frontend/src/pages/AdminPage.tsx | 30 +++++++++++++++++++----------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 4ced279..0651db5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -29,14 +29,22 @@ const app = new Elysia() }) .get("/uploads/:filename", async ({ params, set }) => { const filename = basename(params.filename); - if (filename !== params.filename || !filename.toLowerCase().endsWith(".pdf")) { + const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase(); + const CONTENT_TYPES: Record = { + ".pdf": "application/pdf", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".md": "text/markdown", + }; + if (filename !== params.filename || !CONTENT_TYPES[ext]) { set.status = 400; - return {message: "Invalid filename"} + return { message: "Invalid filename" }; } try { const buf = await readFile(join(UPLOADS_DIR, filename)); return new Response(buf, { - headers: { "Content-Type": "application/pdf", "Content-Disposition": `inline; filename="${filename}"` }, + headers: { "Content-Type": CONTENT_TYPES[ext], "Content-Disposition": `inline; filename="${filename}"` }, }); } catch { set.status = 404; diff --git a/backend/src/services/subject.ts b/backend/src/services/subject.ts index ce673aa..b025cea 100644 --- a/backend/src/services/subject.ts +++ b/backend/src/services/subject.ts @@ -18,12 +18,15 @@ const DIFFICULTY_LABEL: Record = { const UPLOADS_DIR = process.env.UPLOADS_DIR ?? join(import.meta.dir, "../../uploads"); -async function savePdfs(files: File[]): Promise { +const ALLOWED_EXTENSIONS = new Set([".pdf", ".png", ".jpg", ".jpeg", ".md"]); + +async function saveFiles(files: File[]): Promise { const saved: string[] = []; for (const f of files) { const fileName = f.name.replace(/[\\/]/g, "_"); - if (!fileName.toLowerCase().endsWith(".pdf")) - throw new Error("Only PDF files are allowed"); + const ext = fileName.slice(fileName.lastIndexOf(".")).toLowerCase(); + if (!ALLOWED_EXTENSIONS.has(ext)) + throw new Error(`Type de fichier non autorisé : ${ext}`); const buffer = Buffer.from(await f.arrayBuffer()); await writeFile(join(UPLOADS_DIR, fileName), buffer); saved.push(fileName); @@ -38,7 +41,7 @@ export const subjectService = { const tags = data.tags.split(",").map(t => t.trim()).filter(Boolean); const urls = (data.urls ?? "").split("\n").map(u => u.trim()).filter(Boolean); - const files = await savePdfs(data.newFiles ?? []); + const files = await saveFiles(data.newFiles ?? []); return subjectModel.create({ name: data.name, description: data.description, difficulty, tags, files, urls }); }, @@ -59,7 +62,7 @@ export const subjectService = { const tags = (data.tags ?? "").split(",").map(t => t.trim()).filter(Boolean); const urls = (data.urls ?? "").split("\n").map(u => u.trim()).filter(Boolean); - const files = await savePdfs(data.newFiles ?? []); + const files = await saveFiles(data.newFiles ?? []); return subjectModel.create({ name: data.name, @@ -94,7 +97,7 @@ export const subjectService = { const tags = data.tags.split(",").map(t => t.trim()).filter(Boolean); const urls = (data.urls ?? "").split("\n").map(u => u.trim()).filter(Boolean); - const newFileNames = await savePdfs(data.newFiles ?? []); + const newFileNames = await saveFiles(data.newFiles ?? []); const files = [...(data.existingFiles ?? []), ...newFileNames]; return subjectModel.update(id, { name: data.name, description: data.description, difficulty, tags, files, urls }); diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 10c6380..86daf63 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -15,7 +15,7 @@ const server = serve({ const publicUrl = process.env.PUBLIC_URL ?? server.url.toString(); console.log(` ┌─────────────────────────────────────────┐ -│ GCC — Dev Environment │ +│ GCC Dev Environment │ ├─────────────────────────────────────────┤ │ Frontend → ${publicUrl.padEnd(25)}│ │ Backend → http://localhost:8080 │ diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index 7fab2da..da78764 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -204,10 +204,14 @@ function SubjectForm({ token }: { token: string }) { const [error, setError] = useState(""); const fileInputRef = useRef(null); + const ALLOWED_EXTS = new Set([".pdf", ".png", ".jpg", ".jpeg", ".md"]); const addFiles = (list: FileList | null) => { if (!list) return; - const pdfs = Array.from(list).filter(f => f.type === "application/pdf"); - setPdfFiles(prev => [...prev, ...pdfs]); + const valid = Array.from(list).filter(f => { + const ext = f.name.slice(f.name.lastIndexOf(".")).toLowerCase(); + return ALLOWED_EXTS.has(ext); + }); + setPdfFiles(prev => [...prev, ...valid]); }; const reset = () => { @@ -402,7 +406,7 @@ function SubjectForm({ token }: { token: string }) {
- Fichiers PDF (optionnel) + Fichiers (PDF, image, Markdown (optionnel)) {pdfFiles.map((f, i) => (
@@ -429,11 +433,11 @@ function SubjectForm({ token }: { token: string }) { > - Glisse des PDFs ici ou clique pour choisir - PDF uniquement (plusieurs fichiers acceptés) + Glisse des fichiers ici ou clique pour choisir + PDF, PNG, JPG, Markdown (plusieurs fichiers acceptés)
- { addFiles(e.target.files); if (fileInputRef.current) fileInputRef.current.value = ""; }} />
@@ -494,10 +498,14 @@ function EditModal({ subject, token, onClose, onSaved }: { const [loading, setLoading] = useState(false); const [error, setError] = useState(""); + const ALLOWED_EXTS = new Set([".pdf", ".png", ".jpg", ".jpeg", ".md"]); const addFiles = (list: FileList | null) => { if (!list) return; - const pdfs = Array.from(list).filter(f => f.type === "application/pdf"); - setNewPdfFiles(prev => [...prev, ...pdfs]); + const valid = Array.from(list).filter(f => { + const ext = f.name.slice(f.name.lastIndexOf(".")).toLowerCase(); + return ALLOWED_EXTS.has(ext); + }); + setNewPdfFiles(prev => [...prev, ...valid]); }; const submit = async (e: { preventDefault: () => void }) => { @@ -618,7 +626,7 @@ function EditModal({ subject, token, onClose, onSaved }: {
- Fichiers PDF (optionnel) + Fichiers (PDF, image, Markdown (optionnel)) {keepFiles.map(name => (
@@ -651,10 +659,10 @@ function EditModal({ subject, token, onClose, onSaved }: { style={{ border: `2px dashed ${dragOver ? "var(--epi-accent)" : "var(--epi-border)"}`, borderRadius: 10, padding: "20px", textAlign: "center", cursor: "pointer", transition: "border-color 0.2s", background: dragOver ? "rgba(128,157,253,0.05)" : "none" }}> - Glisse des PDFs ou clique + Glisse des fichiers ou clique
- { addFiles(e.target.files); if (editFileRef.current) editFileRef.current.value = ""; }} />