Browse Source

Fix validators & login/register

fix/validators
Astri4-4 4 months ago
parent
commit
b414d3fd74
  1. 2
      backend/app/middlewares/playlist.middleware.js
  2. 10
      backend/app/middlewares/user.middleware.js
  3. 8
      backend/app/middlewares/video.middleware.js
  4. 55
      backend/logs/access.log
  5. 28
      backend/server.js
  6. 2
      frontend/src/components/ProtectedRoute.jsx
  7. 113
      frontend/src/pages/Channel.jsx
  8. 9
      frontend/src/pages/Video.jsx
  9. 2
      frontend/src/routes/routes.jsx

2
backend/app/middlewares/playlist.middleware.js

@ -5,7 +5,7 @@ import jwt from "jsonwebtoken";
export const Playlist = { export const Playlist = {
id: param("id").notEmpty().isNumeric().trim(), 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(), owner: body("owner").notEmpty().isNumeric().trim(),
videoId: param("videoId").notEmpty().isNumeric().trim(), videoId: param("videoId").notEmpty().isNumeric().trim(),
} }

10
backend/app/middlewares/user.middleware.js

@ -5,14 +5,14 @@ import jwt from "jsonwebtoken";
export const User = { export const User = {
id: param("id").notEmpty().isNumeric().trim(), id: param("id").notEmpty().isNumeric().trim(),
email: body("email").notEmpty().isEmail().trim(), 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().isAlphanumeric().trim(), password: body("password").notEmpty().trim(),
picture: body("picture").notEmpty().isAlphanumeric().trim(), picture: body("picture").notEmpty().isAlphanumeric().trim(),
} }
export const UserRegister = { export const UserRegister = {
email: body("email").notEmpty().isEmail().trim(), 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({ password: body("password").notEmpty().isStrongPassword({
minLength: 8, minLength: 8,
maxLength: 32, maxLength: 32,
@ -23,7 +23,7 @@ export const UserRegister = {
} }
export const UserLogin = { export const UserLogin = {
username: body("username").notEmpty().isAlphanumeric().trim(), username: body("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(),
password: body("password").notEmpty().isStrongPassword({ password: body("password").notEmpty().isStrongPassword({
minLength: 8, minLength: 8,
maxLength: 32, maxLength: 32,
@ -34,7 +34,7 @@ export const UserLogin = {
} }
export const UserRequest = { 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) { export async function doEmailExists(req, res, next) {

8
backend/app/middlewares/video.middleware.js

@ -5,8 +5,8 @@ import jwt from "jsonwebtoken";
export const Video = { export const Video = {
id: param("id").notEmpty().isNumeric().trim(), id: param("id").notEmpty().isNumeric().trim(),
title: body("title").notEmpty().isAlphanumeric("fr-FR", {'ignore': " _-"}).trim(), title: body("title").notEmpty().trim(),
description: body("description").optional({values: "falsy"}).isAlphanumeric("fr-FR", {ignore: " -_"}).trim(), description: body("description").optional({values: "falsy"}).trim(),
channel: body("channel").notEmpty().isNumeric().trim(), channel: body("channel").notEmpty().isNumeric().trim(),
visibility: body("visibility").notEmpty().isAlpha().trim(), visibility: body("visibility").notEmpty().isAlpha().trim(),
idBody: body("video").notEmpty().isNumeric().trim(), idBody: body("video").notEmpty().isNumeric().trim(),
@ -19,8 +19,8 @@ export const Video = {
} }
export const VideoCreate = { export const VideoCreate = {
title: body("title").notEmpty().isAlphanumeric("fr-FR", {'ignore': " _-"}).trim(), title: body("title").notEmpty().trim(),
description: body("description").optional({values: "falsy"}).isAlphanumeric("fr-FR", {ignore: " -_"}).trim(), description: body("description").optional({values: "falsy"}).trim(),
channel: body("channel").notEmpty().isNumeric().trim(), channel: body("channel").notEmpty().isNumeric().trim(),
visibility: body("visibility").notEmpty().isAlpha().trim(), visibility: body("visibility").notEmpty().isAlpha().trim(),
} }

55
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 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.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-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

28
backend/server.js

@ -18,36 +18,10 @@ dotenv.config();
const app = express(); 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 // Increase body size limits for file uploads
app.use(express.urlencoded({extended: true, limit: '500mb'})); app.use(express.urlencoded({extended: true, limit: '500mb'}));
app.use(express.json({limit: '500mb'})); app.use(express.json({limit: '500mb'}));
app.use(cors(corsOptions)) app.use(cors())
// ROUTES // ROUTES
app.use("/api/users/", UserRoute); app.use("/api/users/", UserRoute);

2
frontend/src/components/ProtectedRoute.jsx

@ -18,7 +18,7 @@ const ProtectedRoute = ({ children, requireAuth = true }) => {
} }
if (!requireAuth && isAuthenticated) { if (!requireAuth && isAuthenticated) {
return <Navigate to="/login" replace />; return <Navigate to="/" replace />;
} }

113
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: () => <ChannelLastVideos videos={channel && channel.videos.slice(0, 10)} /> },
{ id: 'all', label: 'Toutes les vidéos', element: () => <ChannelLastVideos videos={channel && channel.videos} /> },
];
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 (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="pt-[118px] px-36" >
{/* Channel Header */}
<div className="glassmorphism p-4" >
<div className="flex items-center gap-4" >
<img
src={channel && channel.picture}
alt={channel && channel.name}
className="w-[192px] aspect-square object-cover rounded-full border-2 border-white"
/>
<div>
<h1 className="font-montserrat font-bold text-3xl text-white" >{channel && channel.name}</h1>
<p className="font-montserrat font-medium text-xl text-white" >{channel && channel.subscriptions} abonné(es)</p>
</div>
{
isSubscribedToChannel ? (
<button
className="ml-5 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer"
onClick={handleSubscribe}
>
se désabonner
</button>
) : (
<button
className="ml-5 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer"
onClick={handleSubscribe}
>
s'abonner
</button>
)
}
</div>
<h2 className="font-bold font-montserrat text-white text-xl mt-4" >Description</h2>
<p className="text-white text-lg font-medium font-montserrat pl-[24px]">
{ channel && channel.description }
</p>
</div>
{/* Tab selector */}
<TabLayout tabs={tabs}/>
{/* 10 Last videos */}
</main>
</div>
)
}

9
frontend/src/pages/Video.jsx

@ -50,17 +50,16 @@ export default function Video() {
// Add views to the video // Add views to the video
try { try {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (!token) { if (token) {
navigation('/login'); await fetch(`/api/videos/${id}/views`, {
return;
}
await fetch(`/api/videos/${id}/views`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
}); });
}
} catch (error) { } catch (error) {
addAlert('error', 'Erreur lors de l\'ajout des vues à la vidéo'); addAlert('error', 'Erreur lors de l\'ajout des vues à la vidéo');
} }

2
frontend/src/routes/routes.jsx

@ -64,7 +64,7 @@ const routes = [
) )
}, },
{ {
path: "search", path: "/search",
element: <Search /> element: <Search />
} }
] ]

Loading…
Cancel
Save