diff --git a/backend/app/controllers/recommendation.controller.js b/backend/app/controllers/recommendation.controller.js index d7f043c..9aa6340 100644 --- a/backend/app/controllers/recommendation.controller.js +++ b/backend/app/controllers/recommendation.controller.js @@ -5,14 +5,65 @@ export async function getRecommendations(req, res) { const token = req.headers.authorization?.split(' ')[1]; - if (!token) { + if (!req.headers.authorization || !token) { - // GET MOST USED TOKEN + // GET MOST USED TAGS let client = await getClient(); - let queryMostUsedToken = `SELECT id, name FROM tags ORDER BY usage_count DESC LIMIT 3;`; - let result = await client.query(queryMostUsedToken); - - const recommendations = result.rows; + let queryMostUsedTags = `SELECT id, name FROM tags ORDER BY usage_count DESC LIMIT 3;`; + let result = await client.query(queryMostUsedTags); + + // GET 10 VIDEOS WITH THE TAGS + + let tagIds = result.rows.map(tag => tag.id); + let queryVideosWithTags = ` + + SELECT + v.id, + v.title, + v.thumbnail, + v.description AS video_description, + v.channel, + v.visibility, + v.file, + v.slug, + v.release_date, + v.channel AS channel_id, + c.owner, + COUNT(h.id) AS views, + json_build_object( + 'name', c.name, + 'profilePicture', u.picture, + 'description', c.description + ) AS creator, + 'video' AS type + FROM public.videos v + INNER JOIN public.video_tags vt ON v.id = vt.video + INNER JOIN public.tags t ON vt.tag = t.id + INNER JOIN public.channels c ON v.channel = c.id + INNER JOIN public.users u ON c.owner = u.id + LEFT JOIN public.history h ON h.video = v.id + WHERE t.id = ANY($1::int[]) + AND v.visibility = 'public' + GROUP BY + v.id, + v.title, + v.thumbnail, + v.description, + v.channel, + v.visibility, + v.file, + v.slug, + v.release_date, + c.owner, + c.name, + u.picture, + c.description + ORDER BY views DESC, v.release_date DESC + LIMIT 10; + + `; + let videoResult = await client.query(queryVideosWithTags, [tagIds]); + const recommendations = videoResult.rows; res.status(200).json(recommendations); } else { @@ -88,4 +139,28 @@ export async function getTrendingVideos(req, res) { console.error("Error fetching trending videos:", error); res.status(500).json({error: "Internal server error while fetching trending videos."}); } +} + +export async function getTopCreators(req, res) { + try { + // GET TOP 5 CREATORS BASED ON NUMBER OF SUBSCRIBERS + let client = await getClient(); + let queryTopCreators = ` + SELECT c.id, c.name, c.description, u.picture AS profilePicture, COUNT(s.id) AS subscriber_count + FROM channels c + JOIN users u ON c.owner = u.id + LEFT JOIN subscriptions s ON c.id = s.channel + GROUP BY c.id, u.picture + ORDER BY subscriber_count DESC + LIMIT 10; + `; + let result = await client.query(queryTopCreators); + const topCreators = result.rows; + + client.end(); + res.status(200).json(topCreators); + } catch (error) { + console.error("Error fetching top creators:", error); + res.status(500).json({error: "Internal server error while fetching top creators."}); + } } \ No newline at end of file diff --git a/backend/app/routes/redommendation.route.js b/backend/app/routes/redommendation.route.js index dbb7f2c..4757b0f 100644 --- a/backend/app/routes/redommendation.route.js +++ b/backend/app/routes/redommendation.route.js @@ -1,5 +1,5 @@ import { Router } from 'express'; -import {getRecommendations, getTrendingVideos} from "../controllers/recommendation.controller.js"; +import {getRecommendations, getTrendingVideos, getTopCreators} from "../controllers/recommendation.controller.js"; const router = Router(); @@ -7,4 +7,6 @@ router.get('/', [], getRecommendations); router.get('/trending', [], getTrendingVideos); +router.get("/creators", [], getTopCreators) + export default router; \ No newline at end of file diff --git a/backend/logs/access.log b/backend/logs/access.log index d76fa21..20a1a00 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -8440,3 +8440,87 @@ [2025-08-22 17:20:25.400] [undefined] GET(/:id/history): try to retrieve history of user 1 [2025-08-22 17:20:25.407] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404 [2025-08-22 17:20:25.418] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-22 17:45:26.016] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 17:45:26.020] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-22 17:45:26.024] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-08-22 17:45:26.028] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404 +[2025-08-22 17:45:26.038] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-22 17:45:31.307] [undefined] POST(/): try to create new channel with owner 1 and name Astri4 +[2025-08-22 17:45:31.311] [undefined] POST(/): Successfully created new channel with name Astri4 with status 200 +[2025-08-22 17:45:31.326] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 17:45:31.329] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-22 17:45:32.380] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-22 17:45:32.390] [undefined] GET(/:id/stats): try to get stats +[2025-08-22 17:45:32.396] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-22 17:45:32.405] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-22 17:45:33.382] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 17:45:33.387] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-22 17:48:00.202] [undefined] POST(/): try to upload video with status undefined +[2025-08-22 17:48:00.209] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-22 17:48:00.320] [undefined] POST(/thumbnail): try to add thumbnail to video 1 +[2025-08-22 17:48:00.326] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-22 17:48:00.348] [undefined] PUT(/:id/tags): try to add tags to video 1 +[2025-08-22 17:48:00.363] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200 +[2025-08-22 17:52:47.227] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 17:52:47.232] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-22 17:52:47.237] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-22 17:52:47.242] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404 +[2025-08-22 17:52:47.251] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-22 17:52:48.223] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-22 17:52:48.233] [undefined] GET(/:id/stats): try to get stats +[2025-08-22 17:52:48.237] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-22 17:52:48.247] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-22 17:52:49.012] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 17:52:49.016] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-22 17:53:03.301] [undefined] POST(/): try to upload video with status undefined +[2025-08-22 17:53:03.307] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-22 17:53:03.417] [undefined] POST(/thumbnail): try to add thumbnail to video 2 +[2025-08-22 17:53:03.423] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-22 17:53:03.445] [undefined] PUT(/:id/tags): try to add tags to video 2 +[2025-08-22 17:53:03.458] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200 +[2025-08-22 17:53:12.167] [undefined] GET(/:id): try to get video 1 +[2025-08-22 17:53:12.171] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-22 17:53:12.184] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-08-22 17:53:12.201] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-08-22 17:53:12.228] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-08-22 17:53:12.255] [undefined] GET(/:id/views): try to add views for video 1 +[2025-08-22 17:53:12.300] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-08-22 17:53:14.457] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 17:53:14.462] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-22 17:53:14.466] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-22 17:53:14.471] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-22 17:53:14.482] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-22 17:53:15.772] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-22 17:53:15.783] [undefined] GET(/:id/stats): try to get stats +[2025-08-22 17:53:15.789] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-22 17:53:15.800] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-22 17:53:16.871] [undefined] GET(/:id/likes/day): try to get likes per day +[2025-08-22 17:53:16.882] [undefined] GET(/:id): try to get video 2 +[2025-08-22 17:53:16.886] [undefined] GET(/:id/likes/day): successfully retrieved likes per day with status 200 +[2025-08-22 17:53:16.897] [undefined] GET(/:id): successfully get video 2 with status 200 +[2025-08-22 17:53:19.714] [undefined] PUT(/:id/tags): try to add tags to video 2 +[2025-08-22 17:53:19.724] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200 +[2025-08-22 17:53:23.383] [undefined] PUT(/:id/tags): try to add tags to video 2 +[2025-08-22 17:53:23.393] [undefined] PUT(/:id/tags): Tag wankil studio already exists for video 2 with status 200 +[2025-08-22 17:53:23.400] [undefined] PUT(/:id/tags): successfully added tags to video 2 with status 200 +[2025-08-22 20:37:48.060] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-22 20:37:48.067] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-22 20:37:48.072] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-22 20:37:48.077] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-22 20:37:48.087] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-22 20:37:52.902] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-22 20:37:52.914] [undefined] GET(/:id/stats): try to get stats +[2025-08-22 20:37:52.919] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-22 20:37:52.928] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-22 20:37:58.472] [undefined] PUT(/:id): try to update channel with id 1 +[2025-08-22 20:37:58.478] [undefined] PUT(/:id): Successfully updated channel with status 200 +[2025-08-22 20:40:57.463] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-22 20:40:57.475] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-22 20:40:57.491] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1 +[2025-08-22 20:40:57.495] [undefined] GET(/:id/channel/subscribed): user 1 is not subscribed to channel 1 with status 200 +[2025-08-22 20:41:04.433] [undefined] POST(/:id/subscribe): try to toggle subscription for channel with id 1 +[2025-08-22 20:41:04.446] [undefined] POST(/:id/subscribe): Successfully subscribed to channel with status 200 +[2025-08-22 20:42:05.622] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-22 20:42:05.633] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-22 20:42:05.685] [undefined] GET(/:id/channel/subscribed): check if user 1 is subscribed to channel 1 +[2025-08-22 20:42:05.690] [undefined] GET(/:id/channel/subscribed): user 1 is subscribed to channel 1 with status 200 diff --git a/backend/requests/top-tags-videos.http b/backend/requests/top-tags-videos.http new file mode 100644 index 0000000..1891010 --- /dev/null +++ b/backend/requests/top-tags-videos.http @@ -0,0 +1,17 @@ +### 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) diff --git a/frontend/src/components/Recommendations.jsx b/frontend/src/components/Recommendations.jsx index 6a118dc..03819a1 100644 --- a/frontend/src/components/Recommendations.jsx +++ b/frontend/src/components/Recommendations.jsx @@ -1,14 +1,12 @@ - +import VideoCard from "./VideoCard"; export default function Recommendations({videos}) { - - - + console.log(videos); return (
+ {creator.description.slice(0, 100) + (creator.description.length > 100 ? '...' : '')} +