Compare commits

...

12 Commits

  1. 2
      backend/app/controllers/channel.controller.js
  2. 15
      backend/app/controllers/media.controller.js
  3. 2
      backend/app/controllers/oauth.controller.js
  4. 2
      backend/app/controllers/search.controller.js
  5. 12
      backend/app/controllers/user.controller.js
  6. 8
      backend/app/controllers/video.controller.js
  7. 2
      backend/app/middlewares/error.middleware.js
  8. 0
      backend/app/middlewares/media.middleware.js
  9. 2
      backend/app/middlewares/video.middleware.js
  10. 12
      backend/app/routes/user.route.js
  11. BIN
      backend/app/uploads/profiles/astri6.jpg
  12. BIN
      backend/app/uploads/profiles/astri7.jpg
  13. BIN
      backend/app/uploads/profiles/astria.png
  14. BIN
      backend/app/uploads/profiles/astria2.jpg
  15. BIN
      backend/app/uploads/profiles/astria3.jpg
  16. BIN
      backend/app/uploads/profiles/default.png
  17. 7
      backend/app/utils/database.js
  18. 2
      backend/app/utils/mail.js
  19. 16
      backend/app/utils/wait-for-it.sh
  20. 14
      backend/freetube.sh
  21. 162
      backend/logs/access.log
  22. 1
      backend/package.json
  23. 30
      backend/requests/channel.http
  24. 25
      backend/requests/comment.http
  25. 11
      backend/requests/medias.http
  26. 11
      backend/requests/playlist.http
  27. 5
      backend/requests/recommendation.http
  28. 17
      backend/requests/top-tags-videos.http
  29. 49
      backend/requests/user.http
  30. 33
      backend/requests/video.http
  31. 9
      backend/server.js
  32. 190
      backend/test/channel.test.js
  33. 175
      backend/test/comment.test.js
  34. 231
      backend/test/playlist.test.js
  35. 413
      backend/test/user.test.js
  36. 198
      backend/test/video.test.js
  37. 29
      backend/tools.js
  38. 6
      backend/vitest.config.js
  39. 2
      developpement.yaml
  40. 6
      docker-compose.yaml
  41. 7
      frontend/Docker.Development
  42. 12
      frontend/README.md
  43. 15
      frontend/rebuild.sh
  44. 4
      frontend/src/components/CreatorCard.jsx
  45. 2
      frontend/src/components/Recommendations.jsx
  46. 2
      frontend/src/modals/CreateChannelModal.jsx
  47. 2
      frontend/src/modals/EmailVerificationModal.jsx
  48. 4
      frontend/src/pages/AddVideo.jsx
  49. 35
      frontend/src/pages/Home.jsx
  50. 6
      frontend/src/pages/LoginSuccess.jsx
  51. 4
      frontend/src/pages/ManageVideo.jsx
  52. 2
      frontend/src/pages/Register.jsx
  53. 37
      frontend/src/pages/Video.jsx
  54. 2
      frontend/src/services/channel.service.js
  55. 4
      frontend/src/services/user.service.js
  56. BIN
      uploads/profiles/sacha.jpg

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

@ -236,7 +236,7 @@ export async function getStats(req, res) {
client.release();
res.status(200).json(result.rows[0]);
} catch (error) {
console.log(error);
res.status(500).json({error: error.message});
}

15
backend/app/controllers/media.controller.js

@ -15,12 +15,13 @@ export async function getProfilePicture(req, res) {
path.join('/app/uploads/profiles', file)
];
console.log('Possible paths:', possiblePaths);
console.log('Current working directory:', process.cwd());
console.log('__dirname:', __dirname);
// Try the most likely path first (based on your volume mapping)
const filePath = path.join('/app/app/uploads/profiles', file);
console.log(path.join(__dirname, '../uploads/profiles', file))
const filePath = path.join(__dirname, '../uploads/profiles', file);
try {
res.sendFile(filePath, (err) => {
@ -28,7 +29,7 @@ export async function getProfilePicture(req, res) {
console.error("Error sending profile picture:", err);
res.status(404).json({ error: "Profile picture not found." });
} else {
console.log("Profile picture sent successfully.");
}
});
} catch (error) {
@ -47,7 +48,7 @@ export async function getThumbnail(req, res) {
console.error("Error sending thumbnail:", err);
res.status(404).json({ error: "Thumbnail not found." });
} else {
console.log("Thumbnail sent successfully.");
}
});
} catch (error) {
@ -65,7 +66,7 @@ export async function getVideo(req, res) {
if (err) {
console.error("Error sending video:", err);
} else {
console.log("Video sent successfully.");
}
});
} catch (error) {

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

@ -26,7 +26,7 @@ export async function callback(req, res, next) {
const avatarUrl = user.photos && user.photos[0] ? user.photos[0].value : null;
const displayName = user.displayName || username;
console.log(user);
console.log("GitHub user info:", {
githubId,

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

@ -2,7 +2,7 @@ import {getClient} from "../utils/database.js";
export async function search(req, res) {
try {
console.log(req.query);
const query = req.query.q;
const type = req.query.type || 'all';
const offset = parseInt(req.query.offset) || 0;

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

@ -111,7 +111,7 @@ export async function register(req, res) {
</html>
`;
console.log("Sending email to:", user.email);
await sendEmail(user.email, "🎬 Bienvenue sur Freetube - Vérifiez votre email", textMessage, htmlMessage);
@ -122,11 +122,11 @@ export async function register(req, res) {
const insertQuery = `INSERT INTO email_verification (email, token, expires_at) VALUES ($1, $2, $3)`;
await client.query(insertQuery, [user.email, token, expirationDate]);
console.log("Successfully registered");
logger.write("successfully registered", 200);
res.status(200).send({ user: user });
} catch (err) {
console.log(err);
logger?.write("failed to register user", 500);
res.status(500).json({ error: "Internal server error" });
} finally {
@ -217,7 +217,7 @@ export async function login(req, res) {
logger.write("Successfully logged in", 200);
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 {
@ -317,7 +317,7 @@ export async function update(req, res) {
let __filename = fileURLToPath(import.meta.url);
let __dirname = dirname(__filename);
console.log(__dirname);
let profilePicture = userInBase.picture.split("/").pop();
fs.rename(
@ -338,7 +338,7 @@ export async function update(req, res) {
client.release();
res.status(200).json(result.rows[0]);
} catch (err) {
console.log(err);
client.release()
res.status(500).json({ error: err });
}

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

@ -74,7 +74,7 @@ export async function upload(req, res) {
if (Array.isArray(authorizedUsers) && authorizedUsers.length > 0) {
for (let i = 0; i < authorizedUsers.length; i++) {
const user = authorizedUsers[i];
console.log("authorized user", user);
const query = `INSERT INTO video_authorized_users (video_id, user_id) VALUES ($1, $2)`;
// SEND EMAIL TO AUTHORIZED USER
@ -565,7 +565,7 @@ export async function getLikesPerDay(req, res) {
const resultLikes = await client.query(likeQuery, [id]);
response.likes = resultLikes.rows;
console.log(response);
logger.write("successfully retrieved likes per day", 200);
res.status(200).json(response);
@ -607,14 +607,14 @@ export async function updateAuthorizedUsers(req, res) {
const { authorizedUsers } = req.body;
console.log(authorizedUsers);
const client = await getClient();
try {
// Remove all existing authorized users
const deleteQuery = `DELETE FROM video_authorized_users WHERE video_id = $1`;
console.log(`DELETE FROM video_authorized_users WHERE video_id = ${id}`);
await client.query(deleteQuery, [id]);
// Add new authorized users

2
backend/app/middlewares/error.middleware.js

@ -6,7 +6,7 @@ export default async function validator(req, res, next) {
if (!errors.isEmpty()) {
const logger = req.body.logger;
logger.write("failed due to invalid values", 400);
console.log(req.body);
return res.status(400).json({ errors: errors.array() });
} else {
next()

0
backend/app/middlewares/media.middleware.js

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

@ -150,7 +150,7 @@ export async function hasAccess(req, res, next) {
const videoResult = await client.query(videoQuery, [videoId]);
const video = videoResult.rows[0];
console.log(video);
if (video.visibility === 'private') {
const token = req.headers.authorization?.split(" ")[1];

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

@ -32,25 +32,25 @@ import {Channel} from "../middlewares/channel.middleware.js";
const router = Router();
// REGISTER A USER
// REGISTER A USER+
router.post("/", [profileUpload.single("profile"), addLogger, UserRegister.email, UserRegister.username, UserRegister.password, validator, doEmailExists, doUsernameExists], register);
// LOGIN A USER
// LOGIN A USER+
router.post("/login", [addLogger, UserLogin.username, UserLogin.password, validator], login)
// SEARCH BY USERNAME
router.get("/search", [addLogger, isTokenValid, UserSearch.username, validator], searchByUsername);
// GET USER BY ID
// GET USER BY ID+
router.get("/:id", [addLogger, isTokenValid, User.id, validator], getById)
// GET USER BY USERNAME
// GET USER BY USERNAME+
router.get("/username/:username", [addLogger, isTokenValid, UserRequest.username, validator], getByUsername);
// UPDATE USER
// UPDATE USER+
router.put("/:id", [addLogger, isTokenValid, User.id, UserRegister.email, UserRegister.username, validator, doUserExists, isOwner], update);
// DELETE USER
// DELETE USER+
router.delete("/:id", [addLogger, isTokenValid, User.id, validator, doUserExists, isOwner], deleteUser);
// GET USER CHANNEL

BIN
backend/app/uploads/profiles/astri6.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

BIN
backend/app/uploads/profiles/astri7.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

BIN
backend/app/uploads/profiles/astria.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 MiB

BIN
backend/app/uploads/profiles/astria2.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

BIN
backend/app/uploads/profiles/astria3.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

BIN
backend/app/uploads/profiles/default.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

7
backend/app/utils/database.js

@ -1,5 +1,6 @@
import pg from "pg";
export async function getClient() {
// Create a connection pool instead of individual connections
const pool = new pg.Pool({
user: process.env.POSTGRES_USER,
@ -13,7 +14,7 @@ const pool = new pg.Pool({
acquireTimeoutMillis: 10000, // Wait up to 10 seconds for a connection
});
export async function getClient() {
// Use pool.connect() instead of creating new clients
return await pool.connect();
}
@ -21,7 +22,7 @@ export async function getClient() {
// Graceful shutdown
process.on('SIGINT', () => {
pool.end(() => {
console.log('Pool has ended');
process.exit(0);
});
});
@ -150,7 +151,7 @@ export async function initDb() {
await client.query(`ALTER TABLE users ALTER COLUMN email DROP NOT NULL`);
await client.query(`ALTER TABLE users ALTER COLUMN password DROP NOT NULL`);
} catch (e) {
console.log("OAuth columns already exist or error adding them:", e.message);
}
} catch (e) {

2
backend/app/utils/mail.js

@ -7,7 +7,7 @@ function getTransporter() {
secure: false,
auth: {
user: process.env.GMAIL_USER,
pass: "yuuu kvoi ytrf blla",
pass: process.env.GMAIL_PASSWORD,
},
});
};

16
backend/app/utils/wait-for-it.sh

@ -1,16 +0,0 @@
#!/bin/bash
hostport="$1"
shift
cmd="$@"
host="${hostport%%:*}"
port="${hostport##*:}"
while ! nc -z "$host" "$port"; do
echo "Waiting for $host:$port..."
sleep 1
done
echo "$host:$port is available. Running command: $cmd"
exec $cmd

14
backend/freetube.sh

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Script pour exécuter Node.js avec les paramètres passés en ligne de commande
# Récupérer tous les paramètres passés au script
PARAMS="$@"
# Vérifier si des paramètres ont été fournis
if [ $# -eq 0 ]; then
echo "Aucun paramètre fourni."
else
echo "Exécution de Node.js avec les paramètres: $PARAMS"
node /app/tools.js $PARAMS
fi

162
backend/logs/access.log

@ -12072,3 +12072,165 @@
[2025-09-05 17:57:44.224] [undefined] POST(/:id): user not the owner of the playlist with id 1 with status 403
[2025-09-05 17:58:14.088] [undefined] POST(/:id): Video added to playlist with id 2 with status 200
[2025-09-05 17:58:21.531] [undefined] GET(/:id): Playlist retrieved with id 2 with status 200
[2025-09-06 10:11:19.116] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:11:56.023] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:12:16.248] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:12:23.630] [undefined] GET(/:id): failed due to invalid values with status 400
[2025-09-06 10:12:35.862] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:13:32.803] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:13:32.809] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:13:32.824] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:13:32.829] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 1 with status 200
[2025-09-06 10:13:58.268] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1
[2025-09-06 10:13:58.276] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200
[2025-09-06 10:14:41.764] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:14:41.775] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:14:41.789] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:14:41.794] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:14:42.428] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:14:42.437] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:14:42.447] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:14:42.457] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:14:42.464] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:14:42.479] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:14:42.484] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:14:49.450] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:14:49.454] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:14:49.462] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:14:49.473] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:14:49.480] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:14:49.529] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:14:49.536] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:14:51.048] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:14:51.054] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:14:51.065] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:14:51.071] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:15:00.735] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:15:00.741] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:15:00.754] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:15:00.759] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:15:01.676] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:15:01.681] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:15:01.690] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:15:01.700] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:15:01.706] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:15:01.723] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:15:01.731] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:15:54.639] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:15:54.651] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:15:54.658] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:15:54.669] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:15:54.676] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:15:54.696] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:15:54.704] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:15:57.359] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:15:57.365] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:15:57.377] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1
[2025-09-06 10:15:57.382] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200
[2025-09-06 10:16:01.637] [undefined] GET(/:id): try to get video 1
[2025-09-06 10:16:01.642] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:16:01.649] [undefined] GET(/:id): successfully get video 1 with status 200
[2025-09-06 10:16:01.658] [undefined] GET(/:id/similar): try to get similar videos for video 1
[2025-09-06 10:16:01.666] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200
[2025-09-06 10:16:01.710] [undefined] GET(/:id/views): try to add views for video 1
[2025-09-06 10:16:01.716] [undefined] GET(/:id/views): successfully added views for video 1 with status 200
[2025-09-06 10:16:04.193] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:16:13.098] [undefined] GET(/:id): try to get channel with id 2
[2025-09-06 10:16:13.154] [undefined] GET(/:id): Successfully get channel with id 2 with status 200
[2025-09-06 10:16:13.165] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 2
[2025-09-06 10:16:13.170] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 2 with status 200
[2025-09-06 10:19:06.785] [undefined] GET(/:id): try to get channel with id 2
[2025-09-06 10:19:06.798] [undefined] GET(/:id): Successfully get channel with id 2 with status 200
[2025-09-06 10:19:06.814] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 2
[2025-09-06 10:19:06.819] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 2 with status 200
[2025-09-06 10:19:15.009] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-06 10:23:43.105] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-09-06 10:23:43.111] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-09-06 10:23:43.117] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-09-06 10:23:43.124] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
[2025-09-06 10:23:43.129] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:23:44.349] [undefined] GET(/:id): try to get channel with id 1
[2025-09-06 10:23:44.354] [undefined] GET(/:id/stats): try to get stats
[2025-09-06 10:23:44.359] [undefined] GET(/:id/stats): Successfully get stats with status 200
[2025-09-06 10:23:44.365] [undefined] GET(/:id): Successfully get channel with id 1 with status 200
[2025-09-06 10:23:47.007] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-09-06 10:23:47.013] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-09-06 10:23:47.066] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200
[2025-09-06 10:23:47.070] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200
[2025-09-06 10:23:47.077] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-06 10:23:54.346] [undefined] GET(/:id): try to get channel with id 2
[2025-09-06 10:23:54.352] [undefined] GET(/:id): Successfully get channel with id 2 with status 200
[2025-09-06 10:23:54.364] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 2
[2025-09-06 10:23:54.370] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 2 with status 200
[2025-09-08 16:38:02.990] [undefined] POST(/): failed because email already exists with status 400
[2025-09-08 16:38:11.467] [undefined] POST(/): failed because email already exists with status 400
[2025-09-08 16:38:28.340] [undefined] POST(/): failed because email already exists with status 400
[2025-09-08 16:39:29.271] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-09-08 16:39:31.245] [undefined] POST(/): successfully registered with status 200
[2025-09-08 16:39:43.524] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token 86155
[2025-09-08 16:39:43.543] [undefined] POST(/verify-email): successfully verified email for sachaguerin.sg@gmail.com with status 200
[2025-09-08 16:39:50.092] [undefined] POST(/login): try to login with username 'astria'
[2025-09-08 16:39:50.164] [undefined] POST(/login): Successfully logged in with status 200
[2025-09-08 16:39:50.339] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200
[2025-09-08 16:39:53.065] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-09-08 16:39:53.069] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-09-08 16:39:53.077] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
[2025-09-08 16:39:53.084] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-09-08 16:39:53.095] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-09-08 16:43:38.736] [undefined] POST(/): try to register a user with username: sacha and email: thelolshow974@gmail.com
[2025-09-08 16:43:40.388] [undefined] POST(/): successfully registered with status 200
[2025-09-08 16:43:55.477] [undefined] POST(/verify-email): try to verify email for thelolshow974@gmail.com with token dd574
[2025-09-08 16:43:55.494] [undefined] POST(/verify-email): successfully verified email for thelolshow974@gmail.com with status 200
[2025-09-08 16:44:02.186] [undefined] POST(/login): try to login with username 'sacha'
[2025-09-08 16:44:02.255] [undefined] POST(/login): Successfully logged in with status 200
[2025-09-08 16:44:02.388] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 2 with status 200
[2025-09-08 16:44:04.504] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:44:04.508] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:44:04.517] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:44:04.523] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:44:04.537] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:45:13.811] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:45:13.816] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:45:13.824] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:45:13.829] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:45:13.838] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:46:03.637] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:46:03.643] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:46:03.650] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:46:03.656] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:46:03.666] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:46:17.413] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:46:17.417] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:46:17.426] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:46:17.431] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:46:17.442] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:46:35.561] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:46:35.567] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:46:35.576] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:46:35.583] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:46:35.595] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:47:27.440] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:47:27.445] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:47:27.454] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:47:27.459] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:47:27.469] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:47:49.574] [undefined] POST(/login): try to login with username 'sacha'
[2025-09-08 16:47:49.634] [undefined] POST(/login): Successfully logged in with status 200
[2025-09-08 16:47:49.727] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 2 with status 200
[2025-09-08 16:47:51.469] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:47:51.474] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:47:51.482] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:47:51.492] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:47:51.503] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-09-08 16:48:31.139] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-09-08 16:48:31.145] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-09-08 16:48:31.153] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-09-08 16:48:31.160] [undefined] GET(/user/:id): failed because user doesn't exists with status 404
[2025-09-08 16:48:31.165] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-09-08 16:49:08.966] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-09-08 16:49:10.628] [undefined] POST(/): successfully registered with status 200
[2025-09-08 16:49:20.080] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token fe65c
[2025-09-08 16:49:20.097] [undefined] POST(/verify-email): successfully verified email for sachaguerin.sg@gmail.com with status 200
[2025-09-08 16:49:30.515] [undefined] POST(/login): try to login with username 'astria'
[2025-09-08 16:49:30.577] [undefined] POST(/login): Successfully logged in with status 200
[2025-09-08 16:49:30.682] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200

1
backend/package.json

@ -5,7 +5,6 @@
"type": "module",
"scripts": {
"dev": "nodemon .",
"test": "vitest",
"start": "node server.js"
},
"keywords": [],

30
backend/requests/channel.http

@ -1,30 +0,0 @@
@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMwNDAxMTh9.GNGee4BYCL-wO3I8FtXXJIc-xsLMfuOoUlBhHjQJcWo
### CREATE CHANNEL
POST http://localhost:8000/api/channels
Authorization: Bearer {{token}}
Content-Type: application/json
{
"name": "Arcanas",
"description": "kljsdfjklsdfjkl",
"owner": 1
}
### GET CHANNEL BY ID
GET http://localhost:8000/api/channels/20
Authorization: Bearer {{token}}
### GET ALL CHANNEL
GET http://localhost:8000/api/channels/
Authorization: Bearer {{token}}
### UPDATE CHANNEL
PUT http://localhost:8000/api/channels/2
Authorization: Bearer {{token}}
Content-Type: application/json
{
"name": "Salut cest cool",
"description": "hjqdfshjklsdqfhjkl"
}

25
backend/requests/comment.http

@ -1,25 +0,0 @@
@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTI5NDQxMDF9.dbGCL8qqqLR3e7Ngns-xPfZAvp0WQzAbjaEHjDVg1HI
### POST COMMENT
POST http://127.0.0.1:8000/api/comments/
Content-Type: application/json
Authorization: Bearer {{token}}
{
"content": "Lorem",
"video": 3
}
### GET COMMENT FROM VIDEO ID
GET http://127.0.0.1:8000/api/comments/video/astria
Authorization: Bearer {{token}}
### POST COMMENT
PUT http://127.0.0.1:8000/api/comments/1
Content-Type: application/json
Authorization: Bearer {{token}}
{
"content": "fsd",
"video": 14
}

11
backend/requests/medias.http

@ -1,11 +0,0 @@
### GET PROFILE PICTURE (through nginx)
GET http://localhost/api/media/profile/sacha.jpg
### GET PROFILE PICTURE (direct backend - for testing)
GET http://localhost:8000/api/media/profile/sacha.jpg
### GET THUMBNAIL (through nginx)
GET http://localhost/api/media/thumbnail/E90B982DE9C5112C.jpg
### GET THUMBNAIL (direct backend - for testing)
GET http://localhost:8000/api/media/thumbnail/E90B982DE9C5112C.jpg

11
backend/requests/playlist.http

@ -1,11 +0,0 @@
@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMwNDAxMTh9.GNGee4BYCL-wO3I8FtXXJIc-xsLMfuOoUlBhHjQJcWo
### ADD PLAYLIST
POST http://localhost/api/playlists
Authorization: Bearer {{token}}
Content-Type: application/json
{
"name": "A regarder plus tard",
"owner": 1
}

5
backend/requests/recommendation.http

@ -1,5 +0,0 @@
### GET NON-AUTHENTICATED RECOMMENDATIONS
GET http://localhost:8000/api/recommendations/
### GET TRENDING VIDS
GET http://localhost:8000/api/recommendations/trending/

17
backend/requests/top-tags-videos.http

@ -1,17 +0,0 @@
### Get all videos from the three most watched tags
GET http://127.0.0.1:8000/api/videos/top-tags/videos
### Alternative localhost URL
GET http://localhost:8000/api/videos/top-tags/videos
### With frontend URL (if using nginx proxy)
GET http://localhost/api/videos/top-tags/videos
###
# This endpoint returns:
# - topTags: The 3 most used tags with their usage count
# - videos: All public videos that have any of these top 3 tags
# - totalVideos: Count of videos returned
#
# Videos are ordered by popularity score (calculated from views, likes, comments)
# and then by release date (newest first)

49
backend/requests/user.http

@ -1,49 +0,0 @@
@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMwNDAxMTh9.GNGee4BYCL-wO3I8FtXXJIc-xsLMfuOoUlBhHjQJcWo
### CREATE USER
POST http://localhost:8000/api/users/
Content-Type: application/json
{
"email": "sacha@gmail.com",
"username": "astria",
"password": "Test_974",
"picture": "null"
}
### LOGIN USER
POST http://localhost:8000/api/users/login
Content-Type: application/json
{
"username": "astria",
"password": "Test1234!"
}
### GET USER BY ID
GET http://localhost:8000/api/users/astria
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTE3NDYxMzJ9.i0MPfB7IJbMGNnaktV0OlxoCRs6ujKtVmSmlxLa2eNY
### GET USER BY USERNAME
GET http://localhost:8000/api/users/username/astria
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTE3NDYxMzJ9.i0MPfB7IJbMGNnaktV0OlxoCRs6ujKtVmSmlxLa2eNY
### UPDATE USER
PUT http://localhost:8000/api/users/5
Authorization: Bearer {{token}}
Content-Type: application/json
{
"email": "test@test.com",
"username": "test",
"password": "Rwqfsfasxc_974",
"picture": "null"
}
### DELETE USER
DELETE http://localhost:8000/api/users/4
Authorization: Bearer {{token}}
### GET HISTORY
GET http://localhost:8000/api/users/1/history/
Authorization: Bearer {{token}}

33
backend/requests/video.http

@ -1,33 +0,0 @@
@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhc3RyaWEiLCJpYXQiOjE3NTMzODAyNjB9._rUcieo3acJp6tjQao7V3UQz0_ngHuB2z36_fG_fIX8
### UPDATE VIDEO
PUT http://127.0.0.1:8000/api/videos/3
Content-Type: application/json
Authorization: Bearer {{token}}
{
"title": " I built a REDSTONE FACTORY in Minecraft Create Mod! [#28] ",
"description": "I've been struggling for Redstone, so it's time we make a Crete Mod 6 redstone farm! This does mean we need to also make a cobble generator, netherwart farm and blaze farm to reach our goals, all in one new victorian-era-steampunky-type-brick buiilding.",
"visibility": "public"
}
### TOGGLE LIKE
GET http://127.0.0.1:8000/api/videos/14/like
Authorization: Bearer {{token}}
### ADD TAGS
PUT http://127.0.0.1:8000/api/videos/3/tags
Content-Type: application/json
Authorization: Bearer {{token}}
{
"tags": [
"Minecraft",
"Create Mod",
"Redstone"
],
"channel": 1
}
###
GET http://localhost/api/search?q=minecraft&type=videos&offset=0&limit=10

9
backend/server.js

@ -22,7 +22,7 @@ import { Strategy as GitHubStrategy } from "passport-github2";
console.clear();
dotenv.config();
console.log(process.env)
const app = express();
@ -91,15 +91,16 @@ app.use("/api/media", MediaRoutes);
app.use("/api/search", SearchRoute);
app.use("/api/oauth", OAuthRoute);
const port = process.env.PORT;
const port = process.env.BACKEND_PORT;
if (process.env.NODE_ENV !== "test") {
const server = http.createServer(app);
server.listen(port, '0.0.0.0', async () => {
console.log("Server's listening on port " + port);
console.log("Initializing database...");
console.log(`Server started on port ${port}`);
await initDb();
console.log("Database initialized successfully.");
});
}

190
backend/test/channel.test.js

@ -1,190 +0,0 @@
import vitest, {describe} from "vitest";
import app from "../server.js";
import request from "supertest";
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NSwidXNlcm5hbWUiOiJ0ZXN0IiwiaWF0IjoxNzUxNzk2MjM2fQ.XmaVA_NQcpW7fxRtDWOinMyQXPaFixpp3ib_mzo6M6c"
describe("CREATE CHANNEL", async function() {
it("Should return 401 if token isn't valid", async function() {
const channel = {
"name": "Astria",
"description": "A channel already exists",
"owner": 5
}
const req = await request(app).post("/api/channels/").send(channel);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if owner is not number", async function() {
const channel = {
"name": "Machin",
"description": "kljsdfjklsdfjkl",
"owner": "astria"
}
const req = await request(app).post("/api/channels/").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if owner don't exist", async function() {
const channel = {
"name": "Machin",
"description": "kljsdfjklsdfjkl",
"owner": 55
}
const req = await request(app).post("/api/channels/").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user isn't owner of owner", async function() {
const channel = {
"name": "Machin",
"description": "kljsdfjklsdfjkl",
"owner": 3
}
const req = await request(app).post("/api/channels/").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(403);
})
it("Should return 400 if channel name already exists", async function() {
const channel = {
"name": "Sacha",
"description": "A channel already exists",
"owner": 5
}
const req = await request(app).post("/api/channels/").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 400 if channel name is empty", async function() {
const channel = {
"description": "A channel already exists",
"owner": 5
}
const req = await request(app).post("/api/channels/").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 400 if owner is empty", async function() {
const channel = {
"name": "Astria",
"description": "A channel already exists",
}
const req = await request(app).post("/api/channels/").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
})
describe("GET CHANNEL BY ID", async function() {
it("Should return 401 if token is not valid", async function() {
const req = await request(app).get("/api/channels/2").send();
expect(req.statusCode).toBe(401);
})
it("Should return 400 if id is not number", async function() {
const req = await request(app).get("/api/channels/sacha").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if channel does not exist", async function() {
const req = await request(app).get("/api/channels/200").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(404);
})
it("Should return 200 if OK", async function() {
const req = await request(app).get("/api/channels/2").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(200);
})
})
describe("GET ALL CHANNELS", async function() {
it("Should be 401 if token is not valid", async function() {
const req = await request(app).get("/api/channels/").send();
expect(req.statusCode).toBe(401);
})
it("Should return 200 if OK", async function() {
const req = await request(app).get("/api/channels/").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(200);
})
})
describe("UPDATE CHANNEL", async function() {
it("Should be 401 if token is not valid", async function() {
const channel = {
"name": "Machin",
"description": "kljsdfjklsdfjkl",
}
const req = await request(app).put("/api/channels/2").send(channel);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if name is empty", async function() {
const channel = {
"name": null,
"description": "A channel already exists",
}
const req = await request(app).put("/api/channels/2").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if channel does not exist", async function() {
const channel = {
"name": "Machin",
"description": "kljsdfjklsdfjkl",
}
const req = await request(app).put("/api/channels/404").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the channel", async function() {
const channel = {
"name": "Machin",
"description": "kljsdfjklsdfjkl",
}
const req = await request(app).put("/api/channels/3").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(403);
})
it("Should return 400 if channel name is changed but already used", async function() {
const channel = {
"name": "Astria",
"description": "kljsdfjklsdfjkl",
}
const req = await request(app).put("/api/channels/2").send(channel).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
})
describe("DELETE CHANNEL", async function() {
it("Should be 401 if token is not valid", async function() {
const req = await request(app).del("/api/channels/2").send();
expect(req.statusCode).toBe(401);
})
it("Should return 400 if id is not number", async function() {
const req = await request(app).del("/api/channels/astria").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if channel does not exist", async function() {
const req = await request(app).del("/api/channels/404").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the channel", async function() {
const req = await request(app).del("/api/channels/3").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(403);
})
})
describe("TOGGLE SUBSCRIPTION", async function() {
it("Should return 401 if token is not valid", async function() {
const req = await request(app).post("/api/channels/2/subscribe").send();
expect(req.statusCode).toBe(401);
})
it("Should return 400 if id is not number", async function() {
const req = await request(app).post("/api/channels/sacha/subscribe").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if channel does not exist", async function() {
const req = await request(app).post("/api/channels/404/subscribe").send().set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(404);
})
it("Should return 200 if OK", async function() {
const body = {
"userId": 5
}
const req = await request(app).post("/api/channels/2/subscribe").send(body).set("Authorization", "Bearer " + token);
expect(req.statusCode).toBe(200);
})
})

175
backend/test/comment.test.js

@ -1,175 +0,0 @@
import app from "../server.js";
import request from "supertest";
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NSwidXNlcm5hbWUiOiJ0ZXN0IiwiaWF0IjoxNzUxNzk2MjM2fQ.XmaVA_NQcpW7fxRtDWOinMyQXPaFixpp3ib_mzo6M6c"
const videoId = 14
const commentId = 1
describe("POST COMMENT", async function() {
it("Should return 401 if token is missing", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"video": videoId
}
const req = await request(app).post("/api/comments/").send(comment);
expect(req.status).toBe(401);
})
it("Should return 400 if video is missing", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
}
const req = await request(app).post("/api/comments/").send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 400 if content is missing", async function() {
const comment = {
'video': videoId
}
const req = await request(app).post("/api/comments/").send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 400 if video is not number", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"video": "lorem"
}
const req = await request(app).post("/api/comments/").send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 404 if video doesn't exist", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"author": 5,
"video": 404
}
const req = await request(app).post("/api/comments/").send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
})
describe("GET COMMENTS FROM VIDEO ID", async function() {
it("Should return 401 if token is missing", async function() {
const req = await request(app).get("/api/comments/video/" + videoId).send();
expect(req.status).toBe(401);
})
it("Should return 404 if video doesn't exist", async function() {
const req = await request(app).get("/api/comments/video/404").send().set("Authorization", "Bearer " + token)
expect(req.status).toBe(404);
})
it("Should return 200 if OK", async function() {
const req = await request(app).get("/api/comments/video/" + videoId).send().set("Authorization", "Bearer " + token)
expect(req.status).toBe(200);
})
})
describe("GET COMMENT BY ID", async function() {
it("Should return 401 if token is missing", async function() {
const req = await request(app).get("/api/comments/1").send()
expect(req.status).toBe(401);
})
it("Should return 400 if id is not number", async function() {
const req = await request(app).get("/api/comments/sacha").send().set("Authorization", "Bearer " + token)
expect(req.status).toBe(400);
})
it("Should return 404 if comment doesn't exist", async function() {
const req = await request(app).get("/api/comments/404").send().set("Authorization", "Bearer " + token)
expect(req.status).toBe(404);
})
it("Should return 200 if OK", async function() {
const req = await request(app).get("/api/comments/1").send().set("Authorization", "Bearer " + token)
expect(req.status).toBe(200);
})
})
describe("UPDATE COMMENT", async function() {
it("Should return 401 if token is missing", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"video": videoId
}
const req = await request(app).put("/api/comments/" + commentId).send(comment);
expect(req.status).toBe(401);
})
it("Should return 400 if video is missing", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
}
const req = await request(app).put("/api/comments/" + commentId).send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 400 if content is missing", async function() {
const comment = {
'video': videoId
}
const req = await request(app).put("/api/comments/" + commentId).send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 400 if video is not number", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"video": "lorem"
}
const req = await request(app).put("/api/comments/" + commentId).send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 403 if user is not the author", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"video": videoId
}
const req = await request(app).put("/api/comments/2").send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(403);
})
it("Should return 404 if comment doesn't exist", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"video": videoId
}
const req = await request(app).put("/api/comments/12").send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
it("Should return 404 if video doesn't exist", async function() {
const comment = {
"content": "Lorem ipsum dolor sit amet",
"author": 5,
"video": 404
}
const req = await request(app).put("/api/comments/" + commentId).send(comment).set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
})
describe("DELETE COMMENT", async function() {
it("Should return 401 if token is missing", async function() {
const req = await request(app).del("/api/comments/" + commentId).send();
expect(req.status).toBe(401);
})
it("Should return 403 if user is not the author", async function() {
const req = await request(app).del("/api/comments/2").send();
expect(req.status).toBe(401);
})
it("Should return 404 if comment doesn't exist", async function() {
const req = await request(app).del("/api/comments/404" + commentId).send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
})

231
backend/test/playlist.test.js

@ -1,231 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../server.js';
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NSwidXNlcm5hbWUiOiJ0ZXN0IiwiaWF0IjoxNzUxODA5OTA3fQ.uCJFysaOiXC8qZKykWLi58WDcItKSkQPUIZ7kH-87Tg";
const playlistId = 1;
const playlistId2 = 3;
const videoId = 14;
const falseVideoId = 404;
const falseplaylistId = 404;
const userId = 5;
describe('CREATE PLAYLIST', () => {
it("Should return 401 if token is missing", async () => {
const playlist = {
"name": "Playlist Test",
}
const req = await request(app).post("/api/playlists").send(playlist);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if name is missing", async () => {
const playlist = {}
const req = await request(app).post("/api/playlists").set("Authorization", `Bearer ${token}`).send(playlist);
expect(req.statusCode).toBe(400);
})
it("Should return 400 if name is not alphanumeric", async () => {
const playlist = {
"name": "Playlist Test !@#"
}
const req = await request(app).post("/api/playlists").set("Authorization", `Bearer ${token}`).send(playlist);
expect(req.statusCode).toBe(400);
})
it("Should return 200 if playlist is created", async () => {
const playlist = {
"name": "Playlist Test",
}
const req = await request(app).post("/api/playlists").set("Authorization", `Bearer ${token}`).send(playlist);
expect(req.statusCode).toBe(200);
})
});
describe('ADD VIDEO TO PLAYLIST', () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).post(`/api/playlists/${playlistId}`).send({ video: videoId });
expect(req.statusCode).toBe(401);
})
it("Should return 404 if playlist does not exist", async () => {
const req = await request(app).post(`/api/playlists/${falseplaylistId}`).set("Authorization", `Bearer ${token}`).send({ video: videoId });
console.log(req.body);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the playlist", async () => {
const req = await request(app).post(`/api/playlists/${playlistId2}`).set("Authorization", `Bearer ${token}`).send({ video: videoId });
expect(req.statusCode).toBe(403);
})
it("Should return 400 if video id is missing", async () => {
const req = await request(app).post(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({});
expect(req.statusCode).toBe(400);
})
it("Should return 400 if video id is not numeric", async () => {
const req = await request(app).post(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({ video: "test" });
expect(req.statusCode).toBe(400);
})
it("Should return 404 if video does not exist", async () => {
const req = await request(app).post(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({ video: falseVideoId });
expect(req.statusCode).toBe(404);
})
it("Should return 200 if video is added to playlist", async () => {
const req = await request(app).post(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({ video: videoId });
expect(req.statusCode).toBe(200);
})
});
describe('GET PLAYLIST BY USER', () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).get("/api/playlists/user/" + userId);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if user id is not numeric", async () => {
const req = await request(app).get("/api/playlists/user/test").set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if user does not exist", async () => {
const req = await request(app).get("/api/playlists/user/404").set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the account", async () => {
const req = await request(app).get("/api/playlists/user/" + 6).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(403);
})
it("Should return 200 if playlists are found for user", async () => {
const req = await request(app).get("/api/playlists/user/" + 5).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(200);
})
});
describe('GET PLAYLIST BY ID', () => {
it("Should return 401 if token is missing", async ()=> {
const req = await request(app).get(`/api/playlists/${playlistId}`);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if playlist id is not numeric", async () => {
const req = await request(app).get("/api/playlists/test").set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if playlist does not exist", async () => {
const req = await request(app).get(`/api/playlists/${falseplaylistId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the playlist", async () => {
const req = await request(app).get(`/api/playlists/${playlistId2}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(403);
})
it("Should return 200 if playlist is found", async () => {
const req = await request(app).get(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(200);
})
})
describe("UPDATE PLAYLIST", () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).put(`/api/playlists/${playlistId}`).send({ name: "New Playlist Name" });
expect(req.statusCode).toBe(401);
})
it("Should return 400 if playlist id is not numeric", async () => {
const req = await request(app).put("/api/playlists/test").set("Authorization", `Bearer ${token}`).send({ name: "New Playlist Name" });
expect(req.statusCode).toBe(400);
})
it("Should return 404 if playlist does not exist", async () => {
const req = await request(app).put(`/api/playlists/${falseplaylistId}`).set("Authorization", `Bearer ${token}`).send({ name: "New Playlist Name" });
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the playlist", async () => {
const req = await request(app).put(`/api/playlists/${playlistId2}`).set("Authorization", `Bearer ${token}`).send({ name: "New Playlist Name" });
expect(req.statusCode).toBe(403);
})
it("Should return 400 if name is missing", async () => {
const req = await request(app).put(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({});
expect(req.statusCode).toBe(400);
})
it("Should return 400 if name is not alphanumeric", async () => {
const req = await request(app).put(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({ name: "New Playlist Name !@#" });
expect(req.statusCode).toBe(400);
})
it("Should return 200 if playlist is updated", async () => {
const req = await request(app).put(`/api/playlists/${playlistId}`).set("Authorization", `Bearer ${token}`).send({ name: "Updated Playlist Name" });
expect(req.statusCode).toBe(200);
})
})
describe("DELETE VIDEO FROM PLAYLIST", () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId}/video/${videoId}`);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if playlist id is not numeric", async () => {
const req = await request(app).delete("/api/playlists/test/video/14").set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(400);
})
it("Should return 400 if video id is not numeric", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId}/video/test`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if playlist does not exist", async () => {
const req = await request(app).delete(`/api/playlists/${falseplaylistId}/video/${videoId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the playlist", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId2}/video/${videoId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(403);
})
it("Should return 404 if video is not in the playlist", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId}/video/${falseVideoId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(404);
})
it("Should return 200 if video is deleted from playlist", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId}/video/${videoId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(200);
})
})
describe("DELETE PLAYLIST", () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId}`);
expect(req.statusCode).toBe(401);
})
it("Should return 400 if playlist id is not numeric", async () => {
const req = await request(app).delete("/api/playlists/test").set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(400);
})
it("Should return 404 if playlist does not exist", async () => {
const req = await request(app).delete(`/api/playlists/${falseplaylistId}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(404);
})
it("Should return 403 if user is not the owner of the playlist", async () => {
const req = await request(app).delete(`/api/playlists/${playlistId2}`).set("Authorization", `Bearer ${token}`);
expect(req.statusCode).toBe(403);
})
})

413
backend/test/user.test.js

@ -1,413 +0,0 @@
import { describe, it, expect } from 'vitest';
import app from '../server.js';
import request from 'supertest';
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NSwidXNlcm5hbWUiOiJ0ZXN0IiwiaWF0IjoxNzUxNzk2MjM2fQ.XmaVA_NQcpW7fxRtDWOinMyQXPaFixpp3ib_mzo6M6c"
describe('USER REGISTER', () => {
it('should return 400 when password is too short', async () => {
const user = {
email: "sachaguerin@gmail.com",
username: "sachaguerin",
password: "Toor!9",
picture: "null"
};
const res = await request(app)
.post('/api/users')
.send(user);
expect(res.status).toBe(400);
});
it('should return 400 when password is too long', async () => {
const user = {
email: "sachaguerin",
username: "sachaguerin",
password: "Toor!95555555555555555555555555555555555555555555555555555555",
picture: "null"
}
const res = await request(app)
.post("/api/users")
.send(user);
expect(res.status).toBe(400);
});
it('should return 400 when password don\'t have capital letters', async () => {
const user = {
email: "sachaguerin",
username: "sachaguerin",
password: "toooooooooor_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
});
it('should return 400 when password don\'t have lowercase letters', async () => {
const user = {
email: "sachaguerin",
username: "sachaguerin",
password: "TOOOOOOOOOOOOOOOOOOOORRR_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
});
it('should return 400 when password don\'t have symbols', async () => {
const user = {
email: "sachaguerin",
username: "sachaguerin",
password: "Rwqfsfasxc974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
});
it("Should return 400 if email is invalid", async () => {
const user = {
email: "sachaguerin",
username: "sachaguerin",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if username isn't string", async () => {
const user = {
email: "sachaguerin",
username: 48,
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if email is already used", async () => {
const user = {
email: "sachaguerin.sg@gmail.com",
username: "sachaguerin",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if username is already used", async () => {
const user = {
email: "sachaguerin@gmail.com",
username: "astria",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if email isn't provided", async () => {
const user = {
username: "sachaguerin",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if username isn't provided", async () => {
const user = {
email: "sachaguerin@gmail.com",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if password isn't provided", async () => {
const user = {
email: "sachaguerin@gmail.com",
username: "sachaguerin",
picture: "null"
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if picture isn't provided", async () => {
const user = {
email: "sachaguerin@gmail.com",
username: "sachaguerin",
password: "Rwqfsfasxc_974",
}
const res = await request(app).post('/api/users').send(user);
expect(res.status).toBe(400);
})
});
describe('USER LOGIN', () => {
it('Should return 401 if account doesn\'t exist', async () => {
const user = {
username: "sachaguerin",
password: "Rwqfsfasxc__974",
}
const req = await request(app).post('/api/users/login').send(user);
expect(req.status).toBe(401);
})
it('Should return 401 if password is not valid', async () => {
const user = {
username: "astria",
password: "Rwqfsfasxc__974",
}
const req = await request(app).post('/api/users/login').send(user);
expect(req.status).toBe(401);
})
it("Should return 400 if username isn't provided", async () => {
const user = {
password: "Rwqfsfasxc_974",
}
const res = await request(app).post('/api/users/login').send(user);
expect(res.status).toBe(400);
})
it("Should return 400 if password isn't provided", async () => {
const user = {
username: "astria",
}
const res = await request(app).post('/api/users/login').send(user);
expect(res.status).toBe(400);
})
it("Should return 200 if OK", async () => {
const user = {
username: "astria",
password: "Rwqfsfasxc_974",
}
const res = await request(app).post('/api/users/login').send(user);
expect(res.status).toBe(200);
})
})
describe('GET USER BY ID', async () => {
it("Should return 401 if token is not valid", async () => {
const res = await request(app).get("/api/users/5").send();
expect(res.status).toBe(401);
})
it("Should return 404 if user doesn\'t exist", async () => {
const res = await request(app).get("/api/users/3333").send().set("Authorization", "Bearer " + token);
expect(res.status).toBe(404);
})
it("Should return 400 if id is not integer", async () => {
const res = await request(app).get("/api/users/sacha").send().set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
})
it("Should return 200 if OK", async () => {
const res = await request(app).get("/api/users/3").send().set("Authorization", "Bearer " + token);
expect(res.status).toBe(200);
})
})
describe('GET USER BY USERNAME', async () => {
it("Should return 401 if token is not valid", async () => {
const req = await request(app).get("/api/users/username/astria").send();
expect(req.status).toBe(401);
})
it("Should return 404 if user doesn\'t exist", async () => {
const req = await request(app).get("/api/users/username/congolexicomatision").send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
it("Should return 200 if OK", async () => {
const req = await request(app).get("/api/users/username/astria").send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(200);
})
})
describe('UPDATE USER', () => {
it("Should return 401 if token is not valid", async () => {
const user = {
email: "sachaguerin@gmail.com",
username: "sacha",
password: "Rwqfsfasxc__974",
picture: "Sachaguerin",
}
const res = await request(app).put('/api/users/3').send(user);
expect(res.status).toBe(401);
})
it('should return 400 when password is too short', async () => {
const user = {
email: "test@test.com",
username: "test",
password: "Toor!9",
picture: "null"
};
const res = await request(app)
.put('/api/users/5')
.send(user)
.set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
});
it('should return 400 when password don\'t have capital letters', async () => {
const user = {
email: "test@test.com",
username: "test",
password: "toooooooooor_974",
picture: "null"
}
const res = await request(app).put('/api/users/5').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
});
it('should return 400 when password don\'t have lowercase letters', async () => {
const user = {
email: "test@test.com",
username: "test",
password: "TOOOOOOOOOOOOOOOOOOOORRR_974",
picture: "null"
}
const res = await request(app).put('/api/users/5').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
});
it('should return 400 when password don\'t have symbols', async () => {
const user = {
email: "test@test.com",
username: "test",
password: "Rwqfsfasxc974",
picture: "null"
}
const res = await request(app).put('/api/users/5').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
});
it("Should return 400 if email is invalid", async () => {
const user = {
email: "sachaguerin",
username: "test",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).put('/api/users/5').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
})
it("Should return 403 if the user is not the owner", async () => {
const user = {
email: "test@test.com",
username: "test",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).put('/api/users/3').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(403);
})
it("Should return 400 if mail change but already taken", async () => {
const user = {
email: "sachaguerin.sg@gmail.com",
username: "test",
picture: "null",
password: "Test_974"
}
const res = await request(app).put('/api/users/5').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
})
it("Should return 400 if username changed but already taken", async () => {
const user = {
email: "test@test.com",
username: "astria",
password: "Test_974",
picture: "null"
}
const res = await request(app).put('/api/users/5').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(400);
})
it("Should return 404 if user doesn't exist", async () => {
const user = {
email: "test@test.com",
username: "test",
password: "Rwqfsfasxc_974",
picture: "null"
}
const res = await request(app).put('/api/users/666').send(user).set("Authorization", "Bearer " + token);
expect(res.status).toBe(404);
})
})
describe ('DELETE USER', async function () {
it("Should return 401 if token is not valid", async () => {
const req = await request(app).del('/api/users/5').send();
expect(req.status).toBe(401);
})
it ("Should return 403 if user isn't the owner", async () => {
const req = await request(app).del('/api/users/4').send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(403);
})
it("Should return 400 if id is empty", async () => {
const req = await request(app).del('/api/users').send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
it("Should return 400 if id isn't number", async () => {
const req = await request(app).del('/api/users/sacha').send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(400);
})
it("Should return 404 if user doesn't exist", async () => {
const req = await request(app).del('/api/users/666').send().set("Authorization", "Bearer " + token);
expect(req.status).toBe(404);
})
})

198
backend/test/video.test.js

@ -1,198 +0,0 @@
import vitest from "vitest";
import app from "../server.js";
import request from "supertest";
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NSwidXNlcm5hbWUiOiJ0ZXN0IiwiaWF0IjoxNzUxNzk2MjM2fQ.XmaVA_NQcpW7fxRtDWOinMyQXPaFixpp3ib_mzo6M6c"
const videoId = 14
describe("GET BY ID", async () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).get("/api/videos/" + videoId).send()
expect(req.status).toBe(401)
})
it("Should return 400 if id is not number", async () => {
const req = await request(app).get("/api/videos/sacha").send().set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 404 if video does not exist", async () => {
const req = await request(app).get("/api/videos/404").send().set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(404)
})
it("Should return 200 if OK", async () => {
const req = await request(app).get("/api/videos/" + videoId).send().set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(200)
})
})
describe("GET BY CHANNEL", async () => {
it("Should return 401 if token is missing", async () => {
const req = await request(app).get("/api/videos/channel/2").send()
expect(req.status).toBe(401)
})
it("Should return 400 if channeld id is not number", async () => {
const req = await request(app).get("/api/videos/channel/astria").send().set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 404 if channel does not exist", async () => {
const req = await request(app).get("/api/videos/channel/404").send().set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(404)
})
it("Should return 200 if OK", async () => {
const req = await request(app).get("/api/videos/channel/2").send().set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(200)
})
})
describe("UPDATE VIDEO DATA", async () => {
it("Should return 401 if token is missing", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
"channel": 2
}
const req = await request(app).put(`/api/videos/` + videoId).send(video)
expect(req.status).toBe(401)
})
it("Should return 400 if id is not number", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
"channel": 2
}
const req = await request(app).put(`/api/videos/sacha`).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 400 if title is missing", async () => {
const video = {
"description": "video",
"visibility": "private",
"channel": 2
}
const req = await request(app).put(`/api/videos/` + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 400 if visibility is missing", async () => {
const video = {
"title": "video",
"description": "video",
"channel": 2
}
const req = await request(app).put('/api/videos/' + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 400 if visibility is not string", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": 400,
"channel": 2
}
const req = await request(app).put("/api/videos/" + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 400 if channel is missing", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
}
const req = await request(app).put("/api/videos/" + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 400 if channel is not number", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
"channel": "astria"
}
const req = await request(app).put("/api/videos/" + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 404 if video does not exist", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
"channel": 2
}
const req = await request(app).put("/api/videos/404").send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(404);
})
it("Should return 404 if channel does not exist", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
"channel": 404
}
const req = await request(app).put("/api/videos/" + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(404);
})
it("Should return 403 if user not the owner", async () => {
const video = {
"title": "video",
"description": "video",
"visibility": "private",
"channel": 3
}
const req = await request(app).put("/api/videos/" + videoId).send(video).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(403);
})
})
describe("UPDATE TAGS", async () => {
it("Should return 401 if token is missing", async () => {
const tags = {
"tags": ["tag1", "tag2"]
}
const req = await request(app).put(`/api/videos/` + videoId + "/tags").send(tags)
expect(req.status).toBe(401)
})
it("Should return 400 if id is not number", async () => {
const tags = {
"tags": ["tag1", "tag2"]
}
const req = await request(app).put(`/api/videos/sacha/tags`).send(tags).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 400 if tags is not array", async () => {
const tags = {
"tags": "tag1, tag2"
}
const req = await request(app).put(`/api/videos/` + videoId + "/tags").send(tags).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(400)
})
it("Should return 404 if video does not exist", async () => {
const tags = {
"tags": ["tag1", "tag2"]
}
const req = await request(app).put("/api/videos/404/tags").send(tags).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(404);
})
it("Should return 403 if user not the owner", async () => {
const tags = {
"tags": ["tag1", "tag2"],
"channel": 3
}
const req = await request(app).put("/api/videos/" + videoId + "/tags").send(tags).set("Authorization", `Bearer ${token}`);
expect(req.status).toBe(403);
})
})

29
backend/tools.js

@ -1,29 +0,0 @@
import pg from "pg";
console.log(process.argv[2]);
if (process.argv[2] === "flush") {
flushDatabase();
}
async function flushDatabase() {
const client = new pg.Client({
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
port: 5432
});
try {
await client.connect();
await client.query('TRUNCATE TABLE users CASCADE');
console.log('Database flushed successfully.');
} catch (err) {
console.error('Error flushing database:', err);
} finally {
await client.end();
}
}

6
backend/vitest.config.js

@ -1,6 +0,0 @@
export default {
test: {
globals: true,
environment: 'node',
},
};

2
developpement.yaml

@ -15,7 +15,7 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
LOG_FILE: ${LOG_FILE}
PORT: ${BACKEND_PORT}
BACKEND_PORT: ${BACKEND_PORT}
GMAIL_USER: ${GMAIL_USER}
GMAIL_PASSWORD: ${GMAIL_PASSWORD}
GITHUB_ID: ${GITHUB_ID}

6
docker-compose.yaml

@ -15,7 +15,7 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
LOG_FILE: ${LOG_FILE}
PORT: ${BACKEND_PORT}
BACKEND_PORT: ${BACKEND_PORT}
GMAIL_USER: ${GMAIL_USER}
GMAIL_PASSWORD: ${GMAIL_PASSWORD}
GITHUB_ID: ${GITHUB_ID}
@ -23,7 +23,7 @@ services:
FRONTEND_URL: ${FRONTEND_URL}
volumes:
- ./backend/logs:/var/log/freetube
- ./backend:/app
- ./uploads:/app/app/uploads/
depends_on:
db:
condition: service_healthy
@ -61,3 +61,5 @@ services:
volumes:
db_data:
driver: local
upload:
driver: local

7
frontend/Docker.Development

@ -1,7 +0,0 @@
# Build stage
FROM node:22-alpine3.20 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

12
frontend/README.md

@ -1,12 +0,0 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

15
frontend/rebuild.sh

@ -1,15 +0,0 @@
#!/bin/bash
echo "🧹 Nettoyage des caches..."
rm -rf dist
rm -rf node_modules/.vite
rm -rf node_modules/.cache
echo "📦 Build de l'application..."
npm run build
echo "🔄 Redémarrage du container frontend..."
cd ../
sudo docker-compose restart frontend
echo "✅ Build terminé et container redémarré !"

4
frontend/src/components/CreatorCard.jsx

@ -2,9 +2,11 @@
export default function CreatorCard({ creator }) {
const handleClick = () => {
window.location.href = `/manage-channel/${creator.id}`;
window.location.href = `/channel/${creator.channel_id}`;
};
return (
<div className="flex flex-col glassmorphism w-full p-6 cursor-pointer" onClick={handleClick}>
<img

2
frontend/src/components/Recommendations.jsx

@ -1,7 +1,7 @@
import VideoCard from "./VideoCard";
export default function Recommendations({videos}) {
console.log(videos);
return (
<div className="">
<h2 className="text-3xl font-bold mb-4 text-white">Recommandations</h2>

2
frontend/src/modals/CreateChannelModal.jsx

@ -22,7 +22,7 @@ export default function CreateChannelModal({isOpen, onClose, addAlert}) {
const data = await createChannel(body, token, addAlert);
console.log(data);
onClose();
}

2
frontend/src/modals/EmailVerificationModal.jsx

@ -17,7 +17,7 @@ export default function EmailVerificationModal({ isOpen, onSubmit, onClose }) {
onChange={(e) => setVerificationCode(e.target.value)}
/>
<button className="bg-primary px-3 py-2 rounded-sm text-white font-montserrat text-lg font-semibold cursor-pointer mt-2" onClick={() => {
console.log("Verification code submitted:", verificationCode);
onSubmit(verificationCode)
}}>Vérifier</button>
</div>

4
frontend/src/pages/AddVideo.jsx

@ -35,7 +35,7 @@ export default function AddVideo() {
const fetchChannel = async () => {
const fetchedChannel = await getChannel(user.id, token, addAlert);
setChannel(fetchedChannel.channel);
console.log(fetchedChannel.channel);
}
const handleTagKeyDown = (e) => {
@ -123,7 +123,7 @@ export default function AddVideo() {
// Call the API to search for users
searchByUsername(searchUser, token, addAlert)
.then((results) => {
console.log(results);
setSearchResults(results);
})
.catch((error) => {

35
frontend/src/pages/Home.jsx

@ -77,40 +77,7 @@ export default function Home() {
<div className=" lg:min-w-screen lg:min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} />
<main className=" px-5 lg:px-36">
{/* Hero section */}
<div className="flex flex-col items-center w-full pt-[128px] lg:pt-[304px]">
<img src={HeroImage} alt="" className=" w-1700/1920 lg:w-1046/1920" />
{isAuthenticated ? (
<h1 className="font-montserrat text-4xl lg:text-8xl font-black w-1200/1920 text-center text-white -translate-y-1/2">
Bienvenue {user?.username} !
</h1>
) : (
<>
<h1 className="font-montserrat text-4xl lg:text-8xl font-black w-1200/1920 text-center text-white -translate-y-1/2">
Regarder des vidéos comme jamais auparavant
</h1>
<div className="flex justify-center gap-10 -translate-y-[100px] mt-10">
<button className="bg-white text-black font-montserrat p-3 rounded-sm text-xl lg:text-2xl font-bold">
<a href="/login">
<p>Se connecter</p>
</a>
</button>
<button className="bg-primary p-3 rounded-sm text-white font-montserrat text-xl lg:text-2xl font-bold cursor-pointer">
<a href="/register">
<p>Créer un compte</p>
</a>
</button>
</div>
</>
)}
</div>
<main className="pt-[30px] px-5 lg:px-36">
{/* Recommendations section */}
<Recommendations videos={recommendations} />

6
frontend/src/pages/LoginSuccess.jsx

@ -15,14 +15,14 @@ export default function LoginSuccess() {
const token = searchParams.get('token');
const userParam = searchParams.get('user');
console.log('Processing OAuth callback:', { token: !!token, userParam: !!userParam });
if (token && userParam) {
try {
hasProcessed.current = true;
const userData = JSON.parse(decodeURIComponent(userParam));
console.log('Parsed user data:', userData);
// Use the OAuth login method to update auth context
loginWithOAuth(userData, token);
@ -40,7 +40,7 @@ export default function LoginSuccess() {
}, 100);
}
} else {
console.log('Missing token or user data');
hasProcessed.current = true;
setTimeout(() => {
navigate('/login?error=missing_data', { replace: true });

4
frontend/src/pages/ManageVideo.jsx

@ -173,7 +173,7 @@ export default function ManageVideo() {
channel: video.channel
};
console.log("Sending to API:", body); // Debug log
// Debug log
const request = await updateAuthorizedUsers(id, body, token, addAlert);
@ -188,7 +188,7 @@ export default function ManageVideo() {
}
const onRemoveAuthorizedUser = async (user) => {
console.log("Êtes-vous sûr de vouloir supprimer cet utilisateur autorisé ?", user.id);
const body = {
authorizedUsers: video.authorizedUsers.filter(u => u.id !== user.id),
channel: video.channel

2
frontend/src/pages/Register.jsx

@ -77,7 +77,7 @@ export default function Register() {
const onVerificationSubmit = async (token) => {
console.log("Submitting email verification with token:", token);
const response = await verifyEmail(formData.email, token, addAlert);

37
frontend/src/pages/Video.jsx

@ -84,32 +84,32 @@ export default function Video() {
const token = localStorage.getItem('token');
if (!token) return;
console.log("Fetching next video");
console.log("currentPlaylist", currentPlaylist);
console.log("current video id from params:", id, "type:", typeof id);
console.log("playlist videos:", currentPlaylist?.videos?.map(v => ({ id: v.id, type: typeof v.id })));
//Find position of current video id in currentPlaylist.videos
const currentIndex = currentPlaylist?.videos.findIndex(video => {
console.log(`Comparing video.id: ${video.id} (${typeof video.id}) with id: ${id} (${typeof id})`);
return video.id.toString() === id.toString();
});
console.log("currentIndex", currentIndex);
if (currentIndex !== -1) {
if (currentPlaylist?.videos[currentIndex + 1]) {
setNextVideo(currentPlaylist.videos[currentIndex + 1]);
console.log("nextVideo", currentPlaylist.videos[currentIndex + 1]);
}
}
}
const passToNextVideo = () => {
if (!nextVideo) {
console.log("No next video available");
return;
}
console.log("Passing to next video:", nextVideo);
// Navigate to the next video with playlist context
if (playlistId) {
@ -209,7 +209,7 @@ export default function Video() {
const handlePlaying = () => {
if (videoRef.current) {
console.log(`Video is playing at ${videoRef.current.currentTime} seconds`);
}
}
@ -249,7 +249,7 @@ export default function Video() {
const response = await subscribe(video.creator.id, addAlert);
console.log('Subscription successful:', response);
const subscriptionCount = response.subscriptions || 0;
setVideo((prevVideo) => {
@ -372,8 +372,7 @@ export default function Video() {
{/* Video controls */}
<div
className={`glassmorphism-rounded-md p-4 hidden lg:flex gap-4 items-center transition-opacity duration-300 ${
showControls ? 'opacity-100' : 'opacity-0'
className={`glassmorphism-rounded-md p-4 hidden lg:flex gap-4 items-center transition-opacity duration-300 ${showControls ? 'opacity-100' : 'opacity-0'
}
${isFullscreen ? 'fixed bottom-8 left-1/2 transform -translate-x-1/2 opacity-100 z-[60] w-[70%] max-w-3xl' : 'absolute bottom-4 left-4 right-4'}`}
ref={controllerRef}
@ -454,10 +453,18 @@ export default function Video() {
<img
src={video.creator?.profile_picture || "https://placehold.co/48"}
alt={video.creator?.name || "Creator"}
className="w-12 h-12 rounded-full object-cover mr-3"
className="w-12 h-12 rounded-full object-cover mr-3 cursor-pointer"
onClick={() => {
window.location.href = `/channel/${video.creator?.id}`;
}}
/>
<div>
<p className="text-white font-bold font-montserrat">{video.creator?.name}</p>
<p
className="text-white font-bold font-montserrat cursor-pointer"
onClick={() => {
window.location.href = `/channel/${video.creator?.id}`;
}}
>{video.creator?.name}</p>
<p className="text-gray-300 text-sm">{video.creator?.subscribers || 0} abonnés</p>
</div>
<button className="ml-14 bg-primary text-white font-montserrat font-bold px-4 py-2 rounded-md cursor-pointer" onClick={handleSubscribe} >

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

@ -56,7 +56,7 @@ export async function updateChannel(channelId, data, token, addAlert) {
export async function subscribe(channelId, addAlert) {
const token = localStorage.getItem('token');
const user = JSON.parse(localStorage.getItem('user'));
console.log("Subscribing to channel with ID:", channelId, "for user:", user.id);
return fetch(`/api/channels/${channelId}/subscribe`, {
method: 'POST',
headers: {

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

@ -11,7 +11,7 @@ export async function isSubscribed(channelId, addAlert) {
const request = await fetch(`/api/users/${channelId}/channel/subscribed`, { headers })
const result = await request.json();
console.log("Subscription status for channel ID", channelId, ":", result);
return result.subscribed;
}
@ -140,7 +140,7 @@ export async function searchByUsername(username, token, addAlert) {
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
console.log(errorData);
throw new Error(errorData.error || "Failed to search user");
}
const data = await response.json();

BIN
uploads/profiles/sacha.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Loading…
Cancel
Save