import {getClient} from "../utils/database.js"; import * as path from "node:path"; import * as fs from "node:fs"; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import jwt from "jsonwebtoken"; import {query} from "express-validator"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export async function upload(req, res) { const fileBuffer = req.file.buffer; let isGenerate = false; while (isGenerate === false) { const client = await getClient(); let letters = "0123456789ABCDEF"; let hex = ''; for (let i = 0; i < 16; i++) hex += letters[(Math.floor(Math.random() * 16))]; const query = `SELECT * FROM videos WHERE slug = $1`; const result = await client.query(query, [hex]); client.end(); if (result.rows.length === 0) { isGenerate = true; req.body.slug = hex; } } const finalName = req.body.slug + "." + req.file.originalname.split(".")[1]; const destinationPath = path.join(__dirname, "../uploads/videos/" + finalName) fs.writeFileSync(destinationPath, fileBuffer); const client = await getClient(); const video = { title: req.body.title, description: req.body.description, //TODO thumbnail file: "/api/media/video/" + finalName, channel: req.body.channel, slug: req.body.slug, format: req.file.originalname.split(".")[1], visibility: req.body.visibility, } const logger = req.body.logger; logger.write("try to upload video"); const releaseDate = new Date(Date.now()).toISOString(); const query = `INSERT INTO videos (title, thumbnail, description, channel, visibility, file, slug, format, release_date) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`; const idResult = await client.query(query, [video.title, 'null', video.description, video.channel, video.visibility, video.file, video.slug, video.format, releaseDate]); const id = idResult.rows[0].id; logger.write("successfully uploaded video", 200); await client.end() res.status(200).json({"message": "Successfully uploaded video", "id":id}); } export async function uploadThumbnail(req, res) { const fileBuffer = req.file.buffer; const client = await getClient(); const videoId = req.body.video; const videoSlugQuery = `SELECT * from videos WHERE id = $1`; const result = await client.query(videoSlugQuery, [videoId]); const fileName = result.rows[0].slug + "." + req.file.originalname.split(".")[1]; const destinationPath = path.join(__dirname, "../uploads/thumbnails/", fileName); fs.writeFileSync(destinationPath, fileBuffer); const logger = req.body.logger; logger.action("try to add thumbnail to video " + req.body.video); const file = "/api/media/thumbnail/" + fileName; const updateQuery = `UPDATE videos SET thumbnail = $1 WHERE id = $2`; await client.query(updateQuery, [file, req.body.video]); logger.write("successfully uploaded thumbnail", 200); await client.end(); res.status(200).json({"message": "Successfully uploaded thumbnail"}); } export async function getById(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to get video " + id); const client = await getClient(); const query = `SELECT * FROM videos WHERE id = $1`; const result = await client.query(query, [id]); const video = result.rows[0]; // GET VIEWS AND LIKES COUNT const viewsQuery = `SELECT COUNT(*) AS view_count FROM history WHERE video = $1`; const viewsResult = await client.query(viewsQuery, [id]); const likesQuery = `SELECT COUNT(*) AS like_count FROM likes WHERE video = $1`; const likesResult = await client.query(likesQuery, [id]); video.views = viewsResult.rows[0].view_count; video.likes = likesResult.rows[0].like_count; // GET COMMENTS const commentsQuery = `SELECT c.id, c.content, c.created_at, u.username, u.picture FROM comments c JOIN users u ON c.author = u.id WHERE c.video = $1 ORDER BY c.created_at DESC`; const commentsResult = await client.query(commentsQuery, [id]); video.comments = commentsResult.rows; // GET CREATOR const creatorQuery = `SELECT c.id, c.name, c.owner FROM channels c JOIN videos v ON c.id = v.channel WHERE v.id = $1`; const creatorResult = await client.query(creatorQuery, [id]); video.creator = creatorResult.rows[0]; // GET CREATOR PROFILE PICTURE const profilePictureQuery = `SELECT picture FROM users WHERE id = $1`; const profilePictureResult = await client.query(profilePictureQuery, [video.creator.owner]); video.creator.profile_picture = profilePictureResult.rows[0].picture; // GET CREATOR SUBSCRIBERS COUNT const subscribersQuery = `SELECT COUNT(*) AS subscriber_count FROM subscriptions WHERE channel = $1`; const subscribersResult = await client.query(subscribersQuery, [video.creator.id]); video.creator.subscribers = subscribersResult.rows[0].subscriber_count; // GET TAGS const tagsQuery = `SELECT t.name FROM tags t JOIN video_tags vt ON t.id = vt.tag WHERE vt.video = $1`; const tagsResult = await client.query(tagsQuery, [id]); video.tags = tagsResult.rows.map(tag => tag.name); logger.write("successfully get video " + id, 200); client.end() res.status(200).json(video); } export async function getByChannel(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to get video from channel " + id); const client = await getClient(); const query = `SELECT * FROM videos WHERE channel = $1`; const result = await client.query(query, [id]); logger.write("successfully get video from channel " + id, 200); client.end() res.status(200).json(result.rows); } export async function update(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to update video " + id); const client = await getClient(); const query = `UPDATE videos SET title = $1, description = $2, visibility = $3 WHERE id = $4`; await client.query(query, [req.body.title, req.body.description, req.body.visibility, id]); logger.write("successfully updated video", 200); client.end() res.status(200).json({"message": "Successfully updated video"}); } export async function updateVideo(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to update video file " + id); const client = await getClient(); const videoQuery = `SELECT slug, format FROM videos WHERE id = $1`; const videoResult = await client.query(videoQuery, [id]); const video = videoResult.rows[0]; const slug = video.slug; const format = video.format; const pathToDelete = path.join(__dirname, "../uploads/videos/", slug + "." + format ); fs.unlink(pathToDelete, (error) => { if (error) { logger.write(error, 500); client.end() res.status(500).json({"message": "Failed to delete video"}); return } logger.action("successfully deleted video " + slug + "." + format ); const fileBuffer = req.file.buffer; const finalName = slug + "." + format; const destinationPath = path.join(__dirname, "../uploads/videos/" + finalName) fs.writeFileSync(destinationPath, fileBuffer); logger.write("successfully updated video", 200); client.end() res.status(200).json({"message": "Successfully updated video"}); }) } export async function del(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to delete video " + id); const client = await getClient(); const query = `SELECT slug, format, thumbnail FROM videos WHERE id = $1`; const result = await client.query(query, [id]); const video = result.rows[0]; const slug = video.slug; const format = video.format; const thumbnailFile = video.thumbnail.split("/")[4]; let pathToDelete = path.join(__dirname, "../uploads/videos/", slug + "." + format); fs.unlink(pathToDelete, (error) => { if (error) { logger.write(error, 500); client.end() res.status(500).json({"message": "Failed to delete video"}); return } pathToDelete = path.join(__dirname, "../uploads/thumbnails/", thumbnailFile); fs.unlink(pathToDelete, async (error) => { if (error) { logger.write(error, 500); res.status(500).json({"message": "Failed to delete video"}); return } const query = `DELETE FROM videos WHERE id = $1`; await client.query(query, [id]); logger.write("successfully deleted video", 200); client.end() res.status(200).json({"message": "Successfully deleted video"}); }) }) } export async function toggleLike(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to toggle like on video " + id); const token = req.headers.authorization.split(" ")[1]; const claims = jwt.decode(token); const client = await getClient(); const userId = claims.id; const getLikeQuery = `SELECT * FROM likes WHERE owner = $1 AND video = $2`; const likeResult = await client.query(getLikeQuery, [userId, id]); if (likeResult.rows.length === 0) { const query = `INSERT INTO likes (video, owner) VALUES ($1, $2)`; await client.query(query, [id, userId]); // GET LIKES COUNT const likesCountQuery = `SELECT COUNT(*) AS like_count FROM likes WHERE video = $1`; const likesCountResult = await client.query(likesCountQuery, [id]); const likesCount = likesCountResult.rows[0].like_count; logger.write("no likes found adding likes for video " + id, 200); client.end(); res.status(200).json({"message": "Successfully added like", "likes": likesCount}); } else { const query = `DELETE FROM likes WHERE owner = $1 AND video = $2`; await client.query(query, [userId, id]); // GET LIKES COUNT const likesCountQuery = `SELECT COUNT(*) AS like_count FROM likes WHERE video = $1`; const likesCountResult = await client.query(likesCountQuery, [id]); const likesCount = likesCountResult.rows[0].like_count; logger.write("likes found, removing like for video " + id, 200); client.end(); res.status(200).json({"message": "Successfully removed like", "likes": likesCount}); } } export async function addTags(req, res) { const tags = req.body.tags; const videoId = req.params.id; const logger = req.body.logger; logger.action("try to add tags to video " + videoId); const client = await getClient(); // DECREASE USAGE COUNT FOR ALL TAGS const decreaseUsageCountQuery = `UPDATE tags SET usage_count = usage_count - 1 WHERE id IN (SELECT tag FROM video_tags WHERE video = $1)`; await client.query(decreaseUsageCountQuery, [videoId]); // DELETE ALL TAGS FOR VIDEO let deleteQuery = `DELETE FROM video_tags WHERE video = $1`; await client.query(deleteQuery, [videoId]); // INSERT NEW TAGS for (const tag of tags) { const tagQuery = `SELECT * FROM tags WHERE name = $1`; const tagResult = await client.query(tagQuery, [tag]); let id = null; if (tagResult.rows.length === 0) { const insertTagQuery = `INSERT INTO tags (name, usage_count) VALUES ($1, 1) RETURNING id`; const result = await client.query(insertTagQuery, [tag]); id = result.rows[0].id; } else { logger.write("Tag " + tag + " already exists for video " + videoId, 200); const getTagQuery = `SELECT usage_count FROM tags WHERE name = $1`; const getTagResult = await client.query(getTagQuery, [tag]); const usageCount = getTagResult.rows[0].usage_count + 1; const updateTagQuery = `UPDATE tags SET usage_count = $1 WHERE name = $2`; await client.query(updateTagQuery, [usageCount, tag]); id = tagResult.rows[0].id; } // INSERT INTO video_tags const insertVideoTagQuery = `INSERT INTO video_tags (tag, video) VALUES ($1, $2)`; await client.query(insertVideoTagQuery, [id, videoId]); } // GET UPDATED TAGS FOR VIDEO const updatedTagsQuery = `SELECT t.name FROM tags t JOIN video_tags vt ON t.id = vt.tag WHERE vt.video = $1`; const updatedTagsResult = await client.query(updatedTagsQuery, [videoId]); const updatedTags = updatedTagsResult.rows; logger.write("successfully added tags to video " + videoId, 200); await client.end(); res.status(200).json({"message": "Successfully added tags to video", "tags" : updatedTags.map(tag => tag.name)}); } export async function getSimilarVideos(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to get similar videos for video " + id); const client = await getClient(); // Get tags for the video const tagsQuery = `SELECT t.name FROM tags t JOIN video_tags vt ON t.id = vt.tag WHERE vt.video = $1`; const tagsResult = await client.query(tagsQuery, [id]); const tags = tagsResult.rows.map(row => row.name); if (tags.length === 0) { logger.write("No tags found for video " + id, 404); res.status(404).json({"message": "No similar videos found"}); return; } // Find similar videos based on tags which are not the same as the current video and limit to 10 results const similarVideosQuery = ` SELECT v.id, v.title, v.thumbnail, v.file, v.slug, v.format, v.release_date, c.name AS creator_name, c.id AS creator_id, c.owner AS creator_owner FROM videos v JOIN video_tags vt ON v.id = vt.video JOIN tags t ON vt.tag = t.id JOIN channels c ON v.channel = c.id WHERE t.name = ANY($1) AND v.id != $2 GROUP BY v.id, c.name, c.id LIMIT 10; `; const result = await client.query(similarVideosQuery, [tags, id]); for (let video of result.rows) { // Get views count for each video const viewsQuery = `SELECT COUNT(*) AS view_count FROM history WHERE video = $1`; const viewsResult = await client.query(viewsQuery, [video.id]); video.views = viewsResult.rows[0].view_count; // Get creator profile picture const profilePictureQuery = `SELECT picture FROM users WHERE id = $1`; const profilePictureResult = await client.query(profilePictureQuery, [video.creator_owner]); // Put creator info in video.creator video.creator = { id: video.creator_id, name: video.creator_name, profilePicture: profilePictureResult.rows[0].picture }; // Remove creator_id and creator_name from the video object delete video.creator_id; delete video.creator_name; } logger.write("successfully get similar videos for video " + id, 200); await client.end(); res.status(200).json(result.rows); } export async function getLikesPerDay(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to get likes per day"); const client = await getClient(); try { const response = {} const likeQuery = ` SELECT DATE(created_at) as date, COUNT(*) as count FROM likes WHERE video = $1 GROUP BY DATE(created_at) ORDER BY date DESC LIMIT 30 `; const viewQuery = ` SELECT DATE(viewed_at) as date, COUNT(*) as count FROM history WHERE video = $1 GROUP BY DATE(viewed_at) ORDER BY date DESC LIMIT 30 `; const resultViews = await client.query(viewQuery, [id]); response.views = resultViews.rows; 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); } catch (error) { logger.write("Error retrieving likes per day: " + error.message, 500); res.status(500).json({ error: "Internal server error" }); } finally { await client.end(); } } export async function addViews(req, res) { const id = req.params.id; const logger = req.body.logger; logger.action("try to add views for video " + id); const token = req.headers.authorization.split(" ")[1]; const claims = jwt.decode(token); const userId = claims.id; // ADD VIEW TO HISTORY IF NOT EXISTS const client = await getClient(); const query = `SELECT * FROM history WHERE video = $1 AND user_id = $2`; const result = await client.query(query, [id, userId]); if (result.rows.length === 0) { const insertQuery = `INSERT INTO history (video, user_id) VALUES ($1, $2)`; await client.query(insertQuery, [id, userId]); } logger.write("successfully added views for video " + id, 200); await client.end(); res.status(200).json({"message": "Successfully added views"}); }