import bcrypt from "bcrypt"; import { getClient } from "../utils/database.js"; import jwt from "jsonwebtoken"; import path, { dirname } from "path"; import fs from "fs"; import { fileURLToPath } from "url"; import crypto from "crypto"; import { sendEmail } from "../utils/mail.js"; export async function register(req, res) { try { const user = { email: req.body.email, username: req.body.username, password: req.body.password, picture: req.body.picture, } const logger = req.body.logger; logger.action("try to register a user with username: " + req.body.username + " and email: " + req.body.email, user); user.password = await bcrypt.hash(req.body.password, 10); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); let file = req.file.buffer; if (file) { const finalName = user.username + "." + req.file.originalname.split(".")[1]; const destinationPath = path.join(__dirname, "../uploads/profiles/" + finalName) console.log(destinationPath) fs.writeFileSync(destinationPath, file); user.picture = "/api/media/profile/" + finalName; } else { user.picture = "/api/media/profile/default.png"; } const client = await getClient(); const query = `INSERT INTO users (email, username, password, picture) VALUES ($1, $2, $3, $4) RETURNING id`; const result = await client.query(query, [user.email, user.username, user.password, user.picture]); const id = result.rows[0].id; const playlistQuery = `INSERT INTO playlists (name, owner) VALUES ('A regarder plus tard', $1)`; await client.query(playlistQuery, [id]); // GENERATE EMAIL HEXA VERIFICATION TOKEN const token = crypto.randomBytes(32).toString("hex").slice(0, 5); const textMessage = "Merci de vous être inscrit. Veuillez vérifier votre e-mail. Code: " + token; const htmlMessage = ` Bienvenue sur Freetube

🎬 Bienvenue sur Freetube!

Bonjour ${user.username}! 👋

Merci de vous être inscrit sur Freetube! Nous sommes ravis de vous accueillir dans notre communauté.

Pour finaliser votre inscription, veuillez utiliser le code de vérification ci-dessous :

Code de vérification

${token}

⚠️ Ce code expirera dans 1 heure.

Si vous n'avez pas créé de compte, vous pouvez ignorer cet e-mail.

© 2025 Freetube. Tous droits réservés.

`; await sendEmail(user.email, "🎬 Bienvenue sur Freetube - Vérifiez votre email", textMessage, htmlMessage); // Store the token in the database const expirationDate = new Date(); expirationDate.setHours(expirationDate.getHours() + 1); // Token expires in 1 hour const insertQuery = `INSERT INTO email_verification (email, token, expires_at) VALUES ($1, $2, $3)`; await client.query(insertQuery, [user.email, token, expirationDate]); logger.write("successfully registered", 200); res.status(200).send({ user: user }); } catch (err) { logger?.write("failed to register user", 500); res.status(500).json({ error: "Internal server error" }); } finally { } } export async function verifyEmail(req, res) { const { email, token } = req.body; const logger = req.body.logger; logger.action("try to verify email for " + email + " with token " + token); const client = await getClient(); try { const query = `SELECT * FROM email_verification WHERE email = $1 AND token = $2`; const result = await client.query(query, [email, token]); if (result.rows.length === 0) { logger.write("failed to verify email for " + email, 404); return res.status(404).json({ error: "Invalid token or email" }); } // If we reach this point, the email is verified const queryDelete = `DELETE FROM email_verification WHERE email = $1`; await client.query(queryDelete, [email]); const updateQuery = `UPDATE users SET is_verified = TRUE WHERE email = $1`; await client.query(updateQuery, [email]); logger.write("successfully verified email for " + email, 200); res.status(200).json({ message: "Email verified successfully" }); } catch (error) { console.error("Error verifying email:", error); logger.write("failed to verify email for " + email, 500); res.status(500).json({ error: "Internal server error" }); } finally { client.release(); } } export async function login(req, res) { const user = { username: req.body.username, password: req.body.password, } const logger = req.body.logger; logger.action("try to login with username '" + user.username + "'"); const client = await getClient(); try { let query = `SELECT id, username, email, picture, password FROM users WHERE username = $1`; const result = await client.query(query, [user.username]); const userInBase = result.rows[0]; if (!userInBase) { logger.write("failed to login", 401) res.status(401).json({ error: "Invalid credentials" }); return } const isPasswordValid = await bcrypt.compare(req.body.password, userInBase.password); if (!isPasswordValid) { logger.write("failed to login", 401) res.status(401).json({ error: "Invalid credentials" }); return } const payload = { id: userInBase.id, username: userInBase.username, } const token = jwt.sign(payload, process.env.JWT_SECRET); const userData = { id: userInBase.id, username: userInBase.username, email: userInBase.email, picture: userInBase.picture } logger.write("Successfully logged in", 200); res.status(200).json({ token: token, user: userData }); } catch (err) { logger?.write("failed to login", 500); res.status(500).json({ error: "Internal server error" }); } finally { client.release(); } } export async function getById(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to retrieve user " + id); const client = await getClient(); const query = `SELECT id, email, username, picture FROM users WHERE id = $1`; const result = await client.query(query, [id]); if (!result.rows[0]) { logger.write("failed to retrieve user " + id + " because it doesn't exist", 404); client.release() res.status(404).json({ error: "Not Found" }); return } logger.write("successfully retrieved user " + id, 200); if (result.rows[0].picture) { return res.status(200).json({ user: result.rows[0] }); } } export async function getByUsername(req, res) { const username = req.params.username; const client = await getClient(); const logger = req.body.logger; logger.action("try to retrieve user " + username); const query = `SELECT id, email, username, picture FROM users WHERE username = $1`; const result = await client.query(query, [username]); if (!result.rows[0]) { logger.write("failed to retrieve user " + username + " because it doesn't exist", 404); client.release() res.status(404).json({ error: "Not Found" }); return } logger.write("successfully retrieved user " + username, 200); client.release(); return res.status(200).json({ user: result.rows[0] }); } export async function update(req, res) { try { const id = req.params.id; let user = { email: req.body.email, username: req.body.username, password: req.body.password, picture: req.body.picture, } const client = await getClient(); const userQuery = `SELECT * FROM users WHERE id = $1`; const userResult = await client.query(userQuery, [id]); const userInBase = userResult.rows[0]; const logger = req.body.logger; logger.action("try to update user " + id); if (user.email !== userInBase.email) { const emailQuery = `SELECT email FROM users WHERE email = $1`; const emailResult = await client.query(emailQuery, [user.email]); if (emailResult.rows[0]) { logger.write("failed to update because email is already used", 400) client.release(); res.status(400).json({ error: "Email already exists" }); } } if (user.username !== userInBase.username) { const usernameQuery = `SELECT username FROM users WHERE username = $1`; const usernameResult = await client.query(usernameQuery, [user.username]); if (usernameResult.rows[0]) { logger.write("failed to update because username is already used", 400) client.release(); res.status(400).json({ error: "Username already exists" }); } } if (user.password) { const isPasswordValid = await bcrypt.compare(req.body.password, userInBase.password); if (!isPasswordValid) { user.password = await bcrypt.hash(req.body.password, 10); } else { user.password = userInBase.password; } } else { user.password = userInBase.password; } let __filename = fileURLToPath(import.meta.url); let __dirname = dirname(__filename); let profilePicture = userInBase.picture.split("/").pop(); fs.rename( path.join(__dirname, "..", "uploads", "profiles", profilePicture), path.join(__dirname, "..", "uploads", "profiles", user.username + "." + profilePicture.split(".").pop()), (err) => { if (err) { logger.write("failed to update profile picture", 500); console.error("Error renaming file:", err); throw err; } }); profilePicture = "/api/media/profile/" + user.username + "." + profilePicture.split(".").pop(); 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]); logger.write("successfully updated user " + id, 200); client.release(); res.status(200).json(result.rows[0]); } catch (err) { client.release() res.status(500).json({ error: err }); } } export async function deleteUser(req, res) { const id = req.params.id; const client = await getClient(); const logger = req.body.logger; logger.action("try to delete user " + id); const query = `DELETE FROM users WHERE id = $1`; await client.query(query, [id]); logger.write("successfully deleted user " + id); client.release(); res.status(200).json({ message: 'User deleted' }); } export async function getChannel(req, res) { const id = req.params.id; const client = await getClient(); const logger = req.body.logger; logger.action("try to retrieve channel of user " + id); const query = `SELECT * FROM channels WHERE owner = $1`; const result = await client.query(query, [id]); if (!result.rows[0]) { logger.write("failed to retrieve channel of user " + id + " because it doesn't exist", 404); client.release(); res.status(404).json({ error: "Channel Not Found" }); return; } logger.write("successfully retrieved channel of user " + id, 200); client.release(); res.status(200).json({ channel: result.rows[0] }); } export async function getHistory(req, res) { const id = req.params.id; const client = await getClient(); const logger = req.body.logger; logger.action("try to retrieve history of user " + id); const query = `SELECT v.title, v.thumbnail, v.release_date, v.channel, c.name, u.picture, v.id FROM history JOIN public.videos v on history.video = v.id JOIN public.channels c on v.channel = c.id JOIN public.users u on c.owner = u.id WHERE user_id = $1 LIMIT 10 `; const result = await client.query(query, [id]); const videos = []; for (const video of result.rows) { // GET VIDEO VIEW COUNT const videoQuery = `SELECT COUNT(*) as view_count FROM history WHERE video = $1`; const videoResult = await client.query(videoQuery, [video.id]); const videoToAdd = { title: video.title, thumbnail: video.thumbnail, release_date: video.release_date, views: videoResult.rows[0].view_count, id: video.id, creator: { id: video.channel, name: video.name, profilePicture: video.picture }, user_picture: video.picture }; videos.push(videoToAdd); } if (!result.rows[0]) { logger.write("failed to retrieve history of user " + id + " because it doesn't exist", 404); res.status(404).json({ error: "History Not Found" }); client.release(); return; } logger.write("successfully retrieved history of user " + id, 200); client.release(); res.status(200).json(videos); } export async function isSubscribed(req, res) { const token = req.headers.authorization.split(" ")[1]; const tokenPayload = jwt.decode(token); const userId = tokenPayload.id; const channelId = req.params.id; const client = await getClient(); const logger = req.body.logger; logger.action(`check if user ${userId} is subscribed to channel ${channelId}`); const query = `SELECT * FROM subscriptions WHERE owner = $1 AND channel = $2`; const result = await client.query(query, [userId, channelId]); if (result.rows[0]) { logger.write(`user ${userId} is subscribed to channel ${channelId}`, 200); client.release(); return res.status(200).json({ subscribed: true }); } else { logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200); client.release(); return res.status(200).json({ subscribed: false }); } } export async function searchByUsername(req, res) { const username = req.query.username; const client = await getClient(); const logger = req.body.logger; logger.action("try to search user by username " + username); const query = `SELECT id, username, picture, email, is_verified FROM users WHERE username ILIKE $1`; const result = await client.query(query, [`%${username}%`]); if (result.rows.length === 0) { logger.write("no user found with username " + username, 404); client.release(); return res.status(404).json({ error: "User Not Found" }); } logger.write("successfully found user with username " + username, 200); 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); }