From 98550c0f221fef0d50809785474f7229ffec6902 Mon Sep 17 00:00:00 2001 From: Astri4-4 Date: Sun, 20 Jul 2025 20:59:00 +0000 Subject: [PATCH 1/6] NEARLY FINISH profile page --- .../app/controllers/playlist.controller.js | 13 +- backend/app/controllers/user.controller.js | 90 +++- backend/app/routes/user.route.js | 16 +- backend/logs/access.log | 386 ++++++++++++++++++ backend/requests/channel.http | 6 +- backend/requests/playlist.http | 11 + backend/requests/user.http | 6 +- docker-compose.yaml | 2 +- frontend/src/assets/img/default_thumbnail.png | Bin 0 -> 27525 bytes frontend/src/components/PlaylistCard.jsx | 14 + frontend/src/pages/Account.jsx | 181 +++++++- 11 files changed, 697 insertions(+), 28 deletions(-) create mode 100644 backend/requests/playlist.http create mode 100644 frontend/src/assets/img/default_thumbnail.png create mode 100644 frontend/src/components/PlaylistCard.jsx diff --git a/backend/app/controllers/playlist.controller.js b/backend/app/controllers/playlist.controller.js index 3bc2cb8..b300572 100644 --- a/backend/app/controllers/playlist.controller.js +++ b/backend/app/controllers/playlist.controller.js @@ -43,7 +43,18 @@ export async function getByUser(req, res) { const logger = req.body.logger; const client = await getClient(); - const query = `SELECT * FROM playlists WHERE owner = $1`; + const query = ` + SELECT * + FROM ( + SELECT playlists.id, playlists.name, playlists.owner, v.thumbnail as thumbnail, + ROW_NUMBER() OVER (PARTITION BY playlists.id ORDER BY pt.id DESC) as rn + FROM playlists + LEFT JOIN playlist_elements pt on playlist = playlists.id + LEFT JOIN videos v on pt.video = v.id + WHERE owner = $1 + ) ranked + WHERE rn = 1 OR rn IS NULL + `; try { const result = await client.query(query, [id]); diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 69c1e69..1193315 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -35,8 +35,13 @@ export async function register(req, res) { const client = await getClient(); - const query = `INSERT INTO users (email, username, password, picture) VALUES ($1, $2, $3, $4)`; - await client.query(query, [user.email, user.username, user.password, user.picture]); + const query = `INSERT INTO users (email, username, password, picture) VALUES ($1, $2, $3, $4) RETURNING id`; + const result = await client.query(query, [user.email, user.username, user.password, user.picture]); + + const id = result.rows[0].id; + + const playlistQuery = `INSERT INTO playlists (name, owner) VALUES ('A regarder plus tard', $2)`; + await client.query(playlistQuery, [id]); console.log("Successfully registered"); client.end(); @@ -167,15 +172,19 @@ export async function update(req, res) { } } - const isPasswordValid = await bcrypt.compare(req.body.password, userInBase.password); + if (user.password) { + const isPasswordValid = await bcrypt.compare(req.body.password, userInBase.password); - if (!isPasswordValid) { - user.password = await bcrypt.hash(req.body.password, 10); + if (!isPasswordValid) { + user.password = await bcrypt.hash(req.body.password, 10); + } else { + user.password = userInBase.password; + } } else { user.password = userInBase.password; } - const updateQuery = `UPDATE users SET email = $1, username = $2, password = $3, picture = $4 WHERE id = $5`; + const updateQuery = `UPDATE users SET email = $1, username = $2, password = $3, picture = $4 WHERE id = $5 RETURNING id, email, username, picture`; const result = await client.query(updateQuery, [user.email, user.username, user.password, user.picture, id]); logger.write("successfully updated user " + id, 200); res.status(200).send({user: result.rows[0]}); @@ -195,4 +204,73 @@ export async function deleteUser(req, res) { await client.query(query, [id]); logger.write("successfully deleted user " + id); res.status(200).json({message: 'User deleted'}); +} + +export async function getChannel(req, res) { + const id = req.params.id; + const client = await getClient(); + const logger = req.body.logger; + logger.action("try to retrieve channel of user " + id); + + const query = `SELECT * FROM channels WHERE owner = $1`; + const result = await client.query(query, [id]); + + if (!result.rows[0]) { + logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404); + res.status(404).json({error: "Channel Not Found"}); + return; + } + + logger.write("successfully retrieved channel of user " + id, 200); + res.status(200).json({channel: result.rows[0]}); +} + +export async function getHistory(req, res) { + const id = req.params.id; + const client = await getClient(); + const logger = req.body.logger; + logger.action("try to retrieve history of user " + id); + + const query = `SELECT + v.title, v.thumbnail, v.release_date, v.channel, c.name, u.picture, v.id + FROM history + JOIN public.videos v on history.video = v.id + JOIN public.channels c on v.channel = c.id + JOIN public.users u on c.owner = u.id + WHERE user_id = $1 + LIMIT 10 + `; + + const result = await client.query(query, [id]); + + const videos = []; + + for (const video of result.rows) { + // GET VIDEO VIEW COUNT + const videoQuery = `SELECT COUNT(*) as view_count FROM history WHERE video = $1`; + const videoResult = await client.query(videoQuery, [video.channel]); + const videoToAdd = { + title: video.title, + thumbnail: video.thumbnail, + release_date: video.release_date, + views: videoResult.rows[0].view_count, + id: video.id, + creator: { + id: video.channel, + name: video.name, + profilePicture: video.picture + }, + user_picture: video.picture + }; + videos.push(videoToAdd); + } + + if (!result.rows[0]) { + logger.write("failed to retrieve history of user " + id + " because it doesn't exist", 404); + res.status(404).json({error: "History Not Found"}); + return; + } + + logger.write("successfully retrieved history of user " + id, 200); + res.status(200).json(videos); } \ No newline at end of file diff --git a/backend/app/routes/user.route.js b/backend/app/routes/user.route.js index 2b10c5b..75928f9 100644 --- a/backend/app/routes/user.route.js +++ b/backend/app/routes/user.route.js @@ -1,5 +1,13 @@ import {Router} from "express"; -import {register, login, getById, getByUsername, update, deleteUser} from "../controllers/user.controller.js"; +import { + register, + login, + getById, + getByUsername, + update, + deleteUser, + getChannel, getHistory +} from "../controllers/user.controller.js"; import { UserRegister, doEmailExists, @@ -35,4 +43,10 @@ router.put("/:id", [addLogger, isTokenValid, User.id, UserRegister.email, UserRe // DELETE USER router.delete("/:id", [addLogger, isTokenValid, User.id, validator, doUserExists, isOwner], deleteUser); +// GET USER CHANNEL +router.get("/:id/channel", [addLogger, isTokenValid, User.id, validator], getChannel); + +// GET USER HISTORY +router.get("/:id/history", [addLogger, isTokenValid, User.id, validator], getHistory); + export default router; \ No newline at end of file diff --git a/backend/logs/access.log b/backend/logs/access.log index 0863867..9feeea6 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -832,3 +832,389 @@ [2025-07-19 23:05:34.328] [undefined] GET(/:id/views): try to add views for video 3 [2025-07-19 23:05:34.333] [undefined] GET(/:id/views): successfully added views for video 3 with status 200 +[2025-07-20 19:33:35.299] [undefined] GET(/:id/channel): Invalid token with status 401 +[2025-07-20 19:35:03.964] [undefined] GET(/:id/channel): Invalid token with status 401 +[2025-07-20 19:35:18.137] [undefined] POST(/login): try to login with username 'astria' +[2025-07-20 19:35:18.199] [undefined] POST(/login): Successfully logged in with status 200 +[2025-07-20 19:35:27.429] [undefined] GET(/:id/channel): Invalid token with status 401 +[2025-07-20 19:35:45.291] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:35:45.293] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:37:19.690] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:37:19.691] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:39:50.682] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:39:50.683] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:40:09.868] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:40:09.869] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:40:22.261] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:40:22.262] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:40:35.388] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:40:35.390] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:40:41.890] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:40:41.892] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:41:00.392] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:41:00.393] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:41:05.286] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:41:05.288] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:41:17.319] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:41:17.321] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:41:26.059] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:41:26.061] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:41:37.603] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:41:37.604] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:02.913] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:02.915] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:13.514] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:13.516] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:25.385] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:25.386] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:26.021] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:26.022] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:26.448] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:26.450] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:37.675] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:37.676] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:42:50.565] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:42:50.566] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:43:05.604] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:43:05.606] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:43:12.166] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:43:12.167] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:44:00.052] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:44:00.053] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:44:03.413] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:44:03.414] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-07-20 19:45:44.513] [undefined] POST(/): failed because he wasn't the owner of the user with status 403 +[2025-07-20 19:46:05.183] [undefined] POST(/): try to create new channel with owner 1 and name Arcanas +[2025-07-20 19:46:05.184] [undefined] POST(/): Successfully created new channel with name Arcanas with status 200 +[2025-07-20 19:46:07.316] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:46:07.318] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:46:33.406] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:46:33.407] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:46:40.771] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:46:40.773] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:47:12.944] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:47:12.946] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:47:24.237] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:47:24.239] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:47:26.535] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:47:26.537] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:47:35.076] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:47:35.077] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:47:41.727] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:47:41.729] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:48:25.815] [undefined] POST(/): try to upload video with status undefined +[2025-07-20 19:48:25.818] [undefined] POST(/): successfully uploaded video with status 200 +[2025-07-20 19:48:28.228] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:48:28.229] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:48:40.072] [undefined] POST(/thumbnail): failed because user is not owner with status 403 +[2025-07-20 19:48:44.641] [undefined] POST(/thumbnail): try to add thumbnail to video 1 +[2025-07-20 19:48:44.643] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-07-20 19:48:49.206] [undefined] GET(/:id): try to get video 1 +[2025-07-20 19:48:49.214] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-07-20 19:48:49.223] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-07-20 19:48:49.229] [undefined] GET(/:id/similar): No tags found for video 1 with status 404 +[2025-07-20 19:48:49.241] [undefined] GET(/:id/views): try to add views for video 1 +[2025-07-20 19:48:49.249] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-07-20 19:48:53.255] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:48:53.257] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:52:02.652] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:52:02.655] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:52:48.499] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 19:52:48.501] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 19:53:49.379] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:53:49.380] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 19:53:49.382] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:53:49.383] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 19:56:57.396] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:56:57.397] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 19:56:57.399] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:56:57.401] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 19:57:46.670] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:57:46.671] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 19:57:46.672] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:57:46.673] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 19:58:47.885] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:58:47.886] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 19:58:47.887] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:58:47.888] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 19:58:52.839] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 19:58:52.840] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 19:58:52.841] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 19:58:52.843] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:00:18.041] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:00:18.042] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:00:18.044] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:00:18.045] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:00:18.052] [undefined] GET(/user/:id): No playlists found for user with id 1 with status 404 +[2025-07-20 20:03:37.108] [undefined] POST(/): Playlist created with id 1 with status 200 +[2025-07-20 20:03:39.906] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:03:39.908] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:03:39.912] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:03:39.913] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:03:39.918] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:11:24.718] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:11:24.720] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:11:24.722] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:11:24.725] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:11:24.732] [undefined] GET(/user/:id): No playlists found for user with id 1 with status 404 +[2025-07-20 20:12:16.190] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:12:16.192] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:12:16.194] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:12:16.197] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:12:16.204] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:23:33.055] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:23:33.057] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:23:33.058] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:23:33.060] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:23:33.066] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:24:48.901] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:24:48.902] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:24:48.905] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:24:48.906] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:24:48.914] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:25:21.836] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:25:21.838] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:25:21.839] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:25:21.841] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:25:21.847] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:25:51.530] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:25:51.531] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:25:51.531] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:25:51.533] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:25:51.540] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:26:08.646] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:26:08.647] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:26:08.648] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:26:08.649] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:26:08.655] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:26:20.383] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:26:20.384] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:26:20.385] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:26:20.386] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:26:20.392] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:26:56.257] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:26:56.258] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:26:56.260] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:26:56.261] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:26:56.268] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:27:10.084] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:27:10.085] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:27:10.087] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:27:10.088] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:27:10.095] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:27:20.830] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:27:20.831] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:27:20.833] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:27:20.835] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:27:20.842] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:27:47.761] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:27:47.762] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:27:47.764] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:27:47.765] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:27:47.774] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:28:02.001] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:28:02.003] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:28:02.004] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:28:02.006] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:28:02.014] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:28:08.842] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:28:08.843] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:28:08.846] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:28:08.848] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:28:08.854] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:28:30.508] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:28:30.509] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:28:30.510] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:28:30.511] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:28:30.516] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:28:41.406] [undefined] POST(/login): try to login with username 'astria' +[2025-07-20 20:28:41.454] [undefined] POST(/login): Successfully logged in with status 200 +[2025-07-20 20:28:47.226] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:28:47.227] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:28:47.227] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:28:47.229] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:28:47.235] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:29:15.892] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:29:15.892] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:29:15.894] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:29:15.896] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:29:15.904] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:31:06.851] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:31:06.853] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:31:06.855] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:31:06.857] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:31:06.864] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:31:08.350] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:31:08.352] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:31:08.353] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:31:08.355] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:31:08.362] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:31:16.188] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:31:16.190] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:31:16.192] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:31:16.195] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:31:16.200] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:32:01.914] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:32:01.916] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:32:01.917] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:32:01.919] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:32:01.924] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:32:17.682] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:32:17.684] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:32:17.686] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:32:17.688] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:32:17.696] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:32:59.642] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:32:59.644] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:32:59.646] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:32:59.648] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:32:59.653] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:33:52.972] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:33:52.972] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:33:52.974] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:33:52.976] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:33:52.982] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:36:04.373] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:36:04.374] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:36:04.376] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:36:04.377] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:36:04.384] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:36:18.347] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:36:18.350] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:36:18.352] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:36:18.354] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:36:18.363] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:37:37.444] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:37:37.446] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:37:37.456] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:37:37.460] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:37:37.469] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:37:41.175] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-07-20 20:37:41.179] [undefined] GET(/:id/similar): failed due to invalid values with status 400 +[2025-07-20 20:37:41.183] [undefined] GET(/:id/views): failed due to invalid values with status 400 +[2025-07-20 20:37:42.993] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:37:42.994] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:37:42.997] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:37:42.999] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:37:43.003] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:38:25.527] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:38:25.529] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:38:25.532] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:38:25.535] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:38:25.543] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:38:26.205] [undefined] GET(/:id): try to get video 1 +[2025-07-20 20:38:26.213] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-07-20 20:38:26.223] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-07-20 20:38:26.239] [undefined] GET(/:id/similar): No tags found for video 1 with status 404 +[2025-07-20 20:38:26.254] [undefined] GET(/:id/views): try to add views for video 1 +[2025-07-20 20:38:26.263] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-07-20 20:38:26.820] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:38:26.821] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:38:26.824] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:38:26.826] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:38:26.831] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:38:49.852] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:38:49.853] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:38:49.855] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:38:49.857] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:38:49.863] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:39:00.548] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:39:00.550] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:39:00.553] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:39:00.555] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:39:00.561] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:39:20.688] [undefined] GET(/:id): try to get video 1 +[2025-07-20 20:39:20.697] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-07-20 20:39:20.706] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-07-20 20:39:20.714] [undefined] GET(/:id/similar): No tags found for video 1 with status 404 +[2025-07-20 20:39:20.727] [undefined] GET(/:id/views): try to add views for video 1 +[2025-07-20 20:39:20.734] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-07-20 20:39:21.942] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:39:21.942] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:39:21.944] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:39:21.946] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:39:21.951] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:39:25.246] [undefined] GET(/:id): try to get video 1 +[2025-07-20 20:39:25.254] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-07-20 20:39:25.280] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-07-20 20:39:25.289] [undefined] GET(/:id/similar): No tags found for video 1 with status 404 +[2025-07-20 20:39:25.300] [undefined] GET(/:id/views): try to add views for video 1 +[2025-07-20 20:39:25.307] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-07-20 20:39:34.645] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:39:34.646] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:39:34.648] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:39:34.649] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:39:34.656] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:40:05.834] [undefined] GET(/:id): try to get video 1 +[2025-07-20 20:40:05.842] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-07-20 20:40:05.853] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-07-20 20:40:05.861] [undefined] GET(/:id/similar): No tags found for video 1 with status 404 +[2025-07-20 20:40:05.889] [undefined] GET(/:id/views): try to add views for video 1 +[2025-07-20 20:40:05.900] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-07-20 20:40:17.058] [undefined] GET(/:id/like): try to toggle like on video 1 +[2025-07-20 20:40:17.065] [undefined] GET(/:id/like): no likes found adding likes for video 1 with status 200 +[2025-07-20 20:40:19.272] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:40:19.273] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:40:19.275] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:40:19.276] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:40:19.283] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:42:28.889] [undefined] POST(/): failed due to invalid values with status 400 +[2025-07-20 20:42:40.896] [undefined] POST(/): try to upload video with status undefined +[2025-07-20 20:42:40.899] [undefined] POST(/): successfully uploaded video with status 200 +[2025-07-20 20:42:59.423] [undefined] POST(/thumbnail): try to add thumbnail to video 2 +[2025-07-20 20:42:59.425] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-07-20 20:43:04.899] [undefined] GET(/:id): try to get video 2 +[2025-07-20 20:43:04.908] [undefined] GET(/:id): successfully get video 2 with status 200 +[2025-07-20 20:43:04.917] [undefined] GET(/:id/similar): try to get similar videos for video 2 +[2025-07-20 20:43:04.924] [undefined] GET(/:id/similar): No tags found for video 2 with status 404 +[2025-07-20 20:43:04.938] [undefined] GET(/:id/views): try to add views for video 2 +[2025-07-20 20:43:04.946] [undefined] GET(/:id/views): successfully added views for video 2 with status 200 +[2025-07-20 20:43:06.587] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:43:06.589] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:43:06.591] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:43:06.594] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:43:06.600] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:43:22.392] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:43:22.394] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:43:22.395] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:43:22.398] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:43:22.404] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:46:19.020] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:46:19.021] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:46:19.023] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:46:19.025] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:46:19.032] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:46:54.686] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:46:54.687] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:46:54.689] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:46:54.691] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:46:54.699] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:47:58.417] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:47:58.418] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:47:58.421] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:47:58.423] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:47:58.429] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:48:25.448] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:48:25.449] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:48:25.449] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:48:25.451] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:49:10.898] [undefined] POST(/login): try to login with username 'sacha' +[2025-07-20 20:49:34.658] [undefined] POST(/login): try to login with username 'sacha' +[2025-07-20 20:49:34.667] [undefined] POST(/login): failed to login with status 401 +[2025-07-20 20:50:00.176] [undefined] POST(/login): try to login with username 'astria' +[2025-07-20 20:50:00.225] [undefined] POST(/login): Successfully logged in with status 200 +[2025-07-20 20:50:05.771] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:50:05.772] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:50:05.773] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:50:05.775] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:50:05.785] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:51:22.884] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-07-20 20:51:22.885] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-07-20 20:51:22.886] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-07-20 20:51:22.888] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-07-20 20:51:22.893] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-07-20 20:51:33.668] [undefined] PUT(/:id): try to update user 1 +[2025-07-20 20:51:33.710] [undefined] PUT(/:id): successfully updated user 1 with status 200 +[2025-07-20 20:51:37.656] [undefined] GET(/:id/channel): failed due to invalid values with status 400 +[2025-07-20 20:51:37.660] [undefined] GET(/:id/history): failed due to invalid values with status 400 +[2025-07-20 20:51:37.662] [undefined] GET(/user/:id): failed due to invalid values with status 400 diff --git a/backend/requests/channel.http b/backend/requests/channel.http index 940cb73..e9b8a30 100644 --- a/backend/requests/channel.http +++ b/backend/requests/channel.http @@ -1,4 +1,4 @@ -@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTI4NjQ1ODN9.yyKcb1vLhAxEftBN1Z27wV7uM1pSruVEMb4dtiqDTrg +@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMwNDAxMTh9.GNGee4BYCL-wO3I8FtXXJIc-xsLMfuOoUlBhHjQJcWo ### CREATE CHANNEL POST http://localhost:8000/api/channels @@ -6,9 +6,9 @@ Authorization: Bearer {{token}} Content-Type: application/json { - "name": "Machin", + "name": "Arcanas", "description": "kljsdfjklsdfjkl", - "owner": 2 + "owner": 1 } ### GET CHANNEL BY ID diff --git a/backend/requests/playlist.http b/backend/requests/playlist.http new file mode 100644 index 0000000..ed94895 --- /dev/null +++ b/backend/requests/playlist.http @@ -0,0 +1,11 @@ +@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMwNDAxMTh9.GNGee4BYCL-wO3I8FtXXJIc-xsLMfuOoUlBhHjQJcWo + +### ADD PLAYLIST +POST http://localhost/api/playlists +Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "name": "A regarder plus tard", + "owner": 1 +} \ No newline at end of file diff --git a/backend/requests/user.http b/backend/requests/user.http index be24e85..f46841f 100644 --- a/backend/requests/user.http +++ b/backend/requests/user.http @@ -1,4 +1,4 @@ -@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTI3ODUwNTR9.yOEsanvke0yejwbizkA33SwJMzohfn-xymetVJwSFSs +@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMwNDAxMTh9.GNGee4BYCL-wO3I8FtXXJIc-xsLMfuOoUlBhHjQJcWo ### CREATE USER POST http://localhost:8000/api/users/ @@ -42,4 +42,8 @@ Content-Type: application/json ### DELETE USER DELETE http://localhost:8000/api/users/4 +Authorization: Bearer {{token}} + +### GET HISTORY +GET http://localhost:8000/api/users/1/history/ Authorization: Bearer {{token}} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 997e29b..f2c6e24 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: PORT: ${BACKEND_PORT} volumes: - ./backend/logs:/var/log/freetube - - ./backend/app/uploads:/app/app/uploads + - ./backend:/app depends_on: - db diff --git a/frontend/src/assets/img/default_thumbnail.png b/frontend/src/assets/img/default_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2ee09423d1d983370727c041bc0428143766ef GIT binary patch literal 27525 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 B^F9Co literal 0 HcmV?d00001 diff --git a/frontend/src/components/PlaylistCard.jsx b/frontend/src/components/PlaylistCard.jsx new file mode 100644 index 0000000..c9e9156 --- /dev/null +++ b/frontend/src/components/PlaylistCard.jsx @@ -0,0 +1,14 @@ +import Default from "../assets/img/default_thumbnail.png" + +export default function PlaylistCard(props){ + const {playlist, onClick} = props; + + return ( +
{onClick(playlist.id)}}> + {playlist.name} +
+

{playlist.name}

+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/Account.jsx b/frontend/src/pages/Account.jsx index e87785f..f537494 100644 --- a/frontend/src/pages/Account.jsx +++ b/frontend/src/pages/Account.jsx @@ -1,22 +1,32 @@ import Navbar from "../components/Navbar.jsx"; -import {useState} from "react"; +import {useEffect, useState} from "react"; +import PlaylistCard from "../components/PlaylistCard.jsx"; +import VideoCard from "../components/VideoCard.jsx"; export default function Account() { let user = JSON.parse(localStorage.getItem("user")) || {}; + let token = localStorage.getItem("token") || ""; const [username, setUsername] = useState(user.username || ""); const [email, setEmail] = useState(user.email || ""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [isPictureEditActive, setIsPictureEditActive] = useState(false); + const [userHistory, setUserHistory] = useState([]); + const [userPlaylists, setUserPlaylists] = useState([]); const [userChannel, setUserChannel] = useState(null); const fetchUserChannel = async () => { try { - const response = await fetch(`/api/channels/${user.id}`); + const response = await fetch(`/api/users/${user.id}/channel`, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + } + }); if (!response.ok) { throw new Error("Failed to fetch user data"); } @@ -27,6 +37,48 @@ export default function Account() { return null; } } + const fetchUserHistory = async () => { + try { + const response = await fetch(`/api/users/${user.id}/history`, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + } + }); + if (!response.ok) { + throw new Error("Failed to fetch user history"); + } + const data = await response.json(); + setUserHistory(data); + } catch (error) { + console.error("Error fetching user history:", error); + return null; + } + } + const fetchUserPlaylists = async () => { + try { + const response = await fetch(`/api/playlists/user/${user.id}`, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + } + }); + if (!response.ok) { + throw new Error("Failed to fetch user playlists"); + } + const data = await response.json(); + setUserPlaylists(data); + } catch (error) { + console.error("Error fetching user playlists:", error); + return null; + } + } + + useEffect(() => { + fetchUserChannel(); + fetchUserHistory(); + fetchUserPlaylists(); + }, []); const [editMode, setEditMode] = useState(false); @@ -34,11 +86,51 @@ export default function Account() { const nonEditModeClasses = "text-2xl font-bold text-white p-2 focus:text-white focus:outline-none w-full font-montserrat"; const editModeClasses = nonEditModeClasses + " glassmorphism"; + const handlePlaylistClick = (playlistId) => { + + } + + const handleUpdateUser = async () => { + if (password !== confirmPassword) { + alert("Les mots de passe ne correspondent pas."); + return; + } + + const updatedUser = { + username, + email, + password: password || undefined, // Only send password if it's not empty + }; + + try { + const response = await fetch(`/api/users/${user.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + }, + body: JSON.stringify(updatedUser), + }); + + if (!response.ok) { + throw new Error("Failed to update user"); + } + + const data = await response.json(); + localStorage.setItem("user", JSON.stringify(data)); + setEditMode(false); + alert("Profil mis à jour avec succès !"); + } catch (error) { + console.error("Error updating user:", error); + alert("Erreur lors de la mise à jour du profil."); + } + } + return (
-
+
{/* Left side */} {/* Profile / Edit profile */} @@ -116,23 +208,82 @@ export default function Account() { }
- + { + editMode ? ( +
+ + +
+ ) : ( + + ) + }
{ /* Right side */} - {/* Channel */} - - {/* Playlists */} - - {/* History */} +
+ {/* Channel */} + {userChannel ? ( +
+

{userChannel.channel.name}

+ +
+ ) : ( +
+

Chaîne

+

Aucune chaîne associée à ce compte.

+ +
+ )} + {/* Playlists */} +

Playlists

+
+ { + userPlaylists.map((playlist, index) => ( + + )) + } +
+ {/* History */} +

Historique

+
+ { + userHistory.map((video, index) => ( +
+ +
+ )) + } +
+
From fbf1d8819b980da5460dbf009182303be078fe57 Mon Sep 17 00:00:00 2001 From: Sacha GUERIN Date: Sun, 20 Jul 2025 21:07:03 +0000 Subject: [PATCH 2/6] Update backend/app/controllers/user.controller.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- backend/app/controllers/user.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 1193315..4c78e63 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -40,7 +40,7 @@ export async function register(req, res) { const id = result.rows[0].id; - const playlistQuery = `INSERT INTO playlists (name, owner) VALUES ('A regarder plus tard', $2)`; + const playlistQuery = `INSERT INTO playlists (name, owner) VALUES ('A regarder plus tard', $1)`; await client.query(playlistQuery, [id]); console.log("Successfully registered"); From 0310ef4772661d78e4eff74692dbe1ebfaee0ac9 Mon Sep 17 00:00:00 2001 From: Sacha GUERIN Date: Sun, 20 Jul 2025 21:07:52 +0000 Subject: [PATCH 3/6] Update backend/app/controllers/user.controller.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- backend/app/controllers/user.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 4c78e63..a6d9300 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -248,7 +248,7 @@ export async function getHistory(req, res) { for (const video of result.rows) { // GET VIDEO VIEW COUNT const videoQuery = `SELECT COUNT(*) as view_count FROM history WHERE video = $1`; - const videoResult = await client.query(videoQuery, [video.channel]); + const videoResult = await client.query(videoQuery, [video.id]); const videoToAdd = { title: video.title, thumbnail: video.thumbnail, From a1cfe5b64e6462cda89893e8f13c76f557a79d9a Mon Sep 17 00:00:00 2001 From: Sacha GUERIN Date: Sun, 20 Jul 2025 21:09:21 +0000 Subject: [PATCH 4/6] Update frontend/src/pages/Account.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- frontend/src/pages/Account.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/Account.jsx b/frontend/src/pages/Account.jsx index f537494..8fe243a 100644 --- a/frontend/src/pages/Account.jsx +++ b/frontend/src/pages/Account.jsx @@ -117,7 +117,7 @@ export default function Account() { } const data = await response.json(); - localStorage.setItem("user", JSON.stringify(data)); + localStorage.setItem("user", JSON.stringify(data.user)); setEditMode(false); alert("Profil mis à jour avec succès !"); } catch (error) { From e69eff39e804be29c2745f0b82f84981f1391b28 Mon Sep 17 00:00:00 2001 From: Sacha GUERIN Date: Sun, 20 Jul 2025 21:12:23 +0000 Subject: [PATCH 5/6] Update frontend/src/pages/Account.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- frontend/src/pages/Account.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/Account.jsx b/frontend/src/pages/Account.jsx index 8fe243a..aad1545 100644 --- a/frontend/src/pages/Account.jsx +++ b/frontend/src/pages/Account.jsx @@ -38,6 +38,10 @@ export default function Account() { } } const fetchUserHistory = async () => { + if (!user.id || !token) { + console.warn("User ID or token missing, skipping history fetch"); + return; + } try { const response = await fetch(`/api/users/${user.id}/history`, { method: "GET", @@ -52,7 +56,6 @@ export default function Account() { setUserHistory(data); } catch (error) { console.error("Error fetching user history:", error); - return null; } } const fetchUserPlaylists = async () => { From 60e706f598dd4e462b2c66af35437d0798980ea1 Mon Sep 17 00:00:00 2001 From: Sacha GUERIN Date: Sun, 20 Jul 2025 21:12:51 +0000 Subject: [PATCH 6/6] Update frontend/src/pages/Account.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- frontend/src/pages/Account.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/Account.jsx b/frontend/src/pages/Account.jsx index aad1545..2931bf2 100644 --- a/frontend/src/pages/Account.jsx +++ b/frontend/src/pages/Account.jsx @@ -59,6 +59,10 @@ export default function Account() { } } const fetchUserPlaylists = async () => { + if (!user.id || !token) { + console.warn("User ID or token missing, skipping playlists fetch"); + return; + } try { const response = await fetch(`/api/playlists/user/${user.id}`, { method: "GET", @@ -73,7 +77,6 @@ export default function Account() { setUserPlaylists(data); } catch (error) { console.error("Error fetching user playlists:", error); - return null; } }