Browse Source

Fixed playlist

features/visibility
astria 4 months ago
parent
commit
f730b4af3d
  1. 60
      backend/app/controllers/user.controller.js
  2. 6
      backend/app/middlewares/user.middleware.js
  3. 9
      backend/app/routes/user.route.js
  4. 159
      backend/logs/access.log
  5. 1
      docker-compose.yaml
  6. 7
      frontend/package-lock.json
  7. 18
      frontend/src/components/UserCard.jsx
  8. 49
      frontend/src/pages/AddVideo.jsx
  9. 10
      frontend/src/services/playlist.service.js
  10. 14
      frontend/src/services/user.service.js

60
backend/app/controllers/user.controller.js

@ -1,11 +1,11 @@
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import {getClient} from "../utils/database.js"; import { getClient } from "../utils/database.js";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import path, {dirname} from "path"; import path, { dirname } from "path";
import fs from "fs"; import fs from "fs";
import {fileURLToPath} from "url"; import { fileURLToPath } from "url";
import crypto from "crypto"; import crypto from "crypto";
import {sendEmail} from "../utils/mail.js"; import { sendEmail } from "../utils/mail.js";
export async function register(req, res) { export async function register(req, res) {
try { try {
@ -49,7 +49,7 @@ export async function register(req, res) {
const token = crypto.randomBytes(32).toString("hex").slice(0, 5); const token = crypto.randomBytes(32).toString("hex").slice(0, 5);
const textMessage = "Merci de vous être inscrit. Veuillez vérifier votre e-mail. Code: " + token; const textMessage = "Merci de vous être inscrit. Veuillez vérifier votre e-mail. Code: " + token;
const htmlMessage = ` const htmlMessage = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -126,7 +126,7 @@ export async function register(req, res) {
console.log("Successfully registered"); console.log("Successfully registered");
client.end(); client.end();
logger.write("successfully registered", 200); logger.write("successfully registered", 200);
res.status(200).send({user: user}); res.status(200).send({ user: user });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
@ -187,7 +187,7 @@ export async function login(req, res) {
if (!userInBase) { if (!userInBase) {
logger.write("failed to login", 401) logger.write("failed to login", 401)
res.status(401).json({error: "Invalid credentials"}); res.status(401).json({ error: "Invalid credentials" });
return return
} }
@ -195,7 +195,7 @@ export async function login(req, res) {
if (!isPasswordValid) { if (!isPasswordValid) {
logger.write("failed to login", 401) logger.write("failed to login", 401)
res.status(401).json({error: "Invalid credentials"}); res.status(401).json({ error: "Invalid credentials" });
return return
} }
@ -215,7 +215,7 @@ export async function login(req, res) {
logger.write("Successfully logged in", 200); logger.write("Successfully logged in", 200);
client.end(); client.end();
res.status(200).json({token: token, user: userData}); res.status(200).json({ token: token, user: userData });
} }
@ -231,12 +231,12 @@ export async function getById(req, res) {
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve user " + id + " because it doesn't exist", 404); logger.write("failed to retrieve user " + id + " because it doesn't exist", 404);
client.end() client.end()
res.status(404).json({error: "Not Found"}); res.status(404).json({ error: "Not Found" });
return return
} }
logger.write("successfully retrieved user " + id, 200); logger.write("successfully retrieved user " + id, 200);
if (result.rows[0].picture) { if (result.rows[0].picture) {
return res.status(200).json({user: result.rows[0]}); return res.status(200).json({ user: result.rows[0] });
} }
} }
@ -250,12 +250,12 @@ export async function getByUsername(req, res) {
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve user " + username + " because it doesn't exist", 404); logger.write("failed to retrieve user " + username + " because it doesn't exist", 404);
client.end() client.end()
res.status(404).json({error: "Not Found"}); res.status(404).json({ error: "Not Found" });
return return
} }
logger.write("successfully retrieved user " + username, 200); logger.write("successfully retrieved user " + username, 200);
client.end(); client.end();
return res.status(200).json({user: result.rows[0]}); return res.status(200).json({ user: result.rows[0] });
} }
export async function update(req, res) { export async function update(req, res) {
@ -283,7 +283,7 @@ export async function update(req, res) {
if (emailResult.rows[0]) { if (emailResult.rows[0]) {
logger.write("failed to update because email is already used", 400) logger.write("failed to update because email is already used", 400)
client.end(); client.end();
res.status(400).json({error: "Email already exists"}); res.status(400).json({ error: "Email already exists" });
} }
} }
@ -293,7 +293,7 @@ export async function update(req, res) {
if (usernameResult.rows[0]) { if (usernameResult.rows[0]) {
logger.write("failed to update because username is already used", 400) logger.write("failed to update because username is already used", 400)
client.end(); client.end();
res.status(400).json({error: "Username already exists"}); res.status(400).json({ error: "Username already exists" });
} }
} }
@ -318,12 +318,12 @@ export async function update(req, res) {
path.join(__dirname, "..", "uploads", "profiles", profilePicture), path.join(__dirname, "..", "uploads", "profiles", profilePicture),
path.join(__dirname, "..", "uploads", "profiles", user.username + "." + profilePicture.split(".").pop()), path.join(__dirname, "..", "uploads", "profiles", user.username + "." + profilePicture.split(".").pop()),
(err) => { (err) => {
if (err) { if (err) {
logger.write("failed to update profile picture", 500); logger.write("failed to update profile picture", 500);
console.error("Error renaming file:", err); console.error("Error renaming file:", err);
throw err; throw err;
} }
}); });
profilePicture = "/api/media/profile/" + user.username + "." + profilePicture.split(".").pop(); profilePicture = "/api/media/profile/" + user.username + "." + profilePicture.split(".").pop();
const updateQuery = `UPDATE users SET email = $1, username = $2, password = $3, picture = $4 WHERE id = $5 RETURNING id, email, username, picture`; const updateQuery = `UPDATE users SET email = $1, username = $2, password = $3, picture = $4 WHERE id = $5 RETURNING id, email, username, picture`;
@ -334,7 +334,7 @@ export async function update(req, res) {
} catch (err) { } catch (err) {
console.log(err); console.log(err);
client.end() client.end()
res.status(500).json({error: err}); res.status(500).json({ error: err });
} }
} }
@ -348,7 +348,7 @@ export async function deleteUser(req, res) {
await client.query(query, [id]); await client.query(query, [id]);
logger.write("successfully deleted user " + id); logger.write("successfully deleted user " + id);
client.end(); client.end();
res.status(200).json({message: 'User deleted'}); res.status(200).json({ message: 'User deleted' });
} }
export async function getChannel(req, res) { export async function getChannel(req, res) {
@ -363,13 +363,13 @@ export async function getChannel(req, res) {
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404); logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404);
client.end(); client.end();
res.status(404).json({error: "Channel Not Found"}); res.status(404).json({ error: "Channel Not Found" });
return; return;
} }
logger.write("successfully retrieved channel of user " + id, 200); logger.write("successfully retrieved channel of user " + id, 200);
client.end(); client.end();
res.status(200).json({channel: result.rows[0]}); res.status(200).json({ channel: result.rows[0] });
} }
export async function getHistory(req, res) { export async function getHistory(req, res) {
@ -414,7 +414,7 @@ export async function getHistory(req, res) {
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve history of user " + id + " because it doesn't exist", 404); logger.write("failed to retrieve history of user " + id + " because it doesn't exist", 404);
res.status(404).json({error: "History Not Found"}); res.status(404).json({ error: "History Not Found" });
client.end(); client.end();
return; return;
} }
@ -440,11 +440,11 @@ export async function isSubscribed(req, res) {
if (result.rows[0]) { if (result.rows[0]) {
logger.write(`user ${userId} is subscribed to channel ${channelId}`, 200); logger.write(`user ${userId} is subscribed to channel ${channelId}`, 200);
client.end(); client.end();
return res.status(200).json({subscribed: true}); return res.status(200).json({ subscribed: true });
} else { } else {
logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200); logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200);
client.end(); client.end();
return res.status(200).json({subscribed: false}); return res.status(200).json({ subscribed: false });
} }
} }
@ -454,13 +454,13 @@ export async function searchByUsername(req, res) {
const logger = req.body.logger; const logger = req.body.logger;
logger.action("try to search user by username " + username); logger.action("try to search user by username " + username);
const query = `SELECT * FROM users WHERE username ILIKE $1`; const query = `SELECT id, username, picture, email, is_verified FROM users WHERE username ILIKE $1`;
const result = await client.query(query, [`%${username}%`]); const result = await client.query(query, [`%${username}%`]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("no user found with username " + username, 404); logger.write("no user found with username " + username, 404);
client.end(); client.end();
return res.status(404).json({error: "User Not Found"}); return res.status(404).json({ error: "User Not Found" });
} }
logger.write("successfully found user with username " + username, 200); logger.write("successfully found user with username " + username, 200);

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

@ -1,4 +1,4 @@
import {body, param} from "express-validator"; import {body, param, query} from "express-validator";
import {getClient} from "../utils/database.js"; import {getClient} from "../utils/database.js";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
@ -37,6 +37,10 @@ export const UserRequest = {
username: param("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(), username: param("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(),
} }
export const UserSearch = {
username: query("username").notEmpty().isAlphanumeric("fr-FR", {ignore: " _-"}).trim(),
}
export async function doEmailExists(req, res, next) { export async function doEmailExists(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;

9
backend/app/routes/user.route.js

@ -19,7 +19,8 @@ import {
isOwner, isOwner,
UserLogin, UserLogin,
User, User,
UserRequest UserRequest,
UserSearch
} from "../middlewares/user.middleware.js"; } from "../middlewares/user.middleware.js";
import validator from "../middlewares/error.middleware.js"; import validator from "../middlewares/error.middleware.js";
import {isTokenValid} from "../middlewares/jwt.middleware.js"; import {isTokenValid} from "../middlewares/jwt.middleware.js";
@ -35,6 +36,9 @@ router.post("/", [profileUpload.single("profile"), addLogger, UserRegister.email
// LOGIN A USER // LOGIN A USER
router.post("/login", [addLogger, UserLogin.username, UserLogin.password, validator], login) router.post("/login", [addLogger, UserLogin.username, UserLogin.password, validator], login)
// SEARCH BY USERNAME
router.get("/search", [addLogger, isTokenValid, UserSearch.username, validator], searchByUsername);
// GET USER BY ID // GET USER BY ID
router.get("/:id", [addLogger, isTokenValid, User.id, validator], getById) router.get("/:id", [addLogger, isTokenValid, User.id, validator], getById)
@ -59,7 +63,4 @@ router.get("/:id/channel/subscribed", [addLogger, isTokenValid, User.id, Channel
// VERIFY EMAIL // VERIFY EMAIL
router.post("/verify-email", [addLogger, validator], verifyEmail); router.post("/verify-email", [addLogger, validator], verifyEmail);
// SEARCH BY USERNAME
router.get("/search", [addLogger, isTokenValid], searchByUsername);
export default router; export default router;

159
backend/logs/access.log

@ -7465,3 +7465,162 @@
[2025-08-17 10:59:30.938] [undefined] GET(/:id): Invalid token with status 401 [2025-08-17 10:59:30.938] [undefined] GET(/:id): Invalid token with status 401
[2025-08-17 11:04:33.569] [undefined] GET(/:id): Invalid token with status 401 [2025-08-17 11:04:33.569] [undefined] GET(/:id): Invalid token with status 401
[2025-08-17 11:07:46.550] [undefined] POST(/): Invalid token with status 401 [2025-08-17 11:07:46.550] [undefined] POST(/): Invalid token with status 401
[2025-08-18 16:43:00.578] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-18 16:43:05.605] [undefined] POST(/): successfully registered with status 200
[2025-08-18 16:43:20.564] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token 4563d
[2025-08-18 16:43:20.576] [undefined] POST(/verify-email): successfully verified email for sachaguerin.sg@gmail.com with status 200
[2025-08-18 16:55:58.836] [undefined] POST(/login): try to login with username 'astria'
[2025-08-18 16:55:58.891] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-18 16:56:01.446] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 16:56:01.449] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-08-18 16:56:01.453] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-18 16:56:01.458] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
[2025-08-18 16:56:01.467] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-08-18 16:56:13.681] [undefined] POST(/): try to create new channel with owner 1 and name astria
[2025-08-18 16:56:13.684] [undefined] POST(/): Successfully created new channel with name astria with status 200
[2025-08-18 16:56:13.698] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 16:56:13.701] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 16:56:15.038] [undefined] GET(/:id): try to get channel with id 1
[2025-08-18 16:56:15.049] [undefined] GET(/:id/stats): try to get stats
[2025-08-18 16:56:15.053] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-18 16:56:15.063] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-18 16:57:54.267] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 16:57:54.270] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 16:58:07.426] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-18 16:58:17.830] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-18 16:59:16.058] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-18 17:02:02.699] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:02:02.704] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:02:14.301] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-18 17:04:10.822] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:04:10.825] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:04:18.423] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-18 17:17:56.386] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:17:56.389] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:17:59.561] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-18 17:19:04.710] [undefined] GET(/search): try to search user by username as
[2025-08-18 17:19:04.715] [undefined] GET(/search): successfully found user with username as with status 200
[2025-08-18 17:37:33.042] [undefined] GET(/search): try to search user by username as
[2025-08-18 17:37:33.048] [undefined] GET(/search): successfully found user with username as with status 200
[2025-08-18 17:39:59.796] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:39:59.800] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:40:00.890] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:40:00.893] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:40:01.717] [undefined] GET(/search): try to search user by username as
[2025-08-18 17:40:01.721] [undefined] GET(/search): successfully found user with username as with status 200
[2025-08-18 17:40:02.564] [undefined] GET(/search): try to search user by username ast
[2025-08-18 17:40:02.567] [undefined] GET(/search): successfully found user with username ast with status 200
[2025-08-18 17:40:02.713] [undefined] GET(/search): try to search user by username astr
[2025-08-18 17:40:02.717] [undefined] GET(/search): successfully found user with username astr with status 200
[2025-08-18 17:40:03.133] [undefined] GET(/search): try to search user by username astri
[2025-08-18 17:40:03.137] [undefined] GET(/search): successfully found user with username astri with status 200
[2025-08-18 17:44:43.499] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:44:43.502] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:44:46.281] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:44:46.285] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:44:50.193] [undefined] GET(/search): try to search user by username as
[2025-08-18 17:44:50.196] [undefined] GET(/search): successfully found user with username as with status 200
[2025-08-18 17:44:50.396] [undefined] GET(/search): try to search user by username ast
[2025-08-18 17:44:50.400] [undefined] GET(/search): successfully found user with username ast with status 200
[2025-08-18 17:44:50.485] [undefined] GET(/search): try to search user by username astr
[2025-08-18 17:44:50.489] [undefined] GET(/search): successfully found user with username astr with status 200
[2025-08-18 17:44:50.666] [undefined] GET(/search): try to search user by username astri
[2025-08-18 17:44:50.671] [undefined] GET(/search): successfully found user with username astri with status 200
[2025-08-18 17:45:11.427] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:45:11.431] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:45:12.426] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:45:12.429] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:46:02.236] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:46:02.239] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:46:04.407] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:46:04.411] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:46:06.643] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:46:06.646] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:46:10.552] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:46:10.557] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:46:32.809] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:46:32.813] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:46:34.704] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:46:34.708] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:48:10.395] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:48:10.399] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:48:12.431] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:48:12.434] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:48:15.528] [undefined] GET(/search): try to search user by username as
[2025-08-18 17:48:15.532] [undefined] GET(/search): successfully found user with username as with status 200
[2025-08-18 17:48:15.707] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:48:15.710] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:48:47.100] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:48:47.104] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:48:48.333] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:48:48.337] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:49:01.390] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:49:01.394] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:49:04.296] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:49:04.300] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:49:37.858] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:49:37.862] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:49:38.853] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:49:38.857] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:49:57.908] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:49:57.912] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:49:59.214] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:49:59.218] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:50:47.064] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:50:47.068] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:50:48.062] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:50:48.065] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:50:57.499] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:50:57.503] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:50:58.394] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:50:58.398] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:51:33.403] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:51:33.407] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:51:34.352] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:51:34.356] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:51:41.430] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:51:41.433] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:51:42.519] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:51:42.523] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:51:51.265] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:51:51.268] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:51:53.003] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:51:53.006] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:52:09.869] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:52:09.872] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:52:11.174] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:52:11.178] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:52:18.702] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:52:18.706] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:52:19.655] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:52:19.659] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:53:23.241] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:53:23.245] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:53:24.233] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:53:24.236] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:53:40.448] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:53:40.452] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:53:42.339] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:53:42.343] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:56:02.368] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:56:02.371] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:56:04.593] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:56:04.596] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:56:25.107] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:56:25.110] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:56:52.324] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:56:52.328] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:56:53.719] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:56:53.723] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:57:15.686] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:57:15.690] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:57:16.727] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:57:16.730] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:58:20.073] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:58:20.077] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:58:20.844] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:58:20.848] [undefined] GET(/search): successfully found user with username a with status 200
[2025-08-18 17:59:00.905] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-18 17:59:00.909] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-08-18 17:59:02.119] [undefined] GET(/search): try to search user by username a
[2025-08-18 17:59:02.123] [undefined] GET(/search): successfully found user with username a with status 200

1
docker-compose.yaml

@ -4,6 +4,7 @@ services:
build: build:
context: ./backend context: ./backend
dockerfile: Dockerfile dockerfile: Dockerfile
network: host
container_name: resit_backend container_name: resit_backend
ports: ports:
- "8000:8000" - "8000:8000"

7
frontend/package-lock.json

@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
"chartjs": "^0.3.24",
"react": "^19.1.0", "react": "^19.1.0",
"react-chartjs-2": "^5.3.0", "react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
@ -1815,12 +1814,6 @@
"pnpm": ">=8" "pnpm": ">=8"
} }
}, },
"node_modules/chartjs": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/chartjs/-/chartjs-0.3.24.tgz",
"integrity": "sha512-h6G9qcDqmFYnSWqjWCzQMeOLiypS+pM6Fq2Rj7LPty8Kjx5yHonwwJ7oEHImZpQ2u9Pu36XGYfardvvBiQVrhg==",
"license": "MIT"
},
"node_modules/chownr": { "node_modules/chownr": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",

18
frontend/src/components/UserCard.jsx

@ -1,13 +1,19 @@
export default function UserCard({user, doShowControls}) { export default function UserCard({ user, onSubmit, doShowControls, control }) {
return ( return (
<div className="flex items-center justify-between p-2 border-b border-gray-700"> <div className="glassmorphism flex items-center justify-between p-2">
<span className="text-white">{user.username}</span> <div className="flex items-center gap-4">
<img src={user.picture || '/default-profile.png'} alt={`${user.username}'s profile`} className="w-10 h-10 rounded-full mr-2" />
<span className="text-white text-lg font-montserrat font-semibold">{user.username}</span>
</div>
{doShowControls && ( {doShowControls && (
<button className="text-red-500" onClick={() => {}}>
Supprimer <div className="flex items-center gap-2" onClick={(e) => {
</button> onSubmit(user);
}}>
{control}
</div>
)} )}
</div> </div>
); );

49
frontend/src/pages/AddVideo.jsx

@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
import Tag from "../components/Tag.jsx"; import Tag from "../components/Tag.jsx";
import { getChannel, searchByUsername } from "../services/user.service.js"; import { getChannel, searchByUsername } from "../services/user.service.js";
import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js"; import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js";
import { on } from "node:events"; import UserCard from "../components/UserCard.jsx";
export default function AddVideo() { export default function AddVideo() {
@ -112,6 +112,7 @@ export default function AddVideo() {
// Call the API to search for users // Call the API to search for users
searchByUsername(searchUser, token, addAlert) searchByUsername(searchUser, token, addAlert)
.then((results) => { .then((results) => {
console.log(results);
setSearchResults(results); setSearchResults(results);
}) })
.catch((error) => { .catch((error) => {
@ -122,6 +123,14 @@ export default function AddVideo() {
} }
} }
const onAuthorizedUserAdd = (user) => {
setAuthorizedUsers([...authorizedUsers, user]);
};
const onAuthorizedUserRemove = (user) => {
setAuthorizedUsers(authorizedUsers.filter((u) => u.id !== user.id));
};
return ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <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} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
@ -208,26 +217,38 @@ export default function AddVideo() {
value={searchUser} value={searchUser}
onChange={(e) => setSearchUser(e.target.value)} onChange={(e) => setSearchUser(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' && searchUser.trim() !== "") { onUserSearch(e)
onUserSearch(e);
}
}} }}
/> />
<div> <div>
{searchResults && searchResults.map((user, index) => (
<UserCard
user={user}
onSubmit={onAuthorizedUserAdd}
doShowControls={true}
key={index}
control={
<button type="button" className="bg-primary text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
ajouter
</button>
}
/>
))}
</div> </div>
</div> </div>
<div className="max-h-40 overflow-y-auto"> <div className="max-h-40 overflow-y-auto">
{authorizedUsers.map((user, index) => ( {authorizedUsers.map((user, index) => (
<div key={index} className="flex items-center justify-between p-2 border-b border-gray-700"> <UserCard
<span className="text-white">{user}</span> user={user}
<button onSubmit={onAuthorizedUserRemove}
className="text-red-500" doShowControls={true}
onClick={() => setAuthorizedUsers(authorizedUsers.filter((u) => u !== user))} key={index}
> control={
Supprimer <button type="button" className="bg-red-500 text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
</button> supprimer
</div> </button>
}
/>
))} ))}
</div> </div>
</div> </div>

10
frontend/src/services/playlist.service.js

@ -1,7 +1,7 @@
export async function createPlaylist(body, token, addAlert) { export async function createPlaylist(body, token, addAlert) {
try { try {
const response = await fetch(`https://localhost/api/playlists`, { const response = await fetch(`/api/playlists`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -24,7 +24,7 @@ export async function createPlaylist(body, token, addAlert) {
export async function addToPlaylist(id, body, token, addAlert) { export async function addToPlaylist(id, body, token, addAlert) {
try { try {
const response = await fetch(`https://localhost/api/playlists/${id}`, { const response = await fetch(`/api/playlists/${id}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -48,7 +48,7 @@ export async function addToPlaylist(id, body, token, addAlert) {
export async function getPlaylistById(id, token, addAlert) { export async function getPlaylistById(id, token, addAlert) {
try { try {
const response = await fetch(`https://localhost/api/playlists/${id}`, { const response = await fetch(`/api/playlists/${id}`, {
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
@ -67,7 +67,7 @@ export async function getPlaylistById(id, token, addAlert) {
export async function deletePlaylist(id, token, addAlert) { export async function deletePlaylist(id, token, addAlert) {
try { try {
const response = await fetch(`https://localhost/api/playlists/${id}`, { const response = await fetch(`/api/playlists/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
@ -86,7 +86,7 @@ export async function deletePlaylist(id, token, addAlert) {
export async function deleteVideo(playlistId, videoId, token, addAlert) { export async function deleteVideo(playlistId, videoId, token, addAlert) {
try { try {
const response = await fetch(`https://localhost/api/playlists/${playlistId}/video/${videoId}`, { const response = await fetch(`/api/playlists/${playlistId}/video/${videoId}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`

14
frontend/src/services/user.service.js

@ -126,19 +126,27 @@ export async function verifyEmail(email, token, addAlert) {
export async function searchByUsername(username, token, addAlert) { export async function searchByUsername(username, token, addAlert) {
try { try {
const response = await fetch(`/api/users/search?username=${encodeURIComponent(username)}`, { // Validate input before sending request
if (!username || username.trim().length === 0) {
addAlert('error', "Le nom d'utilisateur ne peut pas être vide.");
return;
}
const response = await fetch(`/api/users/search?username=${encodeURIComponent(username.trim())}`, {
method: "GET", method: "GET",
headers: { headers: {
"Authorization": `Bearer ${token}`, "Authorization": `Bearer ${token}`,
} }
}); });
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to search user"); const errorData = await response.json().catch(() => ({}));
console.log(errorData);
throw new Error(errorData.error || "Failed to search user");
} }
const data = await response.json(); const data = await response.json();
return data; return data;
} catch (error) { } catch (error) {
console.error("Error searching user by username:", error); console.error("Error searching user by username:", error);
addAlert('error', "Erreur lors de la recherche de l'utilisateur."); addAlert('error', error.message || "Erreur lors de la recherche de l'utilisateur.");
} }
} }
Loading…
Cancel
Save