feat: token refresh, video upload, playlist routes complets
- auth: cookie od_token 7j, refresh token od_refresh 30j, POST /api/auth/refresh, GET /api/auth/me/optional - admin: POST /api/admin/videos/upload via multer (mp4/webm, 4Go max, UUID filename) - playlist: PATCH /:id, DELETE /:id, POST /:id/videos, DELETE /:id/videos/:videoId - env: UPLOADS_DIR documenté dans .env.example
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import { Router, Request, Response } from "express";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import multer, { FileFilterCallback } from "multer";
|
||||
import { AppDataSource } from "../config/data-source";
|
||||
import { Video } from "../entities/Video";
|
||||
import { User } from "../entities/User";
|
||||
@@ -9,6 +12,26 @@ import { SubscriptionPlan } from "../entities/SubscriptionPlan";
|
||||
import { requireAuth, AuthenticatedRequest } from "../middleware/auth.middleware";
|
||||
import { requireAdmin } from "../middleware/admin.middleware";
|
||||
|
||||
const UPLOADS_DIR = process.env.UPLOADS_DIR ?? path.join(process.cwd(), "uploads");
|
||||
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
||||
|
||||
const videoStorage = multer.diskStorage({
|
||||
destination: (_req, _file, cb) => cb(null, UPLOADS_DIR),
|
||||
filename: (_req, _file, cb) => cb(null, `${crypto.randomUUID()}.mp4`),
|
||||
});
|
||||
|
||||
const videoUpload = multer({
|
||||
storage: videoStorage,
|
||||
limits: { fileSize: 4 * 1024 * 1024 * 1024 }, // 4 Go
|
||||
fileFilter: (_req: Request, file: Express.Multer.File, cb: FileFilterCallback) => {
|
||||
if (["video/mp4", "video/webm"].includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error("INVALID_MIME_TYPE"));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Applique requireAuth + requireAdmin sur toutes les routes de ce routeur
|
||||
@@ -19,6 +42,41 @@ router.use(requireAdmin as unknown as (req: Request, res: Response, next: () =>
|
||||
// VIDEOS
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* POST /api/admin/videos/upload
|
||||
* Upload un fichier vidéo (mp4 / webm) dans UPLOADS_DIR.
|
||||
* Retourne le storageKey à passer ensuite à POST /api/admin/videos.
|
||||
*/
|
||||
router.post(
|
||||
"/videos/upload",
|
||||
(req: Request, res: Response, next) => {
|
||||
videoUpload.single("file")(req, res, (err) => {
|
||||
if (err) {
|
||||
const message = err instanceof Error ? err.message : "UPLOAD_ERROR";
|
||||
if (message === "INVALID_MIME_TYPE") {
|
||||
res.status(415).json({ success: false, error: "INVALID_MIME_TYPE", message: "Only video/mp4 and video/webm are accepted" });
|
||||
} else if (err instanceof multer.MulterError && err.code === "LIMIT_FILE_SIZE") {
|
||||
res.status(413).json({ success: false, error: "FILE_TOO_LARGE" });
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
},
|
||||
(req: Request, res: Response): void => {
|
||||
if (!req.file) {
|
||||
res.status(400).json({ success: false, error: "NO_FILE" });
|
||||
return;
|
||||
}
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: { storageKey: req.file.filename, storageType: "local" },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/admin/videos
|
||||
* Liste toutes les vidéos (publiées et non publiées), tous les champs.
|
||||
|
||||
Reference in New Issue
Block a user