diff --git a/backend/app/controllers/playlist.controller.js b/backend/app/controllers/playlist.controller.js
index dc90b56..cd78d46 100644
--- a/backend/app/controllers/playlist.controller.js
+++ b/backend/app/controllers/playlist.controller.js
@@ -227,4 +227,76 @@ export async function del(req, res) {
client.end();
res.status(500).json({ error: "Internal server error" });
}
+}
+
+export async function getSeeLater(req, res) {
+
+ const token = req.headers.authorization.split(' ')[1];
+ const userId = jwt.decode(token)["id"];
+ const logger = req.body.logger;
+
+ const client = await getClient();
+ const query = `
+ SELECT
+ JSON_AGG(
+ json_build_object(
+ 'video_id', videos.id,
+ 'title', videos.title,
+ 'thumbnail', videos.thumbnail,
+ 'video_decscription', videos.description,
+ 'channel', videos.channel,
+ 'visibility', videos.visibility,
+ 'file', videos.file,
+ 'format', videos.format,
+ 'release_date', videos.release_date,
+ 'channel_id', channels.id,
+ 'owner', channels.owner,
+ 'views', COALESCE(video_views.view_count, 0),
+ 'creator', json_build_object(
+ 'name', channels.name,
+ 'profilePicture', users.picture,
+ 'description', channels.description
+ )
+ )
+ ) AS videos
+ FROM
+ public.playlists
+ LEFT JOIN public.playlist_elements ON public.playlists.id = public.playlist_elements.playlist
+ LEFT JOIN (
+ SELECT
+ *
+ FROM public.videos
+ LIMIT 10
+ ) videos ON public.playlist_elements.video = videos.id
+ LEFT JOIN public.channels ON videos.channel = public.channels.id
+ LEFT JOIN public.users ON public.channels.owner = public.users.id
+ LEFT JOIN (
+ SELECT video, COUNT(*) as view_count
+ FROM public.history
+ GROUP BY video
+ ) video_views ON videos.id = video_views.video
+ WHERE
+ playlists.owner = $1
+ GROUP BY playlists.id, playlists.name
+ ORDER BY
+ playlists.id ASC
+ LIMIT 1;
+ `;
+ try {
+ const result = await client.query(query, [userId]);
+ if (result.rows.length === 0) {
+ logger.write("No 'See Later' playlist found for user with id " + userId, 404);
+ client.end();
+ res.status(404).json({ error: "'See Later' playlist not found" });
+ return;
+ }
+ logger.write("'See Later' playlist retrieved for user with id " + userId, 200);
+ client.end();
+ res.status(200).json(result.rows[0].videos);
+ } catch (error) {
+ logger.write("Error retrieving 'See Later' playlist: " + error.message, 500);
+ client.end();
+ res.status(500).json({ error: "Internal server error" });
+ }
+
}
\ No newline at end of file
diff --git a/backend/app/controllers/recommendation.controller.js b/backend/app/controllers/recommendation.controller.js
index d7f043c..5e6897c 100644
--- a/backend/app/controllers/recommendation.controller.js
+++ b/backend/app/controllers/recommendation.controller.js
@@ -1,39 +1,195 @@
import {getClient} from "../utils/database.js";
-
+import jwt from 'jsonwebtoken';
export async function getRecommendations(req, res) {
const token = req.headers.authorization?.split(' ')[1];
- if (!token) {
+ if (!req.headers.authorization || !token) {
- // GET MOST USED TOKEN
+ // GET MOST USED TAGS
let client = await getClient();
- let queryMostUsedToken = `SELECT id, name FROM tags ORDER BY usage_count DESC LIMIT 3;`;
- let result = await client.query(queryMostUsedToken);
+ let queryMostUsedTags = `SELECT id, name FROM tags ORDER BY usage_count DESC LIMIT 3;`;
+ let result = await client.query(queryMostUsedTags);
+
+ // GET 10 VIDEOS WITH THE TAGS
- const recommendations = result.rows;
+ let tagIds = result.rows.map(tag => tag.id);
+ let queryVideosWithTags = `
+
+ SELECT
+ v.id,
+ v.title,
+ v.thumbnail,
+ v.description AS video_description,
+ v.channel,
+ v.visibility,
+ v.file,
+ v.slug,
+ v.release_date,
+ v.channel AS channel_id,
+ c.owner,
+ COUNT(h.id) AS views,
+ json_build_object(
+ 'name', c.name,
+ 'profilePicture', u.picture,
+ 'description', c.description
+ ) AS creator,
+ 'video' AS type
+ FROM public.videos v
+ INNER JOIN public.video_tags vt ON v.id = vt.video
+ INNER JOIN public.tags t ON vt.tag = t.id
+ INNER JOIN public.channels c ON v.channel = c.id
+ INNER JOIN public.users u ON c.owner = u.id
+ LEFT JOIN public.history h ON h.video = v.id
+ WHERE t.id = ANY($1::int[])
+ AND v.visibility = 'public'
+ GROUP BY
+ v.id,
+ v.title,
+ v.thumbnail,
+ v.description,
+ v.channel,
+ v.visibility,
+ v.file,
+ v.slug,
+ v.release_date,
+ c.owner,
+ c.name,
+ u.picture,
+ c.description
+ ORDER BY views DESC, v.release_date DESC
+ LIMIT 10;
+
+ `;
+ let videoResult = await client.query(queryVideosWithTags, [tagIds]);
+ const recommendations = videoResult.rows;
res.status(200).json(recommendations);
} else {
- // Recuperer les 20 derniere vu de l'historique
let client = await getClient();
- let queryLastVideos = `SELECT video_id FROM history WHERE user_id = $1 ORDER BY viewed_at DESC LIMIT 20;`;
- // TODO: Implement retrieval of recommendations based on user history and interactions
-
- // Recuperer les likes de l'utilisateur sur les 20 derniere videos recuperees
-
- // Recuperer les commentaires de l'utilisateur sur les 20 derniere videos recuperees
+ const claims = jwt.decode(token)
+ const query = `
+ -- Recommandation de contenu similaire non vu basée sur les interactions utilisateur
+ -- Paramètre: $1 = user_id
+ WITH user_interactions AS (
+ -- Récupérer tous les contenus avec lesquels l'utilisateur a interagi
+ SELECT DISTINCT v.id as video_id, v.channel, t.id as tag_id, t.name as tag_name
+ FROM videos v
+ JOIN video_tags vt ON v.id = vt.video
+ JOIN tags t ON vt.tag = t.id
+ WHERE v.id IN (
+ -- Vidéos likées par l'utilisateur
+ SELECT DISTINCT l.video FROM likes l WHERE l.owner = $1
+ UNION
+ -- Vidéos commentées par l'utilisateur
+ SELECT DISTINCT c.video FROM comments c WHERE c.author = $1
+ UNION
+ -- Vidéos ajoutées aux playlists de l'utilisateur
+ SELECT DISTINCT pe.video
+ FROM playlist_elements pe
+ JOIN playlists p ON pe.playlist = p.id
+ WHERE p.owner = $1
+ )
+ ),
+ user_preferred_tags AS (
+ -- Tags préférés basés sur les interactions
+ SELECT tag_id, tag_name, COUNT(*) as interaction_count
+ FROM user_interactions
+ GROUP BY tag_id, tag_name
+ ),
+ user_preferred_channels AS (
+ -- Chaînes préférées basées sur les interactions
+ SELECT channel, COUNT(*) as interaction_count
+ FROM user_interactions
+ GROUP BY channel
+ ),
+ unseen_videos AS (
+ -- Vidéos que l'utilisateur n'a jamais vues
+ SELECT v.id, v.title, v.thumbnail, v.description, v.channel, v.visibility,
+ v.file, v.slug, v.format, v.release_date, ch.owner
+ FROM videos v
+ JOIN channels ch ON v.channel = ch.id
+ WHERE v.visibility = 'public'
+ AND v.id NOT IN (
+ -- Exclure les vidéos déjà vues
+ SELECT DISTINCT h.video FROM history h WHERE h.user_id = $1
+ UNION
+ -- Exclure les vidéos déjà likées
+ SELECT DISTINCT l.video FROM likes l WHERE l.owner = $1
+ UNION
+ -- Exclure les vidéos déjà commentées
+ SELECT DISTINCT c.video FROM comments c WHERE c.author = $1
+ UNION
+ -- Exclure les vidéos déjà ajoutées aux playlists
+ SELECT DISTINCT pe.video
+ FROM playlist_elements pe
+ JOIN playlists p ON pe.playlist = p.id
+ WHERE p.owner = $1
+ )
+ )
+ -- Requête principale : recommander du contenu similaire
+ SELECT
+ uv.id,
+ uv.title,
+ uv.thumbnail,
+ uv.description as video_description,
+ uv.channel,
+ uv.visibility,
+ uv.file,
+ uv.slug,
+ uv.format,
+ uv.release_date,
+ uv.channel as channel_id,
+ uv.owner,
+ COALESCE(view_counts.views::text, '0') as views,
+ json_build_object(
+ 'name', u.username,
+ 'profilePicture', u.picture,
+ 'description', ch.description
+ ) as creator,
+ 'video' as type
+ FROM unseen_videos uv
+ JOIN channels ch ON uv.channel = ch.id
+ JOIN users u ON ch.owner = u.id
+ -- Compter les vues
+ LEFT JOIN (
+ SELECT video, COUNT(*) as views
+ FROM history
+ GROUP BY video
+ ) view_counts ON uv.id = view_counts.video
+ -- Score basé sur les tags similaires
+ LEFT JOIN (
+ SELECT
+ vt.video,
+ SUM(upt.interaction_count * 0.7) as score
+ FROM video_tags vt
+ JOIN user_preferred_tags upt ON vt.tag = upt.tag_id
+ GROUP BY vt.video
+ ) tag_score ON uv.id = tag_score.video
+ -- Score basé sur les chaînes similaires
+ LEFT JOIN (
+ SELECT
+ uv2.channel,
+ MAX(upc.interaction_count * 0.3) as score
+ FROM unseen_videos uv2
+ JOIN user_preferred_channels upc ON uv2.channel = upc.channel
+ GROUP BY uv2.channel
+ ) channel_score ON uv.channel = channel_score.channel
+ WHERE (tag_score.score > 0 OR channel_score.score > 0) -- Au moins une similarité
+ GROUP BY uv.id, uv.title, uv.thumbnail, uv.description, uv.channel, uv.visibility,
+ uv.file, uv.slug, uv.format, uv.release_date, uv.owner, u.username, u.picture,
+ ch.description, view_counts.views, tag_score.score, channel_score.score
+ ORDER BY (COALESCE(tag_score.score, 0) + COALESCE(channel_score.score, 0)) DESC, uv.release_date DESC
+ LIMIT 20;
- // Recuperer les 3 tags avec lesquels l'utilisateur a le plus interagi
+ `;
+ let result = await client.query(query, [claims.id]);
- // Recuperer 10 videos avec les 3 tags ayant le plus d'interaction avec l'utilisateur
client.end()
- res.status(200).json({
- message: "Recommendations based on user history and interactions are not yet implemented."
- });
+ res.status(200).json(result.rows);
}
}
@@ -88,4 +244,28 @@ export async function getTrendingVideos(req, res) {
console.error("Error fetching trending videos:", error);
res.status(500).json({error: "Internal server error while fetching trending videos."});
}
+}
+
+export async function getTopCreators(req, res) {
+ try {
+ // GET TOP 5 CREATORS BASED ON NUMBER OF SUBSCRIBERS
+ let client = await getClient();
+ let queryTopCreators = `
+ SELECT c.id, c.name, c.description, u.picture AS profilePicture, COUNT(s.id) AS subscriber_count
+ FROM channels c
+ JOIN users u ON c.owner = u.id
+ LEFT JOIN subscriptions s ON c.id = s.channel
+ GROUP BY c.id, u.picture
+ ORDER BY subscriber_count DESC
+ LIMIT 10;
+ `;
+ let result = await client.query(queryTopCreators);
+ const topCreators = result.rows;
+
+ client.end();
+ res.status(200).json(topCreators);
+ } catch (error) {
+ console.error("Error fetching top creators:", error);
+ res.status(500).json({error: "Internal server error while fetching top creators."});
+ }
}
\ No newline at end of file
diff --git a/backend/app/routes/playlist.route.js b/backend/app/routes/playlist.route.js
index ee5c030..00b8cd0 100644
--- a/backend/app/routes/playlist.route.js
+++ b/backend/app/routes/playlist.route.js
@@ -3,7 +3,7 @@ import {addLogger} from "../middlewares/logger.middleware.js";
import {isTokenValid} from "../middlewares/jwt.middleware.js";
import {doPlaylistExists, Playlist, isOwner, isVideoInPlaylist} from "../middlewares/playlist.middleware.js";
import validator from "../middlewares/error.middleware.js";
-import {addVideo, create, del, deleteVideo, getById, getByUser, update} from "../controllers/playlist.controller.js";
+import {addVideo, create, del, deleteVideo, getById, getByUser, update, getSeeLater} from "../controllers/playlist.controller.js";
import {doVideoExists, Video} from "../middlewares/video.middleware.js";
import {doUserExists, User, isOwner as isOwnerUser} from "../middlewares/user.middleware.js";
@@ -12,6 +12,9 @@ const router = new Router();
// CREATE PLAYLIST
router.post("/", [addLogger, isTokenValid, Playlist.name, validator], create);
+// GET SEE LATER PLAYLIST
+router.get("/see-later", [addLogger, isTokenValid], getSeeLater);
+
// ADD VIDEO TO PLAYLIST
router.post("/:id", [addLogger, isTokenValid, Playlist.id, Video.idBody, validator, doPlaylistExists, isOwner, doVideoExists], addVideo);
@@ -30,4 +33,5 @@ router.delete("/:id/video/:videoId", [addLogger, isTokenValid, Video.id, Playlis
// DELETE PLAYLIST
router.delete("/:id", [addLogger, isTokenValid, Playlist.id, validator, doPlaylistExists, isOwner], del);
+
export default router;
\ No newline at end of file
diff --git a/backend/app/routes/redommendation.route.js b/backend/app/routes/redommendation.route.js
index dbb7f2c..4757b0f 100644
--- a/backend/app/routes/redommendation.route.js
+++ b/backend/app/routes/redommendation.route.js
@@ -1,5 +1,5 @@
import { Router } from 'express';
-import {getRecommendations, getTrendingVideos} from "../controllers/recommendation.controller.js";
+import {getRecommendations, getTrendingVideos, getTopCreators} from "../controllers/recommendation.controller.js";
const router = Router();
@@ -7,4 +7,6 @@ router.get('/', [], getRecommendations);
router.get('/trending', [], getTrendingVideos);
+router.get("/creators", [], getTopCreators)
+
export default router;
\ No newline at end of file
diff --git a/backend/app/utils/database.js b/backend/app/utils/database.js
index ad85431..780d8ec 100644
--- a/backend/app/utils/database.js
+++ b/backend/app/utils/database.js
@@ -2,10 +2,10 @@ import pg from "pg";
export async function getClient() {
const client = new pg.Client({
- user: process.env.DB_USER,
- password: process.env.DB_PASSWORD,
- host: process.env.DB_HOST,
- database: process.env.DB_NAME,
+ user: process.env.POSTGRES_USER,
+ password: process.env.POSTGRES_PASSWORD,
+ host: process.env.POSTGRES_HOST,
+ database: process.env.POSTGRES_DB,
port: 5432
})
diff --git a/backend/logs/access.log b/backend/logs/access.log
index d76fa21..df1d83c 100644
--- a/backend/logs/access.log
+++ b/backend/logs/access.log
@@ -8440,3 +8440,369 @@
[2025-08-22 17:20:25.400] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-22 17:20:25.407] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
[2025-08-22 17:20:25.418] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-22 17:45:26.016] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 17:45:26.020] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-22 17:45:26.024] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
+[2025-08-22 17:45:26.028] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
+[2025-08-22 17:45:26.038] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-22 17:45:31.307] [undefined] POST(/): try to create new channel with owner 1 and name Astri4
+[2025-08-22 17:45:31.311] [undefined] POST(/): Successfully created new channel with name Astri4 with status 200
+[2025-08-22 17:45:31.326] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 17:45:31.329] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-22 17:45:32.380] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-22 17:45:32.390] [undefined] GET(/:id/stats): try to get stats
+[2025-08-22 17:45:32.396] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-22 17:45:32.405] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-22 17:45:33.382] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 17:45:33.387] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-22 17:48:00.202] [undefined] POST(/): try to upload video with status undefined
+[2025-08-22 17:48:00.209] [undefined] POST(/): successfully uploaded video with status 200
+[2025-08-22 17:48:00.320] [undefined] POST(/thumbnail): try to add thumbnail to video 1
+[2025-08-22 17:48:00.326] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200
+[2025-08-22 17:48:00.348] [undefined] PUT(/:id/tags): try to add tags to video 1
+[2025-08-22 17:48:00.363] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200
+[2025-08-22 17:52:47.227] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 17:52:47.232] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-22 17:52:47.237] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-22 17:52:47.242] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
+[2025-08-22 17:52:47.251] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-22 17:52:48.223] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-22 17:52:48.233] [undefined] GET(/:id/stats): try to get stats
+[2025-08-22 17:52:48.237] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-22 17:52:48.247] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-22 17:52:49.012] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 17:52:49.016] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-22 17:53:03.301] [undefined] POST(/): try to upload video with status undefined
+[2025-08-22 17:53:03.307] [undefined] POST(/): successfully uploaded video with status 200
+[2025-08-22 17:53:03.417] [undefined] POST(/thumbnail): try to add thumbnail to video 2
+[2025-08-22 17:53:03.423] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200
+[2025-08-22 17:53:03.445] [undefined] PUT(/:id/tags): try to add tags to video 2
+[2025-08-22 17:53:03.458] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200
+[2025-08-22 17:53:12.167] [undefined] GET(/:id): try to get video 1
+[2025-08-22 17:53:12.171] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-22 17:53:12.184] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-22 17:53:12.201] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-22 17:53:12.228] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-22 17:53:12.255] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-22 17:53:12.300] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-22 17:53:14.457] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 17:53:14.462] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-22 17:53:14.466] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-22 17:53:14.471] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-22 17:53:14.482] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-22 17:53:15.772] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-22 17:53:15.783] [undefined] GET(/:id/stats): try to get stats
+[2025-08-22 17:53:15.789] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-22 17:53:15.800] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-22 17:53:16.871] [undefined] GET(/:id/likes/day): try to get likes per day
+[2025-08-22 17:53:16.882] [undefined] GET(/:id): try to get video 2
+[2025-08-22 17:53:16.886] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
+[2025-08-22 17:53:16.897] [undefined] GET(/:id): successfully get video 2 with status 200
+[2025-08-22 17:53:19.714] [undefined] PUT(/:id/tags): try to add tags to video 2
+[2025-08-22 17:53:19.724] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200
+[2025-08-22 17:53:23.383] [undefined] PUT(/:id/tags): try to add tags to video 2
+[2025-08-22 17:53:23.393] [undefined] PUT(/:id/tags): Tag wankil studio already exists for video 2 with status 200
+[2025-08-22 17:53:23.400] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200
+[2025-08-22 20:37:48.060] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-22 20:37:48.067] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-22 20:37:48.072] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-22 20:37:48.077] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-22 20:37:48.087] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-22 20:37:52.902] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-22 20:37:52.914] [undefined] GET(/:id/stats): try to get stats
+[2025-08-22 20:37:52.919] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-22 20:37:52.928] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-22 20:37:58.472] [undefined] PUT(/:id): try to update channel with id 1
+[2025-08-22 20:37:58.478] [undefined] PUT(/:id): Successfully updated channel with status 200
+[2025-08-22 20:40:57.463] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-22 20:40:57.475] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-22 20:40:57.491] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
+[2025-08-22 20:40:57.495] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 1 with status 200
+[2025-08-22 20:41:04.433] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1
+[2025-08-22 20:41:04.446] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200
+[2025-08-22 20:42:05.622] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-22 20:42:05.633] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-22 20:42:05.685] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
+[2025-08-22 20:42:05.690] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
+[2025-08-23 10:58:17.412] [undefined] GET(/:id): try to get video 1
+[2025-08-23 10:58:17.417] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-23 10:58:17.430] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-23 10:58:17.449] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-23 10:58:17.462] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-23 10:58:17.490] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-23 10:58:17.503] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-23 10:58:36.017] [undefined] GET(/:id/like): try to toggle like on video 1
+[2025-08-23 10:58:36.027] [undefined] GET(/:id/like): no likes found adding likes for video 1 with status 200
+[2025-08-23 10:58:41.944] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-23 10:58:41.949] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-23 10:58:41.953] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-23 10:58:41.958] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-23 10:58:41.967] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-23 10:58:47.879] [undefined] PUT(/:id): try to update user 1
+[2025-08-23 10:58:47.883] [undefined] PUT(/:id): failed to update profile picture with status 500
+[2025-08-23 11:11:35.296] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-23 11:11:35.302] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-23 11:11:35.306] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-23 11:11:35.311] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-23 11:11:35.323] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-23 11:11:38.491] [undefined] GET(/:id): try to get video 1
+[2025-08-23 11:11:38.519] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-23 11:11:38.531] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-23 11:11:38.551] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-23 11:11:38.568] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-23 11:11:38.606] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-23 11:11:38.623] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-23 11:11:42.769] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-23 11:11:42.773] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-23 11:11:42.824] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-23 11:11:42.829] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-23 11:11:42.837] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 08:39:21.217] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 08:39:21.222] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
+[2025-08-24 08:39:21.226] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 08:39:21.231] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
+[2025-08-24 08:39:21.239] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 08:39:33.320] [undefined] POST(/): try to create new channel with owner 1 and name Astri4-4
+[2025-08-24 08:39:33.324] [undefined] POST(/): Successfully created new channel with name Astri4-4 with status 200
+[2025-08-24 08:39:33.422] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 08:39:33.427] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 08:39:34.528] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-24 08:39:34.541] [undefined] GET(/:id/stats): try to get stats
+[2025-08-24 08:39:34.548] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-24 08:39:34.558] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-24 08:39:44.708] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 08:39:44.712] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 08:40:20.863] [undefined] POST(/): try to upload video with status undefined
+[2025-08-24 08:40:20.868] [undefined] POST(/): successfully uploaded video with status 200
+[2025-08-24 08:40:20.975] [undefined] POST(/thumbnail): try to add thumbnail to video 1
+[2025-08-24 08:40:20.980] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200
+[2025-08-24 08:40:21.090] [undefined] PUT(/:id/tags): try to add tags to video 1
+[2025-08-24 08:40:21.098] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200
+[2025-08-24 08:42:13.079] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 08:42:13.084] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
+[2025-08-24 08:42:13.088] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 08:42:13.092] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 08:42:13.102] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 08:42:14.233] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-24 08:42:14.243] [undefined] GET(/:id/stats): try to get stats
+[2025-08-24 08:42:14.248] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-24 08:42:14.256] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-24 08:42:15.169] [undefined] GET(/:id/likes/day): try to get likes per day
+[2025-08-24 08:42:15.181] [undefined] GET(/:id): try to get video 1
+[2025-08-24 08:42:15.186] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
+[2025-08-24 08:42:15.199] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 08:42:18.349] [undefined] PUT(/:id/tags): try to add tags to video 1
+[2025-08-24 08:42:18.359] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200
+[2025-08-24 09:09:41.627] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 09:09:41.632] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 09:09:41.636] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
+[2025-08-24 09:09:41.641] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 09:09:41.650] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 09:09:46.047] [undefined] POST(/): Playlist created with id 2 with status 200
+[2025-08-24 09:09:46.102] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 09:09:49.883] [undefined] POST(/): Playlist created with id 3 with status 200
+[2025-08-24 09:09:49.901] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 09:11:01.101] [undefined] GET(/:id): try to get video 1
+[2025-08-24 09:11:01.106] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 09:11:01.117] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 09:11:01.135] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-24 09:11:01.150] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-24 09:11:01.215] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-24 09:11:01.227] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-24 09:11:03.606] [undefined] POST(/:id): Video added to playlist with id 1 with status 200
+[2025-08-24 09:34:20.753] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 09:34:20.757] [undefined] GET(/:id): try to get video 1
+[2025-08-24 09:34:20.769] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 09:34:20.794] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-24 09:34:20.808] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-24 09:34:20.863] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-24 09:34:20.874] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-24 09:54:38.736] [undefined] GET(/:id): failed due to invalid values with status 400
+[2025-08-24 09:54:44.233] [undefined] GET(/:id): failed due to invalid values with status 400
+[2025-08-24 09:55:34.639] [undefined] GET(/see-later): Error retrieving 'See Later' playlist: bind message supplies 1 parameters, but prepared statement "" requires 0 with status 500
+[2025-08-24 09:56:07.897] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 09:57:45.228] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 09:58:20.325] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 09:59:01.147] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 09:59:13.221] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 09:59:23.788] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 10:26:50.298] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 10:26:50.302] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 10:26:50.308] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 10:26:50.315] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 10:26:50.324] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 10:26:52.180] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-24 10:26:52.191] [undefined] GET(/:id/stats): try to get stats
+[2025-08-24 10:26:52.196] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-24 10:26:52.204] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-24 10:26:54.334] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 10:26:54.338] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 10:27:16.503] [undefined] POST(/): try to upload video with status undefined
+[2025-08-24 10:27:16.534] [undefined] POST(/): successfully uploaded video with status 200
+[2025-08-24 10:27:16.645] [undefined] POST(/thumbnail): try to add thumbnail to video 2
+[2025-08-24 10:27:16.650] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200
+[2025-08-24 10:27:16.675] [undefined] PUT(/:id/tags): try to add tags to video 2
+[2025-08-24 10:27:16.685] [undefined] PUT(/:id/tags): Tag wankil already exists for video 2 with status 200
+[2025-08-24 10:27:16.691] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200
+[2025-08-24 10:27:19.733] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 10:56:34.988] [undefined] GET(/:id): try to get video 2
+[2025-08-24 10:56:34.991] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 10:56:35.003] [undefined] GET(/:id): successfully get video 2 with status 200
+[2025-08-24 10:56:35.025] [undefined] GET(/:id/similar): try to get similar videos for video 2
+[2025-08-24 10:56:35.042] [undefined] GET(/:id/similar): successfully get similar videos for video 2 with status 200
+[2025-08-24 10:56:35.146] [undefined] GET(/:id/views): try to add views for video 2
+[2025-08-24 10:56:35.187] [undefined] GET(/:id/views): successfully added views for video 2 with status 200
+[2025-08-24 10:56:41.980] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 12:42:15.221] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 12:42:18.258] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 12:42:18.262] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 12:42:18.266] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 12:42:18.271] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 12:42:18.279] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 12:42:18.806] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200
+[2025-08-24 12:42:19.274] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 12:42:19.278] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 12:42:19.282] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 12:42:19.287] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 12:42:19.296] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 12:42:19.928] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 12:48:46.238] [undefined] GET(/:id): try to get video 2
+[2025-08-24 12:48:46.242] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 12:48:46.254] [undefined] GET(/:id): successfully get video 2 with status 200
+[2025-08-24 12:48:46.271] [undefined] GET(/:id/similar): try to get similar videos for video 2
+[2025-08-24 12:48:46.283] [undefined] GET(/:id/similar): successfully get similar videos for video 2 with status 200
+[2025-08-24 12:48:46.326] [undefined] GET(/:id/views): try to add views for video 2
+[2025-08-24 12:48:46.337] [undefined] GET(/:id/views): successfully added views for video 2 with status 200
+[2025-08-24 12:48:47.143] [undefined] GET(/:id/like): try to toggle like on video 2
+[2025-08-24 12:48:47.153] [undefined] GET(/:id/like): no likes found adding likes for video 2 with status 200
+[2025-08-24 12:48:48.072] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 12:48:48.816] [undefined] GET(/:id): try to get video 2
+[2025-08-24 12:48:48.819] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 12:48:48.830] [undefined] GET(/:id): successfully get video 2 with status 200
+[2025-08-24 12:48:48.851] [undefined] GET(/:id/similar): try to get similar videos for video 2
+[2025-08-24 12:48:48.865] [undefined] GET(/:id/similar): successfully get similar videos for video 2 with status 200
+[2025-08-24 12:48:48.898] [undefined] GET(/:id/views): try to add views for video 2
+[2025-08-24 12:48:48.907] [undefined] GET(/:id/views): successfully added views for video 2 with status 200
+[2025-08-24 12:48:49.852] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 12:48:50.610] [undefined] GET(/:id): try to get video 1
+[2025-08-24 12:48:50.613] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 12:48:50.624] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 12:48:50.644] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-24 12:48:50.658] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-24 12:48:50.704] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-24 12:48:50.713] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-24 12:48:51.443] [undefined] GET(/:id/like): try to toggle like on video 1
+[2025-08-24 12:48:51.452] [undefined] GET(/:id/like): no likes found adding likes for video 1 with status 200
+[2025-08-24 12:48:51.747] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 12:52:54.497] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 12:52:54.503] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 12:52:54.507] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 12:52:54.512] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 12:52:54.523] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 12:52:56.045] [undefined] GET(/:id): try to get channel with id 1
+[2025-08-24 12:52:56.055] [undefined] GET(/:id/stats): try to get stats
+[2025-08-24 12:52:56.060] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
+[2025-08-24 12:52:56.067] [undefined] GET(/:id/stats): Successfully get stats with status 200
+[2025-08-24 12:52:56.859] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 12:52:56.864] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 12:53:15.671] [undefined] POST(/): try to upload video with status undefined
+[2025-08-24 12:53:15.678] [undefined] POST(/): successfully uploaded video with status 200
+[2025-08-24 12:53:15.783] [undefined] POST(/thumbnail): try to add thumbnail to video 3
+[2025-08-24 12:53:15.789] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200
+[2025-08-24 12:53:15.816] [undefined] PUT(/:id/tags): try to add tags to video 3
+[2025-08-24 12:53:15.837] [undefined] PUT(/:id/tags): Tag wankil already exists for video 3 with status 200
+[2025-08-24 12:53:15.842] [undefined] PUT(/:id/tags): successfully added tags to video 3 with status 200
+[2025-08-24 12:53:18.269] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 14:31:53.485] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 14:33:34.728] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 14:34:06.863] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 14:56:14.294] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 14:56:45.221] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 14:56:52.544] [undefined] GET(/:id): try to get video 3
+[2025-08-24 14:56:52.548] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 14:56:52.607] [undefined] GET(/:id): successfully get video 3 with status 200
+[2025-08-24 14:56:52.625] [undefined] GET(/:id/similar): try to get similar videos for video 3
+[2025-08-24 14:56:52.640] [undefined] GET(/:id/similar): successfully get similar videos for video 3 with status 200
+[2025-08-24 14:56:52.708] [undefined] GET(/:id/views): try to add views for video 3
+[2025-08-24 14:56:52.719] [undefined] GET(/:id/views): successfully added views for video 3 with status 200
+[2025-08-24 14:56:53.526] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:00:04.764] [undefined] GET(/:id): failed due to invalid values with status 400
+[2025-08-24 15:00:04.774] [undefined] GET(/:id/similar): failed due to invalid values with status 400
+[2025-08-24 15:00:04.783] [undefined] GET(/:id/views): failed due to invalid values with status 400
+[2025-08-24 15:00:04.792] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 15:00:07.941] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:00:09.836] [undefined] GET(/:id): failed due to invalid values with status 400
+[2025-08-24 15:00:09.848] [undefined] GET(/:id/similar): failed due to invalid values with status 400
+[2025-08-24 15:00:09.858] [undefined] GET(/:id/views): failed due to invalid values with status 400
+[2025-08-24 15:00:09.869] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 15:00:13.276] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:00:14.341] [undefined] GET(/:id): try to get video 1
+[2025-08-24 15:00:14.345] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 15:00:14.356] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 15:00:14.379] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-24 15:00:14.396] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-24 15:00:14.443] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-24 15:00:14.452] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-24 15:00:15.454] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:00:17.865] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 15:00:17.868] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 15:00:17.872] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 15:00:17.877] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 15:00:17.886] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 15:00:18.411] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200
+[2025-08-24 15:00:19.030] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 15:00:19.036] [undefined] GET(/:id): try to get video 1
+[2025-08-24 15:00:19.047] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200
+[2025-08-24 15:00:19.052] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 15:00:19.072] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-24 15:00:19.086] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-24 15:00:19.122] [undefined] GET(/:id/views): try to add views for video 1
+[2025-08-24 15:00:19.131] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
+[2025-08-24 15:00:21.743] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200
+[2025-08-24 15:00:22.774] [undefined] DELETE(/:id/video/:videoId): Video deleted from playlist with id 1 with status 200
+[2025-08-24 15:00:22.844] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200
+[2025-08-24 15:00:24.102] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:00:26.909] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:00:35.516] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 15:19:55.359] [undefined] GET(/:id): failed due to invalid values with status 400
+[2025-08-24 15:19:55.364] [undefined] GET(/:id/stats): failed due to invalid values with status 400
+[2025-08-24 15:20:00.400] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:16:06.897] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:30:27.130] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:30:53.040] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:31:03.350] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:31:40.409] [undefined] GET(/:id): try to get video 1
+[2025-08-24 18:31:40.421] [undefined] GET(/:id): successfully get video 1 with status 200
+[2025-08-24 18:31:40.483] [undefined] GET(/:id/similar): try to get similar videos for video 1
+[2025-08-24 18:31:40.493] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
+[2025-08-24 18:32:05.446] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 18:32:05.450] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 18:32:05.454] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 18:32:05.462] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 18:32:05.474] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 18:32:06.968] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 18:32:06.972] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 18:32:06.976] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 18:32:06.981] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 18:32:06.990] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 18:32:07.865] [undefined] GET(/:id/channel): try to retrieve channel of user 1
+[2025-08-24 18:32:07.869] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
+[2025-08-24 18:32:07.873] [undefined] GET(/:id/history): try to retrieve history of user 1
+[2025-08-24 18:32:07.880] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
+[2025-08-24 18:32:07.888] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
+[2025-08-24 18:32:10.777] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:32:16.357] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:32:17.190] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:32:17.648] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:32:25.811] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:32:39.691] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:32:49.962] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:33:46.919] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:01.427] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:25.992] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:42.706] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:43.614] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:44.171] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:48.277] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:34:54.944] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
+[2025-08-24 18:35:28.834] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
diff --git a/backend/requests/top-tags-videos.http b/backend/requests/top-tags-videos.http
new file mode 100644
index 0000000..1891010
--- /dev/null
+++ b/backend/requests/top-tags-videos.http
@@ -0,0 +1,17 @@
+### Get all videos from the three most watched tags
+GET http://127.0.0.1:8000/api/videos/top-tags/videos
+
+### Alternative localhost URL
+GET http://localhost:8000/api/videos/top-tags/videos
+
+### With frontend URL (if using nginx proxy)
+GET http://localhost/api/videos/top-tags/videos
+
+###
+# This endpoint returns:
+# - topTags: The 3 most used tags with their usage count
+# - videos: All public videos that have any of these top 3 tags
+# - totalVideos: Count of videos returned
+#
+# Videos are ordered by popularity score (calculated from views, likes, comments)
+# and then by release date (newest first)
diff --git a/backend/server.js b/backend/server.js
index ffd5df4..1b3aeb0 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -19,6 +19,7 @@ import { Strategy as GitHubStrategy } from "passport-github2";
console.clear();
dotenv.config();
+console.log(process.env)
const app = express();
diff --git a/create_db.sql b/create_db.sql
new file mode 100644
index 0000000..d4129fd
--- /dev/null
+++ b/create_db.sql
@@ -0,0 +1,4 @@
+
+ CREATE ROLE 'sacha' WITH PASSWORD 'sacha';
+ CREATE DATABASE 'sacha' OWNER 'sacha';
+
diff --git a/db.sql b/db.sql
new file mode 100644
index 0000000..d4129fd
--- /dev/null
+++ b/db.sql
@@ -0,0 +1,4 @@
+
+ CREATE ROLE 'sacha' WITH PASSWORD 'sacha';
+ CREATE DATABASE 'sacha' OWNER 'sacha';
+
diff --git a/default.conf b/default.conf
new file mode 100644
index 0000000..65c5154
--- /dev/null
+++ b/default.conf
@@ -0,0 +1,68 @@
+server {
+ server_name localhost;
+ listen 80;
+
+ return 301 https://$host$request_uri;
+}
+
+server {
+ server_name localhost;
+ listen 443 ssl;
+
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+
+ # Allow large file uploads for videos (up to 500MB)
+ client_max_body_size 500M;
+
+ ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
+ ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
+
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+
+ # API routes - proxy to backend (MUST come before static file rules)
+ location /api/ {
+ # Handle preflight OPTIONS requests
+ if ($request_method = 'OPTIONS') {
+ add_header 'Access-Control-Allow-Origin' '$http_origin' always;
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
+ add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Max-Age' 1728000 always;
+ add_header 'Content-Type' 'text/plain; charset=utf-8' always;
+ add_header 'Content-Length' 0 always;
+ return 204;
+ }
+
+ proxy_pass http://localhost:8000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Origin $http_origin;
+ proxy_buffering off;
+
+ # CORS headers for actual requests
+ add_header 'Access-Control-Allow-Origin' '$http_origin' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+
+ # Also set timeout for large uploads
+ proxy_read_timeout 300s;
+ proxy_send_timeout 300s;
+ }
+
+ # Static assets - NO CACHING for development
+ location ~* ^/(?!api/).*\.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header Pragma "no-cache";
+ add_header Expires "0";
+ try_files $uri =404;
+ }
+
+ # Handle React Router - all other routes should serve index.html
+ location / {
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ try_files $uri $uri/ /index.html;
+ }
+}
\ No newline at end of file
diff --git a/deploy.sh b/deploy.sh
new file mode 100755
index 0000000..93566dc
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+if [[ $EUID -ne 0 ]]; then
+ echo "Ce script doit être lancé avec les droits root"
+ exit 1
+fi
+
+pwd=$PWD
+
+# Environment variables
+echo -e "\033[0;32m Création des variables d'environnement \033[0m"
+echo -e "\033[1;33m Nom d'utilisateur de la base de données \033[0m"
+read POSTGRES_USER
+echo -e "\033[1;33m Mot de passe de la base de données \033[0m"
+read POSTGRES_PASSWORD
+echo -e "\033[1;33m Nom de la base de données \033[0m"
+read POSTGRES_DB
+
+echo -e "\033[1;33m Clé d'encryption des JWTs \033[0m"
+read JWT_SECRET
+
+echo -e "\033[1;33m Utilisateur Gmail \033[0m"
+read GMAIL_USER
+echo -e "\033[1;33m Mot de passe de l'application Gmail \033[0m"
+read GMAIL_PASSWORD
+
+echo -e "\033[1;33m Url du site web \033[0m"
+read FRONTEND_URL
+
+echo -e "\033[1;33m Client ID de l'application Github OAuth \033[0m"
+read GITHUB_ID
+echo -e "\033[1;33m Client secret de l'application Github OAuth \033[0m"
+read GITHUB_PASSWORD
+
+touch $pwd/backend/.env
+echo "
+ POSTGRES_USER=$POSTGRES_USER
+ POSTGRES_PASSWORD=$POSTGRES_PASSWORD
+ POSTGRES_DB=$POSTGRES_DB
+ POSTGRES_HOST=localhost
+
+ BACKEND_PORT=8000
+
+ JWT_SECRET=$JWT_SECRET
+
+ LOG_FILE=/var/log/freetube/access.log
+
+ GMAIL_USER=$GMAIL_USER
+ GMAIL_PASSWORD=$GMAIL_PASSWORD
+
+ FRONTEND_URL=$FRONTEND_URL
+
+ GITHUB_ID=$GITHUB_ID
+ GITHUB_SECRET=$GITHUB_PASSWORD
+" > $pwd/backend/.env
+
+# Install dependencies (NodeJS 22/PostgreSQL/Nginx)
+echo -e "\033[0;32m Installation des dépendances... \033[0m"
+apt install postgresql nginx openssl curl &&
+
+# Install NVM and NodeJS & NPM
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash &&
+export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
+[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+\. "$HOME/.nvm/nvm.sh" &&
+nvm install 22 &&
+
+# Install node packages for backend
+echo -e "\033[0;32m Installation des paquets NodeJS \033[0m"
+cd $pwd/backend && npm install &&
+cd $pwd/frontend && npm install &&
+
+echo "Construction du frontend"
+npx vite build
+cd $pwd
+
+# Create Nginx configuration
+echo -e "\033[0;32m Création de la configuration Nginx \033[0m"
+mkdir /etc/nginx/ssl/
+openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx-selfsigned.key -out /etc/nginx/ssl/nginx-selfsigned.crt
+touch /etc/nginx/conf.d/freetube.conf
+cat $pwd/default.conf > /etc/nginx/conf.d/freetube.conf
+
+echo -e "\033[0;32m Copie des fichiers vers /usr/share/nginx/html \033[0m"
+rm /usr/share/nginx/html/index.html
+mv $pwd/frontend/dist/* /usr/share/nginx/html/
+
+# Create PostgreSQL database
+echo -e "\033[0;32m Création de l'utilisateur $POSTGRES_USER \033[0m"
+
+sudo -u postgres psql -c "CREATE USER $POSTGRES_USER WITH PASSWORD '$POSTGRES_PASSWORD';"
+sudo -u postgres psql -c "CREATE ROLE $POSTGRES_USER WITH PASSWORD '$POSTGRES_PASSWORD';"
+sudo -u postgres psql -c "CREATE DATABASE $POSTGRES_DB OWNER $POSTGRES_USER;"
+
+# Add log file
+mkdir /var/log/freetube
+touch /var/log/freetube/access.log
+systemctl enable nginx
+systemctl enable postgresql
+systemctl start nginx
+systemctl start postgresql
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 1f2014d..9daba49 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -9,10 +9,10 @@ services:
ports:
- "8000:8000"
environment:
- DB_USER: ${POSTGRES_USER}
- DB_NAME: ${POSTGRES_DB}
- DB_HOST: db
- DB_PASSWORD: ${POSTGRES_PASSWORD}
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_DB: ${POSTGRES_DB}
+ POSTGRES_HOST: db
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
LOG_FILE: ${LOG_FILE}
PORT: ${BACKEND_PORT}
diff --git a/freetube.sh b/freetube.sh
new file mode 100755
index 0000000..4a4af30
--- /dev/null
+++ b/freetube.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+node ./backend/server.js
\ No newline at end of file
diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx
index 3b64e47..195a98f 100644
--- a/frontend/src/components/Navbar.jsx
+++ b/frontend/src/components/Navbar.jsx
@@ -7,6 +7,7 @@ export default function Navbar({ isSearchPage = false, alerts = [], onCloseAlert
const { user, logout, isAuthenticated } = useAuth();
const [searchQuery, setSearchQuery] = useState('');
const [internalAlerts, setInternalAlerts] = useState([]);
+ const [isNavbarOpen, setIsNavbarOpen] = useState(false);
const navigate = useNavigate();
const handleLogout = () => {
@@ -40,70 +41,167 @@ export default function Navbar({ isSearchPage = false, alerts = [], onCloseAlert
return (
<>
-
+ {creator.description.slice(0, 100) + (creator.description.length > 100 ? '...' : '')} +