From 0a9ff8eaa4f3677be97de7359c5900103b543ebf Mon Sep 17 00:00:00 2001 From: Astri4-4 Date: Sun, 17 Aug 2025 12:32:59 +0000 Subject: [PATCH 1/3] PTN DE SYSTEMD DE MERDE --- backend/app/controllers/user.controller.js | 20 ++++++ backend/app/routes/user.route.js | 6 +- backend/logs/access.log | 38 ++++++++++ frontend/src/components/UserCard.jsx | 14 ++++ frontend/src/pages/AddVideo.jsx | 83 +++++++++++++++++++++- frontend/src/services/user.service.js | 19 +++++ 6 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/UserCard.jsx diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 56f2708..13eab1d 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -446,4 +446,24 @@ export async function isSubscribed(req, res) { client.end(); return res.status(200).json({subscribed: false}); } +} + +export async function searchByUsername(req, res) { + const username = req.query.username; + const client = await getClient(); + const logger = req.body.logger; + logger.action("try to search user by username " + username); + + const query = `SELECT * FROM users WHERE username ILIKE $1`; + const result = await client.query(query, [`%${username}%`]); + + if (result.rows.length === 0) { + logger.write("no user found with username " + username, 404); + client.end(); + return res.status(404).json({error: "User Not Found"}); + } + + logger.write("successfully found user with username " + username, 200); + client.end(); + res.status(200).json(result.rows); } \ No newline at end of file diff --git a/backend/app/routes/user.route.js b/backend/app/routes/user.route.js index bb2e095..e405495 100644 --- a/backend/app/routes/user.route.js +++ b/backend/app/routes/user.route.js @@ -8,7 +8,8 @@ import { deleteUser, getChannel, getHistory, isSubscribed, - verifyEmail + verifyEmail, + searchByUsername } from "../controllers/user.controller.js"; import { UserRegister, @@ -58,4 +59,7 @@ router.get("/:id/channel/subscribed", [addLogger, isTokenValid, User.id, Channel // VERIFY EMAIL router.post("/verify-email", [addLogger, validator], verifyEmail); +// SEARCH BY USERNAME +router.get("/search", [addLogger, isTokenValid], searchByUsername); + export default router; \ No newline at end of file diff --git a/backend/logs/access.log b/backend/logs/access.log index c893ed1..f3e3a42 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -7427,3 +7427,41 @@ [2025-08-17 10:06:21.133] [undefined] GET(/:id/history): try to retrieve history of user 2 [2025-08-17 10:06:21.138] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404 [2025-08-17 10:06:21.148] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-17 10:24:55.268] [undefined] POST(/): try to create new channel with owner 2 and name astria +[2025-08-17 10:24:55.271] [undefined] POST(/): Successfully created new channel with name astria with status 200 +[2025-08-17 10:24:55.288] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:24:55.292] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:24:56.366] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-17 10:24:56.380] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-17 10:24:56.386] [undefined] GET(/:id/stats): try to get stats +[2025-08-17 10:24:56.395] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-17 10:25:00.825] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:25:00.830] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:28:34.803] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:28:34.807] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:29:51.209] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:29:51.214] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:32:59.876] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:32:59.880] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:33:09.144] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:33:09.148] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:43:59.502] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:43:59.507] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:45:08.860] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:45:08.863] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:45:12.454] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-08-17 10:46:31.555] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-08-17 10:53:32.094] [undefined] POST(/): Invalid token with status 401 +[2025-08-17 10:53:43.691] [undefined] POST(/): Invalid token with status 401 +[2025-08-17 10:54:16.538] [undefined] POST(/): Invalid token with status 401 +[2025-08-17 10:55:19.631] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:55:19.636] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:55:19.640] [undefined] GET(/:id/history): try to retrieve history of user 2 +[2025-08-17 10:55:19.645] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404 +[2025-08-17 10:55:19.656] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-17 10:55:25.476] [undefined] POST(/): Playlist created with id 3 with status 200 +[2025-08-17 10:55:25.497] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-17 10:55:57.559] [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:07:46.550] [undefined] POST(/): Invalid token with status 401 diff --git a/frontend/src/components/UserCard.jsx b/frontend/src/components/UserCard.jsx new file mode 100644 index 0000000..b64e7fe --- /dev/null +++ b/frontend/src/components/UserCard.jsx @@ -0,0 +1,14 @@ + + +export default function UserCard({user, doShowControls}) { + return ( +
+ {user.username} + {doShowControls && ( + + )} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/AddVideo.jsx b/frontend/src/pages/AddVideo.jsx index 560b9ec..0fa8132 100644 --- a/frontend/src/pages/AddVideo.jsx +++ b/frontend/src/pages/AddVideo.jsx @@ -1,8 +1,9 @@ import Navbar from "../components/Navbar.jsx"; import { useEffect, useState } from "react"; import Tag from "../components/Tag.jsx"; -import { getChannel } from "../services/user.service.js"; +import { getChannel, searchByUsername } from "../services/user.service.js"; import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js"; +import { on } from "node:events"; export default function AddVideo() { @@ -14,10 +15,13 @@ export default function AddVideo() { const [videoTitle, setVideoTitle] = useState(""); const [videoDescription, setVideoDescription] = useState(""); const [videoTags, setVideoTags] = useState([]); - const [visibility, setVisibility] = useState("public"); + const [visibility, setVisibility] = useState("private"); const [videoThumbnail, setVideoThumbnail] = useState(null); const [videoFile, setVideoFile] = useState(null); const [channel, setChannel] = useState(null); + const [searchUser, setSearchUser] = useState(""); + const [authorizedUsers, setAuthorizedUsers] = useState([]); + const [searchResults, setSearchResults] = useState([]); const [alerts, setAlerts] = useState([]); useEffect(() => { @@ -102,6 +106,22 @@ export default function AddVideo() { setAlerts(alerts.filter(alert => alert !== alertToRemove)); }; + const onUserSearch = (e) => { + const searchUser = e.target.value; + if (searchUser.trim() !== "") { + // Call the API to search for users + searchByUsername(searchUser, token, addAlert) + .then((results) => { + setSearchResults(results); + }) + .catch((error) => { + addAlert('error', 'Erreur lors de la recherche d\'utilisateurs.'); + }); + } else { + setSearchResults([]); + } + } + return (
@@ -166,6 +186,65 @@ export default function AddVideo() { + + + + + + + + + { + visibility == "private" && ( +
+ +
+ setSearchUser(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && searchUser.trim() !== "") { + onUserSearch(e); + } + }} + /> +
+ +
+
+
+ {authorizedUsers.map((user, index) => ( +
+ {user} + +
+ ))} +
+
+ ) + } + + + + + + + + + + + + Date: Mon, 18 Aug 2025 18:30:45 +0000 Subject: [PATCH 2/3] Fixed playlist --- backend/app/controllers/user.controller.js | 60 ++++---- backend/app/middlewares/user.middleware.js | 6 +- backend/app/routes/user.route.js | 9 +- backend/logs/access.log | 159 +++++++++++++++++++++ docker-compose.yaml | 1 + frontend/package-lock.json | 7 - frontend/src/components/UserCard.jsx | 18 ++- frontend/src/pages/AddVideo.jsx | 49 +++++-- frontend/src/services/playlist.service.js | 10 +- frontend/src/services/user.service.js | 14 +- 10 files changed, 263 insertions(+), 70 deletions(-) diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 13eab1d..97e9c95 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -1,11 +1,11 @@ import bcrypt from "bcrypt"; -import {getClient} from "../utils/database.js"; +import { getClient } from "../utils/database.js"; import jwt from "jsonwebtoken"; -import path, {dirname} from "path"; +import path, { dirname } from "path"; import fs from "fs"; -import {fileURLToPath} from "url"; +import { fileURLToPath } from "url"; import crypto from "crypto"; -import {sendEmail} from "../utils/mail.js"; +import { sendEmail } from "../utils/mail.js"; export async function register(req, res) { try { @@ -49,7 +49,7 @@ export async function register(req, res) { 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 htmlMessage = ` @@ -126,7 +126,7 @@ export async function register(req, res) { console.log("Successfully registered"); client.end(); logger.write("successfully registered", 200); - res.status(200).send({user: user}); + res.status(200).send({ user: user }); } catch (err) { console.log(err); @@ -187,7 +187,7 @@ export async function login(req, res) { if (!userInBase) { logger.write("failed to login", 401) - res.status(401).json({error: "Invalid credentials"}); + res.status(401).json({ error: "Invalid credentials" }); return } @@ -195,7 +195,7 @@ export async function login(req, res) { if (!isPasswordValid) { logger.write("failed to login", 401) - res.status(401).json({error: "Invalid credentials"}); + res.status(401).json({ error: "Invalid credentials" }); return } @@ -215,7 +215,7 @@ export async function login(req, res) { logger.write("Successfully logged in", 200); 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]) { logger.write("failed to retrieve user " + id + " because it doesn't exist", 404); client.end() - res.status(404).json({error: "Not Found"}); + res.status(404).json({ error: "Not Found" }); return } logger.write("successfully retrieved user " + id, 200); 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]) { logger.write("failed to retrieve user " + username + " because it doesn't exist", 404); client.end() - res.status(404).json({error: "Not Found"}); + res.status(404).json({ error: "Not Found" }); return } logger.write("successfully retrieved user " + username, 200); 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) { @@ -283,7 +283,7 @@ export async function update(req, res) { if (emailResult.rows[0]) { logger.write("failed to update because email is already used", 400) 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]) { logger.write("failed to update because username is already used", 400) 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", user.username + "." + profilePicture.split(".").pop()), (err) => { - if (err) { - logger.write("failed to update profile picture", 500); - console.error("Error renaming file:", err); - throw err; - } - }); + if (err) { + logger.write("failed to update profile picture", 500); + console.error("Error renaming file:", err); + throw err; + } + }); 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`; @@ -334,7 +334,7 @@ export async function update(req, res) { } catch (err) { console.log(err); 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]); logger.write("successfully deleted user " + id); client.end(); - res.status(200).json({message: 'User deleted'}); + res.status(200).json({ message: 'User deleted' }); } export async function getChannel(req, res) { @@ -363,13 +363,13 @@ export async function getChannel(req, res) { if (!result.rows[0]) { logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404); client.end(); - res.status(404).json({error: "Channel Not Found"}); + res.status(404).json({ error: "Channel Not Found" }); return; } logger.write("successfully retrieved channel of user " + id, 200); client.end(); - res.status(200).json({channel: result.rows[0]}); + res.status(200).json({ channel: result.rows[0] }); } export async function getHistory(req, res) { @@ -414,7 +414,7 @@ export async function getHistory(req, res) { if (!result.rows[0]) { 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(); return; } @@ -440,11 +440,11 @@ export async function isSubscribed(req, res) { if (result.rows[0]) { logger.write(`user ${userId} is subscribed to channel ${channelId}`, 200); client.end(); - return res.status(200).json({subscribed: true}); + return res.status(200).json({ subscribed: true }); } else { logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200); 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; 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}%`]); if (result.rows.length === 0) { logger.write("no user found with username " + username, 404); 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); diff --git a/backend/app/middlewares/user.middleware.js b/backend/app/middlewares/user.middleware.js index 303179d..d75cb93 100644 --- a/backend/app/middlewares/user.middleware.js +++ b/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 jwt from "jsonwebtoken"; @@ -37,6 +37,10 @@ export const UserRequest = { 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) { const client = await getClient(); const logger = req.body.logger; diff --git a/backend/app/routes/user.route.js b/backend/app/routes/user.route.js index e405495..5ec8765 100644 --- a/backend/app/routes/user.route.js +++ b/backend/app/routes/user.route.js @@ -19,7 +19,8 @@ import { isOwner, UserLogin, User, - UserRequest + UserRequest, + UserSearch } from "../middlewares/user.middleware.js"; import validator from "../middlewares/error.middleware.js"; import {isTokenValid} from "../middlewares/jwt.middleware.js"; @@ -35,6 +36,9 @@ router.post("/", [profileUpload.single("profile"), addLogger, UserRegister.email // LOGIN A USER 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 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 router.post("/verify-email", [addLogger, validator], verifyEmail); -// SEARCH BY USERNAME -router.get("/search", [addLogger, isTokenValid], searchByUsername); - export default router; \ No newline at end of file diff --git a/backend/logs/access.log b/backend/logs/access.log index f3e3a42..707ab93 100644 --- a/backend/logs/access.log +++ b/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 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-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 diff --git a/docker-compose.yaml b/docker-compose.yaml index 42be633..66550d8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,6 +4,7 @@ services: build: context: ./backend dockerfile: Dockerfile + network: host container_name: resit_backend ports: - "8000:8000" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index adf4da8..a2cbbf8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@tailwindcss/vite": "^4.1.11", "chart.js": "^4.5.0", - "chartjs": "^0.3.24", "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", @@ -1815,12 +1814,6 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", diff --git a/frontend/src/components/UserCard.jsx b/frontend/src/components/UserCard.jsx index b64e7fe..6daba1b 100644 --- a/frontend/src/components/UserCard.jsx +++ b/frontend/src/components/UserCard.jsx @@ -1,13 +1,19 @@ -export default function UserCard({user, doShowControls}) { +export default function UserCard({ user, onSubmit, doShowControls, control }) { return ( -
- {user.username} +
+
+ {`${user.username}'s + {user.username} +
{doShowControls && ( - + +
{ + onSubmit(user); + }}> + {control} +
)}
); diff --git a/frontend/src/pages/AddVideo.jsx b/frontend/src/pages/AddVideo.jsx index 0fa8132..06916a1 100644 --- a/frontend/src/pages/AddVideo.jsx +++ b/frontend/src/pages/AddVideo.jsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import Tag from "../components/Tag.jsx"; import { getChannel, searchByUsername } from "../services/user.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() { @@ -112,6 +112,7 @@ export default function AddVideo() { // Call the API to search for users searchByUsername(searchUser, token, addAlert) .then((results) => { + console.log(results); setSearchResults(results); }) .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 (
@@ -208,26 +217,38 @@ export default function AddVideo() { value={searchUser} onChange={(e) => setSearchUser(e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter' && searchUser.trim() !== "") { - onUserSearch(e); - } + onUserSearch(e) }} />
- + {searchResults && searchResults.map((user, index) => ( + + ajouter + + } + /> + ))}
{authorizedUsers.map((user, index) => ( -
- {user} - -
+ + supprimer + + } + /> ))}
diff --git a/frontend/src/services/playlist.service.js b/frontend/src/services/playlist.service.js index 8a30711..e19e401 100644 --- a/frontend/src/services/playlist.service.js +++ b/frontend/src/services/playlist.service.js @@ -1,7 +1,7 @@ export async function createPlaylist(body, token, addAlert) { try { - const response = await fetch(`https://localhost/api/playlists`, { + const response = await fetch(`/api/playlists`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -24,7 +24,7 @@ export async function createPlaylist(body, token, addAlert) { export async function addToPlaylist(id, body, token, addAlert) { try { - const response = await fetch(`https://localhost/api/playlists/${id}`, { + const response = await fetch(`/api/playlists/${id}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -48,7 +48,7 @@ export async function addToPlaylist(id, body, token, addAlert) { export async function getPlaylistById(id, token, addAlert) { try { - const response = await fetch(`https://localhost/api/playlists/${id}`, { + const response = await fetch(`/api/playlists/${id}`, { headers: { 'Authorization': `Bearer ${token}` } @@ -67,7 +67,7 @@ export async function getPlaylistById(id, token, addAlert) { export async function deletePlaylist(id, token, addAlert) { try { - const response = await fetch(`https://localhost/api/playlists/${id}`, { + const response = await fetch(`/api/playlists/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` @@ -86,7 +86,7 @@ export async function deletePlaylist(id, token, addAlert) { export async function deleteVideo(playlistId, videoId, token, addAlert) { try { - const response = await fetch(`https://localhost/api/playlists/${playlistId}/video/${videoId}`, { + const response = await fetch(`/api/playlists/${playlistId}/video/${videoId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` diff --git a/frontend/src/services/user.service.js b/frontend/src/services/user.service.js index 1dec4c6..81f4630 100644 --- a/frontend/src/services/user.service.js +++ b/frontend/src/services/user.service.js @@ -126,19 +126,27 @@ export async function verifyEmail(email, token, addAlert) { export async function searchByUsername(username, token, addAlert) { 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", headers: { "Authorization": `Bearer ${token}`, } }); 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(); return data; } catch (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."); } } \ No newline at end of file From da619172b6c076928003992ab0940a45e32cdea1 Mon Sep 17 00:00:00 2001 From: astria Date: Wed, 20 Aug 2025 21:36:10 +0200 Subject: [PATCH 3/3] Finish features/visibility --- backend/app/controllers/channel.controller.js | 2 +- .../controllers/recommendation.controller.js | 7 +- backend/app/controllers/search.controller.js | 2 +- backend/app/controllers/video.controller.js | 146 +++- backend/app/middlewares/video.middleware.js | 84 ++ backend/app/routes/video.route.js | 19 +- backend/app/utils/database.js | 7 + backend/logs/access.log | 733 ++++++++++++++++++ frontend/src/components/UserCard.jsx | 2 +- frontend/src/pages/AddVideo.jsx | 27 +- frontend/src/pages/ManageVideo.jsx | 120 ++- frontend/src/pages/Video.jsx | 2 +- frontend/src/services/video.service.js | 33 +- 13 files changed, 1162 insertions(+), 22 deletions(-) diff --git a/backend/app/controllers/channel.controller.js b/backend/app/controllers/channel.controller.js index 3595517..0cced34 100644 --- a/backend/app/controllers/channel.controller.js +++ b/backend/app/controllers/channel.controller.js @@ -64,7 +64,7 @@ export async function getById(req, res) { LEFT JOIN public.history h ON h.video = videos.id LEFT JOIN public.likes ON likes.video = videos.id LEFT JOIN public.comments c ON c.video = videos.id - WHERE videos.channel = $1 + WHERE videos.channel = $1 AND videos.visibility = 'public' GROUP BY videos.id, channels.name, channels.description, users.username, users.picture `; diff --git a/backend/app/controllers/recommendation.controller.js b/backend/app/controllers/recommendation.controller.js index f549246..d7f043c 100644 --- a/backend/app/controllers/recommendation.controller.js +++ b/backend/app/controllers/recommendation.controller.js @@ -43,14 +43,15 @@ export async function getTrendingVideos(req, res) { // GET 10 VIDEOS WITH THE MOST LIKES AND COMMENTS let client = await getClient(); let queryTrendingVideos = ` - SELECT v.id, v.title, v.description, v.release_date, v.thumbnail, - COUNT(DISTINCT l.id) AS like_count, COUNT(DISTINCT c.id) AS comment_count + SELECT v.id, v.title, v.description, v.release_date, v.thumbnail, v.visibility, + COUNT(DISTINCT l.id) AS like_count, COUNT(DISTINCT c.id) AS comment_count FROM videos v LEFT JOIN likes l ON v.id = l.video LEFT JOIN comments c ON v.id = c.video + WHERE v.visibility = 'public' GROUP BY v.id ORDER BY like_count DESC, comment_count DESC - LIMIT 10; + LIMIT 10 `; let result = await client.query(queryTrendingVideos); const trendingVideos = result.rows; diff --git a/backend/app/controllers/search.controller.js b/backend/app/controllers/search.controller.js index 1f94b80..e5c369c 100644 --- a/backend/app/controllers/search.controller.js +++ b/backend/app/controllers/search.controller.js @@ -28,7 +28,7 @@ export async function search(req, res) { JOIN public.channels c on v.channel = c.id JOIN public.users u on c.owner = u.id LEFT JOIN public.history h on h.video = v.id - WHERE v.title ILIKE $1 + WHERE v.title ILIKE $1 AND v.visibility = 'public' GROUP BY v.id, v.title, v.thumbnail, v.description, v.channel, v.visibility, v.file, v.slug, v.format, v.release_date, c.id, c.owner, c.description, c.name, u.picture OFFSET $2 diff --git a/backend/app/controllers/video.controller.js b/backend/app/controllers/video.controller.js index 2125818..c1e5f35 100644 --- a/backend/app/controllers/video.controller.js +++ b/backend/app/controllers/video.controller.js @@ -4,12 +4,13 @@ import * as fs from "node:fs"; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import jwt from "jsonwebtoken"; -import { query } from "express-validator"; +import { sendEmail } from "../utils/mail.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export async function upload(req, res) { + // HANDLE VIDEO FILE const fileBuffer = req.file.buffer; let isGenerate = false; while (isGenerate === false) { @@ -45,6 +46,7 @@ export async function upload(req, res) { visibility: req.body.visibility, } + // HANDLE VIDEO DETAILS const logger = req.body.logger; logger.write("try to upload video"); const releaseDate = new Date(Date.now()).toISOString(); @@ -52,6 +54,108 @@ export async function upload(req, res) { const query = `INSERT INTO videos (title, thumbnail, description, channel, visibility, file, slug, format, release_date) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`; const idResult = await client.query(query, [video.title, 'null', video.description, video.channel, video.visibility, video.file, video.slug, video.format, releaseDate]); const id = idResult.rows[0].id; + + console.log(req.body.visibility, req.body.authorizedUsers) + + // HANDLE AUTHORIZED USERS + if (req.body.visibility === "private" && req.body.authorizedUsers) { + let authorizedUsers = req.body.authorizedUsers; + + // Parse if still a string (safety check) + if (typeof authorizedUsers === 'string') { + try { + authorizedUsers = JSON.parse(authorizedUsers); + } catch (error) { + console.error("Failed to parse authorizedUsers:", error); + authorizedUsers = []; + } + } + + if (Array.isArray(authorizedUsers) && authorizedUsers.length > 0) { + for (let i = 0; i < authorizedUsers.length; i++) { + const user = authorizedUsers[i]; + console.log("authorized user", user); + const query = `INSERT INTO video_authorized_users (video_id, user_id) VALUES ($1, $2)`; + + // SEND EMAIL TO AUTHORIZED USER + const emailQuery = `SELECT email FROM users WHERE id = $1`; + const emailResult = await client.query(emailQuery, [user]); + const email = emailResult.rows[0].email; + + const textMessage = `Vous êtes autorisé à visionner une vidéo privée. ${process.env.FRONTEND_URL}/videos/${id}`; + const htmlMessage = ` + + + + + + Accès à une vidéo privée - Freetube + + +
+ +
+

+ 🔐 Vidéo privée partagée avec vous! +

+
+ + +
+

+ Bonjour! 👋 +

+ +

+ Vous avez été autorisé(e) à visionner une vidéo privée sur Freetube! +

+ +

+ On vous a partagé une vidéo privée avec vous. Cliquez sur le bouton ci-dessous pour la regarder : +

+ + +
+

+ ${video.title} +

+

+ ${video.description} +

+ +
+ +

+ 🔒 Cette vidéo est privée et n'est accessible qu'aux personnes autorisées. +

+ +

+ Si vous pensez avoir reçu cet e-mail par erreur, vous pouvez l'ignorer. +

+
+ + +
+

+ © 2025 Freetube. Tous droits réservés. +

+
+
+ + + `; + + sendEmail(email, "Invitation à visionner une vidéo privée", textMessage, htmlMessage); + + await client.query(query, [id, user]); + } + } + } + logger.write("successfully uploaded video", 200); await client.end() res.status(200).json({ "message": "Successfully uploaded video", "id": id }); @@ -124,6 +228,11 @@ export async function getById(req, res) { const tagsResult = await client.query(tagsQuery, [id]); video.tags = tagsResult.rows.map(tag => tag.name); + // GET AUTHORIZED USERS + const authorizedUsersQuery = `SELECT u.id, u.username, u.picture FROM users u JOIN video_authorized_users vp ON u.id = vp.user_id WHERE vp.video_id = $1`; + const authorizedUsersResult = await client.query(authorizedUsersQuery, [id]); + video.authorizedUsers = authorizedUsersResult.rows; + logger.write("successfully get video " + id, 200); client.end() res.status(200).json(video); @@ -489,4 +598,39 @@ export async function addViews(req, res) { logger.write("successfully added views for video " + id, 200); await client.end(); res.status(200).json({ "message": "Successfully added views" }); +} + +export async function updateAuthorizedUsers(req, res) { + const id = req.params.id; + const logger = req.body.logger; + logger.action("try to update authorized users for video " + id); + + const { authorizedUsers } = req.body; + + console.log(authorizedUsers); + + const client = await getClient(); + + try { + // Remove all existing authorized users + const deleteQuery = `DELETE FROM video_authorized_users WHERE video_id = $1`; + console.log(`DELETE FROM video_authorized_users WHERE video_id = ${id}`); + await client.query(deleteQuery, [id]); + + // Add new authorized users + const insertQuery = `INSERT INTO video_authorized_users (video_id, user_id) VALUES ($1, $2)`; + for (let i = 0; i < authorizedUsers.length; i++) { + const user = authorizedUsers[i]; + console.log(`INSERT INTO video_authorized_users (video_id, user_id) VALUES (${id}, ${user})`) + await client.query(insertQuery, [id, user]); + } + + logger.write("successfully updated authorized users for video " + id, 200); + res.status(200).json({ "message": "Successfully updated authorized users" }); + } catch (error) { + logger.write("Error updating authorized users: " + error.message, 500); + res.status(500).json({ error: "Internal server error" }); + } finally { + await client.end(); + } } \ No newline at end of file diff --git a/backend/app/middlewares/video.middleware.js b/backend/app/middlewares/video.middleware.js index 9f68afa..fb01628 100644 --- a/backend/app/middlewares/video.middleware.js +++ b/backend/app/middlewares/video.middleware.js @@ -23,6 +23,21 @@ export const VideoCreate = { description: body("description").optional({values: "falsy"}).trim(), channel: body("channel").notEmpty().isNumeric().trim(), visibility: body("visibility").notEmpty().isAlpha().trim(), + authorizedUsers: body("authorizedUsers") + .optional({values: "falsy"}) + .customSanitizer((value) => { + // Parse JSON string back to array + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch (error) { + return []; + } + } + return value; + }) + .isArray() + .withMessage("Authorized users must be an array"), } export const VideoThumbnail = { @@ -79,3 +94,72 @@ export async function doVideoExistsParam(req, res, next) { next() } + +export async function doAuthorizedUserExists(req, res, next) { + const logger = req.body.logger; + let authorizedUsers = req.body.authorizedUsers; + + // Parse JSON string if needed + if (typeof authorizedUsers === 'string') { + try { + authorizedUsers = JSON.parse(authorizedUsers); + } catch (error) { + logger.write("failed because authorizedUsers is not valid JSON", 400); + res.status(400).json({error: "Invalid authorized users format"}); + return; + } + } + + if (authorizedUsers && Array.isArray(authorizedUsers) && authorizedUsers.length > 0) { + const client = await getClient(); + + for (const userId of authorizedUsers) { + const query = `SELECT id FROM users WHERE id = $1`; + const result = await client.query(query, [userId]); + const foundUser = result.rows[0]; + if (!foundUser) { + logger.write("failed because authorized user not found", 404); + res.status(404).json({error: "Not Found"}); + return + } + } + } + next() +} + +export async function hasAccess(req, res, next) { + const logger = req.body.logger; + const videoId = req.params.id; + + const client = await getClient(); + const videoQuery = "SELECT visibility FROM videos WHERE id = $1"; + const videoResult = await client.query(videoQuery, [videoId]); + const video = videoResult.rows[0]; + + console.log(video); + + if (video.visibility === 'private') { + const token = req.headers.authorization?.split(" ")[1]; + + if (!req.headers.authorization || !token) { + logger.write("failed because no token provided", 401); + res.status(401).json({error: "Unauthorized"}); + return; + } + + const claims = jwt.decode(token); + const userId = claims.id; + const query = `SELECT * FROM video_authorized_users WHERE video_id = $1 AND user_id = $2`; + const result = await client.query(query, [videoId, userId]); + const isAuthorized = result.rows.length > 0; + + if (!isAuthorized) { + logger.write("failed because user is not authorized", 403); + res.status(403).json({error: "Not authorized"}); + return; + } + } + + + next(); +} diff --git a/backend/app/routes/video.route.js b/backend/app/routes/video.route.js index fd71e5c..36d5a41 100644 --- a/backend/app/routes/video.route.js +++ b/backend/app/routes/video.route.js @@ -10,7 +10,7 @@ import { uploadThumbnail, updateVideo, toggleLike, - addTags, getSimilarVideos, addViews, getLikesPerDay + addTags, getSimilarVideos, addViews, getLikesPerDay, updateAuthorizedUsers } from "../controllers/video.controller.js"; import { doVideoExists, @@ -18,7 +18,9 @@ import { isOwner, Video, VideoCreate, - VideoThumbnail + VideoThumbnail, + doAuthorizedUserExists, + hasAccess } from "../middlewares/video.middleware.js"; import {Channel, doChannelExistBody, doChannelExists} from "../middlewares/channel.middleware.js"; import {thumbnailUpload, videoUpload} from "../middlewares/file.middleware.js"; @@ -27,19 +29,20 @@ import validator from "../middlewares/error.middleware.js"; const router = Router(); // UPLOAD VIDEO -router.post("/", [videoUpload.single('file'), addLogger, isTokenValid, VideoCreate.title, VideoCreate.description, VideoCreate.visibility, VideoCreate.channel, validator, doChannelExistBody, isOwner], upload); +router.post("/", [videoUpload.single('file'), addLogger, isTokenValid, VideoCreate.title, VideoCreate.description, VideoCreate.visibility, VideoCreate.authorizedUsers, VideoCreate.channel, validator, doChannelExistBody, isOwner, doAuthorizedUserExists], upload); // UPLOAD/UPDATE THUMBNAIL router.post("/thumbnail", [thumbnailUpload.single('file'), addLogger, isTokenValid, VideoThumbnail.video, Video.channel, validator, doChannelExistBody, isOwner, doVideoExists], uploadThumbnail ) // GET BY ID -router.get("/:id", [addLogger, Video.id, validator, doVideoExistsParam], getById); +router.get("/:id", [addLogger, Video.id, validator, doVideoExistsParam, hasAccess], getById); // GET BY CHANNEL -router.get("/channel/:id", [addLogger, isTokenValid, Channel.id, validator, doChannelExists], getByChannel); +router.get("/channel/:id", [addLogger, isTokenValid, Channel.id, validator, doChannelExists, hasAccess], getByChannel); // UPDATE VIDEO DATA router.put("/:id", [addLogger, isTokenValid, Video.id, VideoCreate.title, VideoCreate.description, VideoCreate.visibility, VideoCreate.channel, validator, doVideoExistsParam, doChannelExistBody, isOwner], update); + // UPDATE VIDEO router.put("/:id/video", [videoUpload.single("file"), addLogger, isTokenValid, Video.id, Video.channel, validator, doVideoExistsParam, doChannelExistBody, isOwner ], updateVideo); @@ -53,13 +56,15 @@ router.get("/:id/like", [addLogger, isTokenValid, Video.id, validator, doVideoEx router.put("/:id/tags", [addLogger, isTokenValid, Video.id, Video.tags, validator, doVideoExistsParam, isOwner], addTags); // GET SIMILAR VIDEOS -router.get("/:id/similar", [addLogger, Video.id, validator, doVideoExistsParam], getSimilarVideos); +router.get("/:id/similar", [addLogger, Video.id, validator, doVideoExistsParam, hasAccess], getSimilarVideos); // ADD VIEWS -router.get("/:id/views", [addLogger, isTokenValid, Video.id, validator, doVideoExistsParam], addViews); +router.get("/:id/views", [addLogger, isTokenValid, Video.id, validator, doVideoExistsParam, hasAccess], addViews); // GET LIKE PER DAY router.get("/:id/likes/day", [addLogger, isTokenValid, Video.id, validator, doVideoExistsParam], getLikesPerDay); +// UPDATE AUTHORIZED USERS +router.put("/:id/authorized-users", [addLogger, isTokenValid, Video.id, VideoCreate.authorizedUsers, validator, doVideoExistsParam, isOwner, doAuthorizedUserExists], updateAuthorizedUsers); export default router; \ No newline at end of file diff --git a/backend/app/utils/database.js b/backend/app/utils/database.js index 57ce344..315335f 100644 --- a/backend/app/utils/database.js +++ b/backend/app/utils/database.js @@ -119,6 +119,13 @@ export async function initDb() { )`; await client.query(query); + query = `CREATE TABLE IF NOT EXISTS video_authorized_users ( + id SERIAL PRIMARY KEY, + video_id INTEGER NOT NULL REFERENCES videos(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE + )`; + await client.query(query); + } catch (e) { console.error("Error initializing database:", e); } diff --git a/backend/logs/access.log b/backend/logs/access.log index 707ab93..2b06713 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -7624,3 +7624,736 @@ [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 +[2025-08-19 16:50:31.703] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com +[2025-08-19 16:50:32.709] [undefined] POST(/): successfully registered with status 200 +[2025-08-19 16:50:44.851] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token 8f9c5 +[2025-08-19 16:50:44.861] [undefined] POST(/verify-email): successfully verified email for sachaguerin.sg@gmail.com with status 200 +[2025-08-19 16:50:53.604] [undefined] POST(/login): failed due to invalid values with status 400 +[2025-08-19 16:51:09.256] [undefined] POST(/login): failed due to invalid values with status 400 +[2025-08-19 16:51:19.455] [undefined] POST(/login): try to login with username 'astria' +[2025-08-19 16:51:19.508] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-19 16:51:23.577] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 16:51:23.581] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-19 16:51:23.584] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-08-19 16:51:23.588] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404 +[2025-08-19 16:51:23.598] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-19 16:51:27.795] [undefined] POST(/): try to create new channel with owner 1 and name astria +[2025-08-19 16:51:27.798] [undefined] POST(/): Successfully created new channel with name astria with status 200 +[2025-08-19 16:51:27.811] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 16:51:27.814] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 16:51:30.510] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-19 16:51:30.526] [undefined] GET(/:id/stats): try to get stats +[2025-08-19 16:51:30.531] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-19 16:51:30.539] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-19 16:51:33.254] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 16:51:33.258] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 16:51:40.046] [undefined] GET(/search): try to search user by username a +[2025-08-19 16:51:40.050] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 16:51:40.285] [undefined] GET(/search): try to search user by username as +[2025-08-19 16:51:40.289] [undefined] GET(/search): successfully found user with username as with status 200 +[2025-08-19 16:51:40.470] [undefined] GET(/search): try to search user by username ast +[2025-08-19 16:51:40.474] [undefined] GET(/search): successfully found user with username ast with status 200 +[2025-08-19 16:51:40.573] [undefined] GET(/search): try to search user by username astr +[2025-08-19 16:51:40.577] [undefined] GET(/search): successfully found user with username astr with status 200 +[2025-08-19 16:51:40.705] [undefined] GET(/search): try to search user by username astri +[2025-08-19 16:51:40.708] [undefined] GET(/search): successfully found user with username astri with status 200 +[2025-08-19 17:46:55.010] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:46:55.015] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:47:00.226] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:47:00.229] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:47:00.465] [undefined] GET(/search): try to search user by username as +[2025-08-19 17:47:00.469] [undefined] GET(/search): successfully found user with username as with status 200 +[2025-08-19 17:47:00.646] [undefined] GET(/search): try to search user by username ast +[2025-08-19 17:47:00.649] [undefined] GET(/search): successfully found user with username ast with status 200 +[2025-08-19 17:47:00.741] [undefined] GET(/search): try to search user by username astr +[2025-08-19 17:47:00.744] [undefined] GET(/search): successfully found user with username astr with status 200 +[2025-08-19 17:47:00.880] [undefined] GET(/search): try to search user by username astri +[2025-08-19 17:47:00.883] [undefined] GET(/search): successfully found user with username astri with status 200 +[2025-08-19 17:47:31.274] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:47:31.277] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:47:32.638] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:47:32.642] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:47:59.477] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:47:59.480] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:48:00.360] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:48:00.363] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:48:10.809] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:48:10.813] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:48:11.729] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:48:11.732] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:48:28.248] [undefined] GET(/search): try to search user by username as +[2025-08-19 17:48:28.252] [undefined] GET(/search): successfully found user with username as with status 200 +[2025-08-19 17:48:50.152] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:48:50.156] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:48:51.115] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:48:51.119] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:48:55.267] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:48:55.270] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:51:38.016] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:51:38.020] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:51:39.279] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:51:39.282] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:51:40.911] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:51:40.915] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:51:53.055] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:51:53.059] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:51:53.750] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:51:53.754] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:51:54.033] [undefined] GET(/search): try to search user by username as +[2025-08-19 17:51:54.036] [undefined] GET(/search): successfully found user with username as with status 200 +[2025-08-19 17:51:56.357] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:51:56.360] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:52:06.577] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:52:06.582] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:52:07.598] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:52:07.602] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:52:09.890] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:52:09.893] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 17:52:50.407] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 17:52:50.411] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 17:52:52.482] [undefined] GET(/search): try to search user by username a +[2025-08-19 17:52:52.486] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 18:09:16.637] [undefined] GET(/search): try to search user by username a +[2025-08-19 18:09:16.640] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 18:09:52.553] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:09:52.559] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:09:52.636] [undefined] POST(/thumbnail): try to add thumbnail to video 1 +[2025-08-19 18:09:52.640] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:09:52.656] [undefined] PUT(/:id/tags): try to add tags to video 1 +[2025-08-19 18:09:52.666] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200 +[2025-08-19 18:10:20.468] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:10:20.473] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:10:20.560] [undefined] POST(/thumbnail): try to add thumbnail to video 2 +[2025-08-19 18:10:20.563] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:10:20.582] [undefined] PUT(/:id/tags): try to add tags to video 2 +[2025-08-19 18:10:20.590] [undefined] PUT(/:id/tags): Tag sa already exists for video 2 with status 200 +[2025-08-19 18:10:20.594] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200 +[2025-08-19 18:10:34.430] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 18:10:34.434] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 18:10:39.472] [undefined] GET(/search): try to search user by username a +[2025-08-19 18:10:39.475] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 18:10:48.026] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:10:48.036] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:10:48.238] [undefined] POST(/thumbnail): try to add thumbnail to video 3 +[2025-08-19 18:10:48.243] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:10:48.260] [undefined] PUT(/:id/tags): try to add tags to video 3 +[2025-08-19 18:10:48.268] [undefined] PUT(/:id/tags): Tag sa already exists for video 3 with status 200 +[2025-08-19 18:10:48.272] [undefined] PUT(/:id/tags): successfully added tags to video 3 with status 200 +[2025-08-19 18:11:37.923] [undefined] POST(/): failed due to invalid values with status 400 +[2025-08-19 18:11:49.155] [undefined] POST(/): failed due to invalid values with status 400 +[2025-08-19 18:13:15.887] [undefined] POST(/): failed due to invalid values with status 400 +[2025-08-19 18:15:41.313] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:15:41.318] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:15:41.402] [undefined] POST(/thumbnail): try to add thumbnail to video 4 +[2025-08-19 18:15:41.405] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:15:41.429] [undefined] PUT(/:id/tags): try to add tags to video 4 +[2025-08-19 18:15:41.437] [undefined] PUT(/:id/tags): Tag sa already exists for video 4 with status 200 +[2025-08-19 18:15:41.441] [undefined] PUT(/:id/tags): successfully added tags to video 4 with status 200 +[2025-08-19 18:16:20.905] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:16:20.933] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:16:21.025] [undefined] POST(/thumbnail): try to add thumbnail to video 5 +[2025-08-19 18:16:21.029] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:16:21.054] [undefined] PUT(/:id/tags): try to add tags to video 5 +[2025-08-19 18:16:21.063] [undefined] PUT(/:id/tags): Tag sa already exists for video 5 with status 200 +[2025-08-19 18:16:21.068] [undefined] PUT(/:id/tags): successfully added tags to video 5 with status 200 +[2025-08-19 18:17:10.822] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:17:10.834] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:17:10.916] [undefined] POST(/thumbnail): try to add thumbnail to video 6 +[2025-08-19 18:17:10.920] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:17:10.943] [undefined] PUT(/:id/tags): try to add tags to video 6 +[2025-08-19 18:17:10.951] [undefined] PUT(/:id/tags): Tag sa already exists for video 6 with status 200 +[2025-08-19 18:17:10.955] [undefined] PUT(/:id/tags): successfully added tags to video 6 with status 200 +[2025-08-19 18:18:05.738] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:18:05.744] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:18:05.824] [undefined] POST(/thumbnail): try to add thumbnail to video 7 +[2025-08-19 18:18:05.827] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:18:05.850] [undefined] PUT(/:id/tags): try to add tags to video 7 +[2025-08-19 18:18:05.858] [undefined] PUT(/:id/tags): Tag sa already exists for video 7 with status 200 +[2025-08-19 18:18:05.863] [undefined] PUT(/:id/tags): successfully added tags to video 7 with status 200 +[2025-08-19 18:18:48.664] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:18:48.671] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:18:48.756] [undefined] POST(/thumbnail): try to add thumbnail to video 8 +[2025-08-19 18:18:48.759] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:18:48.785] [undefined] PUT(/:id/tags): try to add tags to video 8 +[2025-08-19 18:18:48.793] [undefined] PUT(/:id/tags): Tag sa already exists for video 8 with status 200 +[2025-08-19 18:18:48.798] [undefined] PUT(/:id/tags): successfully added tags to video 8 with status 200 +[2025-08-19 18:20:25.872] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:20:25.877] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:20:25.959] [undefined] POST(/thumbnail): try to add thumbnail to video 9 +[2025-08-19 18:20:25.962] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:20:25.984] [undefined] PUT(/:id/tags): try to add tags to video 9 +[2025-08-19 18:20:25.992] [undefined] PUT(/:id/tags): Tag sa already exists for video 9 with status 200 +[2025-08-19 18:20:25.996] [undefined] PUT(/:id/tags): successfully added tags to video 9 with status 200 +[2025-08-19 18:21:11.157] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:21:11.164] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:21:11.249] [undefined] POST(/thumbnail): try to add thumbnail to video 10 +[2025-08-19 18:21:11.252] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:21:11.280] [undefined] PUT(/:id/tags): try to add tags to video 10 +[2025-08-19 18:21:11.288] [undefined] PUT(/:id/tags): Tag sa already exists for video 10 with status 200 +[2025-08-19 18:21:11.292] [undefined] PUT(/:id/tags): successfully added tags to video 10 with status 200 +[2025-08-19 18:30:49.537] [undefined] GET(/:id): try to get video 1 +[2025-08-19 18:30:49.547] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-19 18:30:49.565] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-08-19 18:30:49.575] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-08-19 18:30:49.591] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-08-19 18:30:49.610] [undefined] GET(/:id/views): try to add views for video 1 +[2025-08-19 18:30:49.625] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-08-19 18:31:42.637] [undefined] GET(/:id): failed because video not found with status 404 +[2025-08-19 18:31:42.648] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-19 18:31:42.653] [undefined] GET(/:id/similar): failed because video not found with status 404 +[2025-08-19 18:31:42.664] [undefined] GET(/:id/views): failed because video not found with status 404 +[2025-08-19 18:31:47.656] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 18:31:47.660] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 18:31:47.664] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-19 18:31:47.669] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404 +[2025-08-19 18:31:47.679] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-19 18:32:24.652] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-19 18:32:24.662] [undefined] GET(/:id/stats): try to get stats +[2025-08-19 18:32:24.667] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-19 18:32:24.675] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-19 18:32:25.767] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 18:32:25.771] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 18:33:12.054] [undefined] GET(/search): try to search user by username a +[2025-08-19 18:33:12.058] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-19 18:33:25.364] [undefined] POST(/): try to upload video with status undefined +[2025-08-19 18:33:25.372] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-19 18:33:25.453] [undefined] POST(/thumbnail): try to add thumbnail to video 11 +[2025-08-19 18:33:25.456] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-19 18:33:25.474] [undefined] PUT(/:id/tags): try to add tags to video 11 +[2025-08-19 18:33:25.488] [undefined] PUT(/:id/tags): successfully added tags to video 11 with status 200 +[2025-08-19 18:33:30.850] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:33:30.860] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-19 18:33:30.867] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:33:30.876] [undefined] GET(/:id/similar): try to get similar videos for video 11 +[2025-08-19 18:33:30.888] [undefined] GET(/:id/similar): successfully get similar videos for video 11 with status 200 +[2025-08-19 18:33:30.904] [undefined] GET(/:id/views): try to add views for video 11 +[2025-08-19 18:33:30.918] [undefined] GET(/:id/views): successfully added views for video 11 with status 200 +[2025-08-19 18:33:38.304] [undefined] GET(/:id/like): try to toggle like on video 11 +[2025-08-19 18:33:38.312] [undefined] GET(/:id/like): no likes found adding likes for video 11 with status 200 +[2025-08-19 18:33:39.877] [undefined] POST(/:id): Video added to playlist with id 1 with status 200 +[2025-08-19 18:33:41.409] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1 +[2025-08-19 18:33:41.417] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200 +[2025-08-19 18:33:46.037] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-19 18:33:46.041] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-19 18:33:46.048] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-19 18:33:46.052] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-19 18:33:46.056] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-19 18:33:46.647] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-19 18:33:46.657] [undefined] GET(/:id/stats): try to get stats +[2025-08-19 18:33:46.662] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-19 18:33:46.672] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-19 18:33:47.714] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:33:47.717] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:33:47.729] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:33:47.734] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:35:42.063] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:35:42.068] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:35:42.079] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:35:42.084] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:36:03.707] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:36:03.710] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:36:03.721] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:36:03.727] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:37:10.825] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:37:10.829] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:37:10.839] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:37:14.577] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:37:14.580] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:37:14.592] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:38:02.999] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:38:03.002] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:38:03.034] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:38:03.041] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:38:21.751] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:38:21.754] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:38:21.766] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:38:21.773] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:39:10.233] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:39:10.236] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:39:10.247] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:39:10.253] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:39:21.826] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:39:21.829] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:39:21.841] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:39:21.847] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:41:07.602] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:41:07.606] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:41:07.618] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:41:07.624] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-19 18:41:28.238] [undefined] GET(/:id): try to get video 11 +[2025-08-19 18:41:28.242] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-19 18:41:28.253] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-19 18:41:28.259] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 17:38:45.740] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-20 17:38:45.743] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-20 17:38:45.749] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-20 17:38:45.755] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-20 17:38:45.764] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 17:38:49.831] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-20 17:38:49.843] [undefined] GET(/:id/stats): try to get stats +[2025-08-20 17:38:49.847] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-20 17:38:49.855] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-20 17:38:51.435] [undefined] GET(/:id): try to get video 11 +[2025-08-20 17:38:51.437] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 17:38:51.448] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 17:38:51.454] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 17:45:30.859] [undefined] GET(/:id): try to get video 11 +[2025-08-20 17:45:30.865] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 17:45:30.874] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 17:45:30.878] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 17:46:49.844] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 17:46:49.847] [undefined] GET(/:id): try to get video 11 +[2025-08-20 17:46:49.858] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 17:46:49.865] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 17:46:51.040] [undefined] GET(/search): try to search user by username a +[2025-08-20 17:46:51.044] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 17:47:56.099] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 17:47:56.103] [undefined] GET(/:id): try to get video 11 +[2025-08-20 17:47:56.114] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 17:47:56.119] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 17:47:58.471] [undefined] GET(/search): try to search user by username a +[2025-08-20 17:47:58.475] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 17:48:02.406] [undefined] GET(/search): try to search user by username as +[2025-08-20 17:48:02.409] [undefined] GET(/search): successfully found user with username as with status 200 +[2025-08-20 17:48:02.648] [undefined] GET(/search): try to search user by username a +[2025-08-20 17:48:02.651] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:02:01.655] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:02:01.658] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:02:01.669] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:02:01.676] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:02:03.971] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:02:03.977] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:02:09.242] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:02:09.255] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:02:09.261] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:02:09.275] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:02:40.257] [undefined] PUT(/:id/tags): try to add tags to video 11 +[2025-08-20 18:02:40.268] [undefined] PUT(/:id/tags): Tag CS2 already exists for video 11 with status 200 +[2025-08-20 18:02:40.272] [undefined] PUT(/:id/tags): Tag CSGO already exists for video 11 with status 200 +[2025-08-20 18:02:40.277] [undefined] PUT(/:id/tags): Tag Squeezie already exists for video 11 with status 200 +[2025-08-20 18:02:40.281] [undefined] PUT(/:id/tags): successfully added tags to video 11 with status 200 +[2025-08-20 18:02:44.040] [undefined] PUT(/:id/tags): try to add tags to video 11 +[2025-08-20 18:02:44.049] [undefined] PUT(/:id/tags): Tag CS2 already exists for video 11 with status 200 +[2025-08-20 18:02:44.054] [undefined] PUT(/:id/tags): Tag CSGO already exists for video 11 with status 200 +[2025-08-20 18:02:44.059] [undefined] PUT(/:id/tags): Tag Squeezie already exists for video 11 with status 200 +[2025-08-20 18:02:44.066] [undefined] PUT(/:id/tags): successfully added tags to video 11 with status 200 +[2025-08-20 18:35:54.696] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:35:54.699] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:35:54.710] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:35:54.716] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:36:12.459] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:36:12.472] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:36:12.479] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:36:12.486] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:36:18.031] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:36:18.036] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:36:18.048] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:36:18.055] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:37:05.587] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:37:05.590] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:37:05.602] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:37:05.608] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:37:22.780] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:37:22.784] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:37:22.795] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:37:22.800] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:37:42.008] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:37:42.021] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:37:42.028] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:37:42.035] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:37:59.051] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:37:59.058] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:37:59.067] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:37:59.072] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:38:20.881] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:38:20.894] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:38:20.899] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:38:20.907] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:38:24.327] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:38:24.338] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:38:24.341] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 18:38:24.351] [undefined] GET(/:id/similar): try to get similar videos for video 11 +[2025-08-20 18:38:24.364] [undefined] GET(/:id/similar): successfully get similar videos for video 11 with status 200 +[2025-08-20 18:38:24.391] [undefined] GET(/:id/views): try to add views for video 11 +[2025-08-20 18:38:24.400] [undefined] GET(/:id/views): successfully added views for video 11 with status 200 +[2025-08-20 18:38:31.467] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:38:31.471] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:38:31.483] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:38:31.489] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:38:52.804] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:38:52.808] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:38:52.820] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:38:52.826] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:38:53.675] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:38:53.680] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:39:14.264] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:39:14.267] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:39:14.279] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:39:14.285] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:39:34.063] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:39:34.074] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:39:34.081] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:39:34.088] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:39:44.733] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:39:44.737] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:39:44.749] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:39:44.756] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:40:25.085] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:40:25.090] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:40:25.099] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:40:25.103] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:41:11.226] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:41:11.230] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:41:11.242] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:41:11.247] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:41:23.586] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:41:23.589] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:41:23.601] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:41:23.607] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:41:49.036] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:41:49.040] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:41:49.053] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:41:49.059] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:41:57.988] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:41:57.991] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:41:58.002] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:41:58.009] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:42:16.719] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:42:16.722] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:42:16.734] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:42:16.740] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:42:28.808] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:42:28.813] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:42:28.823] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:42:28.827] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:43:05.588] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:43:05.591] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:43:05.609] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:43:05.616] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:43:59.851] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:43:59.862] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:43:59.870] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:43:59.877] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:44:01.475] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:44:01.484] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:44:03.494] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:44:03.498] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:44:03.508] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:44:03.512] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:44:05.260] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:44:05.264] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:44:26.525] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:44:26.529] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:44:26.541] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:44:26.547] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:44:28.395] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:44:28.401] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:44:42.183] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:44:42.189] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:44:42.199] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:44:42.205] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:44:44.521] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:44:44.526] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:45:18.672] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:45:18.677] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:45:18.707] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:45:18.713] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:45:19.810] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:45:19.816] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:45:21.387] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:45:21.396] [undefined] PUT(/:id/authorized-users): Error updating authorized users: null value in column "user_id" of relation "video_authorized_users" violates not-null constraint with status 500 +[2025-08-20 18:47:45.836] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:47:45.842] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:47:49.720] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:47:49.729] [undefined] PUT(/:id/authorized-users): Error updating authorized users: null value in column "user_id" of relation "video_authorized_users" violates not-null constraint with status 500 +[2025-08-20 18:48:11.345] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:48:11.348] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:48:11.361] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:48:11.367] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:48:12.079] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:48:12.083] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:48:13.768] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:48:13.776] [undefined] PUT(/:id/authorized-users): Error updating authorized users: null value in column "user_id" of relation "video_authorized_users" violates not-null constraint with status 500 +[2025-08-20 18:49:02.787] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:02.797] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:49:05.674] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:49:05.678] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:49:10.645] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:10.657] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:49:13.914] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:49:13.918] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:49:13.930] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:49:13.936] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:49:18.941] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:18.949] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:49:19.663] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:49:19.667] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:49:24.635] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:24.650] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:49:37.611] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:49:37.614] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:49:37.627] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:49:37.634] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:49:39.397] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:39.405] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:49:40.021] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:49:40.025] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:49:41.573] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:41.582] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:49:58.339] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:49:58.346] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:49:58.355] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:49:58.361] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:49:59.991] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:49:59.999] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:50:00.647] [undefined] GET(/search): try to search user by username a +[2025-08-20 18:50:00.650] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 18:50:02.527] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 18:50:02.554] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 18:51:15.624] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:51:15.628] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:51:15.640] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:51:15.645] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:51:17.660] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-20 18:51:17.664] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-20 18:51:17.668] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-20 18:51:17.673] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-20 18:51:17.684] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 18:51:18.298] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-20 18:51:18.309] [undefined] GET(/:id/stats): try to get stats +[2025-08-20 18:51:18.314] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-20 18:51:18.321] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-20 18:51:19.216] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-20 18:51:19.219] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-20 18:51:29.478] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 18:51:29.485] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 18:51:29.566] [undefined] POST(/thumbnail): try to add thumbnail to video 12 +[2025-08-20 18:51:29.573] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 18:51:29.594] [undefined] PUT(/:id/tags): try to add tags to video 12 +[2025-08-20 18:51:29.604] [undefined] PUT(/:id/tags): successfully added tags to video 12 with status 200 +[2025-08-20 18:51:30.916] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-20 18:51:30.920] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-20 18:51:30.922] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-20 18:51:30.926] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-20 18:51:30.934] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 18:51:32.526] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-20 18:51:32.537] [undefined] GET(/:id/stats): try to get stats +[2025-08-20 18:51:32.541] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-20 18:51:32.550] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-20 18:51:33.588] [undefined] GET(/:id): try to get video 12 +[2025-08-20 18:51:33.592] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 18:51:33.603] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 18:51:33.608] [undefined] GET(/:id): successfully get video 12 with status 200 +[2025-08-20 18:51:40.247] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:51:40.259] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 18:51:40.263] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:51:40.277] [undefined] GET(/:id/similar): try to get similar videos for video 11 +[2025-08-20 18:51:40.289] [undefined] GET(/:id/similar): successfully get similar videos for video 11 with status 200 +[2025-08-20 18:51:40.315] [undefined] GET(/:id/views): try to add views for video 11 +[2025-08-20 18:51:40.325] [undefined] GET(/:id/views): successfully added views for video 11 with status 200 +[2025-08-20 18:51:44.448] [undefined] GET(/:id/like): try to toggle like on video 11 +[2025-08-20 18:51:44.458] [undefined] GET(/:id/like): likes found, removing like for video 11 with status 200 +[2025-08-20 18:51:44.912] [undefined] GET(/:id/like): try to toggle like on video 11 +[2025-08-20 18:51:44.921] [undefined] GET(/:id/like): no likes found adding likes for video 11 with status 200 +[2025-08-20 18:53:00.124] [undefined] POST(/): try to register a user with username: sacha and email: thelolshow974@gmail.com +[2025-08-20 18:53:01.068] [undefined] POST(/): successfully registered with status 200 +[2025-08-20 18:53:27.277] [undefined] POST(/verify-email): try to verify email for thelolshow974@gmail.com with token 44046 +[2025-08-20 18:53:27.287] [undefined] POST(/verify-email): successfully verified email for thelolshow974@gmail.com with status 200 +[2025-08-20 18:53:37.170] [undefined] POST(/login): failed due to invalid values with status 400 +[2025-08-20 18:54:00.492] [undefined] POST(/login): failed due to invalid values with status 400 +[2025-08-20 18:54:05.877] [undefined] POST(/login): failed due to invalid values with status 400 +[2025-08-20 18:54:12.816] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-20 18:54:12.866] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-20 18:54:22.722] [undefined] GET(/:id): try to get video 11 +[2025-08-20 18:54:22.734] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 18:54:22.740] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 18:54:22.751] [undefined] GET(/:id/similar): try to get similar videos for video 11 +[2025-08-20 18:54:22.765] [undefined] GET(/:id/similar): successfully get similar videos for video 11 with status 200 +[2025-08-20 18:54:22.783] [undefined] GET(/:id/views): try to add views for video 11 +[2025-08-20 18:54:22.796] [undefined] GET(/:id/views): successfully added views for video 11 with status 200 +[2025-08-20 18:59:18.332] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 18:59:23.959] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:00:08.055] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:00:43.038] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:03:20.114] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:03:30.944] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:03:46.729] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:03:46.733] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:03:46.776] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:04:41.036] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:04:41.040] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:00.712] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:00.716] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:05:00.763] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:05:06.854] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:05:06.858] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:06.889] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:05:08.248] [undefined] GET(/:id): try to get video 12 +[2025-08-20 19:05:08.251] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:08.262] [undefined] GET(/:id): successfully get video 12 with status 200 +[2025-08-20 19:05:08.279] [undefined] GET(/:id/similar): try to get similar videos for video 12 +[2025-08-20 19:05:08.292] [undefined] GET(/:id/similar): successfully get similar videos for video 12 with status 200 +[2025-08-20 19:05:08.314] [undefined] GET(/:id/views): try to add views for video 12 +[2025-08-20 19:05:08.328] [undefined] GET(/:id/views): successfully added views for video 12 with status 200 +[2025-08-20 19:05:10.522] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:05:10.526] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:10.557] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:05:21.932] [undefined] POST(/login): try to login with username 'astria' +[2025-08-20 19:05:21.984] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-20 19:05:23.932] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-20 19:05:23.936] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-20 19:05:23.941] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-20 19:05:23.946] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-20 19:05:23.956] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 19:05:24.823] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-20 19:05:24.835] [undefined] GET(/:id/stats): try to get stats +[2025-08-20 19:05:24.840] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-20 19:05:24.849] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-20 19:05:26.215] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 19:05:26.227] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:05:26.230] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 19:05:26.240] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:05:28.154] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:05:28.160] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:05:28.330] [undefined] GET(/search): try to search user by username sa +[2025-08-20 19:05:28.335] [undefined] GET(/search): successfully found user with username sa with status 200 +[2025-08-20 19:05:38.951] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-20 19:05:39.003] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-20 19:05:40.150] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:05:40.154] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:40.190] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:05:52.482] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:05:52.486] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:05:52.522] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:06:24.972] [undefined] GET(/:id): failed because user is not authorized with status 403 +[2025-08-20 19:06:24.975] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:06:25.009] [undefined] GET(/:id/views): failed because user is not authorized with status 403 +[2025-08-20 19:06:45.416] [undefined] POST(/login): try to login with username 'astria' +[2025-08-20 19:06:45.467] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-20 19:06:46.977] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-20 19:06:46.980] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-20 19:06:46.983] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-20 19:06:46.988] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-20 19:06:47.004] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-20 19:06:48.280] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-20 19:06:48.293] [undefined] GET(/:id/stats): try to get stats +[2025-08-20 19:06:48.298] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-20 19:06:48.306] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-20 19:06:49.874] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 19:06:49.889] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:06:49.893] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 19:06:49.904] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:06:51.790] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:06:51.793] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:08:02.813] [undefined] GET(/search): try to search user by username sacha +[2025-08-20 19:08:02.817] [undefined] GET(/search): successfully found user with username sacha with status 200 +[2025-08-20 19:09:14.120] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 19:09:14.132] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:09:14.135] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 19:09:14.147] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:09:15.375] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:09:15.379] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:09:17.129] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 19:09:17.139] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 19:09:19.758] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 19:09:19.768] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:09:19.772] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 19:09:19.782] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:09:27.005] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:09:27.009] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:09:28.823] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 19:09:28.831] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 19:09:51.055] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 19:09:51.067] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:09:51.071] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 19:09:51.083] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:11:19.136] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:11:19.140] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:11:21.265] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 19:11:21.275] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 19:13:19.229] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-20 19:13:19.241] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:13:19.244] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-20 19:13:19.255] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:13:20.095] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:13:20.100] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:13:20.110] [undefined] GET(/search): try to search user by username sq +[2025-08-20 19:13:20.116] [undefined] GET(/search): no user found with username sq with status 404 +[2025-08-20 19:13:21.488] [undefined] GET(/search): try to search user by username s +[2025-08-20 19:13:21.491] [undefined] GET(/search): successfully found user with username s with status 200 +[2025-08-20 19:13:21.707] [undefined] GET(/search): try to search user by username sa +[2025-08-20 19:13:21.711] [undefined] GET(/search): successfully found user with username sa with status 200 +[2025-08-20 19:13:22.932] [undefined] PUT(/:id/authorized-users): try to update authorized users for video 11 +[2025-08-20 19:13:22.942] [undefined] PUT(/:id/authorized-users): successfully updated authorized users for video 11 with status 200 +[2025-08-20 19:13:37.018] [undefined] POST(/login): try to login with username 'sacha' +[2025-08-20 19:13:37.069] [undefined] POST(/login): Successfully logged in with status 200 +[2025-08-20 19:13:38.705] [undefined] GET(/:id): try to get video 11 +[2025-08-20 19:13:38.709] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:13:38.719] [undefined] GET(/:id): successfully get video 11 with status 200 +[2025-08-20 19:13:38.738] [undefined] GET(/:id/similar): failed because no token provided with status 401 +[2025-08-20 19:13:38.762] [undefined] GET(/:id/views): try to add views for video 11 +[2025-08-20 19:13:38.772] [undefined] GET(/:id/views): successfully added views for video 11 with status 200 +[2025-08-20 19:19:28.651] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-20 19:19:28.663] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-20 19:19:28.681] [undefined] GET(/:id/channel/subscribed): check if user 2 is subscribed to channel 1 +[2025-08-20 19:19:28.688] [undefined] GET(/:id/channel/subscribed): user 2 is not subscribed to channel 1 with status 200 +[2025-08-20 19:25:39.807] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-20 19:25:39.813] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404 +[2025-08-20 19:25:39.818] [undefined] GET(/:id/history): try to retrieve history of user 2 +[2025-08-20 19:25:39.824] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200 +[2025-08-20 19:25:39.834] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-20 19:25:44.286] [undefined] POST(/): try to create new channel with owner 2 and name sacha +[2025-08-20 19:25:44.289] [undefined] POST(/): Successfully created new channel with name sacha with status 200 +[2025-08-20 19:25:44.300] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-20 19:25:44.304] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-20 19:25:45.115] [undefined] GET(/:id): try to get channel with id 2 +[2025-08-20 19:25:45.127] [undefined] GET(/:id/stats): try to get stats +[2025-08-20 19:25:45.131] [undefined] GET(/:id): Successfully get channel with id 2 with status 200 +[2025-08-20 19:25:45.139] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-20 19:25:46.051] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-20 19:25:46.055] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-20 19:25:58.216] [undefined] GET(/search): try to search user by username a +[2025-08-20 19:25:58.220] [undefined] GET(/search): successfully found user with username a with status 200 +[2025-08-20 19:26:10.194] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:26:51.145] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:26:51.163] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 19:26:51.250] [undefined] POST(/thumbnail): try to add thumbnail to video 14 +[2025-08-20 19:26:51.254] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 19:26:51.304] [undefined] PUT(/:id/tags): try to add tags to video 14 +[2025-08-20 19:26:51.315] [undefined] PUT(/:id/tags): successfully added tags to video 14 with status 200 +[2025-08-20 19:27:28.048] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:27:28.081] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 19:27:28.191] [undefined] POST(/thumbnail): try to add thumbnail to video 15 +[2025-08-20 19:27:28.197] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 19:27:28.222] [undefined] PUT(/:id/tags): try to add tags to video 15 +[2025-08-20 19:27:28.231] [undefined] PUT(/:id/tags): Tag csgo already exists for video 15 with status 200 +[2025-08-20 19:27:28.236] [undefined] PUT(/:id/tags): successfully added tags to video 15 with status 200 +[2025-08-20 19:29:54.451] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:29:54.468] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 19:29:54.576] [undefined] POST(/thumbnail): try to add thumbnail to video 16 +[2025-08-20 19:29:54.581] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 19:29:54.606] [undefined] PUT(/:id/tags): try to add tags to video 16 +[2025-08-20 19:29:54.615] [undefined] PUT(/:id/tags): Tag csgo already exists for video 16 with status 200 +[2025-08-20 19:29:54.620] [undefined] PUT(/:id/tags): successfully added tags to video 16 with status 200 +[2025-08-20 19:30:18.925] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:30:18.938] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 19:30:19.056] [undefined] POST(/thumbnail): try to add thumbnail to video 17 +[2025-08-20 19:30:19.061] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 19:30:19.086] [undefined] PUT(/:id/tags): try to add tags to video 17 +[2025-08-20 19:30:19.095] [undefined] PUT(/:id/tags): Tag csgo already exists for video 17 with status 200 +[2025-08-20 19:30:19.100] [undefined] PUT(/:id/tags): successfully added tags to video 17 with status 200 +[2025-08-20 19:31:31.875] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:31:31.885] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 19:31:31.987] [undefined] POST(/thumbnail): try to add thumbnail to video 18 +[2025-08-20 19:31:31.990] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 19:31:32.014] [undefined] PUT(/:id/tags): try to add tags to video 18 +[2025-08-20 19:31:32.022] [undefined] PUT(/:id/tags): Tag csgo already exists for video 18 with status 200 +[2025-08-20 19:31:32.026] [undefined] PUT(/:id/tags): successfully added tags to video 18 with status 200 +[2025-08-20 19:34:17.736] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:34:42.939] [undefined] POST(/): try to upload video with status undefined +[2025-08-20 19:34:42.956] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-20 19:34:43.059] [undefined] POST(/thumbnail): try to add thumbnail to video 20 +[2025-08-20 19:34:43.063] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-20 19:34:43.088] [undefined] PUT(/:id/tags): try to add tags to video 20 +[2025-08-20 19:34:43.096] [undefined] PUT(/:id/tags): Tag csgo already exists for video 20 with status 200 +[2025-08-20 19:34:43.100] [undefined] PUT(/:id/tags): successfully added tags to video 20 with status 200 diff --git a/frontend/src/components/UserCard.jsx b/frontend/src/components/UserCard.jsx index 6daba1b..3a18068 100644 --- a/frontend/src/components/UserCard.jsx +++ b/frontend/src/components/UserCard.jsx @@ -2,7 +2,7 @@ export default function UserCard({ user, onSubmit, doShowControls, control }) { return ( -
+
{`${user.username}'s {user.username} diff --git a/frontend/src/pages/AddVideo.jsx b/frontend/src/pages/AddVideo.jsx index 06916a1..3a8562c 100644 --- a/frontend/src/pages/AddVideo.jsx +++ b/frontend/src/pages/AddVideo.jsx @@ -72,6 +72,7 @@ export default function AddVideo() { formData.append("description", videoDescription); formData.append("channel", channel.id.toString()); formData.append("visibility", visibility); + formData.append("authorizedUsers", JSON.stringify(authorizedUsers.map(user => user.id))); formData.append("file", videoFile); const request = await uploadVideo(formData, token, addAlert); @@ -124,7 +125,18 @@ export default function AddVideo() { } const onAuthorizedUserAdd = (user) => { + + // Verify if user is not already authorized + if (authorizedUsers.find((u) => u.id === user.id)) { + addAlert('error', 'Utilisateur déjà autorisé.'); + setSearchUser("") + setSearchResults([]); + return; + } + setAuthorizedUsers([...authorizedUsers, user]); + setSearchResults([]); + setSearchUser("") }; const onAuthorizedUserRemove = (user) => { @@ -205,14 +217,14 @@ export default function AddVideo() { { visibility == "private" && ( -
+
-
+
setSearchUser(e.target.value)} @@ -236,8 +248,11 @@ export default function AddVideo() { ))}
+ + +
- {authorizedUsers.map((user, index) => ( + {authorizedUsers.length > 0 ? authorizedUsers.map((user, index) => ( } /> - ))} + )) : ( +

Aucun utilisateur autorisé

+ )}
) diff --git a/frontend/src/pages/ManageVideo.jsx b/frontend/src/pages/ManageVideo.jsx index d1eedf3..04ab183 100644 --- a/frontend/src/pages/ManageVideo.jsx +++ b/frontend/src/pages/ManageVideo.jsx @@ -5,7 +5,9 @@ import {useEffect, useState} from "react"; import LinearGraph from "../components/LinearGraph.jsx"; import Comment from "../components/Comment.jsx"; import Tag from "../components/Tag.jsx"; -import {getVideoById, getLikesPerDay, updateVideo, updateVideoFile, uploadThumbnail, uploadTags} from "../services/video.service.js"; +import UserCard from "../components/UserCard.jsx"; +import {getVideoById, getLikesPerDay, updateVideo, updateVideoFile, uploadThumbnail, uploadTags, updateAuthorizedUsers} from "../services/video.service.js"; +import {searchByUsername} from "../services/user.service.js"; export default function ManageVideo() { @@ -24,6 +26,8 @@ export default function ManageVideo() { const [thumbnailPreview, setThumbnailPreview] = useState(null); const [videoFile, setVideoFile] = useState(null); const [alerts, setAlerts] = useState([]); + const [searchUser, setSearchUser] = useState(""); + const [searchResults, setSearchResults] = useState([]); const nonEditModeClasses = "text-md font-normal text-white p-2 focus:text-white focus:outline-none w-full font-montserrat resizable-none"; const editModeClasses = nonEditModeClasses + " glassmorphism"; @@ -140,6 +144,60 @@ export default function ManageVideo() { setAlerts(alerts.filter(alert => alert !== alertToRemove)); }; + const onUserSearch = async (value) => { + if (value.trim() === "") { + setSearchResults([]); + return; + } + + const results = await searchByUsername(value, token, addAlert); + setSearchResults(results); + }; + + const onAuthorizedUserAdd = async (user) => { + // Don't modify video directly - use current state + const currentAuthorizedUsers = video?.authorizedUsers || []; + + if (currentAuthorizedUsers.some(u => u.id === user.id)) { + addAlert('error', "Cet utilisateur est déjà autorisé."); + return; + } + + // authorizedUsers = only ids + const body = { + authorizedUsers: [...currentAuthorizedUsers.map(u => u.id), user.id], + channel: video.channel + }; + + console.log("Sending to API:", body); // Debug log + + const request = await updateAuthorizedUsers(id, body, token, addAlert); + + // Only update state after successful API call + setVideo({ + ...video, + authorizedUsers: [...currentAuthorizedUsers, user] + }); + setSearchUser(""); + setSearchResults([]); + addAlert('success', "Utilisateur ajouté avec succès."); + } + + const onRemoveAuthorizedUser = async (user) => { + console.log("Êtes-vous sûr de vouloir supprimer cet utilisateur autorisé ?", user.id); + const body = { + authorizedUsers: video.authorizedUsers.filter(u => u.id !== user.id), + channel: video.channel + }; + + const request = await updateAuthorizedUsers(id, body, token, addAlert); + setVideo({ + ...video, + authorizedUsers: video.authorizedUsers.filter(u => u.id !== user.id) + }); + addAlert('success', "Utilisateur supprimé avec succès."); + } + return (
@@ -267,6 +325,66 @@ export default function ManageVideo() { + {/* AUTHORIZED USERS */} + + { + video && video.visibility === "private" && ( +
+

Utilisateurs autorisés

+ +
+ { + setSearchUser(e.target.value); + onUserSearch(e.target.value); + }} + /> +
+ {searchResults && searchResults.map((user, index) => ( + + ajouter + + } + /> + ))} +
+
+ +
+ {video && video.authorizedUsers && video.authorizedUsers.length > 0 ? ( + video.authorizedUsers.map((user) => ( + + supprimer + + } /> + )) + ) : ( +

Aucun utilisateur autorisé

+ )} +
+
+ ) + } +
diff --git a/frontend/src/pages/Video.jsx b/frontend/src/pages/Video.jsx index 0f82d68..11cae23 100644 --- a/frontend/src/pages/Video.jsx +++ b/frontend/src/pages/Video.jsx @@ -44,7 +44,7 @@ export default function Video() { return; } - const data = await getVideoById(id, addAlert); + const data = await getVideoById(id, navigation, addAlert); if (!data) return; setVideo(data); diff --git a/frontend/src/services/video.service.js b/frontend/src/services/video.service.js index d59adab..1ab266f 100644 --- a/frontend/src/services/video.service.js +++ b/frontend/src/services/video.service.js @@ -68,15 +68,23 @@ export async function uploadTags(body, videoId, token, addAlert) { } } -export async function getVideoById(id, addAlert) { +export async function getVideoById(id, navigator, addAlert) { + const token = localStorage.getItem('token'); try { const request = await fetch(`/api/videos/${id}`, { method: 'GET', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token ? token : ''}` } }); if (!request.ok) { + + if (request.status === 403) { + addAlert('error', 'Accès refusé'); + navigator('/'); + } + addAlert('error', 'Erreur lors de la récupération de la vidéo'); } const data = await request.json(); @@ -209,4 +217,27 @@ export async function toggleLike(id, token, addAlert) { console.error("Error toggling like:", error); addAlert('error', 'Erreur lors du changement de like'); } +} + +export async function updateAuthorizedUsers(id, body, token, addAlert) { + try { + const request = await fetch(`/api/videos/${id}/authorized-users`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(body) + }); + if (!request.ok) { + const errorData = await request.json(); + console.error("Backend validation errors:", errorData); + addAlert('error', 'Erreur lors de la mise à jour des utilisateurs autorisés'); + return; + } + return request; + } catch (error) { + console.error("Error updating authorized users:", error); + addAlert('error', 'Erreur lors de la mise à jour des utilisateurs autorisés'); + } } \ No newline at end of file