Browse Source

Merge pull request 'features/visibility' (#5) from features/visibility into developpement

Reviewed-on: #5
features/oauth
astria 4 months ago
parent
commit
d6c6393f90
  1. 2
      backend/app/controllers/channel.controller.js
  2. 7
      backend/app/controllers/recommendation.controller.js
  3. 2
      backend/app/controllers/search.controller.js
  4. 76
      backend/app/controllers/user.controller.js
  5. 146
      backend/app/controllers/video.controller.js
  6. 6
      backend/app/middlewares/user.middleware.js
  7. 84
      backend/app/middlewares/video.middleware.js
  8. 9
      backend/app/routes/user.route.js
  9. 19
      backend/app/routes/video.route.js
  10. 7
      backend/app/utils/database.js
  11. 930
      backend/logs/access.log
  12. 1
      docker-compose.yaml
  13. 7
      frontend/package-lock.json
  14. 20
      frontend/src/components/UserCard.jsx
  15. 121
      frontend/src/pages/AddVideo.jsx
  16. 120
      frontend/src/pages/ManageVideo.jsx
  17. 2
      frontend/src/pages/Video.jsx
  18. 10
      frontend/src/services/playlist.service.js
  19. 27
      frontend/src/services/user.service.js
  20. 33
      frontend/src/services/video.service.js

2
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.history h ON h.video = videos.id
LEFT JOIN public.likes ON likes.video = videos.id LEFT JOIN public.likes ON likes.video = videos.id
LEFT JOIN public.comments c ON c.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 GROUP BY videos.id, channels.name, channels.description, users.username, users.picture
`; `;

7
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 // GET 10 VIDEOS WITH THE MOST LIKES AND COMMENTS
let client = await getClient(); let client = await getClient();
let queryTrendingVideos = ` let queryTrendingVideos = `
SELECT v.id, v.title, v.description, v.release_date, v.thumbnail, 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 COUNT(DISTINCT l.id) AS like_count, COUNT(DISTINCT c.id) AS comment_count
FROM videos v FROM videos v
LEFT JOIN likes l ON v.id = l.video LEFT JOIN likes l ON v.id = l.video
LEFT JOIN comments c ON v.id = c.video LEFT JOIN comments c ON v.id = c.video
WHERE v.visibility = 'public'
GROUP BY v.id GROUP BY v.id
ORDER BY like_count DESC, comment_count DESC ORDER BY like_count DESC, comment_count DESC
LIMIT 10; LIMIT 10
`; `;
let result = await client.query(queryTrendingVideos); let result = await client.query(queryTrendingVideos);
const trendingVideos = result.rows; const trendingVideos = result.rows;

2
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.channels c on v.channel = c.id
JOIN public.users u on c.owner = u.id JOIN public.users u on c.owner = u.id
LEFT JOIN public.history h on h.video = v.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, 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 c.id, c.owner, c.description, c.name, u.picture
OFFSET $2 OFFSET $2

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

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

146
backend/app/controllers/video.controller.js

@ -4,12 +4,13 @@ import * as fs from "node:fs";
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname } from 'path';
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { query } from "express-validator"; import { sendEmail } from "../utils/mail.js";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
export async function upload(req, res) { export async function upload(req, res) {
// HANDLE VIDEO FILE
const fileBuffer = req.file.buffer; const fileBuffer = req.file.buffer;
let isGenerate = false; let isGenerate = false;
while (isGenerate === false) { while (isGenerate === false) {
@ -45,6 +46,7 @@ export async function upload(req, res) {
visibility: req.body.visibility, visibility: req.body.visibility,
} }
// HANDLE VIDEO DETAILS
const logger = req.body.logger; const logger = req.body.logger;
logger.write("try to upload video"); logger.write("try to upload video");
const releaseDate = new Date(Date.now()).toISOString(); 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 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 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; 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 = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accès à une vidéo privée - Freetube</title>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
<div style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<!-- Header -->
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 20px; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: bold;">
🔐 Vidéo privée partagée avec vous!
</h1>
</div>
<!-- Content -->
<div style="padding: 40px 30px;">
<h2 style="color: #333333; margin-top: 0; font-size: 24px;">
Bonjour! 👋
</h2>
<p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 20px 0;">
Vous avez été autorisé(e) à visionner une vidéo privée sur <strong>Freetube</strong>!
</p>
<p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 20px 0;">
<strong>On vous a partagé une vidéo privée avec vous. Cliquez sur le bouton ci-dessous pour la regarder :
</p>
<!-- Video Info Box -->
<div style="background-color: #f8f9fa; border: 2px dashed #667eea; border-radius: 8px; padding: 25px; margin: 30px 0;">
<h3 style="color: #333333; font-size: 18px; margin: 0 0 10px 0; text-align: center;">
${video.title}
</h3>
<p style="color: #666666; font-size: 14px; margin: 0 0 20px 0; text-align: center; line-height: 1.4;">
${video.description}
</p>
<div style="text-align: center;">
<a href="${process.env.FRONTEND_URL}/videos/${video.id}" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; text-decoration: none; padding: 12px 25px; border-radius: 6px; font-size: 16px; font-weight: bold; display: inline-block;">
Regarder la vidéo
</a>
</div>
</div>
<p style="color: #999999; font-size: 14px; line-height: 1.5; margin: 30px 0 10px 0;">
🔒 Cette vidéo est privée et n'est accessible qu'aux personnes autorisées.
</p>
<p style="color: #999999; font-size: 14px; line-height: 1.5; margin: 10px 0;">
Si vous pensez avoir reçu cet e-mail par erreur, vous pouvez l'ignorer.
</p>
</div>
<!-- Footer -->
<div style="background-color: #f8f9fa; padding: 20px 30px; border-top: 1px solid #eee;">
<p style="color: #999999; font-size: 12px; margin: 0; text-align: center;">
© 2025 Freetube. Tous droits réservés.
</p>
</div>
</div>
</body>
</html>
`;
sendEmail(email, "Invitation à visionner une vidéo privée", textMessage, htmlMessage);
await client.query(query, [id, user]);
}
}
}
logger.write("successfully uploaded video", 200); logger.write("successfully uploaded video", 200);
await client.end() await client.end()
res.status(200).json({ "message": "Successfully uploaded video", "id": id }); 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]); const tagsResult = await client.query(tagsQuery, [id]);
video.tags = tagsResult.rows.map(tag => tag.name); 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); logger.write("successfully get video " + id, 200);
client.end() client.end()
res.status(200).json(video); res.status(200).json(video);
@ -489,4 +598,39 @@ export async function addViews(req, res) {
logger.write("successfully added views for video " + id, 200); logger.write("successfully added views for video " + id, 200);
await client.end(); await client.end();
res.status(200).json({ "message": "Successfully added views" }); 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();
}
} }

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

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

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

@ -23,6 +23,21 @@ export const VideoCreate = {
description: body("description").optional({values: "falsy"}).trim(), description: body("description").optional({values: "falsy"}).trim(),
channel: body("channel").notEmpty().isNumeric().trim(), channel: body("channel").notEmpty().isNumeric().trim(),
visibility: body("visibility").notEmpty().isAlpha().trim(), visibility: body("visibility").notEmpty().isAlpha().trim(),
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 = { export const VideoThumbnail = {
@ -79,3 +94,72 @@ export async function doVideoExistsParam(req, res, next) {
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();
}

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

@ -8,7 +8,8 @@ import {
deleteUser, deleteUser,
getChannel, getHistory, getChannel, getHistory,
isSubscribed, isSubscribed,
verifyEmail verifyEmail,
searchByUsername
} from "../controllers/user.controller.js"; } from "../controllers/user.controller.js";
import { import {
UserRegister, UserRegister,
@ -18,7 +19,8 @@ import {
isOwner, isOwner,
UserLogin, UserLogin,
User, User,
UserRequest UserRequest,
UserSearch
} from "../middlewares/user.middleware.js"; } from "../middlewares/user.middleware.js";
import validator from "../middlewares/error.middleware.js"; import validator from "../middlewares/error.middleware.js";
import {isTokenValid} from "../middlewares/jwt.middleware.js"; import {isTokenValid} from "../middlewares/jwt.middleware.js";
@ -34,6 +36,9 @@ router.post("/", [profileUpload.single("profile"), addLogger, UserRegister.email
// LOGIN A USER // LOGIN A USER
router.post("/login", [addLogger, UserLogin.username, UserLogin.password, validator], login) router.post("/login", [addLogger, UserLogin.username, UserLogin.password, validator], login)
// SEARCH BY USERNAME
router.get("/search", [addLogger, isTokenValid, UserSearch.username, validator], searchByUsername);
// GET USER BY ID // GET USER BY ID
router.get("/:id", [addLogger, isTokenValid, User.id, validator], getById) router.get("/:id", [addLogger, isTokenValid, User.id, validator], getById)

19
backend/app/routes/video.route.js

@ -10,7 +10,7 @@ import {
uploadThumbnail, uploadThumbnail,
updateVideo, updateVideo,
toggleLike, toggleLike,
addTags, getSimilarVideos, addViews, getLikesPerDay addTags, getSimilarVideos, addViews, getLikesPerDay, updateAuthorizedUsers
} from "../controllers/video.controller.js"; } from "../controllers/video.controller.js";
import { import {
doVideoExists, doVideoExists,
@ -18,7 +18,9 @@ import {
isOwner, isOwner,
Video, Video,
VideoCreate, VideoCreate,
VideoThumbnail VideoThumbnail,
doAuthorizedUserExists,
hasAccess
} from "../middlewares/video.middleware.js"; } from "../middlewares/video.middleware.js";
import {Channel, doChannelExistBody, doChannelExists} from "../middlewares/channel.middleware.js"; import {Channel, doChannelExistBody, doChannelExists} from "../middlewares/channel.middleware.js";
import {thumbnailUpload, videoUpload} from "../middlewares/file.middleware.js"; import {thumbnailUpload, videoUpload} from "../middlewares/file.middleware.js";
@ -27,19 +29,20 @@ import validator from "../middlewares/error.middleware.js";
const router = Router(); const router = Router();
// UPLOAD VIDEO // 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 // UPLOAD/UPDATE THUMBNAIL
router.post("/thumbnail", [thumbnailUpload.single('file'), addLogger, isTokenValid, VideoThumbnail.video, Video.channel, validator, doChannelExistBody, isOwner, doVideoExists], uploadThumbnail ) router.post("/thumbnail", [thumbnailUpload.single('file'), addLogger, isTokenValid, VideoThumbnail.video, Video.channel, validator, doChannelExistBody, isOwner, doVideoExists], uploadThumbnail )
// GET BY ID // 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 // 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 // UPDATE VIDEO DATA
router.put("/:id", [addLogger, isTokenValid, Video.id, VideoCreate.title, VideoCreate.description, VideoCreate.visibility, VideoCreate.channel, validator, doVideoExistsParam, doChannelExistBody, isOwner], update); router.put("/:id", [addLogger, isTokenValid, Video.id, VideoCreate.title, VideoCreate.description, VideoCreate.visibility, VideoCreate.channel, validator, doVideoExistsParam, doChannelExistBody, isOwner], update);
// UPDATE VIDEO // UPDATE VIDEO
router.put("/:id/video", [videoUpload.single("file"), addLogger, isTokenValid, Video.id, Video.channel, validator, doVideoExistsParam, doChannelExistBody, isOwner ], updateVideo); 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); router.put("/:id/tags", [addLogger, isTokenValid, Video.id, Video.tags, validator, doVideoExistsParam, isOwner], addTags);
// GET SIMILAR VIDEOS // 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 // 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 // GET LIKE PER DAY
router.get("/:id/likes/day", [addLogger, isTokenValid, Video.id, validator, doVideoExistsParam], getLikesPerDay); 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; export default router;

7
backend/app/utils/database.js

@ -119,6 +119,13 @@ export async function initDb() {
)`; )`;
await client.query(query); 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) { } catch (e) {
console.error("Error initializing database:", e); console.error("Error initializing database:", e);
} }

930
backend/logs/access.log

@ -7427,3 +7427,933 @@
[2025-08-17 10:06:21.133] [undefined] GET(/:id/history): try to retrieve history of user 2 [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.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: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
[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
[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

1
docker-compose.yaml

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

7
frontend/package-lock.json

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

20
frontend/src/components/UserCard.jsx

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

121
frontend/src/pages/AddVideo.jsx

@ -1,8 +1,9 @@
import Navbar from "../components/Navbar.jsx"; import Navbar from "../components/Navbar.jsx";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Tag from "../components/Tag.jsx"; 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 { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js";
import UserCard from "../components/UserCard.jsx";
export default function AddVideo() { export default function AddVideo() {
@ -14,10 +15,13 @@ export default function AddVideo() {
const [videoTitle, setVideoTitle] = useState(""); const [videoTitle, setVideoTitle] = useState("");
const [videoDescription, setVideoDescription] = useState(""); const [videoDescription, setVideoDescription] = useState("");
const [videoTags, setVideoTags] = useState([]); const [videoTags, setVideoTags] = useState([]);
const [visibility, setVisibility] = useState("public"); const [visibility, setVisibility] = useState("private");
const [videoThumbnail, setVideoThumbnail] = useState(null); const [videoThumbnail, setVideoThumbnail] = useState(null);
const [videoFile, setVideoFile] = useState(null); const [videoFile, setVideoFile] = useState(null);
const [channel, setChannel] = useState(null); const [channel, setChannel] = useState(null);
const [searchUser, setSearchUser] = useState("");
const [authorizedUsers, setAuthorizedUsers] = useState([]);
const [searchResults, setSearchResults] = useState([]);
const [alerts, setAlerts] = useState([]); const [alerts, setAlerts] = useState([]);
useEffect(() => { useEffect(() => {
@ -68,6 +72,7 @@ export default function AddVideo() {
formData.append("description", videoDescription); formData.append("description", videoDescription);
formData.append("channel", channel.id.toString()); formData.append("channel", channel.id.toString());
formData.append("visibility", visibility); formData.append("visibility", visibility);
formData.append("authorizedUsers", JSON.stringify(authorizedUsers.map(user => user.id)));
formData.append("file", videoFile); formData.append("file", videoFile);
const request = await uploadVideo(formData, token, addAlert); const request = await uploadVideo(formData, token, addAlert);
@ -102,6 +107,42 @@ export default function AddVideo() {
setAlerts(alerts.filter(alert => alert !== alertToRemove)); 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) => {
console.log(results);
setSearchResults(results);
})
.catch((error) => {
addAlert('error', 'Erreur lors de la recherche d\'utilisateurs.');
});
} else {
setSearchResults([]);
}
}
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) => {
setAuthorizedUsers(authorizedUsers.filter((u) => u.id !== user.id));
};
return ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
@ -166,6 +207,82 @@ export default function AddVideo() {
<option value="private">Privé</option> <option value="private">Privé</option>
</select> </select>
{
visibility == "private" && (
<div className="mb-4 glassmorphism p-4">
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="authorizedUsers">Utilisateurs autorisés</label>
<div className="mb-4">
<input
type="text"
id="authorizedUsers"
name="authorizedUsers"
className="w-full p-2 mb-2 glassmorphism focus:outline-none font-inter text-xl text-white"
placeholder="Rechercher un utilisateur"
value={searchUser}
onChange={(e) => setSearchUser(e.target.value)}
onKeyDown={(e) => {
onUserSearch(e)
}}
/>
<div>
{searchResults && searchResults.map((user, index) => (
<UserCard
user={user}
onSubmit={onAuthorizedUserAdd}
doShowControls={true}
key={index}
control={
<button type="button" className="bg-primary text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
ajouter
</button>
}
/>
))}
</div>
</div>
<div className="max-h-40 overflow-y-auto">
{authorizedUsers.length > 0 ? authorizedUsers.map((user, index) => (
<UserCard
user={user}
onSubmit={onAuthorizedUserRemove}
doShowControls={true}
key={index}
control={
<button type="button" className="bg-red-500 text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
supprimer
</button>
}
/>
)) : (
<p className="text-white">Aucun utilisateur autorisé</p>
)}
</div>
</div>
)
}
<label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="videoThumbnail">Miniature</label> <label className="block text-white text-xl font-montserrat font-semibold mb-2" htmlFor="videoThumbnail">Miniature</label>
<input <input
type="file" type="file"

120
frontend/src/pages/ManageVideo.jsx

@ -5,7 +5,9 @@ import {useEffect, useState} from "react";
import LinearGraph from "../components/LinearGraph.jsx"; import LinearGraph from "../components/LinearGraph.jsx";
import Comment from "../components/Comment.jsx"; import Comment from "../components/Comment.jsx";
import Tag from "../components/Tag.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() { export default function ManageVideo() {
@ -24,6 +26,8 @@ export default function ManageVideo() {
const [thumbnailPreview, setThumbnailPreview] = useState(null); const [thumbnailPreview, setThumbnailPreview] = useState(null);
const [videoFile, setVideoFile] = useState(null); const [videoFile, setVideoFile] = useState(null);
const [alerts, setAlerts] = useState([]); 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 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"; const editModeClasses = nonEditModeClasses + " glassmorphism";
@ -140,6 +144,60 @@ export default function ManageVideo() {
setAlerts(alerts.filter(alert => alert !== alertToRemove)); 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 ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
@ -267,6 +325,66 @@ export default function ManageVideo() {
</form> </form>
{/* AUTHORIZED USERS */}
{
video && video.visibility === "private" && (
<div className="glassmorphism p-4 mt-4 flex-1" >
<h2 className="text-2xl font-bold text-white mb-4">Utilisateurs autorisés</h2>
<div className="mb-4">
<input
type="text"
id="authorizedUsers"
name="authorizedUsers"
className="w-full p-2 mb-2 glassmorphism focus:outline-none font-inter text-xl text-white"
placeholder="Rechercher un utilisateur"
value={searchUser}
onChange={(e) => {
setSearchUser(e.target.value);
onUserSearch(e.target.value);
}}
/>
<div>
{searchResults && searchResults.map((user, index) => (
<UserCard
user={user}
onSubmit={onAuthorizedUserAdd}
doShowControls={true}
key={index}
control={
<button type="button" className="bg-primary text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
ajouter
</button>
}
/>
))}
</div>
</div>
<div>
{video && video.authorizedUsers && video.authorizedUsers.length > 0 ? (
video.authorizedUsers.map((user) => (
<UserCard
key={user.id}
id={user.id}
user={user}
doShowControls={true}
onSubmit={onRemoveAuthorizedUser}
control={
<button type="button" className="bg-red-500 text-white font-montserrat p-3 rounded-lg text-lg font-bold cursor-pointer">
supprimer
</button>
} />
))
) : (
<p className="text-gray-500">Aucun utilisateur autorisé</p>
)}
</div>
</div>
)
}
</div> </div>

2
frontend/src/pages/Video.jsx

@ -44,7 +44,7 @@ export default function Video() {
return; return;
} }
const data = await getVideoById(id, addAlert); const data = await getVideoById(id, navigation, addAlert);
if (!data) return; if (!data) return;
setVideo(data); setVideo(data);

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

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

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

@ -122,4 +122,31 @@ export async function verifyEmail(email, token, addAlert) {
console.error("Error verifying email:", error); console.error("Error verifying email:", error);
addAlert('error', "Erreur lors de la vérification de l'email."); addAlert('error', "Erreur lors de la vérification de l'email.");
} }
}
export async function searchByUsername(username, token, addAlert) {
try {
// 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) {
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', error.message || "Erreur lors de la recherche de l'utilisateur.");
}
} }

33
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 { try {
const request = await fetch(`/api/videos/${id}`, { const request = await fetch(`/api/videos/${id}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${token ? token : ''}`
} }
}); });
if (!request.ok) { 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'); addAlert('error', 'Erreur lors de la récupération de la vidéo');
} }
const data = await request.json(); const data = await request.json();
@ -209,4 +217,27 @@ export async function toggleLike(id, token, addAlert) {
console.error("Error toggling like:", error); console.error("Error toggling like:", error);
addAlert('error', 'Erreur lors du changement de like'); 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');
}
} }
Loading…
Cancel
Save