Browse Source

FINISH alert system

features/alert
Astri4-4 4 months ago
parent
commit
975a523ff6
  1. 127
      backend/app/controllers/search.controller.js
  2. 4
      backend/app/middlewares/channel.middleware.js
  3. 373
      backend/logs/access.log
  4. 145
      checklist.md
  5. 1
      frontend/src/assets/svg/check.svg
  6. 1
      frontend/src/assets/svg/plus.svg
  7. 33
      frontend/src/components/Alert.jsx
  8. 17
      frontend/src/components/AlertList.jsx
  9. 23
      frontend/src/components/Navbar.jsx
  10. 14
      frontend/src/index.css
  11. 24
      frontend/src/pages/Account.jsx
  12. 32
      frontend/src/pages/AddVideo.jsx
  13. 12
      frontend/src/pages/Home.jsx
  14. 25
      frontend/src/pages/Login.jsx
  15. 16
      frontend/src/pages/ManageChannel.jsx
  16. 29
      frontend/src/pages/ManageVideo.jsx
  17. 25
      frontend/src/pages/Register.jsx
  18. 16
      frontend/src/pages/Search.jsx
  19. 28
      frontend/src/pages/Video.jsx
  20. 5
      frontend/src/services/search.service.js
  21. 18
      nginx/default.conf

127
backend/app/controllers/search.controller.js

@ -5,8 +5,8 @@ export async function search(req, res) {
console.log(req.query);
const query = req.query.q;
const type = req.query.type || 'all';
const offset = req.query.offset || 0;
const limit = req.query.limit || 20;
const offset = parseInt(req.query.offset) || 0;
const limit = parseInt(req.query.limit) || 20;
const client = await getClient();
@ -15,71 +15,80 @@ export async function search(req, res) {
}
if (type === 'videos') {
// Search video in database based on the query, video title, tags and author
const videoNameQuery = `SELECT id FROM videos WHERE title ILIKE $1 OFFSET $3 LIMIT $2`;
const videoNameResult = await client.query(videoNameQuery, [`%${query}%`, limit, offset]);
// Search video from tags
const tagQuery = `SELECT id FROM tags WHERE name ILIKE $1 OFFSET $3 LIMIT $2`;
const tagResult = await client.query(tagQuery, [`%${query}%`, limit, offset]);
const tags = tagResult.rows.map(tag => tag.name);
for (const tag of tags) {
const videoTagQuery = `SELECT id FROM videos WHERE id IN (SELECT video FROM video_tags WHERE tag = (SELECT id FROM tags WHERE name = $1)) OFFSET $3 LIMIT $2`;
const videoTagResult = await client.query(videoTagQuery, [tag, limit, offset]);
videoNameResult.rows.push(...videoTagResult.rows);
let videoResults = [];
// Search video in database based on the video title
const videoNameQuery = `
SELECT
v.id, v.title, v.thumbnail, v.description as video_description, v.channel, v.visibility, v.file, v.slug, v.format, v.release_date,
c.id as channel_id, c.owner, c.description as channel_description, c.name,
u.picture as profilePicture,
COUNT(h.id) as views
FROM videos as v
JOIN public.channels c on v.channel = c.id
JOIN public.users u on c.owner = u.id
LEFT JOIN public.history h on h.video = v.id
WHERE v.title ILIKE $1
GROUP BY v.id, v.title, v.thumbnail, v.description, v.channel, v.visibility, v.file, v.slug, v.format, v.release_date,
c.id, c.owner, c.description, c.name, u.picture
OFFSET $2
LIMIT $3;
`;
const videoNameResult = await client.query(videoNameQuery, [`%${query}%`, offset, limit]);
const videoNames = videoNameResult.rows;
for (const video of videoNames) {
// Put all the creator's information in the creator sub-object
video.creator = {
name: video.name,
profilePicture: video.profilepicture,
description: video.channel_description
};
// Remove the creator's information from the video object
delete video.name;
delete video.profilepicture;
delete video.channel_description;
video.type = "video";
videoResults.push(video);
}
// Search video from author
const authorQuery = `SELECT videos.id FROM videos JOIN channels c ON videos.channel = c.id WHERE c.name ILIKE $1`;
const authorResult = await client.query(authorQuery, [`%${query}%`]);
for (const author of authorResult.rows) {
if (!videoNameResult.rows.some(video => video.id === author.id)) {
videoNameResult.rows.push(author);
}
}
const videos = [];
for (let video of videoNameResult.rows) {
video = video.id; // Extracting the video ID
let videoDetails = {};
// Fetching video details
const videoDetailsQuery = `SELECT id, title, description, thumbnail, channel, release_date FROM videos WHERE id = $1`;
const videoDetailsResult = await client.query(videoDetailsQuery, [video]);
if (videoDetailsResult.rows.length === 0) {
continue; // Skip if no video details found
}
videoDetails = videoDetailsResult.rows[0];
// Setting the type
videoDetails.type = 'video';
// Fetching views and likes
const viewsQuery = `SELECT COUNT(*) AS view_count FROM history WHERE video = $1`;
const viewsResult = await client.query(viewsQuery, [video]);
videoDetails.views = viewsResult.rows[0].view_count;
// GET CREATOR
const creatorQuery = `SELECT c.id, c.name, c.owner FROM channels c JOIN videos v ON c.id = v.channel WHERE v.id = $1`;
const creatorResult = await client.query(creatorQuery, [video]);
videoDetails.creator = creatorResult.rows[0];
// GET CREATOR PROFILE PICTURE
const profilePictureQuery = `SELECT picture FROM users WHERE id = $1`;
const profilePictureResult = await client.query(profilePictureQuery, [videoDetails.creator.owner]);
videoDetails.creator.profile_picture = profilePictureResult.rows[0].picture;
videos.push(videoDetails);
client.end()
return res.status(200).json(videoResults);
} else if (type === 'channel') {
let channelResults = [];
// Search channel in database based on the channel name
const channelNameQuery = `
SELECT c.id as channel_id, c.name, c.description as channel_description, c.owner, u.picture as profilePicture, COUNT(s.id) as subscribers
FROM public.channels c
JOIN public.users u on c.owner = u.id
LEFT JOIN public.subscriptions s ON s.channel = c.id
WHERE c.name ILIKE $1
group by c.name, c.id, c.description, c.owner, u.picture
OFFSET $2
LIMIT $3;
`;
const channelNameResult = await client.query(channelNameQuery, [`%${query}%`, offset, limit]);
const channelNames = channelNameResult.rows;
for (const channel of channelNames) {
channel.type = "channel";
channel.profilePicture = channel.profilepicture; // Rename for consistency
delete channel.profilepicture;
channelResults.push(channel);
}
client.end()
return res.status(200).json(videos);
return res.status(200).json(channelResults);
} else {
return res.status(400).json({ message: "Invalid type parameter. Use 'videos' or 'channel'." });
}

4
backend/app/middlewares/channel.middleware.js

@ -7,7 +7,7 @@ export const Channel = {
name: body("name").notEmpty().isAlphanumeric("fr-FR", {
ignore: " _-"
}).trim(),
description: body("description").optional({values: "falsy"}).isAlphanumeric().trim(),
description: body("description").optional({values: "falsy"}).isLength({ max: 500 }).trim(),
owner: body("owner").notEmpty().isNumeric().trim().withMessage("bad owner"),
}
@ -15,7 +15,7 @@ export const ChannelCreate = {
name: body("name").notEmpty().isAlphanumeric("fr-FR", {
ignore: " _-"
}).trim(),
description: body("description").optional({values: "falsy"}).isAlphanumeric().trim(),
description: body("description").optional({values: "falsy"}).isLength({ max: 500 }).trim(),
owner: body("owner").notEmpty().isNumeric().trim(),
}

373
backend/logs/access.log

@ -3823,3 +3823,376 @@
[2025-07-26 09:05:11.448] [undefined] GET(/:id/similar): No tags found for video 8 with status 404
[2025-07-26 09:05:11.474] [undefined] GET(/:id/views): try to add views for video 8
[2025-07-26 09:05:11.481] [undefined] GET(/:id/views): successfully added views for video 8 with status 200
[2025-08-11 11:11:21.571] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 11:11:21.575] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 11:11:21.577] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-08-11 11:11:21.580] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-08-11 11:11:21.589] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 11:11:37.891] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 11:11:37.893] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-08-11 11:11:37.896] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 11:11:37.898] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-08-11 11:11:37.904] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 11:11:47.642] [undefined] POST(/): try to create new channel with owner 2 and name astria
[2025-08-11 11:11:47.644] [undefined] POST(/): Successfully created new channel with name astria with status 200
[2025-08-11 11:11:51.909] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 11:11:51.912] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-08-11 11:11:51.914] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 11:11:51.916] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 11:11:51.922] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 11:11:53.197] [undefined] GET(/:id): try to get channel with id 1
[2025-08-11 11:11:53.206] [undefined] GET(/:id/stats): try to get stats
[2025-08-11 11:11:53.209] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-11 11:11:53.216] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-11 11:12:01.877] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 11:12:01.880] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 11:12:45.598] [undefined] POST(/): failed due to invalid values with status 400
[2025-08-11 11:12:56.047] [undefined] POST(/): failed due to invalid values with status 400
[2025-08-11 11:13:02.078] [undefined] POST(/): failed due to invalid values with status 400
[2025-08-11 11:14:01.356] [undefined] POST(/): try to upload video with status undefined
[2025-08-11 11:14:01.359] [undefined] POST(/): successfully uploaded video with status 200
[2025-08-11 11:14:01.426] [undefined] POST(/thumbnail): try to add thumbnail to video 1
[2025-08-11 11:14:01.428] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200
[2025-08-11 11:14:01.449] [undefined] PUT(/:id/tags): try to add tags to video 1
[2025-08-11 11:14:01.464] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200
[2025-08-11 11:14:08.592] [undefined] GET(/:id): try to get video 1
[2025-08-11 11:14:08.601] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 11:14:08.612] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-11 11:14:08.623] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-11 11:14:08.687] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-11 11:14:08.695] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-11 13:18:23.053] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 13:18:23.055] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 13:18:23.065] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 13:18:23.069] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-11 13:18:23.076] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 13:18:25.052] [undefined] GET(/:id): try to get channel with id 1
[2025-08-11 13:18:25.061] [undefined] GET(/:id/stats): try to get stats
[2025-08-11 13:18:25.063] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-11 13:18:25.072] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-11 13:18:25.688] [undefined] GET(/:id): try to get video 1
[2025-08-11 13:18:25.690] [undefined] GET(/:id/likes/day): try to get likes per day
[2025-08-11 13:18:25.699] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
[2025-08-11 13:18:25.703] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 13:18:31.902] [undefined] PUT(/:id): try to update video 1
[2025-08-11 13:18:31.927] [undefined] PUT(/:id): successfully updated video with status 200
[2025-08-11 13:41:12.278] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-11 13:41:12.282] [undefined] GET(/:id/stats): failed due to invalid values with status 400
[2025-08-11 13:41:16.105] [undefined] GET(/:id): try to get video 1
[2025-08-11 13:41:16.115] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 13:41:16.126] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-11 13:41:16.139] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-11 13:41:16.175] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-11 13:41:16.183] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-11 13:41:17.093] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1
[2025-08-11 13:41:17.101] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200
[2025-08-11 13:41:21.195] [undefined] GET(/:id): try to get video 1
[2025-08-11 13:41:21.206] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 13:41:21.221] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-11 13:41:21.233] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-11 13:41:21.271] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-11 13:41:21.279] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-11 13:41:21.710] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-11 13:41:21.714] [undefined] GET(/:id/stats): failed due to invalid values with status 400
[2025-08-11 13:45:04.724] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-11 13:45:04.730] [undefined] GET(/:id/similar): failed due to invalid values with status 400
[2025-08-11 13:45:04.735] [undefined] GET(/:id/views): failed due to invalid values with status 400
[2025-08-11 13:46:05.149] [undefined] GET(/:id): try to get video 1
[2025-08-11 13:46:05.160] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 13:46:05.174] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-11 13:46:05.183] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-11 13:46:05.247] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-11 13:46:05.254] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-11 13:47:58.621] [undefined] GET(/:id): try to get video 1
[2025-08-11 13:47:58.630] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 13:47:58.642] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-11 13:47:58.651] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-11 13:47:58.670] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-11 13:47:58.679] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-11 13:48:04.381] [undefined] GET(/:id/like): try to toggle like on video 1
[2025-08-11 13:48:04.389] [undefined] GET(/:id/like): no likes found adding likes for video 1 with status 200
[2025-08-11 13:48:05.683] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 13:48:05.685] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 13:48:05.687] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 13:48:05.691] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-11 13:48:05.697] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 13:48:07.023] [undefined] GET(/:id): try to get channel with id 1
[2025-08-11 13:48:07.032] [undefined] GET(/:id/stats): try to get stats
[2025-08-11 13:48:07.034] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-11 13:48:07.042] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-11 13:48:08.705] [undefined] GET(/:id/likes/day): try to get likes per day
[2025-08-11 13:48:08.707] [undefined] GET(/:id): try to get video 1
[2025-08-11 13:48:08.716] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
[2025-08-11 13:48:08.720] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-11 13:48:15.706] [undefined] PUT(/:id/tags): try to add tags to video 1
[2025-08-11 13:48:15.715] [undefined] PUT(/:id/tags): Tag minecraft already exists for video 1 with status 200
[2025-08-11 13:48:15.718] [undefined] PUT(/:id/tags): Tag survie already exists for video 1 with status 200
[2025-08-11 13:48:15.721] [undefined] PUT(/:id/tags): Tag moddée already exists for video 1 with status 200
[2025-08-11 13:48:15.724] [undefined] PUT(/:id/tags): Tag create mod already exists for video 1 with status 200
[2025-08-11 13:48:15.729] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200
[2025-08-11 13:48:18.023] [undefined] PUT(/:id/tags): try to add tags to video 1
[2025-08-11 13:48:18.032] [undefined] PUT(/:id/tags): Tag minecraft already exists for video 1 with status 200
[2025-08-11 13:48:18.035] [undefined] PUT(/:id/tags): Tag survie already exists for video 1 with status 200
[2025-08-11 13:48:18.038] [undefined] PUT(/:id/tags): Tag moddée already exists for video 1 with status 200
[2025-08-11 13:48:18.041] [undefined] PUT(/:id/tags): Tag create mod already exists for video 1 with status 200
[2025-08-11 13:48:18.044] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200
[2025-08-11 13:48:27.781] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 13:48:27.783] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 13:48:27.786] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 13:48:27.789] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-11 13:48:27.796] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 13:53:54.947] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 13:53:54.949] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 13:53:54.951] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-11 13:53:54.953] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 13:53:54.961] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-11 13:56:27.148] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-11 13:56:27.151] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-11 13:56:27.153] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-11 13:56:27.156] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-11 13:56:27.162] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-12 08:01:10.302] [undefined] GET(/:id): try to get video 1
[2025-08-12 08:01:10.312] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-12 08:01:10.324] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-12 08:01:10.334] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-12 08:01:10.356] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-12 08:01:10.366] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-12 08:02:05.673] [undefined] GET(/:id/stats): failed due to invalid values with status 400
[2025-08-12 08:02:05.677] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-08-12 09:58:17.398] [undefined] GET(/:id): try to get video 1
[2025-08-12 09:58:17.407] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-12 09:58:17.420] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-12 09:58:17.431] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-12 09:58:17.492] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-12 09:58:17.499] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-12 15:37:24.916] [undefined] POST(/login): try to login with username 'astria'
[2025-08-12 15:37:24.969] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-12 16:06:07.030] [undefined] POST(/login): failed due to invalid values with status 400
[2025-08-12 16:06:20.090] [undefined] POST(/login): try to login with username 'astria'
[2025-08-12 16:06:20.140] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-13 07:54:19.889] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-13 07:54:19.892] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-08-13 07:54:19.896] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-13 07:54:19.899] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
[2025-08-13 07:54:19.907] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-08-13 07:55:50.168] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-13 07:55:50.171] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-08-13 07:55:50.173] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-13 07:55:50.176] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
[2025-08-13 07:55:50.183] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-08-13 07:58:02.863] [undefined] GET(/:id): try to get video 1
[2025-08-13 07:58:02.874] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 07:58:02.886] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-13 07:58:02.895] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-13 07:58:02.914] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-13 07:58:02.926] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-13 07:58:07.598] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-13 07:58:07.600] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-08-13 07:58:07.609] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-13 07:58:07.613] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
[2025-08-13 07:58:07.620] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-08-13 07:58:19.231] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-13 07:58:19.234] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-13 07:58:19.236] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-08-13 07:58:19.238] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
[2025-08-13 07:58:19.247] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-08-13 07:58:26.874] [undefined] POST(/login): try to login with username 'sacha'
[2025-08-13 07:58:26.925] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-13 07:58:33.080] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 07:58:33.082] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 07:58:33.084] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 07:58:33.087] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 07:58:33.093] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 07:58:34.762] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 07:58:34.771] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 07:58:34.773] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 07:58:34.781] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:01:18.744] [undefined] PUT(/:id): failed due to invalid values with status 400
[2025-08-13 08:01:40.135] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:01:40.144] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:01:40.147] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:01:40.154] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:01:45.190] [undefined] PUT(/:id): failed due to invalid values with status 400
[2025-08-13 08:02:08.907] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:02:08.916] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:02:08.919] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:02:08.927] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:02:14.295] [undefined] PUT(/:id): failed due to invalid values with status 400
[2025-08-13 08:02:32.447] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:02:32.456] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:02:32.459] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:02:32.467] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:02:37.213] [undefined] PUT(/:id): try to update channel with id 1
[2025-08-13 08:02:37.223] [undefined] PUT(/:id): Successfully updated channel with status 200
[2025-08-13 08:02:37.235] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:02:37.242] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:04:41.723] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:04:41.733] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:04:41.744] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-13 08:04:41.756] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-13 08:04:41.780] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-13 08:04:41.790] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-13 08:04:50.802] [undefined] POST(/): try to post comment
[2025-08-13 08:04:50.812] [undefined] POST(/): successfully post comment with status 200
[2025-08-13 08:04:54.530] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:04:54.532] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:04:54.534] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:04:54.537] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:04:54.543] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:04:56.638] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:04:56.648] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:04:56.650] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:04:56.659] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:04:58.247] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:04:58.250] [undefined] GET(/:id/likes/day): try to get likes per day
[2025-08-13 08:04:58.260] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
[2025-08-13 08:04:58.264] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:05:21.729] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:05:21.738] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:05:21.750] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-13 08:05:21.760] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-13 08:05:21.779] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-13 08:05:21.791] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-13 08:05:23.178] [undefined] DELETE(/:id): try to delete comment 1
[2025-08-13 08:05:23.185] [undefined] DELETE(/:id): successfully deleted comment with status 200
[2025-08-13 08:05:23.450] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:05:23.459] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:05:23.479] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-13 08:05:23.506] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-13 08:05:23.521] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-13 08:05:23.529] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-13 08:07:08.206] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:07:08.215] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:07:08.241] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-08-13 08:07:08.253] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-08-13 08:07:08.281] [undefined] GET(/:id/views): try to add views for video 1
[2025-08-13 08:07:08.287] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-08-13 08:07:09.259] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:07:09.261] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:07:09.263] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:07:09.267] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:07:09.273] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:07:11.274] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:07:11.283] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:07:11.287] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:07:11.294] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:07:14.428] [undefined] PUT(/:id): try to update channel with id 1
[2025-08-13 08:07:14.438] [undefined] PUT(/:id): Successfully updated channel with status 200
[2025-08-13 08:07:14.449] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:07:14.457] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:09:19.413] [undefined] PUT(/:id): try to update channel with id 1
[2025-08-13 08:09:19.418] [undefined] PUT(/:id): Successfully updated channel with status 200
[2025-08-13 08:09:19.432] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:09:19.441] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:13:02.276] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:13:02.284] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:13:02.288] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:13:02.294] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:13:05.983] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:13:05.985] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:13:05.988] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:13:05.991] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:13:05.997] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:13:32.065] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:13:32.067] [undefined] PUT(/:id): failed to update because username is already used with status 400
[2025-08-13 08:13:43.652] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:13:43.654] [undefined] PUT(/:id): failed to update because username is already used with status 400
[2025-08-13 08:13:43.702] [undefined] PUT(/:id): failed to update profile picture with status 500
[2025-08-13 08:15:55.326] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:15:55.329] [undefined] PUT(/:id): failed to update because username is already used with status 400
[2025-08-13 08:15:55.377] [undefined] PUT(/:id): failed to update profile picture with status 500
[2025-08-13 08:16:22.726] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:16:22.729] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:16:22.733] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:16:22.739] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:16:22.750] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:17:26.321] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:17:26.324] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:17:26.327] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:17:26.330] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:17:26.341] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:17:53.837] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:17:53.882] [undefined] PUT(/:id): successfully updated user 2 with status 200
[2025-08-13 08:18:53.327] [undefined] POST(/login): try to login with username 'chien'
[2025-08-13 08:18:53.377] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-13 08:18:58.149] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:18:58.151] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:18:58.155] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:18:58.158] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:18:58.166] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:19:31.958] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:19:31.960] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:19:31.970] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:19:31.974] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:19:31.979] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:19:50.162] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:19:50.205] [undefined] PUT(/:id): successfully updated user 2 with status 200
[2025-08-13 08:20:21.321] [undefined] POST(/login): try to login with username 'chienne'
[2025-08-13 08:20:21.374] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-13 08:20:30.730] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:20:30.733] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:20:30.735] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:20:30.739] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:20:30.745] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:20:47.202] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:20:47.247] [undefined] PUT(/:id): successfully updated user 2 with status 200
[2025-08-13 08:20:57.402] [undefined] PUT(/:id): try to update user 2
[2025-08-13 08:20:57.444] [undefined] PUT(/:id): successfully updated user 2 with status 200
[2025-08-13 08:24:14.507] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:24:14.517] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:24:14.519] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:24:14.527] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:24:18.228] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:24:18.230] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:26:51.461] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:26:51.464] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:27:19.201] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:27:19.204] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:27:19.206] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:27:19.209] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:27:19.217] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:32:59.197] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:32:59.199] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:32:59.201] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:32:59.203] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:32:59.209] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:33:01.805] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:33:01.826] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:33:01.838] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:33:01.850] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:33:25.114] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:33:25.116] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:33:25.118] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:33:25.121] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:33:25.127] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:33:25.756] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:33:25.765] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:33:25.767] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:33:25.776] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:34:28.854] [undefined] GET(/:id/likes/day): try to get likes per day
[2025-08-13 08:34:28.856] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:34:28.888] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
[2025-08-13 08:34:28.893] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:34:33.937] [undefined] GET(/:id/likes/day): try to get likes per day
[2025-08-13 08:34:33.939] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:34:33.950] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
[2025-08-13 08:34:33.954] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:34:42.356] [undefined] PUT(/:id): try to update video 1
[2025-08-13 08:34:42.364] [undefined] PUT(/:id): successfully updated video with status 200
[2025-08-13 08:35:02.613] [undefined] GET(/:id): try to get video 1
[2025-08-13 08:35:02.616] [undefined] GET(/:id/likes/day): try to get likes per day
[2025-08-13 08:35:02.626] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200
[2025-08-13 08:35:02.630] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-08-13 08:35:20.147] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:35:20.150] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 08:35:20.154] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-13 08:35:20.158] [undefined] GET(/:id/history): successfully retrieved history of user 2 with status 200
[2025-08-13 08:35:20.164] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-13 08:35:20.898] [undefined] GET(/:id): try to get channel with id 1
[2025-08-13 08:35:20.908] [undefined] GET(/:id/stats): try to get stats
[2025-08-13 08:35:20.912] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-08-13 08:35:20.917] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-08-13 08:35:26.515] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-13 08:35:26.517] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200
[2025-08-13 09:11:32.832] [undefined] POST(/login): try to login with username 'sacha'
[2025-08-13 09:11:32.881] [undefined] POST(/login): Successfully logged in with status 200

145
checklist.md

@ -22,122 +22,125 @@
## **ADMINISTRATION CHAÎNE FREETUBE** (55 points)
### Mettre en ligne une vidéo (30 points)
### Mettre en ligne une vidéo (30 points) - 27/30 points ✅
- [x] Upload média vidéo (10 points)
- [x] Upload miniature vidéo (2 points)
- [x] Titre (2 points)
- [x] Description (2 points)
- [x] Date de mise en ligne automatique (2 points)
- [ ] Mots-clefs/hashtags jusqu'à 10 (2 points)
- [ ] Visibilité publique/privée (5 points)
- [ ] Génération lien partageable (5 points)
- [x] Visibilité publique/privée (5 points)
- [x] Génération lien partageable (5 points) - via slug système
### Gestion vidéos existantes
- [ ] Éditer une vidéo existante (5 points)
- [ ] Changer la visibilité (5 points)
### Gestion vidéos existantes (15 points) - 10/15 points
- [x] Éditer une vidéo existante (5 points)
- [x] Changer la visibilité (5 points)
- [x] Supprimer une vidéo (5 points)
### Statistiques
- [ ] Statistiques par vidéo (vues, likes, commentaires) (5 points)
### Statistiques (10 points) - 5/10 points
- [x] Statistiques par vidéo (vues, likes, commentaires) (5 points)
- [ ] Statistiques globales de la chaîne (5 points)
## **PAGE ACCUEIL** (30 points)
## **RECHERCHE ET NAVIGATION** (20 points) ✅
### Utilisateur authentifié (15 points)
### Système de recherche (20 points) - 20/20 points ✅
- [x] Recherche par titre de vidéo (8 points)
- [x] Recherche par chaîne (8 points)
- [x] Interface de recherche fonctionnelle (4 points)
## **PAGE ACCUEIL** (30 points) - 10/30 points
### Utilisateur authentifié (15 points) - 5/15 points
- [x] Section Tendances (contenu avec plus d'interactions récentes) (5 points)
- [ ] Section Recommendations (contenu similaire non vu) (5 points)
- [ ] Section "À consulter plus tard" (5 points)
- [ ] Section Tendances (contenu avec plus d'interactions récentes) (5 points)
### Utilisateur non-authentifié (15 points)
### Utilisateur non-authentifié (15 points) - 5/15 points
- [x] Section Tendances (5 points)
- [ ] Section Recommendations (3 mots-clefs les plus utilisés) (5 points)
- [ ] Section Tendances (5 points)
- [ ] Section Top créateurs (plus d'abonnés) (5 points)
## **PAGE ABONNEMENTS** (10 points)
- [ ] Fil d'actualité des abonnements (8 points)
- [ ] Redirection pour non-authentifiés (2 points)
## **PAGE UTILISATEUR** (15 points)
## **PAGE UTILISATEUR** (15 points) - 5/15 points
- [ ] Historique des vidéos regardées (10 points)
- [ ] Gestion et liste des playlists (5 points)
- [x] Gestion et liste des playlists (5 points)
## **PAGE PLAYLIST** (10 points)
- [ ] Affichage nom playlist et vidéos
- [ ] Tri par date d'ajout
- [ ] Navigation depuis page utilisateur
## **PAGE PLAYLIST** (10 points) - 8/10 points ✅
- [x] Affichage nom playlist et vidéos (4 points)
- [x] Tri par date d'ajout (2 points)
- [x] Navigation depuis page utilisateur (2 points)
- [ ] Interface utilisateur complète (2 points)
## **PAGE VIDÉO** (50 points)
## **PAGE VIDÉO** (50 points) - 27/50 points
### Lecteur vidéo (20 points)
### Lecteur vidéo (20 points) - 10/20 points
- [x] Média visualisable (10 points)
- [ ] Bouton Pause (2 points)
- [ ] Bouton Play (2 points)
- [ ] Saut XX secondes en avant (3 points)
- [ ] Saut XX secondes en arrière (3 points)
### Informations vidéo (20 points)
### Informations vidéo (20 points) - 12/20 points
- [x] Titre de la vidéo (2 points)
- [x] Description (2 points)
- [x] Nom de la chaîne (2 points)
- [x] Compteur "J'aime" (2 points)
- [x] Compteur vues (2 points)
- [x] Bouton "J'aime" (2 points)
- [ ] Compteur abonnés (2 points)
- [ ] Compteur "J'aime" (2 points)
- [ ] Bouton "J'aime" (5 points)
- [ ] Bouton "S'abonner" (5 points)
- [ ] Bouton "S'abonner" (6 points)
### Commentaires (10 points) ✅
- [x] Créer un commentaire (5 points)
- [x] Voir les commentaires (5 points)
### Recommendations (5 points)
- [ ] Section recommendations/tendances selon authentification
- [ ] Section recommendations/tendances selon authentification (5 points)
## **FONCTIONNALITÉS SYSTÈME**
### Système de playlists (15 points) ✅
- [x] Routes créer/gérer playlists (8 points)
- [x] Ajouter/retirer vidéos des playlists (4 points)
- [x] Playlist "À regarder plus tard" automatique (3 points)
### Système "J'aime" (10 points) ✅
- [x] Routes like/unlike vidéo (5 points)
- [x] Compteur de likes par vidéo (3 points)
- [x] Interface utilisateur (2 points)
### Système d'abonnements (18 points estimés)
- [ ] Routes s'abonner/désabonner à une chaîne
- [ ] Modèle de données abonnements
- [ ] Compteur d'abonnés par chaîne
### Système "J'aime" (10 points estimés)
- [ ] Routes aimer/ne plus aimer vidéo
- [ ] Modèle de données likes
- [ ] Mise à jour compteur likes
### Gestion playlists (25 points estimés)
- [ ] Routes créer/supprimer playlists
- [ ] Ajout/suppression vidéos dans playlists
- [ ] Playlist "À consulter plus tard" par défaut
- [ ] Affichage contenu playlist
### Historique utilisateur (10 points estimés)
- [ ] Enregistrement automatique vidéos regardées
- [ ] Routes consultation historique
### Système recommandations (15 points estimés)
- [ ] Algorithme pour utilisateurs authentifiés
- [ ] Recommendations mots-clés pour non-authentifiés
- [ ] Calcul tendances (interactions récentes)
### Compteurs et statistiques
- [ ] Compteur de vues par vidéo
- [ ] Mise à jour automatique lors du visionnage
- [ ] Statistiques complètes par vidéo et chaîne
## **POINTS CRITIQUES POUR ÉVITER L'AJOURNEMENT**
- **Fonctionnalités : 120/200 points minimum**
- **Qualité code : 60/100 points minimum**
- **Documentation : 30/50 points minimum**
- **Déploiement : 30/50 points minimum**
## **AMÉLIORATIONS TECHNIQUES**
- [ ] Validation robuste données d'entrée
- [ ] Gestion d'erreurs appropriée
- [ ] Middleware de sécurité complet
- [ ] Tests unitaires (fichiers présents à compléter)
- [ ] Architecture REST propre
- [ ] Optimisation performances base de données
- [ ] Routes s'abonner/désabonner à une chaîne (8 points)
- [ ] Modèle de données abonnements (5 points)
- [ ] Compteur d'abonnés par chaîne (5 points)
### Système de tags/mots-clefs (8 points estimés)
- [ ] Modèle de données tags (3 points)
- [ ] Association vidéos-tags (3 points)
- [ ] Interface gestion tags (2 points)
## **SÉCURITÉ ET MIDDLEWARE**
- [x] Middleware d'authentification JWT
- [x] Validation des données d'entrée
- [x] Gestion des erreurs
- [x] Upload sécurisé de fichiers
- [x] Logging des actions
## **INFRASTRUCTURE**
- [x] Configuration Docker
- [x] Base de données PostgreSQL
- [x] Serveur de fichiers médias
- [x] Tests unitaires
---
**Status actuel estimé : ~102/200 points fonctionnalités**
**Objectif prioritaire : Atteindre 120 points minimum**
## **SCORE ESTIMÉ**
**Backend: ~127/183 points (69%)**
**Points prioritaires manquants:**
- OAuth2 (10 points)
- Système d'abonnements (18 points)
- Tags/mots-clefs (8 points)
- Statistiques globales chaîne (5 points)
- Contrôles lecteur vidéo (10 points)

1
frontend/src/assets/svg/check.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="m10 15.586-3.293-3.293-1.414 1.414L10 18.414l9.707-9.707-1.414-1.414z"></path></svg>

After

Width:  |  Height:  |  Size: 176 B

1
frontend/src/assets/svg/plus.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"></path></svg>

After

Width:  |  Height:  |  Size: 139 B

33
frontend/src/components/Alert.jsx

@ -0,0 +1,33 @@
import { useEffect } from 'react';
export default function Alert({ type, message, onClose }) {
const alertClass = `cursor-pointer flex items-center gap-4 p-4 rounded-md text-white transition-opacity duration-300 ${ type === "error" ? "glassmorphism-red" : "glassmorphism-green" } `;
useEffect(() => {
const timer = setTimeout(() => {
onClose();
}, 15000); // 15 seconds
return () => clearTimeout(timer);
}, [onClose]);
return (
<div
className={alertClass}
role="alert"
onClick={onClose}
>
{
type === 'success' ? (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-white" ><path d="m10 15.586-3.293-3.293-1.414 1.414L10 18.414l9.707-9.707-1.414-1.414z"></path></svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-white rotate-45" ><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"></path></svg>
)
}
{message}
</div>
);
}

17
frontend/src/components/AlertList.jsx

@ -0,0 +1,17 @@
import Alert from "./Alert.jsx";
export default function AlertList({ alerts, onCloseAlert }) {
return (
<div className="absolute top-1/1 right-0 flex flex-col gap-2 mt-2 mr-4 z-40">
{alerts.map((alert, index) => (
<Alert
key={index}
type={alert.type}
message={alert.message}
onClose={() => onCloseAlert(alert)}
/>
))}
</div>
);
}

23
frontend/src/components/Navbar.jsx

@ -1,10 +1,12 @@
import { useAuth } from '../contexts/AuthContext';
import React, { useState } from 'react';
import {useNavigate} from "react-router-dom";
import AlertList from "./AlertList.jsx";
export default function Navbar({ isSearchPage = false }) {
export default function Navbar({ isSearchPage = false, alerts = [], onCloseAlert = () => {} }) {
const { user, logout, isAuthenticated } = useAuth();
const [searchQuery, setSearchQuery] = useState('');
const [internalAlerts, setInternalAlerts] = useState([]);
const navigate = useNavigate();
const handleLogout = () => {
@ -20,8 +22,24 @@ export default function Navbar({ isSearchPage = false }) {
}
}
const onCloseInternalAlert = (alertToRemove) => {
setInternalAlerts(internalAlerts.filter(alert => alert !== alertToRemove));
}
// Combine internal alerts with external alerts
const allAlerts = [...internalAlerts, ...alerts];
const handleCloseAlert = (alertToRemove) => {
// Check if it's an internal alert or external alert
if (internalAlerts.includes(alertToRemove)) {
onCloseInternalAlert(alertToRemove);
} else {
onCloseAlert(alertToRemove);
}
};
return (
<nav className="flex justify-between items-center p-4 text-white absolute top-0 left-0 w-screen">
<nav className="flex justify-between items-center p-4 text-white absolute top-0 left-0 w-screen relative">
<div>
<h1 className="font-montserrat text-5xl font-black">
<a href="/">FreeTube</a>
@ -83,6 +101,7 @@ export default function Navbar({ isSearchPage = false }) {
</ul>
</div>
<AlertList alerts={allAlerts} onCloseAlert={handleCloseAlert} />
</nav>
)

14
frontend/src/index.css

@ -42,6 +42,20 @@
resize: none;
}
.glassmorphism-red {
border-radius: 15px;
border: 1px solid rgba(239, 239, 239, 0.60);
background: linear-gradient(93deg, rgba(226, 107, 107, 0.40) 0%, rgba(226, 107, 107, 0.15) 100%);
backdrop-filter: blur(27.5px);
}
.glassmorphism-green {
border-radius: 15px;
border: 1px solid rgba(239, 239, 239, 0.60);
background: linear-gradient(93deg, rgba(127, 226, 107, 0.40) 0%, rgba(127, 226, 107, 0.15) 100%);
backdrop-filter: blur(27.5px);
}
@theme {
/* Fonts */
--font-inter: 'Inter', sans-serif;

24
frontend/src/pages/Account.jsx

@ -20,6 +20,7 @@ export default function Account() {
const [userPlaylists, setUserPlaylists] = useState([]);
const [userChannel, setUserChannel] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [alerts, setAlerts] = useState([]);
const navigation = useNavigate();
@ -38,6 +39,7 @@ export default function Account() {
setUserChannel(data);
} catch (error) {
console.error("Error fetching user channel:", error);
addAlert('error', "Erreur lors de la récupération des données de l'utilisateur.");
return null;
}
}
@ -60,6 +62,7 @@ export default function Account() {
setUserHistory(data);
} catch (error) {
console.error("Error fetching user history:", error);
addAlert('error', "Erreur lors de la récupération de l'historique de l'utilisateur.");
}
}
const fetchUserPlaylists = async () => {
@ -81,6 +84,7 @@ export default function Account() {
setUserPlaylists(data);
} catch (error) {
console.error("Error fetching user playlists:", error);
addAlert('error', "Erreur lors de la récupération des playlists de l'utilisateur.");
}
}
@ -102,7 +106,7 @@ export default function Account() {
const handleUpdateUser = async () => {
if (password !== confirmPassword) {
alert("Les mots de passe ne correspondent pas.");
addAlert('error', "Les mots de passe ne correspondent pas.");
return;
}
@ -123,23 +127,31 @@ export default function Account() {
});
if (!response.ok) {
throw new Error("Failed to update user");
throw new Error(response.statusText);
}
const data = await response.json();
localStorage.setItem("user", JSON.stringify(data.user));
localStorage.setItem("user", JSON.stringify(data));
setEditMode(false);
alert("Profil mis à jour avec succès !");
addAlert('success', "Profil mis à jour avec succès.");
} catch (error) {
console.error("Error updating user:", error);
alert("Erreur lors de la mise à jour du profil.");
addAlert('error', error.message || "Erreur lors de la mise à jour du profil.");
}
}
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar/>
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 pt-[118px] flex justify-between items-start">
{/* Left side */}

32
frontend/src/pages/AddVideo.jsx

@ -12,10 +12,11 @@ export default function AddVideo() {
const [videoTitle, setVideoTitle] = useState("");
const [videoDescription, setVideoDescription] = useState("");
const [videoTags, setVideoTags] = useState([]);
const [visibility, setVisibility] = useState("public"); // Default visibility
const [visibility, setVisibility] = useState("public");
const [videoThumbnail, setVideoThumbnail] = useState(null);
const [videoFile, setVideoFile] = useState(null);
const [channel, setChannel] = useState(null); // Assuming user.channel is the channel ID
const [channel, setChannel] = useState(null);
const [alerts, setAlerts] = useState([]);
useEffect(() => {
fetchChannel();
@ -35,6 +36,7 @@ export default function AddVideo() {
setChannel(data.channel);
} catch (error) {
console.error("Erreur lors de la récupération de la chaîne :", error);
addAlert('error', 'Erreur lors de la récupération de la chaîne');
}
}
@ -57,15 +59,15 @@ export default function AddVideo() {
e.preventDefault();
if (!videoTitle || !videoDescription || !videoThumbnail || !videoFile) {
alert("Veuillez remplir tous les champs requis.");
addAlert('error', 'Veuillez remplir tous les champs requis.');
return;
}
if (!channel || !channel.id) {
alert("Erreur: aucune chaîne trouvée. Veuillez actualiser la page.");
addAlert('error', 'Chaîne non valide veuillez recharger la page.');
return;
}
if (videoTags.length > 10) {
alert("Vous ne pouvez pas ajouter plus de 10 tags.");
addAlert('error', 'Vous ne pouvez pas ajouter plus de 10 tags.');
return;
}
@ -92,9 +94,9 @@ export default function AddVideo() {
const errorMessages = errorData.errors.map(error =>
`${error.path}: ${error.msg}`
).join('\n');
alert(`Erreurs de validation:\n${errorMessages}`);
addAlert('error', `Erreurs de validation:\n${errorMessages}`);
} else {
alert(`Erreur lors de l'ajout de la vidéo : ${errorData.message || 'Erreur inconnue'}`);
addAlert('error', 'Erreurs inconnues');
}
return;
}
@ -116,7 +118,7 @@ export default function AddVideo() {
if (!thumbnailRequest.ok) {
const errorData = await thumbnailRequest.json();
console.error("Backend validation errors:", errorData);
alert(`Erreur lors de l'ajout de la miniature : ${errorData.message || 'Erreur inconnue'}`);
addAlert('error', 'Erreur lors de l\'envoie d la miniature');
return;
}
@ -132,19 +134,27 @@ export default function AddVideo() {
if (!tagsRequest.ok) {
const errorData = await tagsRequest.json();
console.error("Backend validation errors:", errorData);
alert(`Erreur lors de l'ajout des tags : ${errorData.message || 'Erreur inconnue'}`);
addAlert('error', 'Erreur lors de l\'ajout des tags');
return;
}
// If everything is successful, redirect to the video management page
alert("Vidéo ajoutée avec succès !");
addAlert('success', 'Vidéo ajoutée avec succès !');
};
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 pt-[118px]">
<h1 className="font-montserrat text-2xl font-black text-white">

12
frontend/src/pages/Home.jsx

@ -12,6 +12,7 @@ export default function Home() {
const [loading, setLoading] = useState(true);
const [topCreators, setTopCreators] = useState([]);
const [trendingVideos, setTrendingVideos] = useState([]);
const [alerts, setAlerts] = useState([]);
useEffect(() => {
// Fetch recommendations, top creators, and trending videos
@ -31,7 +32,7 @@ export default function Home() {
const trendingData = await trendingResponse.json();
setTrendingVideos(trendingData);
} catch (error) {
console.error('Error fetching trending videos:', error);
addAlert('error', 'Erreur lors du chargement des vidéos tendance');
} finally {
setLoading(false);
}
@ -40,6 +41,15 @@ export default function Home() {
fetchData();
}, []);
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />

25
frontend/src/pages/Login.jsx

@ -8,7 +8,7 @@ export default function Login() {
username: '',
password: ''
});
const [error, setError] = useState('');
const [alerts, setAlerts] = useState([]);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
@ -21,16 +21,25 @@ export default function Login() {
});
};
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setAlerts([]); // Clear existing alerts
setLoading(true);
try {
await login(formData.username, formData.password);
navigate('/');
} catch (err) {
setError(err.message || 'Erreur de connexion');
addAlert('error', err.message || 'Erreur de connexion');
} finally {
setLoading(false);
}
@ -38,17 +47,11 @@ export default function Login() {
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<div className="flex justify-center items-center min-h-screen pt-20">
<div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
<h2 className="text-3xl font-bold text-center mb-6 font-montserrat">Connexion</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>

16
frontend/src/pages/ManageChannel.jsx

@ -16,6 +16,7 @@ export default function ManageChannel() {
const [channelName, setChannelName] = useState(null);
const [description, setDescription] = useState(null);
const [editMode, setEditMode] = useState(false);
const [alerts, setAlerts] = useState([]);
const token = localStorage.getItem("token");
const nonEditModeClasses = "text-2xl font-bold text-white p-2 focus:text-white focus:outline-none w-full font-montserrat resizable-none text-center";
@ -78,18 +79,31 @@ export default function ManageChannel() {
if (response.ok) {
setEditMode(false);
addAlert('success', 'Chaîne mise à jour avec succès');
fetchChannelData(); // Refresh channel data after update
} else {
console.error("Failed to update channel");
const errorData = await response.json();
addAlert('error', errorData.message || 'Erreur lors de la mise à jour de la chaîne');
}
} catch (error) {
console.error("Error updating channel:", error);
addAlert('error', 'Erreur lors de la mise à jour de la chaîne');
}
}
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="pt-[118px] px-36 flex">

29
frontend/src/pages/ManageVideo.jsx

@ -22,6 +22,7 @@ export default function ManageVideo() {
const [editMode, setEditMode] = useState(false);
const [thumbnailPreview, setThumbnailPreview] = useState(null);
const [videoFile, setVideoFile] = useState(null);
const [alerts, setAlerts] = useState([]);
const nonEditModeClasses = "text-md font-normal text-white p-2 focus:text-white focus:outline-none w-full font-montserrat resizable-none";
const editModeClasses = nonEditModeClasses + " glassmorphism";
@ -38,7 +39,7 @@ export default function ManageVideo() {
}
});
if (!request.ok) {
throw new Error("Failed to fetch video");
addAlert('error', 'Erreur lors de la récupération de la vidéo');
}
const data = await request.json();
setVideo(data);
@ -55,7 +56,7 @@ export default function ManageVideo() {
}
});
if (!request.ok) {
throw new Error("Failed to fetch likes per day");
addAlert('error', 'Erreur lors de la récupération des likes par jour');
}
const data = await request.json();
setLikesPerDay(data.likes);
@ -88,7 +89,7 @@ export default function ManageVideo() {
});
if (!request.ok) {
console.error("Failed to update video");
addAlert('error', 'Erreur lors de la mise à jour des détails de la vidéo');
return;
}
@ -107,7 +108,7 @@ export default function ManageVideo() {
});
if (!videoRequest.ok) {
console.error("Failed to update video file");
addAlert('error', 'Erreur lors de la mise à jour de la vidéo');
return;
}
}
@ -115,6 +116,7 @@ export default function ManageVideo() {
const data = await request.json();
setVideo(data);
setEditMode(false);
addAlert('success', 'Vidéo mise à jour avec succès');
}
const handleThumbnailChange = async (e) => {
@ -142,10 +144,11 @@ export default function ManageVideo() {
body: formData
})
if (!request.ok) {
console.error("Failed to upload thumbnail");
addAlert('error', 'Erreur lors de l\'envoi de la miniature');
return;
}
const data = await request.json();
addAlert('success', 'Miniature mise à jour avec succès');
};
const onAddTag = async (e) => {
@ -166,7 +169,7 @@ export default function ManageVideo() {
})
});
if (!request.ok) {
console.error("Failed to add tag");
addAlert('error', 'Erreur lors de l\'ajout du tag');
return;
}
const data = await request.json();
@ -190,7 +193,7 @@ export default function ManageVideo() {
})
});
if (!request.ok) {
console.error("Failed to suppress tag");
addAlert('error', 'Erreur lors de la suppression du tag');
return;
}
const data = await request.json();
@ -199,13 +202,21 @@ export default function ManageVideo() {
...video,
tags: newTags
});
}
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar/>
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 pb-36">

25
frontend/src/pages/Register.jsx

@ -14,6 +14,7 @@ export default function Register() {
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [previewImage, setPreviewImage] = useState(null);
const [alerts, setAlerts] = useState([]);
const navigate = useNavigate();
const { register } = useAuth();
@ -44,16 +45,15 @@ export default function Register() {
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
// Validation
if (formData.password !== formData.confirmPassword) {
setError('Les mots de passe ne correspondent pas');
addAlert("error", "Les mots de passe ne correspondent pas");
return;
}
if (formData.password.length < 6) {
setError('Le mot de passe doit contenir au moins 6 caractères');
addAlert("error", "Le mot de passe doit contenir au moins 6 caractères");
return;
}
@ -63,25 +63,28 @@ export default function Register() {
await register(formData.email, formData.username, formData.password, formData.profile);
navigate('/');
} catch (err) {
setError(err.message || 'Erreur lors de la création du compte');
addAlert('error', 'Erreur lors de la création du compte');
} finally {
setLoading(false);
}
};
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<div className="flex justify-center items-center min-h-screen pt-20 pb-10">
<div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
<h2 className="text-3xl font-bold text-center mb-6 font-montserrat">Créer un compte</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>

16
frontend/src/pages/Search.jsx

@ -15,11 +15,12 @@ export default function Search() {
const [searchQuery, setSearchQuery] = useState(queryFromUrl);
const [results, setResults] = useState({});
const [alerts, setAlerts] = useState([]);
useEffect(() => {
async function fetchData() {
if (searchParams) {
setResults(await search(queryFromUrl, typeFromUrl, 0, 20));
setResults(await search(queryFromUrl, typeFromUrl, 0, 20, addAlert));
}
}
fetchData();
@ -27,7 +28,7 @@ export default function Search() {
useEffect(() => {
async function fetchData() {
if (searchQuery) {
setResults(await search(searchQuery, filter, 0, 20));
setResults(await search(searchQuery, filter, 0, 20, addAlert));
}
}
fetchData();
@ -39,9 +40,18 @@ export default function Search() {
}
}
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={true} />
<Navbar isSearchPage={true} alerts={alerts} onCloseAlert={onCloseAlert}/>
<main className="px-36 pt-[118px]">

28
frontend/src/pages/Video.jsx

@ -22,6 +22,7 @@ export default function Video() {
const [progress, setProgress] = useState(0);
const [showControls, setShowControls] = useState(false);
const [comment, setComment] = useState("");
const [alerts, setAlerts] = useState([]);
const fetchVideo = useCallback(async () => {
// Fetch video data and similar videos based on the video ID from the URL
@ -33,7 +34,7 @@ export default function Video() {
const videoData = await response.json();
setVideo(videoData);
} catch (error) {
console.error('Error fetching video:', error);
addAlert('error', 'Erreur lors de la récupération de la vidéo');
}
try {
const response = await fetch(`/api/videos/${id}/similar`);
@ -43,7 +44,7 @@ export default function Video() {
const similarVideosData = await response.json();
setSimilarVideos(similarVideosData);
} catch (error) {
console.error('Error fetching similar videos:', error);
addAlert('error', 'Erreur lors de la récupération des vidéos similaires');
}
// Add views to the video
@ -61,7 +62,7 @@ export default function Video() {
}
});
} catch (error) {
console.error('Error adding views:', error);
addAlert('error', 'Erreur lors de l\'ajout des vues à la vidéo');
}
}, [id, navigation]);
@ -78,7 +79,7 @@ export default function Video() {
comments: commentsData
}));
} catch (error) {
console.error('Error fetching comments:', error);
addAlert('error', 'Erreur lors de la récupération des commentaires');
}
}, [id]);
@ -210,8 +211,7 @@ export default function Video() {
})
} catch (error) {
console.error('Error subscribing:', error);
alert('Failed to subscribe. Please try again.');
addAlert('error', 'Erreur lors de l\'abonnement');
}
};
@ -243,7 +243,6 @@ export default function Video() {
}
const data = await response.json();
console.log('Video liked successfully:', data);
setVideo((prevVideo) => {
return {
@ -253,7 +252,7 @@ export default function Video() {
})
} catch (error) {
console.error('Error liking video:', error);
addAlert('error', 'Erreur lors de l\'ajout du like');
}
};
@ -304,13 +303,22 @@ export default function Video() {
} catch (error) {
console.error('Error posting comment:', error);
addAlert('error', 'Erreur lors de la publication du commentaire');
}
}
const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID
setAlerts([...alerts, newAlert]);
};
const onCloseAlert = (alertToRemove) => {
setAlerts(alerts.filter(alert => alert !== alertToRemove));
};
return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-36 w-full flex justify-between pt-[118px]">
{video ? (

5
frontend/src/services/search.service.js

@ -1,4 +1,4 @@
export function search(query, filter, offset, limit) {
export function search(query, filter, offset, limit, addAlert) {
return fetch(`https://localhost/api/search?q=${encodeURIComponent(query)}&type=${filter}&offset=${offset}&limit=${limit}`, {
method: 'GET',
credentials: 'include',
@ -14,6 +14,9 @@ export function search(query, filter, offset, limit) {
})
.catch((error) => {
console.error("There was a problem with the fetch operation:", error);
if (addAlert) {
addAlert('error', 'Erreur lors de la recherche');
}
throw error;
});
}

18
nginx/default.conf

@ -23,12 +23,30 @@ server {
# 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://resit_backend: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;

Loading…
Cancel
Save