From b414d3fd74544a312c61b70f4de9e91ff4eea794 Mon Sep 17 00:00:00 2001 From: Astri4-4 Date: Thu, 14 Aug 2025 07:28:59 +0000 Subject: [PATCH] Fix validators & login/register --- .../app/middlewares/playlist.middleware.js | 2 +- backend/app/middlewares/user.middleware.js | 10 +- backend/app/middlewares/video.middleware.js | 8 +- backend/logs/access.log | 55 +++++++++ backend/server.js | 28 +---- frontend/src/components/ProtectedRoute.jsx | 2 +- frontend/src/pages/Channel.jsx | 113 ++++++++++++++++++ frontend/src/pages/Video.jsx | 9 +- frontend/src/routes/routes.jsx | 2 +- 9 files changed, 185 insertions(+), 44 deletions(-) create mode 100644 frontend/src/pages/Channel.jsx diff --git a/backend/app/middlewares/playlist.middleware.js b/backend/app/middlewares/playlist.middleware.js index d33e617..f699053 100644 --- a/backend/app/middlewares/playlist.middleware.js +++ b/backend/app/middlewares/playlist.middleware.js @@ -5,7 +5,7 @@ import jwt from "jsonwebtoken"; export const Playlist = { id: param("id").notEmpty().isNumeric().trim(), - name: body("name").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(), + name: body("name").notEmpty().trim(), owner: body("owner").notEmpty().isNumeric().trim(), videoId: param("videoId").notEmpty().isNumeric().trim(), } diff --git a/backend/app/middlewares/user.middleware.js b/backend/app/middlewares/user.middleware.js index 908fc22..303179d 100644 --- a/backend/app/middlewares/user.middleware.js +++ b/backend/app/middlewares/user.middleware.js @@ -5,14 +5,14 @@ import jwt from "jsonwebtoken"; export const User = { id: param("id").notEmpty().isNumeric().trim(), email: body("email").notEmpty().isEmail().trim(), - username: body("username").notEmpty().isAlphanumeric().trim(), - password: body("password").notEmpty().isAlphanumeric().trim(), + username: body("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(), + password: body("password").notEmpty().trim(), picture: body("picture").notEmpty().isAlphanumeric().trim(), } export const UserRegister = { email: body("email").notEmpty().isEmail().trim(), - username: body("username").notEmpty().isAlphanumeric().trim(), + username: body("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(), password: body("password").notEmpty().isStrongPassword({ minLength: 8, maxLength: 32, @@ -23,7 +23,7 @@ export const UserRegister = { } export const UserLogin = { - username: body("username").notEmpty().isAlphanumeric().trim(), + username: body("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(), password: body("password").notEmpty().isStrongPassword({ minLength: 8, maxLength: 32, @@ -34,7 +34,7 @@ export const UserLogin = { } export const UserRequest = { - username: param("username").notEmpty().isAlphanumeric().trim(), + username: param("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(), } export async function doEmailExists(req, res, next) { diff --git a/backend/app/middlewares/video.middleware.js b/backend/app/middlewares/video.middleware.js index 5c33d76..9f68afa 100644 --- a/backend/app/middlewares/video.middleware.js +++ b/backend/app/middlewares/video.middleware.js @@ -5,8 +5,8 @@ import jwt from "jsonwebtoken"; export const Video = { id: param("id").notEmpty().isNumeric().trim(), - title: body("title").notEmpty().isAlphanumeric("fr-FR", {'ignore': " _-"}).trim(), - description: body("description").optional({values: "falsy"}).isAlphanumeric("fr-FR", {ignore: " -_"}).trim(), + title: body("title").notEmpty().trim(), + description: body("description").optional({values: "falsy"}).trim(), channel: body("channel").notEmpty().isNumeric().trim(), visibility: body("visibility").notEmpty().isAlpha().trim(), idBody: body("video").notEmpty().isNumeric().trim(), @@ -19,8 +19,8 @@ export const Video = { } export const VideoCreate = { - title: body("title").notEmpty().isAlphanumeric("fr-FR", {'ignore': " _-"}).trim(), - description: body("description").optional({values: "falsy"}).isAlphanumeric("fr-FR", {ignore: " -_"}).trim(), + title: body("title").notEmpty().trim(), + description: body("description").optional({values: "falsy"}).trim(), channel: body("channel").notEmpty().isNumeric().trim(), visibility: body("visibility").notEmpty().isAlpha().trim(), } diff --git a/backend/logs/access.log b/backend/logs/access.log index c2a14a4..62b0a65 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -4196,3 +4196,58 @@ [2025-08-13 08:35:26.517] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 [2025-08-13 09:11:32.832] [undefined] POST(/login): try to login with username 'sacha' [2025-08-13 09:11:32.881] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-14 07:15:10.827] [undefined] GET(/:id/history): try to retrieve history of user 2 +[2025-08-14 07:15:10.831] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-14 07:15:10.834] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200 +[2025-08-14 07:15:10.837] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-14 07:15:10.847] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-14 07:15:12.615] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-14 07:15:12.626] [undefined] GET(/:id/stats): try to get stats +[2025-08-14 07:15:12.628] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-14 07:15:12.637] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-14 07:15:17.519] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-14 07:15:17.523] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-14 07:15:18.162] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-14 07:15:18.172] [undefined] GET(/:id/stats): try to get stats +[2025-08-14 07:15:18.174] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-14 07:15:18.182] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-14 07:15:18.766] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-14 07:15:18.768] [undefined] GET(/:id): try to get video 1 +[2025-08-14 07:15:18.778] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-14 07:15:18.784] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-08-14 07:15:20.133] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-14 07:15:20.143] [undefined] GET(/:id/stats): try to get stats +[2025-08-14 07:15:20.146] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-14 07:15:20.154] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-14 07:16:56.508] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-14 07:16:56.513] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-14 07:17:33.024] [undefined] POST(/): try to upload video with status undefined +[2025-08-14 07:17:33.030] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-14 07:17:33.217] [undefined] POST(/thumbnail): try to add thumbnail to video 3 +[2025-08-14 07:17:33.222] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-14 07:17:33.249] [undefined] PUT(/:id/tags): try to add tags to video 3 +[2025-08-14 07:17:33.260] [undefined] PUT(/:id/tags): successfully added tags to video 3 with status 200 +[2025-08-14 07:18:03.803] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-14 07:18:03.853] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-14 07:21:31.736] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-14 07:21:31.787] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-14 07:27:20.113] [undefined] GET(/:id): try to get video 3 +[2025-08-14 07:27:20.122] [undefined] GET(/:id): successfully get video 3 with status 200 +[2025-08-14 07:27:20.134] [undefined] GET(/:id/similar): try to get similar videos for video 3 +[2025-08-14 07:27:20.145] [undefined] GET(/:id/similar): successfully get similar videos for video 3 with status 200 +[2025-08-14 07:27:20.203] [undefined] GET(/:id/views): try to add views for video 3 +[2025-08-14 07:27:20.211] [undefined] GET(/:id/views): successfully added views for video 3 with status 200 +[2025-08-14 07:27:21.854] [undefined] GET(/:id): try to get video 2 +[2025-08-14 07:27:21.864] [undefined] GET(/:id): successfully get video 2 with status 200 +[2025-08-14 07:27:21.880] [undefined] GET(/:id/similar): try to get similar videos for video 2 +[2025-08-14 07:27:21.890] [undefined] GET(/:id/similar): successfully get similar videos for video 2 with status 200 +[2025-08-14 07:27:21.911] [undefined] GET(/:id/views): try to add views for video 2 +[2025-08-14 07:27:21.922] [undefined] GET(/:id/views): successfully added views for video 2 with status 200 +[2025-08-14 07:27:31.005] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-14 07:27:31.054] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-14 07:27:35.549] [undefined] GET(/:id): try to get video 3 +[2025-08-14 07:27:35.559] [undefined] GET(/:id): successfully get video 3 with status 200 +[2025-08-14 07:27:35.570] [undefined] GET(/:id/similar): try to get similar videos for video 3 +[2025-08-14 07:27:35.583] [undefined] GET(/:id/similar): successfully get similar videos for video 3 with status 200 +[2025-08-14 07:27:46.794] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-14 07:27:46.845] [undefined] POST(/login): Successfully logged in with status 200 diff --git a/backend/server.js b/backend/server.js index d4ef7d4..6140769 100644 --- a/backend/server.js +++ b/backend/server.js @@ -18,36 +18,10 @@ dotenv.config(); const app = express(); -// CORS Configuration for production deployment -const corsOptions = { - origin: function (origin, callback) { - // Allow requests with no origin (like mobile apps or curl requests) - if (!origin) return callback(null, true); - - // Get allowed origins from environment variable - const allowedOrigins = process.env.CORS_ORIGIN ? - process.env.CORS_ORIGIN.split(',').map(url => url.trim()) : - ['http://localhost:3000', 'https://localhost', 'http://localhost']; - - if (allowedOrigins.indexOf(origin) !== -1) { - callback(null, true); - } else { - console.warn(`CORS blocked origin: ${origin}`); - callback(new Error('Not allowed by CORS')); - } - }, - credentials: true, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], - optionsSuccessStatus: 200 // Some legacy browsers choke on 204 -}; - -// INITIALIZE DATABASE - // Increase body size limits for file uploads app.use(express.urlencoded({extended: true, limit: '500mb'})); app.use(express.json({limit: '500mb'})); -app.use(cors(corsOptions)) +app.use(cors()) // ROUTES app.use("/api/users/", UserRoute); diff --git a/frontend/src/components/ProtectedRoute.jsx b/frontend/src/components/ProtectedRoute.jsx index 1e5f666..ec62b73 100644 --- a/frontend/src/components/ProtectedRoute.jsx +++ b/frontend/src/components/ProtectedRoute.jsx @@ -18,7 +18,7 @@ const ProtectedRoute = ({ children, requireAuth = true }) => { } if (!requireAuth && isAuthenticated) { - return ; + return ; } diff --git a/frontend/src/pages/Channel.jsx b/frontend/src/pages/Channel.jsx new file mode 100644 index 0000000..ab954c5 --- /dev/null +++ b/frontend/src/pages/Channel.jsx @@ -0,0 +1,113 @@ +import Navbar from "../components/Navbar.jsx"; +import {useEffect, useState} from "react"; +import {useParams} from "react-router-dom"; +import {fetchChannelDetails, subscribe} from "../services/channel.service.js"; +import TabLayout from "../components/TabLayout.jsx"; +import ChannelLastVideos from "../components/ChannelLastVideos.jsx"; +import { isSubscribed } from "../services/user.service.js"; + + +export default function Channel() { + + const id = useParams().id; + + const [alerts, setAlerts] = useState([]); + const [channel, setChannel] = useState(null); + const [isSubscribedToChannel, setIsSubscribedToChannel] = useState(false); + const tabs = [ + { id: 'last', label: 'Dernières vidéos', element: () => }, + { id: 'all', label: 'Toutes les vidéos', element: () => }, + ]; + + useEffect(() => { + async function fetchData() { + const chan = await fetchChannelDetails(id, addAlert); + setChannel(chan); + // If not authenticated, isSubscribed may be undefined -> default to false + const subscribed = await isSubscribed(id, addAlert); + setIsSubscribedToChannel(Boolean(subscribed)); + } + fetchData(); + }, [id]) + + const addAlert = (type, message) => { + const newAlert = { type, message, id: Date.now() }; // Add unique ID + setAlerts(prev => [...prev, newAlert]); + }; + const onCloseAlert = (alertToRemove) => { + setAlerts(alerts.filter(alert => alert !== alertToRemove)); + } + + const handleSubscribe = async () => { + try { + const result = await subscribe(id, addAlert); + // Update local counter from API response + const newCount = Number(result?.subscriptions ?? (channel?.subscriptions ?? 0)); + setChannel(prev => (prev ? { ...prev, subscriptions: newCount } : prev)); + + // Toggle local subscription state and notify + const next = !isSubscribedToChannel; + setIsSubscribedToChannel(next); + } catch (e) { + // Error alert already handled in service + } + }; + + return ( +
+ + +
+ + {/* Channel Header */} +
+
+ {channel +
+

{channel && channel.name}

+

{channel && channel.subscriptions} abonné(es)

+
+ { + isSubscribedToChannel ? ( + + ) : ( + + ) + } +
+ +

Description

+

+ { channel && channel.description } +

+ +
+ + {/* Tab selector */} + + + + {/* 10 Last videos */} + + + +
+ +
+ ) + +} \ No newline at end of file diff --git a/frontend/src/pages/Video.jsx b/frontend/src/pages/Video.jsx index 5372fb0..4a0d35d 100644 --- a/frontend/src/pages/Video.jsx +++ b/frontend/src/pages/Video.jsx @@ -50,17 +50,16 @@ export default function Video() { // Add views to the video try { const token = localStorage.getItem('token'); - if (!token) { - navigation('/login'); - return; - } - await fetch(`/api/videos/${id}/views`, { + if (token) { + await fetch(`/api/videos/${id}/views`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }); + } + } catch (error) { addAlert('error', 'Erreur lors de l\'ajout des vues à la vidéo'); } diff --git a/frontend/src/routes/routes.jsx b/frontend/src/routes/routes.jsx index 706cede..5f1e7e0 100644 --- a/frontend/src/routes/routes.jsx +++ b/frontend/src/routes/routes.jsx @@ -64,7 +64,7 @@ const routes = [ ) }, { - path: "search", + path: "/search", element: } ]