diff --git a/backend/database/schema.sql b/backend/database/schema.sql index ee421b1..8a707af 100644 --- a/backend/database/schema.sql +++ b/backend/database/schema.sql @@ -7,6 +7,8 @@ CREATE TABLE `avatar_filename` VARCHAR(255) ); +-- ! Add a videoFilename column to the Film table to store the video file name in case there is no video URL and the video is stored locally + DROP TABLE IF EXISTS `Film`; CREATE TABLE @@ -17,7 +19,8 @@ CREATE TABLE `cover_url` VARCHAR(255) DEFAULT NULL, `cover_filename` VARCHAR(255) DEFAULT NULL, `title` VARCHAR(255) NOT NULL, - `videoUrl` VARCHAR(255) NOT NULL, + `videoUrl` VARCHAR(255) DEFAULT NULL, + `videoFilename` VARCHAR(255) DEFAULT NULL, `duration` INT NOT NULL, `year` VARCHAR(4) NOT NULL, `description` VARCHAR(700) NOT NULL, diff --git a/backend/public/assets/icons/circle-arrow-left-solid.svg b/backend/public/assets/icons/circle-arrow-left-solid.svg index 4d0bee1..c098c52 100644 --- a/backend/public/assets/icons/circle-arrow-left-solid.svg +++ b/backend/public/assets/icons/circle-arrow-left-solid.svg @@ -1 +1,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/backend/public/assets/icons/xmark-solid.svg b/backend/public/assets/icons/xmark-solid.svg new file mode 100644 index 0000000..c33a8ac --- /dev/null +++ b/backend/public/assets/icons/xmark-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/src/app.js b/backend/src/app.js index 48b2361..27d9775 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -1,3 +1,4 @@ +// const path = require("path"); // Load the express module to create a web application const express = require("express"); @@ -145,4 +146,8 @@ app.use(logErrors); /* ************************************************************************* */ +// app.get("*", (req, res) => { +// res.sendFile(path.join(__dirname, "/../public/index.html")); +// }); + module.exports = app; diff --git a/backend/src/controllers/authControllers.js b/backend/src/controllers/authControllers.js index 19a98ff..7f0de1f 100644 --- a/backend/src/controllers/authControllers.js +++ b/backend/src/controllers/authControllers.js @@ -16,7 +16,6 @@ const login = async (req, res, next) => { user.hashed_password, req.body.password ); - console.warn("verified =>", verified); if (verified) { // delete user.hashed_password; diff --git a/backend/src/controllers/categorieControllers.js b/backend/src/controllers/categorieControllers.js index 9875a0d..748eace 100644 --- a/backend/src/controllers/categorieControllers.js +++ b/backend/src/controllers/categorieControllers.js @@ -19,7 +19,6 @@ const count = async (request, response, next) => { try { // Fetch all items from the database const categories = await tables.Categorie.count(); - console.warn(categories); // Respond with the items in JSON format response.json(categories); diff --git a/backend/src/controllers/filmControllers.js b/backend/src/controllers/filmControllers.js index 4b4fd66..499afeb 100644 --- a/backend/src/controllers/filmControllers.js +++ b/backend/src/controllers/filmControllers.js @@ -1,5 +1,7 @@ const tables = require("../tables"); +// % How to retrieve the uploaded videos from the request object in the edit method? + const browse = async (req, res, next) => { try { const films = await tables.Film.readAll(); @@ -25,6 +27,19 @@ const read = async (req, res, next) => { const edit = async (req, res, next) => { const { id } = req.params; req.body.id = id; + + if (req.files.cover && req.files.cover[0]) { + req.body.cover_filename = req.files.cover[0].filename; + } + + if (req.files.miniature && req.files.miniature[0]) { + req.body.miniature_filename = req.files.miniature[0].filename; + } + + if (req.files.videoFile && req.files.videoFile[0]) { + req.body.videoFilename = req.files.videoFile[0].filename; + } + try { const result = await tables.Film.update(req.body); if (result) { @@ -39,13 +54,16 @@ const edit = async (req, res, next) => { }; const add = async (req, res, next) => { - if (req.body.images.length === 2) { - const miniature = req.body.images[0]; - const cover = req.body.images[1]; - req.body.miniature_filename = miniature; - req.body.cover_filename = cover; - } else { - res.status(403).send({ message: "Missing file" }); + if (req.files.cover && req.files.cover[0]) { + req.body.cover_filename = req.files.cover[0].filename; + } + + if (req.files.miniature && req.files.miniature[0]) { + req.body.miniature_filename = req.files.miniature[0].filename; + } + + if (req.files.videoFile && req.files.videoFile[0]) { + req.body.videoFilename = req.files.videoFile[0].filename; } const film = req.body; @@ -67,17 +85,6 @@ const add = async (req, res, next) => { categoriesIds, }); - // Utiliser Promise.all pour paralléliser les opérations asynchrones - // const response = await Promise.all( - // categories.map(async (category) => { - // // Appeler la fonction asynchrone pour la liaison catégorie-film - // await tables.categorie_par_film.create({ - // filmId: insertId, - // categorieId: category.id, - // }); - // }) - // ); - if (response) { res.status(200).json({ insertId }); } else { diff --git a/backend/src/controllers/userControllers.js b/backend/src/controllers/userControllers.js index 681b868..02f9936 100644 --- a/backend/src/controllers/userControllers.js +++ b/backend/src/controllers/userControllers.js @@ -108,15 +108,6 @@ const edit = async (req, res, next) => { res.status(404).json({ error: "User not found" }); } - console.warn("currentUser =>", currentUser); - console.warn("currentUser.hashed_password =>", currentUser.hashed_password); - - console.warn( - "currentPassword && newPassword =>", - current_password, - new_password - ); - // Ensure currentUser has a hashed_password and it's not empty if ( !currentUser.hashed_password || @@ -133,7 +124,7 @@ const edit = async (req, res, next) => { currentUser.hashed_password, current_password ); - console.warn("isPasswordCorrect =>", isPasswordCorrect); + if (!isPasswordCorrect) { return res.status(400).json({ error: "Incorrect current password" }); } diff --git a/backend/src/models/CategorieParFilmManager.js b/backend/src/models/CategorieParFilmManager.js index cfe0f46..5cfd5f0 100644 --- a/backend/src/models/CategorieParFilmManager.js +++ b/backend/src/models/CategorieParFilmManager.js @@ -20,8 +20,6 @@ class CategorieParFilmManager extends AbstractManager { arrDep.push(filmId, catId, unique_key); }); - // console.log("querySQL =>", querySQL); - // console.log("arrDep =>", arrDep); // Execute the SQL INSERT query to add a new categorieParFilm to the "categorieParFilm" table const result = await this.database.query(`${querySQL};`, arrDep); diff --git a/backend/src/models/FilmManager.js b/backend/src/models/FilmManager.js index 8b0bcb0..1a77bac 100644 --- a/backend/src/models/FilmManager.js +++ b/backend/src/models/FilmManager.js @@ -1,5 +1,7 @@ const AbstractManager = require("./AbstractManager"); +// § Add videoFilename field in each method of the FilmManager class. + class FilmManager extends AbstractManager { constructor() { super({ table: "Film" }); @@ -10,18 +12,20 @@ class FilmManager extends AbstractManager { cover_filename, title, videoUrl, + videoFilename, duration, year, description, isAvailable, }) { const [result] = await this.database.query( - `insert into ${this.table} (miniature_filename, cover_filename, title, videoUrl, duration, year, description, isAvailable) values (?, ?, ?, ?, ?, ?, ?, ?)`, + `insert into ${this.table} (miniature_filename, cover_filename, title, videoUrl, videoFilename, duration, year, description, isAvailable) values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ miniature_filename, cover_filename, title, videoUrl, + videoFilename, duration, year, description, @@ -47,19 +51,23 @@ class FilmManager extends AbstractManager { async update({ id, miniature_filename, + cover_filename, title, videoUrl, + videoFilename, duration, year, description, IsAvailable, }) { const [result] = await this.database.query( - `update ${this.table} SET miniature_filename=?, title=?, videoUrl=?, duration=?, year=?, description=?, IsAvailable=? where id=?`, + `update ${this.table} SET miniature_filename = COALESCE(?, miniature_filename), cover_filename = COALESCE(?, cover_filename), title = COALESCE(?, title), videoUrl = COALESCE(?, videoUrl), videoFilename = COALESCE(?, videoFilename), duration = COALESCE(?, duration), year = COALESCE(?, year), description = COALESCE(?, description), IsAvailable = COALESCE(?, IsAvailable) where id=?`, [ miniature_filename, + cover_filename, title, videoUrl, + videoFilename, duration, year, description, diff --git a/backend/src/router.js b/backend/src/router.js index 5312acd..b330509 100644 --- a/backend/src/router.js +++ b/backend/src/router.js @@ -8,7 +8,7 @@ const router = express.Router(); // Import itemControllers module for handling item-related operations -const { uploadImages } = require("./services/multer"); +const { uploadImages, uploadImages2 } = require("./services/multer"); const { hashPassword, verifyToken } = require("./services/auth"); const userControllers = require("./controllers/userControllers"); @@ -22,6 +22,8 @@ const commentaireFilmControllers = require("./controllers/commentaireFilmControl const authControllers = require("./controllers/authControllers"); +// § I would to implement the upload of video files for adding or editing a film with the multer package. + // Route to get a list of items router.get("/users", userControllers.browse); router.get("/films", filmControllers.browse); @@ -65,7 +67,15 @@ router.get("/category/:id", categorieControllers.read); // Route to edit a specific item by ID router.put("/user/:id", userControllers.edit); router.put("/comments/:commentId", commentaireFilmControllers.updateComment); -router.put("/films/:id", filmControllers.edit); +router.put( + "/films/:id", + uploadImages2.fields([ + { name: "miniature", maxCount: 1 }, + { name: "cover", maxCount: 1 }, + { name: "videoFile", maxCount: 1 }, + ]), + filmControllers.edit +); router.put("/category/:id", categorieControllers.edit); // Route to add a new item @@ -74,7 +84,15 @@ router.post("/users", hashPassword, userControllers.add); router.post("/favorites/film", favoriFilmControllers.addMovieToFavorite); router.post("/watchlist/film", watchlistControllers.addMovieToWatchlist); router.post("/comments", commentaireFilmControllers.addComment); -router.post("/films", uploadImages.array("images", 2), filmControllers.add); +router.post( + "/films", + uploadImages.fields([ + { name: "miniature", maxCount: 1 }, + { name: "cover", maxCount: 1 }, + { name: "videoFile", maxCount: 1 }, + ]), + filmControllers.add +); router.post( "/film/:filmId/category/:categoryId", categorieParFilmControllers.addFilmToCategory diff --git a/backend/src/services/multer.js b/backend/src/services/multer.js index 02414a7..dd50f08 100644 --- a/backend/src/services/multer.js +++ b/backend/src/services/multer.js @@ -2,6 +2,8 @@ const multer = require("multer"); const { v4 } = require("uuid"); +// ! Implement the upload of video files for adding or editing a film with the multer package. + const imagesStorage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, "./public/assets/images"); @@ -12,20 +14,42 @@ const imagesStorage = multer.diskStorage({ // eslint-disable-next-line prefer-template const name = v4() + "." + extension; - if (req.body.images) { - req.body.images.push(name); - } else { - req.body.images = [name]; + if (req.body.cover) { + req.body.cover = name; + } + + if (req.body.miniature) { + req.body.miniature = name; + } + cb(null, name); + }, +}); + +const imagesStorage2 = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, "./public/assets/images"); + }, + filename: (req, file, cb) => { + const extArray = file.mimetype.split("/"); + const extension = extArray[extArray.length - 1]; + // eslint-disable-next-line prefer-template + const name = v4() + "." + extension; + + if (req.body.cover) { + req.body.cover = name; + } + + if (req.body.miniature) { + req.body.miniature = name; } cb(null, name); }, - // limits: { - // fieldSize: 1024 * 5, - // }, }); const uploadImages = multer({ storage: imagesStorage }); +const uploadImages2 = multer({ storage: imagesStorage2 }); module.exports = { uploadImages, + uploadImages2, }; diff --git a/frontend/src/components/CategoryDisplay.jsx b/frontend/src/components/CategoryDisplay.jsx index 1943ba3..383801e 100644 --- a/frontend/src/components/CategoryDisplay.jsx +++ b/frontend/src/components/CategoryDisplay.jsx @@ -77,11 +77,11 @@ function CategoryDisplay({ categorie, getCategories }) { toast.success("Category deleted"); getCategories(); } else { - toast.error("An error occurred"); + toast.error("Error deleting category"); } } catch (error) { console.error(error); - toast.error("An error occurred"); + toast.error("Error deleting category"); } finally { setIsDeleting(false); } diff --git a/frontend/src/components/FreeMovie.jsx b/frontend/src/components/FreeMovie.jsx index d5fb3be..cc364c8 100644 --- a/frontend/src/components/FreeMovie.jsx +++ b/frontend/src/components/FreeMovie.jsx @@ -288,7 +288,7 @@ FreeMovie.propTypes = { movie: PropTypes.shape({ id: PropTypes.number.isRequired, cover_filename: PropTypes.string, - cover_url: PropTypes.string.isRequired, + cover_url: PropTypes.string, title: PropTypes.string.isRequired, year: PropTypes.string.isRequired, duration: PropTypes.number.isRequired, diff --git a/frontend/src/components/MovieDescription.jsx b/frontend/src/components/MovieDescription.jsx index d22253b..d556a9a 100644 --- a/frontend/src/components/MovieDescription.jsx +++ b/frontend/src/components/MovieDescription.jsx @@ -22,7 +22,7 @@ MovieDescription.propTypes = { title: PropTypes.string.isRequired, description: PropTypes.string.isRequired, cover_filename: PropTypes.string, - cover_url: PropTypes.string.isRequired, + cover_url: PropTypes.string, year: PropTypes.string.isRequired, duration: PropTypes.number.isRequired, IsAvailable: PropTypes.number.isRequired, diff --git a/frontend/src/pages/AddVideos.jsx b/frontend/src/pages/AddVideos.jsx index e95753d..2c5beae 100644 --- a/frontend/src/pages/AddVideos.jsx +++ b/frontend/src/pages/AddVideos.jsx @@ -4,6 +4,8 @@ import { useNavigate, useLocation } from "react-router-dom"; import toast from "react-hot-toast"; import { useMovies } from "../contexts/MovieContext"; +// § The user should be able to decide if he wanna upload a video or use a youtube link to add a video. + function AddVideos() { const location = useLocation(); const [file, setFile] = useState(undefined); @@ -13,6 +15,7 @@ function AddVideos() { const [description, setDescription] = useState(""); const [title, setTitle] = useState(""); const [videoUrl, setVideoUrl] = useState(""); + const [videoFilename, setVideoFilename] = useState(""); const [year, setYear] = useState(""); const [duration, setDuration] = useState(""); const [isAvailable, setIsAvailable] = useState(false); @@ -65,7 +68,7 @@ function AddVideos() { duration || isAvailable) === (undefined || "" || false) ) { - toast.error("champ manquant"); + toast.error("Missing fields"); } else if (isAvailable === "utilisateur") { setIsAvailable(true); } else if (isAvailable === "visiteur") { @@ -82,8 +85,9 @@ function AddVideos() { formData.append("year", year); formData.append("isAvailable", isAvailable); formData.append("duration", duration); - formData.append("images", file); - formData.append("images", cover); + formData.append("miniature", file); + formData.append("cover", cover); + formData.append("videoFile", videoFilename); await axios .post(`${import.meta.env.VITE_BACKEND_URL}/api/films`, formData, { @@ -147,14 +151,43 @@ function AddVideos() {
+ Or +
+