Browse Source

Merge pull request 'features/delete' (#10) from features/delete into developpement

Reviewed-on: #10
pull/12/head
astria 4 months ago
parent
commit
196ec84a60
  1. 18
      backend/app/controllers/channel.controller.js
  2. 10
      backend/app/controllers/comment.controller.js
  3. 6
      backend/app/controllers/oauth.controller.js
  4. 42
      backend/app/controllers/playlist.controller.js
  5. 78
      backend/app/controllers/recommendation.controller.js
  6. 4
      backend/app/controllers/search.controller.js
  7. 136
      backend/app/controllers/user.controller.js
  8. 34
      backend/app/controllers/video.controller.js
  9. 42
      backend/app/middlewares/channel.middleware.js
  10. 16
      backend/app/middlewares/comment.middleware.js
  11. 44
      backend/app/middlewares/playlist.middleware.js
  12. 34
      backend/app/middlewares/user.middleware.js
  13. 36
      backend/app/middlewares/video.middleware.js
  14. 10
      backend/app/routes/user.route.js
  15. 27
      backend/app/utils/database.js
  16. 1335
      backend/logs/access.log
  17. 15
      backend/tools.js
  18. 2
      frontend/src/components/Navbar.jsx
  19. 2
      frontend/src/modals/VerificationModal.jsx
  20. 31
      frontend/src/pages/Account.jsx
  21. 28
      frontend/src/pages/ManageChannel.jsx
  22. 23
      frontend/src/pages/ManageVideo.jsx
  23. 95
      frontend/src/pages/Subscription.jsx
  24. 9
      frontend/src/routes/routes.jsx
  25. 24
      frontend/src/services/channel.service.js
  26. 65
      frontend/src/services/user.service.js
  27. 23
      frontend/src/services/video.service.js

18
backend/app/controllers/channel.controller.js

@ -18,7 +18,7 @@ export async function create(req, res) {
logger.action("try to create new channel with owner " + channel.owner + " and name " + channel.name); logger.action("try to create new channel with owner " + channel.owner + " and name " + channel.name);
logger.write("Successfully created new channel with name " + channel.name, 200); logger.write("Successfully created new channel with name " + channel.name, 200);
client.end(); client.release();
res.status(200).json(channel); res.status(200).json(channel);
} }
@ -87,7 +87,7 @@ export async function getById(req, res) {
result.rows[0].videos = videoReturn; result.rows[0].videos = videoReturn;
logger.write("Successfully get channel with id " + id, 200); logger.write("Successfully get channel with id " + id, 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} }
@ -123,7 +123,7 @@ export async function getAll(req, res) {
}) })
logger.write("Successfully get all channels", 200); logger.write("Successfully get all channels", 200);
client.end(); client.release();
res.status(200).json(result); res.status(200).json(result);
} }
@ -147,7 +147,7 @@ export async function update(req, res) {
const nameResult = await client.query(nameQuery, [channel.name]); const nameResult = await client.query(nameQuery, [channel.name]);
if (nameResult.rows.length > 0) { if (nameResult.rows.length > 0) {
logger.write("failed to update channel because name already taken", 400); logger.write("failed to update channel because name already taken", 400);
client.end(); client.release();
res.status(400).json({error: 'Name already used'}); res.status(400).json({error: 'Name already used'});
return return
} }
@ -156,7 +156,7 @@ export async function update(req, res) {
const updateQuery = `UPDATE channels SET name = $1, description = $2 WHERE id = $3`; const updateQuery = `UPDATE channels SET name = $1, description = $2 WHERE id = $3`;
await client.query(updateQuery, [channel.name, channel.description, id]); await client.query(updateQuery, [channel.name, channel.description, id]);
logger.write("Successfully updated channel", 200); logger.write("Successfully updated channel", 200);
client.end(); client.release();
res.status(200).json(channel); res.status(200).json(channel);
} }
@ -169,7 +169,7 @@ export async function del(req, res) {
const query = `DELETE FROM channels WHERE id = $1`; const query = `DELETE FROM channels WHERE id = $1`;
await client.query(query, [id]); await client.query(query, [id]);
logger.write("Successfully deleted channel", 200); logger.write("Successfully deleted channel", 200);
client.end(); client.release();
res.status(200).json({message: 'Successfully deleted'}); res.status(200).json({message: 'Successfully deleted'});
} }
@ -194,7 +194,7 @@ export async function toggleSubscription(req, res) {
const remainingSubscriptions = countResult.rows[0].count; const remainingSubscriptions = countResult.rows[0].count;
logger.write("Successfully unsubscribed from channel", 200); logger.write("Successfully unsubscribed from channel", 200);
client.end(); client.release();
res.status(200).json({message: 'Unsubscribed successfully', subscriptions: remainingSubscriptions}); res.status(200).json({message: 'Unsubscribed successfully', subscriptions: remainingSubscriptions});
} else { } else {
// Subscribe // Subscribe
@ -207,7 +207,7 @@ export async function toggleSubscription(req, res) {
const totalSubscriptions = countResult.rows[0].count; const totalSubscriptions = countResult.rows[0].count;
logger.write("Successfully subscribed to channel", 200); logger.write("Successfully subscribed to channel", 200);
client.end(); client.release();
res.status(200).json({message: 'Subscribed successfully', subscriptions: totalSubscriptions}); res.status(200).json({message: 'Subscribed successfully', subscriptions: totalSubscriptions});
} }
} }
@ -233,7 +233,7 @@ export async function getStats(req, res) {
const client = await getClient(); const client = await getClient();
const result = await client.query(request, [id]); const result = await client.query(request, [id]);
logger.write("Successfully get stats", 200); logger.write("Successfully get stats", 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} catch (error) { } catch (error) {
console.log(error); console.log(error);

10
backend/app/controllers/comment.controller.js

@ -39,7 +39,7 @@ export async function upload(req, res) {
createdAt: createdAt createdAt: createdAt
} }
client.end(); client.release();
res.status(200).json(responseComment); res.status(200).json(responseComment);
} }
@ -52,7 +52,7 @@ export async function getByVideo(req, res) {
const query = `SELECT * FROM comments WHERE video = $1`; const query = `SELECT * FROM comments WHERE video = $1`;
const result = await client.query(query, [videoId]); const result = await client.query(query, [videoId]);
logger.write("successfully get comment", 200); logger.write("successfully get comment", 200);
client.end() client.release()
res.status(200).json(result.rows); res.status(200).json(result.rows);
} }
@ -64,7 +64,7 @@ export async function getById(req, res) {
const query = `SELECT * FROM comments WHERE id = $1`; const query = `SELECT * FROM comments WHERE id = $1`;
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
logger.write("successfully get comment", 200); logger.write("successfully get comment", 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} }
@ -76,7 +76,7 @@ export async function update(req, res) {
const query = `UPDATE comments SET content = $1 WHERE id = $2`; const query = `UPDATE comments SET content = $1 WHERE id = $2`;
const result = await client.query(query, [req.body.content, id]); const result = await client.query(query, [req.body.content, id]);
logger.write("successfully update comment", 200); logger.write("successfully update comment", 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} }
@ -88,6 +88,6 @@ export async function del(req, res) {
const query = `DELETE FROM comments WHERE id = $1`; const query = `DELETE FROM comments WHERE id = $1`;
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
logger.write("successfully deleted comment", 200); logger.write("successfully deleted comment", 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} }

6
backend/app/controllers/oauth.controller.js

@ -102,7 +102,7 @@ export async function callback(req, res, next) {
await client.query(playlistQuery, [dbUser.id]); await client.query(playlistQuery, [dbUser.id]);
} }
client.end(); client.release();
// Generate JWT token // Generate JWT token
const payload = { const payload = {
@ -145,11 +145,11 @@ export async function getUserInfo(req, res) {
const result = await client.query(query, [decoded.id]); const result = await client.query(query, [decoded.id]);
if (!result.rows[0]) { if (!result.rows[0]) {
client.end(); client.release();
return res.status(404).json({ error: "User not found" }); return res.status(404).json({ error: "User not found" });
} }
client.end(); client.release();
res.status(200).json({ user: result.rows[0] }); res.status(200).json({ user: result.rows[0] });
} catch (error) { } catch (error) {

42
backend/app/controllers/playlist.controller.js

@ -14,11 +14,11 @@ export async function create(req, res) {
try { try {
const result = await client.query(query, [name, userId]); const result = await client.query(query, [name, userId]);
logger.write("Playlist created with id " + result.rows[0].id, 200); logger.write("Playlist created with id " + result.rows[0].id, 200);
client.end() client.release()
res.status(200).json({ id: result.rows[0].id }); res.status(200).json({ id: result.rows[0].id });
} catch (error) { } catch (error) {
logger.write("Error creating playlist: " + error.message, 500); logger.write("Error creating playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
} }
@ -33,11 +33,11 @@ export async function addVideo(req, res) {
try { try {
const result = await client.query(query, [video, id]); const result = await client.query(query, [video, id]);
logger.write("Video added to playlist with id " + id, 200); logger.write("Video added to playlist with id " + id, 200);
client.end(); client.release();
res.status(200).json({id: result.rows[0].id}); res.status(200).json({id: result.rows[0].id});
} catch (error) { } catch (error) {
logger.write("Error adding video to playlist: " + error.message, 500); logger.write("Error adding video to playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({error: "Internal server error"}); res.status(500).json({error: "Internal server error"});
} }
} }
@ -64,16 +64,16 @@ export async function getByUser(req, res) {
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("No playlists found for user with id " + id, 404); logger.write("No playlists found for user with id " + id, 404);
client.end(); client.release();
res.status(404).json({ error: "No playlists found" }); res.status(404).json({ error: "No playlists found" });
return; return;
} }
logger.write("Playlists retrieved for user with id " + id, 200); logger.write("Playlists retrieved for user with id " + id, 200);
client.end(); client.release();
res.status(200).json(result.rows); res.status(200).json(result.rows);
} catch (error) { } catch (error) {
logger.write("Error retrieving playlists: " + error.message, 500); logger.write("Error retrieving playlists: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
} }
@ -145,16 +145,16 @@ export async function getById(req, res) {
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("No playlist found with id " + id, 404); logger.write("No playlist found with id " + id, 404);
client.end(); client.release();
res.status(404).json({ error: "Playlist not found" }); res.status(404).json({ error: "Playlist not found" });
return; return;
} }
logger.write("Playlist retrieved with id " + id, 200); logger.write("Playlist retrieved with id " + id, 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} catch (error) { } catch (error) {
logger.write("Error retrieving playlist: " + error.message, 500); logger.write("Error retrieving playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
} }
@ -171,16 +171,16 @@ export async function update(req, res) {
const result = await client.query(query, [name, id]); const result = await client.query(query, [name, id]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("No playlist found with id " + id, 404); logger.write("No playlist found with id " + id, 404);
client.end(); client.release();
res.status(404).json({ error: "Playlist not found", result: result.rows, query: query }); res.status(404).json({ error: "Playlist not found", result: result.rows, query: query });
return; return;
} }
logger.write("Playlist updated with id " + result.rows[0].id, 200); logger.write("Playlist updated with id " + result.rows[0].id, 200);
client.end(); client.release();
res.status(200).json({ id: result.rows[0].id }); res.status(200).json({ id: result.rows[0].id });
} catch (error) { } catch (error) {
logger.write("Error updating playlist: " + error.message, 500); logger.write("Error updating playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
} }
@ -196,16 +196,16 @@ export async function deleteVideo(req, res) {
const result = await client.query(query, [videoId, id]); const result = await client.query(query, [videoId, id]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("No video found in playlist with id " + id, 404); logger.write("No video found in playlist with id " + id, 404);
client.end(); client.release();
res.status(404).json({ error: "Video not found in playlist" }); res.status(404).json({ error: "Video not found in playlist" });
return; return;
} }
logger.write("Video deleted from playlist with id " + id, 200); logger.write("Video deleted from playlist with id " + id, 200);
client.end(); client.release();
res.status(200).json({ id: result.rows[0].id }); res.status(200).json({ id: result.rows[0].id });
} catch (error) { } catch (error) {
logger.write("Error deleting video from playlist: " + error.message, 500); logger.write("Error deleting video from playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
} }
@ -220,11 +220,11 @@ export async function del(req, res) {
try { try {
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
logger.write("Playlist deleted", 200); logger.write("Playlist deleted", 200);
client.end() client.release()
res.status(200).json({ "message": "playlist deleted" }); res.status(200).json({ "message": "playlist deleted" });
} catch (error) { } catch (error) {
logger.write("Error deleting playlist: " + error.message, 500); logger.write("Error deleting playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }
} }
@ -286,16 +286,16 @@ export async function getSeeLater(req, res) {
const result = await client.query(query, [userId]); const result = await client.query(query, [userId]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("No 'See Later' playlist found for user with id " + userId, 404); logger.write("No 'See Later' playlist found for user with id " + userId, 404);
client.end(); client.release();
res.status(404).json({ error: "'See Later' playlist not found" }); res.status(404).json({ error: "'See Later' playlist not found" });
return; return;
} }
logger.write("'See Later' playlist retrieved for user with id " + userId, 200); logger.write("'See Later' playlist retrieved for user with id " + userId, 200);
client.end(); client.release();
res.status(200).json(result.rows[0].videos); res.status(200).json(result.rows[0].videos);
} catch (error) { } catch (error) {
logger.write("Error retrieving 'See Later' playlist: " + error.message, 500); logger.write("Error retrieving 'See Later' playlist: " + error.message, 500);
client.end(); client.release();
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} }

78
backend/app/controllers/recommendation.controller.js

@ -188,68 +188,73 @@ export async function getRecommendations(req, res) {
let result = await client.query(query, [claims.id]); let result = await client.query(query, [claims.id]);
client.end() client.release()
res.status(200).json(result.rows); res.status(200).json(result.rows);
} }
} }
export async function getTrendingVideos(req, res) { export async function getTrendingVideos(req, res) {
const client = await getClient();
try { try {
// GET 10 VIDEOS WITH THE MOST LIKES AND COMMENTS // Optimized single query to get all trending video data
let client = await getClient();
let queryTrendingVideos = ` let queryTrendingVideos = `
SELECT v.id, v.title, v.description, v.release_date, v.thumbnail, v.visibility, SELECT
COUNT(DISTINCT l.id) AS like_count, COUNT(DISTINCT c.id) AS comment_count v.id,
v.title,
v.description,
v.release_date,
v.thumbnail,
v.visibility,
COUNT(DISTINCT l.id) AS like_count,
COUNT(DISTINCT c.id) AS comment_count,
COUNT(DISTINCT h.id) AS views,
ch.id AS creator_id,
ch.name AS creator_name,
u.picture AS creator_profile_picture
FROM videos v FROM videos v
LEFT JOIN likes l ON v.id = l.video LEFT JOIN likes l ON v.id = l.video
LEFT JOIN comments c ON v.id = c.video LEFT JOIN comments c ON v.id = c.video
LEFT JOIN history h ON v.id = h.video
LEFT JOIN channels ch ON v.channel = ch.id
LEFT JOIN users u ON ch.owner = u.id
WHERE v.visibility = 'public' WHERE v.visibility = 'public'
GROUP BY v.id GROUP BY v.id, ch.id, ch.name, u.picture
ORDER BY like_count DESC, comment_count DESC ORDER BY like_count DESC, comment_count DESC, views DESC
LIMIT 10 LIMIT 10
`; `;
let result = await client.query(queryTrendingVideos);
const trendingVideos = result.rows;
for (let video of trendingVideos) {
// Get the number of views for each video
let viewsQuery = `SELECT COUNT(*) AS view_count FROM history WHERE video = $1;`;
let viewsResult = await client.query(viewsQuery, [video.id]);
video.views = viewsResult.rows[0].view_count;
// Get the creator of each video
let creatorQuery = `SELECT c.id, c.name FROM channels c JOIN videos v ON c.id = v.channel WHERE v.id = $1;`;
let creatorResult = await client.query(creatorQuery, [video.id]);
if (creatorResult.rows.length > 0) {
video.creator = creatorResult.rows[0];
} else {
video.creator = {id: null, name: "Unknown"};
}
// GET THE PROFILE PICTURE OF THE CREATOR
let profilePictureQuery = `SELECT u.picture FROM users u JOIN channels c ON u.id = c.owner WHERE c.id = $1;`;
let profilePictureResult = await client.query(profilePictureQuery, [video.creator.id]);
if (profilePictureResult.rows.length > 0) {
video.creator.profilePicture = profilePictureResult.rows[0].picture;
} else {
video.creator.profilePicture = null; // Default or placeholder image can be set here
}
let result = await client.query(queryTrendingVideos);
const trendingVideos = result.rows.map(video => ({
id: video.id,
title: video.title,
description: video.description,
release_date: video.release_date,
thumbnail: video.thumbnail,
visibility: video.visibility,
like_count: video.like_count,
comment_count: video.comment_count,
views: video.views,
creator: {
id: video.creator_id,
name: video.creator_name,
profilePicture: video.creator_profile_picture
} }
}));
client.end();
res.status(200).json(trendingVideos); res.status(200).json(trendingVideos);
} catch (error) { } catch (error) {
console.error("Error fetching trending videos:", error); console.error("Error fetching trending videos:", error);
res.status(500).json({error: "Internal server error while fetching trending videos."}); res.status(500).json({error: "Internal server error while fetching trending videos."});
} finally {
client.release();
} }
} }
export async function getTopCreators(req, res) { export async function getTopCreators(req, res) {
const client = await getClient();
try { try {
// GET TOP 5 CREATORS BASED ON NUMBER OF SUBSCRIBERS // GET TOP 5 CREATORS BASED ON NUMBER OF SUBSCRIBERS
let client = await getClient();
let queryTopCreators = ` let queryTopCreators = `
SELECT c.id, c.name, c.description, u.picture AS profilePicture, COUNT(s.id) AS subscriber_count SELECT c.id, c.name, c.description, u.picture AS profilePicture, COUNT(s.id) AS subscriber_count
FROM channels c FROM channels c
@ -262,10 +267,11 @@ export async function getTopCreators(req, res) {
let result = await client.query(queryTopCreators); let result = await client.query(queryTopCreators);
const topCreators = result.rows; const topCreators = result.rows;
client.end();
res.status(200).json(topCreators); res.status(200).json(topCreators);
} catch (error) { } catch (error) {
console.error("Error fetching top creators:", error); console.error("Error fetching top creators:", error);
res.status(500).json({error: "Internal server error while fetching top creators."}); res.status(500).json({error: "Internal server error while fetching top creators."});
} finally {
client.release();
} }
} }

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

@ -54,7 +54,7 @@ export async function search(req, res) {
videoResults.push(video); videoResults.push(video);
} }
client.end() client.release()
return res.status(200).json(videoResults); return res.status(200).json(videoResults);
@ -83,7 +83,7 @@ export async function search(req, res) {
channelResults.push(channel); channelResults.push(channel);
} }
client.end() client.release()
return res.status(200).json(channelResults); return res.status(200).json(channelResults);

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

@ -121,15 +121,16 @@ export async function register(req, res) {
const insertQuery = `INSERT INTO email_verification (email, token, expires_at) VALUES ($1, $2, $3)`; const insertQuery = `INSERT INTO email_verification (email, token, expires_at) VALUES ($1, $2, $3)`;
await client.query(insertQuery, [user.email, token, expirationDate]); await client.query(insertQuery, [user.email, token, expirationDate]);
client.end();
console.log("Successfully registered"); console.log("Successfully registered");
client.end();
logger.write("successfully registered", 200); logger.write("successfully registered", 200);
res.status(200).send({ user: user }); res.status(200).send({ user: user });
} catch (err) { } catch (err) {
console.log(err); console.log(err);
logger?.write("failed to register user", 500);
res.status(500).json({ error: "Internal server error" });
} finally {
client.release();
} }
} }
@ -165,7 +166,7 @@ export async function verifyEmail(req, res) {
logger.write("failed to verify email for " + email, 500); logger.write("failed to verify email for " + email, 500);
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} finally { } finally {
client.end(); client.release();
} }
} }
@ -180,6 +181,7 @@ export async function login(req, res) {
const client = await getClient(); const client = await getClient();
try {
let query = `SELECT id, username, email, picture, password FROM users WHERE username = $1`; let query = `SELECT id, username, email, picture, password FROM users WHERE username = $1`;
const result = await client.query(query, [user.username]); const result = await client.query(query, [user.username]);
@ -214,9 +216,14 @@ export async function login(req, res) {
} }
logger.write("Successfully logged in", 200); logger.write("Successfully logged in", 200);
client.end();
res.status(200).json({ token: token, user: userData }); res.status(200).json({ token: token, user: userData });
} catch (err) {
console.log(err);
logger?.write("failed to login", 500);
res.status(500).json({ error: "Internal server error" });
} finally {
client.release();
}
} }
export async function getById(req, res) { export async function getById(req, res) {
@ -230,7 +237,7 @@ export async function getById(req, res) {
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve user " + id + " because it doesn't exist", 404); logger.write("failed to retrieve user " + id + " because it doesn't exist", 404);
client.end() client.release()
res.status(404).json({ error: "Not Found" }); res.status(404).json({ error: "Not Found" });
return return
} }
@ -249,12 +256,12 @@ export async function getByUsername(req, res) {
const result = await client.query(query, [username]); const result = await client.query(query, [username]);
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve user " + username + " because it doesn't exist", 404); logger.write("failed to retrieve user " + username + " because it doesn't exist", 404);
client.end() client.release()
res.status(404).json({ error: "Not Found" }); res.status(404).json({ error: "Not Found" });
return return
} }
logger.write("successfully retrieved user " + username, 200); logger.write("successfully retrieved user " + username, 200);
client.end(); client.release();
return res.status(200).json({ user: result.rows[0] }); return res.status(200).json({ user: result.rows[0] });
} }
@ -282,7 +289,7 @@ export async function update(req, res) {
const emailResult = await client.query(emailQuery, [user.email]); const emailResult = await client.query(emailQuery, [user.email]);
if (emailResult.rows[0]) { if (emailResult.rows[0]) {
logger.write("failed to update because email is already used", 400) logger.write("failed to update because email is already used", 400)
client.end(); client.release();
res.status(400).json({ error: "Email already exists" }); res.status(400).json({ error: "Email already exists" });
} }
} }
@ -292,7 +299,7 @@ export async function update(req, res) {
const usernameResult = await client.query(usernameQuery, [user.username]); const usernameResult = await client.query(usernameQuery, [user.username]);
if (usernameResult.rows[0]) { if (usernameResult.rows[0]) {
logger.write("failed to update because username is already used", 400) logger.write("failed to update because username is already used", 400)
client.end(); client.release();
res.status(400).json({ error: "Username already exists" }); res.status(400).json({ error: "Username already exists" });
} }
} }
@ -329,11 +336,11 @@ export async function update(req, res) {
const updateQuery = `UPDATE users SET email = $1, username = $2, password = $3, picture = $4 WHERE id = $5 RETURNING id, email, username, picture`; const updateQuery = `UPDATE users SET email = $1, username = $2, password = $3, picture = $4 WHERE id = $5 RETURNING id, email, username, picture`;
const result = await client.query(updateQuery, [user.email, user.username, user.password, profilePicture, id]); const result = await client.query(updateQuery, [user.email, user.username, user.password, profilePicture, id]);
logger.write("successfully updated user " + id, 200); logger.write("successfully updated user " + id, 200);
client.end(); client.release();
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
client.end() client.release()
res.status(500).json({ error: err }); res.status(500).json({ error: err });
} }
@ -347,7 +354,7 @@ export async function deleteUser(req, res) {
const query = `DELETE FROM users WHERE id = $1`; const query = `DELETE FROM users WHERE id = $1`;
await client.query(query, [id]); await client.query(query, [id]);
logger.write("successfully deleted user " + id); logger.write("successfully deleted user " + id);
client.end(); client.release();
res.status(200).json({ message: 'User deleted' }); res.status(200).json({ message: 'User deleted' });
} }
@ -362,13 +369,13 @@ export async function getChannel(req, res) {
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404); logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404);
client.end(); client.release();
res.status(404).json({ error: "Channel Not Found" }); res.status(404).json({ error: "Channel Not Found" });
return; return;
} }
logger.write("successfully retrieved channel of user " + id, 200); logger.write("successfully retrieved channel of user " + id, 200);
client.end(); client.release();
res.status(200).json({ channel: result.rows[0] }); res.status(200).json({ channel: result.rows[0] });
} }
@ -415,12 +422,12 @@ export async function getHistory(req, res) {
if (!result.rows[0]) { if (!result.rows[0]) {
logger.write("failed to retrieve history of user " + id + " because it doesn't exist", 404); logger.write("failed to retrieve history of user " + id + " because it doesn't exist", 404);
res.status(404).json({ error: "History Not Found" }); res.status(404).json({ error: "History Not Found" });
client.end(); client.release();
return; return;
} }
logger.write("successfully retrieved history of user " + id, 200); logger.write("successfully retrieved history of user " + id, 200);
client.end(); client.release();
res.status(200).json(videos); res.status(200).json(videos);
} }
@ -439,11 +446,11 @@ export async function isSubscribed(req, res) {
if (result.rows[0]) { if (result.rows[0]) {
logger.write(`user ${userId} is subscribed to channel ${channelId}`, 200); logger.write(`user ${userId} is subscribed to channel ${channelId}`, 200);
client.end(); client.release();
return res.status(200).json({ subscribed: true }); return res.status(200).json({ subscribed: true });
} else { } else {
logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200); logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200);
client.end(); client.release();
return res.status(200).json({ subscribed: false }); return res.status(200).json({ subscribed: false });
} }
} }
@ -459,11 +466,96 @@ export async function searchByUsername(req, res) {
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("no user found with username " + username, 404); logger.write("no user found with username " + username, 404);
client.end(); client.release();
return res.status(404).json({ error: "User Not Found" }); return res.status(404).json({ error: "User Not Found" });
} }
logger.write("successfully found user with username " + username, 200); logger.write("successfully found user with username " + username, 200);
client.end(); client.release();
res.status(200).json(result.rows);
}
export async function getAllSubscriptions(req,res) {
const userId = req.params.id;
const client = await getClient();
const logger = req.body.logger;
logger.action("try to retrieve all subscriptions of user " + userId);
const query = `
SELECT
subscriptions.id,
channels.id AS channel_id,
channels.name AS channel_name,
users.picture
FROM
subscriptions
LEFT JOIN channels ON subscriptions.channel = channels.id
LEFT JOIN users ON channels.owner = users.id
WHERE
subscriptions.owner = $1
`;
const result = await client.query(query, [userId]);
if (result.rows.length === 0) {
logger.write("no subscriptions found for user " + userId, 404);
client.release();
return res.status(404).json({ error: "No Subscriptions Found" });
}
logger.write("successfully retrieved all subscriptions of user " + userId, 200);
client.release();
res.status(200).json(result.rows);
}
export async function getAllSubscriptionVideos(req, res) {
const userId = req.params.id;
const client = await getClient();
const logger = req.body.logger;
logger.action("try to retrieve all subscriptions of user " + userId);
const query = `
SELECT
videos.id,
videos.title,
videos.thumbnail,
channels.id AS channel,
videos.visibility,
videos.file,
videos.format,
videos.release_date,
channels.id AS channel_id,
channels.owner,
COUNT(history.id) AS views,
JSON_BUILD_OBJECT(
'name', channels.name,
'profilePicture', users.picture,
'description', channels.description
) AS creator
FROM
subscriptions
LEFT JOIN channels ON subscriptions.channel = channels.id
LEFT JOIN users ON channels.owner = users.id
LEFT JOIN videos ON channels.id = videos.channel
LEFT JOIN history ON videos.id = history.video
WHERE
subscriptions.owner = $1
GROUP BY
videos.id,
channels.id,
users.id;
`;
const result = await client.query(query, [userId]);
if (result.rows.length === 0) {
logger.write("no subscriptions found for user " + userId, 404);
client.release();
return res.status(404).json({ error: "No Subscriptions Found" });
}
logger.write("successfully retrieved all subscriptions of user " + userId, 200);
client.release();
res.status(200).json(result.rows); res.status(200).json(result.rows);
} }

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

@ -23,7 +23,7 @@ export async function upload(req, res) {
const query = `SELECT * FROM videos WHERE slug = $1`; const query = `SELECT * FROM videos WHERE slug = $1`;
const result = await client.query(query, [hex]); const result = await client.query(query, [hex]);
client.end(); client.release();
if (result.rows.length === 0) { if (result.rows.length === 0) {
isGenerate = true; isGenerate = true;
req.body.slug = hex; req.body.slug = hex;
@ -157,7 +157,7 @@ export async function upload(req, res) {
} }
logger.write("successfully uploaded video", 200); logger.write("successfully uploaded video", 200);
await client.end() await client.release()
res.status(200).json({ "message": "Successfully uploaded video", "id": id }); res.status(200).json({ "message": "Successfully uploaded video", "id": id });
} }
@ -181,7 +181,7 @@ export async function uploadThumbnail(req, res) {
const updateQuery = `UPDATE videos SET thumbnail = $1 WHERE id = $2`; const updateQuery = `UPDATE videos SET thumbnail = $1 WHERE id = $2`;
await client.query(updateQuery, [file, req.body.video]); await client.query(updateQuery, [file, req.body.video]);
logger.write("successfully uploaded thumbnail", 200); logger.write("successfully uploaded thumbnail", 200);
await client.end(); await client.release();
res.status(200).json({ "message": "Successfully uploaded thumbnail" }); res.status(200).json({ "message": "Successfully uploaded thumbnail" });
} }
@ -234,7 +234,7 @@ export async function getById(req, res) {
video.authorizedUsers = authorizedUsersResult.rows; video.authorizedUsers = authorizedUsersResult.rows;
logger.write("successfully get video " + id, 200); logger.write("successfully get video " + id, 200);
client.end() client.release()
res.status(200).json(video); res.status(200).json(video);
} }
@ -246,7 +246,7 @@ export async function getByChannel(req, res) {
const query = `SELECT * FROM videos WHERE channel = $1`; const query = `SELECT * FROM videos WHERE channel = $1`;
const result = await client.query(query, [id]); const result = await client.query(query, [id]);
logger.write("successfully get video from channel " + id, 200); logger.write("successfully get video from channel " + id, 200);
client.end() client.release()
res.status(200).json(result.rows); res.status(200).json(result.rows);
} }
@ -297,7 +297,7 @@ export async function update(req, res) {
`; `;
const result = await client.query(resultQuery, [id]); const result = await client.query(resultQuery, [id]);
logger.write("successfully updated video", 200); logger.write("successfully updated video", 200);
client.end() client.release()
res.status(200).json(result.rows[0]); res.status(200).json(result.rows[0]);
} }
@ -315,7 +315,7 @@ export async function updateVideo(req, res) {
fs.unlink(pathToDelete, (error) => { fs.unlink(pathToDelete, (error) => {
if (error) { if (error) {
logger.write(error, 500); logger.write(error, 500);
client.end() client.release()
res.status(500).json({ "message": "Failed to delete video" }); res.status(500).json({ "message": "Failed to delete video" });
return return
} }
@ -327,7 +327,7 @@ export async function updateVideo(req, res) {
fs.writeFileSync(destinationPath, fileBuffer); fs.writeFileSync(destinationPath, fileBuffer);
logger.write("successfully updated video", 200); logger.write("successfully updated video", 200);
client.end() client.release()
res.status(200).json({ "message": "Successfully updated video" }); res.status(200).json({ "message": "Successfully updated video" });
}) })
@ -349,7 +349,7 @@ export async function del(req, res) {
fs.unlink(pathToDelete, (error) => { fs.unlink(pathToDelete, (error) => {
if (error) { if (error) {
logger.write(error, 500); logger.write(error, 500);
client.end() client.release()
res.status(500).json({ "message": "Failed to delete video" }); res.status(500).json({ "message": "Failed to delete video" });
return return
} }
@ -364,7 +364,7 @@ export async function del(req, res) {
const query = `DELETE FROM videos WHERE id = $1`; const query = `DELETE FROM videos WHERE id = $1`;
await client.query(query, [id]); await client.query(query, [id]);
logger.write("successfully deleted video", 200); logger.write("successfully deleted video", 200);
client.end() client.release()
res.status(200).json({ "message": "Successfully deleted video" }); res.status(200).json({ "message": "Successfully deleted video" });
}) })
}) })
@ -395,7 +395,7 @@ export async function toggleLike(req, res) {
const likesCount = likesCountResult.rows[0].like_count; const likesCount = likesCountResult.rows[0].like_count;
logger.write("no likes found adding likes for video " + id, 200); logger.write("no likes found adding likes for video " + id, 200);
client.end(); client.release();
res.status(200).json({ "message": "Successfully added like", "likes": likesCount }); res.status(200).json({ "message": "Successfully added like", "likes": likesCount });
} else { } else {
const query = `DELETE FROM likes WHERE owner = $1 AND video = $2`; const query = `DELETE FROM likes WHERE owner = $1 AND video = $2`;
@ -407,7 +407,7 @@ export async function toggleLike(req, res) {
const likesCount = likesCountResult.rows[0].like_count; const likesCount = likesCountResult.rows[0].like_count;
logger.write("likes found, removing like for video " + id, 200); logger.write("likes found, removing like for video " + id, 200);
client.end(); client.release();
res.status(200).json({ "message": "Successfully removed like", "likes": likesCount }); res.status(200).json({ "message": "Successfully removed like", "likes": likesCount });
} }
@ -462,7 +462,7 @@ export async function addTags(req, res) {
const updatedTags = updatedTagsResult.rows; const updatedTags = updatedTagsResult.rows;
logger.write("successfully added tags to video " + videoId, 200); logger.write("successfully added tags to video " + videoId, 200);
await client.end(); await client.release();
res.status(200).json({ "message": "Successfully added tags to video", "tags": updatedTags.map(tag => tag.name) }); res.status(200).json({ "message": "Successfully added tags to video", "tags": updatedTags.map(tag => tag.name) });
} }
@ -522,7 +522,7 @@ export async function getSimilarVideos(req, res) {
} }
logger.write("successfully get similar videos for video " + id, 200); logger.write("successfully get similar videos for video " + id, 200);
await client.end(); await client.release();
res.status(200).json(result.rows); res.status(200).json(result.rows);
} }
@ -573,7 +573,7 @@ export async function getLikesPerDay(req, res) {
logger.write("Error retrieving likes per day: " + error.message, 500); logger.write("Error retrieving likes per day: " + error.message, 500);
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} finally { } finally {
await client.end(); await client.release();
} }
} }
@ -596,7 +596,7 @@ export async function addViews(req, res) {
} }
logger.write("successfully added views for video " + id, 200); logger.write("successfully added views for video " + id, 200);
await client.end(); await client.release();
res.status(200).json({ "message": "Successfully added views" }); res.status(200).json({ "message": "Successfully added views" });
} }
@ -631,6 +631,6 @@ export async function updateAuthorizedUsers(req, res) {
logger.write("Error updating authorized users: " + error.message, 500); logger.write("Error updating authorized users: " + error.message, 500);
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} finally { } finally {
await client.end(); await client.release();
} }
} }

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

@ -22,8 +22,9 @@ export const ChannelCreate = {
export async function doUserHaveChannel(req, res, next) { export async function doUserHaveChannel(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT id FROM channels WHERE owner = ${req.body.owner}`; try {
const result = await client.query(query); const query = `SELECT id FROM channels WHERE owner = $1`;
const result = await client.query(query, [req.body.owner]);
if (result.rows[0]) { if (result.rows[0]) {
logger.write("failed because user already has a channel", 400); logger.write("failed because user already has a channel", 400);
@ -31,14 +32,17 @@ export async function doUserHaveChannel(req, res, next) {
} else { } else {
next() next()
} }
} finally {
client.release();
}
} }
export async function doChannelNameExists(req, res, next) { export async function doChannelNameExists(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT * FROM channels WHERE name = '${req.body.name}'`; try {
const result = await client.query(query); const query = `SELECT * FROM channels WHERE name = $1`;
const result = await client.query(query, [req.body.name]);
if (result.rows[0]) { if (result.rows[0]) {
logger.write("failed because channel name already exist", 400) logger.write("failed because channel name already exist", 400)
@ -46,32 +50,43 @@ export async function doChannelNameExists(req, res, next) {
} else { } else {
next() next()
} }
} finally {
client.release();
}
} }
export async function doChannelExists(req, res, next) { export async function doChannelExists(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT id FROM channels WHERE id = '${req.params.id}'`; try {
const result = await client.query(query); const query = `SELECT id FROM channels WHERE id = $1`;
const result = await client.query(query, [req.params.id]);
if (result.rows[0]) { if (result.rows[0]) {
next() next()
} else { } else {
logger.write("failed to retrieve channel because it doesn't exist", 404); logger.write("failed to retrieve channel because it doesn't exist", 404);
res.status(404).json({error: "Not Found"}) res.status(404).json({error: "Not Found"})
} }
} finally {
client.release();
}
} }
export async function doChannelExistBody(req, res, next) { export async function doChannelExistBody(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT id FROM channels WHERE id = ${req.body.channel}`; try {
const result = await client.query(query); const query = `SELECT id FROM channels WHERE id = $1`;
const result = await client.query(query, [req.body.channel]);
if (result.rows[0]) { if (result.rows[0]) {
next() next()
} else { } else {
logger.write("failed to retrieve channel because it doesn't exist", 404); logger.write("failed to retrieve channel because it doesn't exist", 404);
res.status(404).json({error: "Not Found"}) res.status(404).json({error: "Not Found"})
} }
} finally {
client.release();
}
} }
export async function isOwner(req, res, next) { export async function isOwner(req, res, next) {
@ -83,9 +98,9 @@ export async function isOwner(req, res, next) {
const logger = req.body.logger; const logger = req.body.logger;
const client = await getClient(); const client = await getClient();
try {
const query = `SELECT id, owner FROM channels WHERE id = ${id}`; const query = `SELECT id, owner FROM channels WHERE id = $1`;
const result = await client.query(query); const result = await client.query(query, [id]);
const channel = result.rows[0]; const channel = result.rows[0];
if (channel.owner != claims.id) { if (channel.owner != claims.id) {
logger.write("failed because user do not own the channel", 403); logger.write("failed because user do not own the channel", 403);
@ -93,4 +108,7 @@ export async function isOwner(req, res, next) {
} else { } else {
next() next()
} }
} finally {
client.release();
}
} }

16
backend/app/middlewares/comment.middleware.js

@ -20,14 +20,18 @@ export async function doCommentExists(req, res, next) {
const logger = req.body.logger; const logger = req.body.logger;
const client = await getClient(); const client = await getClient();
const query = `SELECT * FROM comments WHERE id = ${id}`; try {
const result = await client.query(query); const query = `SELECT * FROM comments WHERE id = $1`;
const result = await client.query(query, [id]);
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("comment not found", 404); logger.write("comment not found", 404);
res.status(404).json({error: "comment not found"}); res.status(404).json({error: "comment not found"});
return return
} }
next() next()
} finally {
client.release();
}
} }
export async function isAuthor(req, res, next) { export async function isAuthor(req, res, next) {
@ -37,12 +41,16 @@ export async function isAuthor(req, res, next) {
const userId = claims.id; const userId = claims.id;
const logger = req.body.logger; const logger = req.body.logger;
const client = await getClient(); const client = await getClient();
const query = `SELECT * FROM comments WHERE id = ${id}`; try {
const result = await client.query(query); const query = `SELECT * FROM comments WHERE id = $1`;
const result = await client.query(query, [id]);
if (userId !== result.rows[0].author) { if (userId !== result.rows[0].author) {
logger.write("is not author of the comment", 403); logger.write("is not author of the comment", 403);
res.status(403).json({error: "You do not have permission"}); res.status(403).json({error: "You do not have permission"});
return return
} }
next() next()
} finally {
client.release();
}
} }

44
backend/app/middlewares/playlist.middleware.js

@ -16,20 +16,23 @@ export async function doPlaylistExists(req, res, next) {
const logger = req.body.logger; const logger = req.body.logger;
const client = await getClient(); const client = await getClient();
const query = `SELECT id FROM playlists WHERE id = ${id}`;
try { try {
var result = await client.query(query); const query = `SELECT id FROM playlists WHERE id = $1`;
} catch (error) { const result = await client.query(query, [id]);
logger.write("Error checking playlist existence: " + error.message, 500);
res.status(500).json({error: error});
return;
}
if (result.rows.length === 0) { if (result.rows.length === 0) {
logger.write("No playlist with id " + id, 404); logger.write("No playlist with id " + id, 404);
res.status(404).json({error: "Playlist not found"}); res.status(404).json({error: "Playlist not found"});
return; return;
} }
next(); next();
} catch (error) {
logger.write("Error checking playlist existence: " + error.message, 500);
res.status(500).json({error: error});
return;
} finally {
client.release();
}
} }
export async function isOwner(req, res, next) { export async function isOwner(req, res, next) {
@ -41,9 +44,9 @@ export async function isOwner(req, res, next) {
const logger = req.body.logger; const logger = req.body.logger;
const client = await getClient(); const client = await getClient();
const query = `SELECT owner FROM playlists WHERE id = ${id}`; try {
const query = `SELECT owner FROM playlists WHERE id = $1`;
const result = await client.query(query); const result = await client.query(query, [id]);
if(result.rows[0].owner !== userId) { if(result.rows[0].owner !== userId) {
logger.write("user not the owner of the playlist with id " + id, 403); logger.write("user not the owner of the playlist with id " + id, 403);
@ -52,7 +55,9 @@ export async function isOwner(req, res, next) {
} }
next(); next();
} finally {
client.release();
}
} }
export async function isVideoInPlaylist(req, res, next) { export async function isVideoInPlaylist(req, res, next) {
@ -61,15 +66,10 @@ export async function isVideoInPlaylist(req, res, next) {
const videoId = req.params.videoId; const videoId = req.params.videoId;
const logger = req.body.logger; const logger = req.body.logger;
const client = await getClient(); const client = await getClient();
const query = `SELECT id FROM playlist_elements WHERE video = ${videoId} AND playlist = ${id}`;
try { try {
var result = await client.query(query); const query = `SELECT id FROM playlist_elements WHERE video = $1 AND playlist = $2`;
} catch (error) { const result = await client.query(query, [videoId, id]);
logger.write("Error checking video in playlist: " + query, 500);
res.status(500).json({error: error });
return;
}
if(result.rows.length === 0) { if(result.rows.length === 0) {
logger.write("video " + videoId + "not found in playlist with id " + id, 404 ); logger.write("video " + videoId + "not found in playlist with id " + id, 404 );
@ -78,5 +78,11 @@ export async function isVideoInPlaylist(req, res, next) {
} }
next(); next();
} catch (error) {
logger.write("Error checking video in playlist: " + error.message, 500);
res.status(500).json({error: error });
return;
} finally {
client.release();
}
} }

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

@ -44,8 +44,9 @@ export const UserSearch = {
export async function doEmailExists(req, res, next) { export async function doEmailExists(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`; try {
const result = await client.query(query); const query = `SELECT * FROM users WHERE email = $1`;
const result = await client.query(query, [req.body.email]);
if (result.rows.length > 0) { if (result.rows.length > 0) {
logger.write("failed because email already exists", 400) logger.write("failed because email already exists", 400)
@ -53,47 +54,60 @@ export async function doEmailExists(req, res, next) {
} else { } else {
next() next()
} }
} finally {
client.release();
}
} }
export async function doUsernameExists(req, res, next) { export async function doUsernameExists(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT * FROM users WHERE username = '${req.body.username}'`; try {
const result = await client.query(query); const query = `SELECT * FROM users WHERE username = $1`;
const result = await client.query(query, [req.body.username]);
if (result.rows.length > 0) { if (result.rows.length > 0) {
logger.write("failed because username already exists", 400) logger.write("failed because username already exists", 400)
res.status(400).send({error: "Username already exists"}) res.status(400).send({error: "Username already exists"})
} else { } else {
next() next()
} }
} finally {
client.release();
}
} }
export async function doUserExists(req, res, next) { export async function doUserExists(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT id FROM users WHERE id = ${req.params.id}`; try {
const result = await client.query(query); const query = `SELECT id FROM users WHERE id = $1`;
const result = await client.query(query, [req.params.id]);
if (result.rows.length > 0) { if (result.rows.length > 0) {
next() next()
}else{ }else{
logger.write("failed because user doesn't exists", 404) logger.write("failed because user doesn't exists", 404)
res.status(404).json({error: "Not Found"}) res.status(404).json({error: "Not Found"})
} }
} finally {
client.release();
}
} }
export async function doUserExistsBody(req, res, next) { export async function doUserExistsBody(req, res, next) {
const client = await getClient(); const client = await getClient();
const logger = req.body.logger; const logger = req.body.logger;
const query = `SELECT id FROM users WHERE id = ${req.body.owner}`; try {
const result = await client.query(query); const query = `SELECT id FROM users WHERE id = $1`;
const result = await client.query(query, [req.body.owner]);
if (result.rows.length > 0) { if (result.rows.length > 0) {
next() next()
}else{ }else{
logger.write("failed because user doesn't exists", 404) logger.write("failed because user doesn't exists", 404)
res.status(404).json({error: "Not Found"}) res.status(404).json({error: "Not Found"})
} }
} finally {
client.release();
}
} }
export async function isOwner(req, res, next) { export async function isOwner(req, res, next) {

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

@ -50,8 +50,9 @@ export async function isOwner(req, res, next) {
const token = req.headers.authorization.split(' ')[1]; const token = req.headers.authorization.split(' ')[1];
const claims = jwt.decode(token); const claims = jwt.decode(token);
const client = await getClient(); const client = await getClient();
const channelQuery = `SELECT owner FROM channels WHERE id = '${channelId}'`; try {
const channelResult = await client.query(channelQuery); const channelQuery = `SELECT owner FROM channels WHERE id = $1`;
const channelResult = await client.query(channelQuery, [channelId]);
const channelInBase = channelResult.rows[0]; const channelInBase = channelResult.rows[0];
if (channelInBase.owner !== claims.id) { if (channelInBase.owner !== claims.id) {
logger.write("failed because user is not owner", 403); logger.write("failed because user is not owner", 403);
@ -59,6 +60,9 @@ export async function isOwner(req, res, next) {
return return
} }
next() next()
} finally {
client.release();
}
} }
export async function doVideoExists(req, res, next) { export async function doVideoExists(req, res, next) {
@ -66,8 +70,9 @@ export async function doVideoExists(req, res, next) {
const videoId = req.body.video; const videoId = req.body.video;
const client = await getClient(); const client = await getClient();
const query = `SELECT * FROM videos WHERE id = ${videoId}`; try {
const result = await client.query(query); const query = `SELECT * FROM videos WHERE id = $1`;
const result = await client.query(query, [videoId]);
const videos = result.rows; const videos = result.rows;
if (videos.length === 0) { if (videos.length === 0) {
logger.write("failed because video not found", 404); logger.write("failed because video not found", 404);
@ -75,7 +80,9 @@ export async function doVideoExists(req, res, next) {
return return
} }
next() next()
} finally {
client.release();
}
} }
export async function doVideoExistsParam(req, res, next) { export async function doVideoExistsParam(req, res, next) {
@ -83,8 +90,9 @@ export async function doVideoExistsParam(req, res, next) {
const videoId = req.params.id; const videoId = req.params.id;
const client = await getClient(); const client = await getClient();
const query = `SELECT * FROM videos WHERE id = ${videoId}`; try {
const result = await client.query(query); const query = `SELECT * FROM videos WHERE id = $1`;
const result = await client.query(query, [videoId]);
const video = result.rows[0]; const video = result.rows[0];
if (!video) { if (!video) {
logger.write("failed because video not found", 404); logger.write("failed because video not found", 404);
@ -92,7 +100,9 @@ export async function doVideoExistsParam(req, res, next) {
return return
} }
next() next()
} finally {
client.release();
}
} }
export async function doAuthorizedUserExists(req, res, next) { export async function doAuthorizedUserExists(req, res, next) {
@ -112,7 +122,7 @@ export async function doAuthorizedUserExists(req, res, next) {
if (authorizedUsers && Array.isArray(authorizedUsers) && authorizedUsers.length > 0) { if (authorizedUsers && Array.isArray(authorizedUsers) && authorizedUsers.length > 0) {
const client = await getClient(); const client = await getClient();
try {
for (const userId of authorizedUsers) { for (const userId of authorizedUsers) {
const query = `SELECT id FROM users WHERE id = $1`; const query = `SELECT id FROM users WHERE id = $1`;
const result = await client.query(query, [userId]); const result = await client.query(query, [userId]);
@ -123,6 +133,9 @@ export async function doAuthorizedUserExists(req, res, next) {
return return
} }
} }
} finally {
client.release();
}
} }
next() next()
} }
@ -132,6 +145,7 @@ export async function hasAccess(req, res, next) {
const videoId = req.params.id; const videoId = req.params.id;
const client = await getClient(); const client = await getClient();
try {
const videoQuery = "SELECT visibility FROM videos WHERE id = $1"; const videoQuery = "SELECT visibility FROM videos WHERE id = $1";
const videoResult = await client.query(videoQuery, [videoId]); const videoResult = await client.query(videoQuery, [videoId]);
const video = videoResult.rows[0]; const video = videoResult.rows[0];
@ -160,6 +174,8 @@ export async function hasAccess(req, res, next) {
} }
} }
next(); next();
} finally {
client.release();
}
} }

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

@ -9,7 +9,9 @@ import {
getChannel, getHistory, getChannel, getHistory,
isSubscribed, isSubscribed,
verifyEmail, verifyEmail,
searchByUsername searchByUsername,
getAllSubscriptions,
getAllSubscriptionVideos
} from "../controllers/user.controller.js"; } from "../controllers/user.controller.js";
import { import {
UserRegister, UserRegister,
@ -63,4 +65,10 @@ router.get("/:id/channel/subscribed", [addLogger, isTokenValid, User.id, Channel
// VERIFY EMAIL // VERIFY EMAIL
router.post("/verify-email", [addLogger, validator], verifyEmail); router.post("/verify-email", [addLogger, validator], verifyEmail);
// GET ALL SUBSCRIPTIONS
router.get("/:id/subscriptions", [addLogger, isTokenValid, User.id, validator, doUserExists], getAllSubscriptions);
// GET ALL SUBSCRIPTIONS VIDEOS
router.get("/:id/subscriptions/videos", [addLogger, isTokenValid, User.id, validator, doUserExists], getAllSubscriptionVideos);
export default router; export default router;

27
backend/app/utils/database.js

@ -1,18 +1,31 @@
import pg from "pg"; import pg from "pg";
export async function getClient() { // Create a connection pool instead of individual connections
const client = new pg.Client({ const pool = new pg.Pool({
user: process.env.POSTGRES_USER, user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD, password: process.env.POSTGRES_PASSWORD,
host: process.env.POSTGRES_HOST, host: process.env.POSTGRES_HOST,
database: process.env.POSTGRES_DB, database: process.env.POSTGRES_DB,
port: 5432 port: 5432,
}) max: 30, // Increased maximum number of connections in the pool
idleTimeoutMillis: 30000, // Close idle connections after 30 seconds
connectionTimeoutMillis: 10000, // Increased timeout to 10 seconds
acquireTimeoutMillis: 10000, // Wait up to 10 seconds for a connection
});
await client.connect(); export async function getClient() {
return client; // Use pool.connect() instead of creating new clients
return await pool.connect();
} }
// Graceful shutdown
process.on('SIGINT', () => {
pool.end(() => {
console.log('Pool has ended');
process.exit(0);
});
});
export async function initDb() { export async function initDb() {
const client = await getClient(); const client = await getClient();
@ -143,7 +156,7 @@ export async function initDb() {
} catch (e) { } catch (e) {
console.error("Error initializing database:", e); console.error("Error initializing database:", e);
} finally { } finally {
client.end(); client.release();
} }
} }

1335
backend/logs/access.log

File diff suppressed because it is too large

15
backend/tools.js

@ -17,14 +17,13 @@ async function flushDatabase() {
port: 5432 port: 5432
}); });
try {
await client.connect(); await client.connect();
await client.query('TRUNCATE TABLE users CASCADE');
await client.query('TRUNCATE TABLE users CASCADE', (err, res) => {
if (err) {
console.error('Error flushing database:', err);
} else {
console.log('Database flushed successfully.'); console.log('Database flushed successfully.');
} catch (err) {
console.error('Error flushing database:', err);
} finally {
await client.end();
} }
});} }

2
frontend/src/components/Navbar.jsx

@ -52,7 +52,7 @@ export default function Navbar({ isSearchPage = false, alerts = [], onCloseAlert
<li><a href="/">Accueil</a></li> <li><a href="/">Accueil</a></li>
{isAuthenticated ? ( {isAuthenticated ? (
<> <>
<li><a href="/">Abonnements</a></li> <li><a href="/subscriptions">Abonnements</a></li>
<li> <li>
<a href="/profile" className="flex items-center space-x-4"> <a href="/profile" className="flex items-center space-x-4">
<span className="text-2xl">{user?.username}</span> <span className="text-2xl">{user?.username}</span>

2
frontend/src/modals/VerificationModal.jsx

@ -4,7 +4,7 @@ export default function VerificationModal({title, onConfirm, onCancel, isOpen})
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 flex items-center justify-center px-5 lg:px-0"> <div className="fixed z-40 inset-0 flex items-center justify-center px-5 lg:px-0">
<div className="glassmorphism w-full lg:w-auto p-6"> <div className="glassmorphism w-full lg:w-auto p-6">
<h2 className="text-lg text-white font-semibold mb-4">{title}</h2> <h2 className="text-lg text-white font-semibold mb-4">{title}</h2>
<div className="flex justify-start lg:justify-end"> <div className="flex justify-start lg:justify-end">

31
frontend/src/pages/Account.jsx

@ -5,7 +5,8 @@ import VideoCard from "../components/VideoCard.jsx";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import CreateChannelModal from "../modals/CreateChannelModal.jsx"; import CreateChannelModal from "../modals/CreateChannelModal.jsx";
import CreatePlaylistModal from "../modals/CreatePlaylistModal.jsx"; import CreatePlaylistModal from "../modals/CreatePlaylistModal.jsx";
import { getChannel, getUserHistory, getPlaylists, updateUser } from "../services/user.service.js"; import { getChannel, getUserHistory, getPlaylists, updateUser, deleteUser } from "../services/user.service.js";
import VerificationModal from "../modals/VerificationModal.jsx";
export default function Account() { export default function Account() {
@ -24,6 +25,7 @@ export default function Account() {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [isCreatePlaylistModalOpen, setIsCreatePlaylistModalOpen] = useState(false); const [isCreatePlaylistModalOpen, setIsCreatePlaylistModalOpen] = useState(false);
const [alerts, setAlerts] = useState([]); const [alerts, setAlerts] = useState([]);
const [isVerificationModalOpen, setIsVerificationModalOpen] = useState(false);
const navigation = useNavigate(); const navigation = useNavigate();
@ -90,6 +92,11 @@ export default function Account() {
setIsCreatePlaylistModalOpen(false); setIsCreatePlaylistModalOpen(false);
fetchUserPlaylists(); fetchUserPlaylists();
} }
const onDeleteAccount = async () => {
await deleteUser(user.id, token, addAlert);
localStorage.removeItem("user");
navigation("/login");
}
return ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
@ -99,7 +106,8 @@ export default function Account() {
{/* Left side */} {/* Left side */}
{/* Profile / Edit profile */} {/* Profile / Edit profile */}
<form className="glassmorphism lg:w-1/3 p-10"> <div className=" lg:w-1/3 ">
<form className="glassmorphism p-10">
<div className="relative w-1/3 aspect-square overflow-hidden mb-3 mx-auto" onMouseEnter={() => setIsPictureEditActive(true)} onMouseLeave={() => setIsPictureEditActive(false)} > <div className="relative w-1/3 aspect-square overflow-hidden mb-3 mx-auto" onMouseEnter={() => setIsPictureEditActive(true)} onMouseLeave={() => setIsPictureEditActive(false)} >
<label htmlFor="image"> <label htmlFor="image">
<img <img
@ -126,7 +134,6 @@ export default function Account() {
placeholder="Nom d'utilisateur" placeholder="Nom d'utilisateur"
disabled={!editMode} disabled={!editMode}
/> />
<label htmlFor="email" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat"> <label htmlFor="email" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat">
Adresse e-mail Adresse e-mail
</label> </label>
@ -139,7 +146,6 @@ export default function Account() {
placeholder="Adresse mail" placeholder="Adresse mail"
disabled={!editMode} disabled={!editMode}
/> />
{editMode && ( {editMode && (
<> <>
<label htmlFor="password" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat"> <label htmlFor="password" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat">
@ -154,7 +160,6 @@ export default function Account() {
placeholder="**************" placeholder="**************"
disabled={!editMode} disabled={!editMode}
/> />
<label htmlFor="confirm-password" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat"> <label htmlFor="confirm-password" className="text-xl lg:text-2xl text-white mb-1 mt-4 block font-montserrat">
Confirmer le mot de passe Confirmer le mot de passe
</label> </label>
@ -169,9 +174,7 @@ export default function Account() {
/> />
</> </>
) )
} }
<div className="flex justify-center mt-5"> <div className="flex justify-center mt-5">
{ {
editMode ? ( editMode ? (
@ -203,6 +206,20 @@ export default function Account() {
} }
</div> </div>
</form> </form>
<button
className="bg-red-500 p-3 rounded-sm text-white font-montserrat text-lg lg:text-2xl font-bold cursor-pointer mt-4 w-full"
onClick={() => setIsVerificationModalOpen(true)}
>
Supprimer le compte
</button>
<VerificationModal
title="Confirmer la suppression du compte"
isOpen={isVerificationModalOpen}
onCancel={() => setIsVerificationModalOpen(false)}
onConfirm={() => onDeleteAccount()}
/>
</div>
{ /* Right side */} { /* Right side */}

28
frontend/src/pages/ManageChannel.jsx

@ -3,7 +3,8 @@ import {useEffect, useState} from "react";
import {useNavigate, useParams} from "react-router-dom"; import {useNavigate, useParams} from "react-router-dom";
import {useAuth} from "../contexts/AuthContext.jsx"; import {useAuth} from "../contexts/AuthContext.jsx";
import VideoStatListElement from "../components/VideoStatListElement.jsx"; import VideoStatListElement from "../components/VideoStatListElement.jsx";
import {fetchChannelDetails, fetchChannelStats, updateChannel} from "../services/channel.service.js"; import {fetchChannelDetails, fetchChannelStats, updateChannel, deleteChannel} from "../services/channel.service.js";
import VerificationModal from "../modals/VerificationModal.jsx";
export default function ManageChannel() { export default function ManageChannel() {
@ -18,6 +19,7 @@ export default function ManageChannel() {
const [description, setDescription] = useState(null); const [description, setDescription] = useState(null);
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const [alerts, setAlerts] = useState([]); const [alerts, setAlerts] = useState([]);
const [isVerificationModalOpen, setIsVerificationModalOpen] = useState(false);
const token = localStorage.getItem("token"); 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"; const nonEditModeClasses = "text-2xl font-bold text-white p-2 focus:text-white focus:outline-none w-full font-montserrat resizable-none text-center";
@ -64,6 +66,11 @@ export default function ManageChannel() {
setAlerts([...alerts, newAlert]); setAlerts([...alerts, newAlert]);
}; };
const onDeleteChannel = async () => {
await deleteChannel(id, token, addAlert);
navigate("/profile");
}
return ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
@ -71,7 +78,8 @@ export default function ManageChannel() {
<main className="pt-[48px] lg:pt-[118px] px-5 lg:px-36 lg:flex pb-10"> <main className="pt-[48px] lg:pt-[118px] px-5 lg:px-36 lg:flex pb-10">
{/* LEFT SIDE */} {/* LEFT SIDE */}
<form className="glassmorphism lg:w-1/3 py-10 px-4 h-max"> <div className="lg:w-1/3">
<form className="glassmorphism py-10 px-4 h-max">
<img src={user.picture} className="w-1/3 aspect-square object-cover rounded-full mx-auto" alt=""/> <img src={user.picture} className="w-1/3 aspect-square object-cover rounded-full mx-auto" alt=""/>
<label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat ${editMode ? "block" : "hidden"} `}> <label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat ${editMode ? "block" : "hidden"} `}>
Nom de chaine Nom de chaine
@ -85,7 +93,6 @@ export default function ManageChannel() {
placeholder="Nom d'utilisateur" placeholder="Nom d'utilisateur"
disabled={!editMode} disabled={!editMode}
/> />
<label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat`}> <label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat`}>
Description Description
</label> </label>
@ -98,7 +105,6 @@ export default function ManageChannel() {
placeholder="Description de votre chaine" placeholder="Description de votre chaine"
disabled={!editMode} disabled={!editMode}
></textarea> ></textarea>
{ {
editMode ? ( editMode ? (
<div className="mt-4"> <div className="mt-4">
@ -127,8 +133,20 @@ export default function ManageChannel() {
</button> </button>
) )
} }
</form> </form>
<button
className="bg-red-500 p-3 rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer mt-4 w-full"
onClick={() => setIsVerificationModalOpen(true)}
>
Supprimer la chaîne
</button>
<VerificationModal
title="Êtes-vous sûr de vouloir supprimer cette chaîne ?"
isOpen={isVerificationModalOpen}
onCancel={() => setIsVerificationModalOpen(false)}
onConfirm={onDeleteChannel}
/>
</div>
{/* RIGHT SIDE */} {/* RIGHT SIDE */}
<div className="lg:w-2/3 lg:pl-10 mt-4 lg:mt-0" > <div className="lg:w-2/3 lg:pl-10 mt-4 lg:mt-0" >

23
frontend/src/pages/ManageVideo.jsx

@ -6,8 +6,10 @@ import LinearGraph from "../components/LinearGraph.jsx";
import Comment from "../components/Comment.jsx"; import Comment from "../components/Comment.jsx";
import Tag from "../components/Tag.jsx"; import Tag from "../components/Tag.jsx";
import UserCard from "../components/UserCard.jsx"; import UserCard from "../components/UserCard.jsx";
import {getVideoById, getLikesPerDay, updateVideo, updateVideoFile, uploadThumbnail, uploadTags, updateAuthorizedUsers} from "../services/video.service.js"; import {getVideoById, getLikesPerDay, updateVideo, updateVideoFile, uploadThumbnail, uploadTags, updateAuthorizedUsers, deleteVideo} from "../services/video.service.js";
import {searchByUsername} from "../services/user.service.js"; import {searchByUsername} from "../services/user.service.js";
import VerificationModal from "../modals/VerificationModal.jsx";
import {useNavigate} from "react-router-dom";
export default function ManageVideo() { export default function ManageVideo() {
@ -15,6 +17,7 @@ export default function ManageVideo() {
const { user } = useAuth(); const { user } = useAuth();
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
const {id} = useParams(); const {id} = useParams();
const navigate = useNavigate();
const [video, setVideo] = useState(null); const [video, setVideo] = useState(null);
const [likesPerDay, setLikesPerDay] = useState([]); const [likesPerDay, setLikesPerDay] = useState([]);
@ -28,6 +31,7 @@ export default function ManageVideo() {
const [alerts, setAlerts] = useState([]); const [alerts, setAlerts] = useState([]);
const [searchUser, setSearchUser] = useState(""); const [searchUser, setSearchUser] = useState("");
const [searchResults, setSearchResults] = useState([]); const [searchResults, setSearchResults] = useState([]);
const [isVerificationModalOpen, setIsVerificationModalOpen] = useState(false);
const nonEditModeClasses = "text-md font-normal text-white p-2 focus:text-white focus:outline-none w-full font-montserrat resizable-none"; const nonEditModeClasses = "text-md font-normal text-white p-2 focus:text-white focus:outline-none w-full font-montserrat resizable-none";
const editModeClasses = nonEditModeClasses + " glassmorphism"; const editModeClasses = nonEditModeClasses + " glassmorphism";
@ -198,6 +202,11 @@ export default function ManageVideo() {
addAlert('success', "Utilisateur supprimé avec succès."); addAlert('success', "Utilisateur supprimé avec succès.");
} }
const onDeleteVideo = async () => {
await deleteVideo(id, video.channel, token, addAlert);
navigate("/manage-channel/" + video.channel);
}
return ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
@ -385,6 +394,18 @@ export default function ManageVideo() {
) )
} }
<button
className="bg-red-600 p-3 rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer mt-4 w-full"
onClick={() => setIsVerificationModalOpen(true)}
>
Supprimer la vidéo
</button>
<VerificationModal
title="Êtes-vous sûr de vouloir supprimer cette vidéo ?"
isOpen={isVerificationModalOpen}
onCancel={() => setIsVerificationModalOpen(false)}
onConfirm={onDeleteVideo}
/>
</div> </div>

95
frontend/src/pages/Subscription.jsx

@ -0,0 +1,95 @@
import React from "react";
import Navbar from "../components/Navbar";
import { useEffect, useState } from "react";
import { getAllUserSubscriptions, getAllUserSubscriptionVideos } from "../services/user.service.js";
import { useAuth } from "../contexts/AuthContext.jsx";
import VideoCard from "../components/VideoCard.jsx";
import { useNavigate } from "react-router-dom";
export default function Subscription() {
const navigate = useNavigate();
const [alerts, setAlerts] = useState([]);
const [subscriptions, setSubscriptions] = React.useState([]);
const [videos, setVideos] = useState([]);
const {user, isAuth} = useAuth();
useEffect(() => {
fetchSubscriptions();
fetchSubscriptionVideos();
}, []);
const fetchSubscriptions = async () => {
const token = localStorage.getItem('token');
if (!token) {
addAlert('error', "User not authenticated");
return;
}
const data = await getAllUserSubscriptions(user.id, token, addAlert);
if (data) {
setSubscriptions(data);
}
};
const fetchSubscriptionVideos = async () => {
const token = localStorage.getItem('token');
if (!token) {
addAlert('error', "User not authenticated");
return;
}
const data = await getAllUserSubscriptionVideos(user.id, token, addAlert);
if (data) {
setVideos(data);
}
};
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} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="px-5 lg:px-36 w-full pt-[48px] lg:pt-[118px] lg:flex gap-8">
{/* LEFT SIDE (subscription list) */}
<div className="w-full lg:w-1/4 border-b border-gray-200 lg:border-b-0 mb-8 lg:mb-0 pb-2 lg:pb-0">
<h2 className="text-2xl text-white font-montserrat font-semibold mb-4">Mes Abonnements</h2>
<ul className="space-y-2">
{subscriptions.map(subscription => (
<li
key={subscription.id}
className="p-2 flex items-center lg:border-r lg:border-gray-200 glassmorphism w-max lg:w-auto"
onClick={() => navigate(`/channel/${subscription.channel_id}`)}
>
<img
src={subscription.picture}
alt={subscription.channel_name}
className="w-14 aspect-square rounded-full inline-block mr-2 align-middle"
/>
<p className="text-white text-lg font-montserrat font-medium" >{subscription.channel_name}</p>
</li>
))}
</ul>
</div>
{/* RIGHT SIDE (videos from subscriptions) */}
<div className="flex-1 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{videos.map(video => (
<VideoCard key={video.id} video={video} />
))}
</div>
</main>
</div>
);
}

9
frontend/src/routes/routes.jsx

@ -11,6 +11,7 @@ import Search from "../pages/Search.jsx";
import Channel from "../pages/Channel.jsx"; import Channel from "../pages/Channel.jsx";
import Playlist from "../pages/Playlist.jsx"; import Playlist from "../pages/Playlist.jsx";
import LoginSuccess from '../pages/LoginSuccess.jsx' import LoginSuccess from '../pages/LoginSuccess.jsx'
import Subscriptions from '../pages/Subscription.jsx'
const routes = [ const routes = [
{ path: "/", element: <Home/> }, { path: "/", element: <Home/> },
@ -95,6 +96,14 @@ const routes = [
element: ( element: (
<LoginSuccess/> <LoginSuccess/>
) )
},
{
path: "/subscriptions",
element: (
<ProtectedRoute requireAuth={true}>
<Subscriptions/>
</ProtectedRoute>
)
} }
] ]

24
frontend/src/services/channel.service.js

@ -98,3 +98,27 @@ export async function createChannel(body, token, addAlert) {
const data = await request.json(); const data = await request.json();
return data; return data;
} }
export async function deleteChannel(channelId, token, addAlert) {
try {
const response = await fetch(`/api/channels/${channelId}`, {
method: "DELETE",
headers: {
"Authorization": `Bearer ${token}`
}
});
if (response.ok) {
addAlert('success', 'Chaîne supprimée avec succès');
return true;
} else {
const errorData = await response.json();
addAlert('error', errorData.message || 'Erreur lors de la suppression de la chaîne');
return false;
}
} catch (error) {
console.error("Error deleting channel:", error);
addAlert('error', 'Erreur lors de la suppression de la chaîne');
return false;
}
}

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

@ -150,3 +150,68 @@ export async function searchByUsername(username, token, addAlert) {
addAlert('error', error.message || "Erreur lors de la recherche de l'utilisateur."); addAlert('error', error.message || "Erreur lors de la recherche de l'utilisateur.");
} }
} }
export async function deleteUser(userId, token, addAlert) {
try {
const response = await fetch(`/api/users/${userId}`, {
method: "DELETE",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to delete user");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error deleting user:", error);
addAlert('error', "Erreur lors de la suppression de l'utilisateur.");
}
}
export async function getAllUserSubscriptions(userId, token ,addAlert) {
if (!userId || !token) {
console.warn("User ID or token missing, skipping subscriptions fetch");
return;
}
try {
const response = await fetch(`/api/users/${userId}/subscriptions`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user subscriptions");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching user subscriptions:", error);
addAlert('error', "Erreur lors de la récupération des abonnements de l'utilisateur.");
}
}
export async function getAllUserSubscriptionVideos(userId, token ,addAlert) {
if (!userId || !token) {
console.warn("User ID or token missing, skipping subscription videos fetch");
return;
}
try {
const response = await fetch(`/api/users/${userId}/subscriptions/videos`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user subscription videos");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching user subscription videos:", error);
addAlert('error', "Erreur lors de la récupération des vidéos des abonnements de l'utilisateur.");
}
}

23
frontend/src/services/video.service.js

@ -241,3 +241,26 @@ export async function updateAuthorizedUsers(id, body, token, addAlert) {
addAlert('error', 'Erreur lors de la mise à jour des utilisateurs autorisés'); addAlert('error', 'Erreur lors de la mise à jour des utilisateurs autorisés');
} }
} }
export async function deleteVideo(id, channel, token, addAlert) {
try {
const request = await fetch(`/api/videos/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ channel })
});
if (!request.ok) {
const errorData = await request.json();
console.error("Backend validation errors:", errorData);
addAlert('error', 'Erreur lors de la suppression de la vidéo');
return;
}
return request;
} catch (error) {
console.error("Error deleting video:", error);
addAlert('error', 'Erreur lors de la suppression de la vidéo');
}
}
Loading…
Cancel
Save