Browse Source

Add top creators and recommendation

features/trending
astria 4 months ago
parent
commit
dddb6ade28
  1. 85
      backend/app/controllers/recommendation.controller.js
  2. 4
      backend/app/routes/redommendation.route.js
  3. 84
      backend/logs/access.log
  4. 17
      backend/requests/top-tags-videos.http
  5. 8
      frontend/src/components/Recommendations.jsx
  6. 21
      frontend/src/components/TopCreators.jsx
  7. 13
      frontend/src/pages/Home.jsx
  8. 13
      frontend/src/services/recommendation.service.js

85
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);
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;
const recommendations = result.rows;
`;
let videoResult = await client.query(queryVideosWithTags, [tagIds]);
const recommendations = videoResult.rows;
res.status(200).json(recommendations);
} else {
@ -89,3 +140,27 @@ export async function getTrendingVideos(req, res) {
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."});
}
}

4
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;

84
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

17
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)

8
frontend/src/components/Recommendations.jsx

@ -1,14 +1,12 @@
import VideoCard from "./VideoCard";
export default function Recommendations({videos}) {
console.log(videos);
return (
<div>
<h2 className="text-3xl font-bold mb-4 text-white">Recommendations</h2>
<div>
<div className="flex flex-wrap">
<div className="grid grid-cols-5 gap-8 mt-8">
{videos && videos.map((video, index) => (
<VideoCard key={video.id || index} video={video} />
))}

21
frontend/src/components/TopCreators.jsx

@ -1,15 +1,22 @@
export default function TopCreators({ creators }) {
export default function TopCreators({ creators, navigate }) {
return (
<div className="mt-10">
<h2 className="text-3xl font-bold mb-4 text-white">Top Creators</h2>
<div className="flex flex-wrap">
<h2 className="text-3xl font-bold mb-4 text-white">Top Créateurs</h2>
<div className="grid grid-cols-5 gap-8 mt-8">
{creators && creators.map((creator, index) => (
<div key={creator.id || index} className="flex flex-col items-center w-1/4 p-4">
<img src={creator.avatar} alt={creator.name} className="w-full h-auto rounded-lg" />
<h3 className="text-xl font-bold mt-2">{creator.name}</h3>
<span className="text-sm text-gray-500">{creator.subscribers} subscribers</span>
<div
key={creator.id || index}
className="flex flex-col items-center glassmorphism "
onClick={() => navigate(`/channel/${creator.id}`)}
>
<img src={creator.profilepicture} alt={creator.name} className="w-[128px] h-auto rounded-full" />
<h3 className="text-xl text-white font-bold mt-1">{creator.name}</h3>
<span className="text-sm text-gray-500">{creator.subscriber_count} abonné{creator.subscriber_count > 1 ? 's' : ''}</span>
<p className="text-center text-gray-400">
<span>{creator.description.slice(0, 100) + (creator.description.length > 100 ? '...' : '')}</span>
</p>
</div>
))}
</div>

13
frontend/src/pages/Home.jsx

@ -5,7 +5,8 @@ import {useState, useEffect} from "react";
import { useAuth } from '../contexts/AuthContext';
import TopCreators from "../components/TopCreators.jsx";
import TrendingVideos from "../components/TrendingVideos.jsx";
import { getRecommendations, getTrendingVideos } from '../services/recommendation.service.js';
import { getRecommendations, getTrendingVideos, getTopCreators } from '../services/recommendation.service.js';
import { useNavigate } from 'react-router-dom';
export default function Home() {
const { isAuthenticated, user } = useAuth();
@ -15,6 +16,8 @@ export default function Home() {
const [trendingVideos, setTrendingVideos] = useState([]);
const [alerts, setAlerts] = useState([]);
const navigate = useNavigate();
useEffect(() => {
// Fetch recommendations, top creators, and trending videos
const fetchData = async () => {
@ -30,6 +33,12 @@ export default function Home() {
setLoading(false);
}
try {
setTopCreators(await getTopCreators(addAlert));
} finally {
setLoading(false);
}
};
fetchData();
}, []);
@ -82,7 +91,7 @@ export default function Home() {
<Recommendations videos={recommendations} />
{/* Top Creators section */}
<TopCreators/>
<TopCreators creators={topCreators} navigate={navigate} />
{/* Trending Videos section */}
<TrendingVideos videos={trendingVideos} />

13
frontend/src/services/recommendation.service.js

@ -4,7 +4,7 @@ export async function getRecommendations(addAlert) {
try {
const response = await fetch('/api/recommendations');
const data = await response.json();
return data.recommendations;
return data;
} catch (error) {
console.error('Error fetching data:', error);
addAlert('error', 'Erreur lors du chargement des recommandations');
@ -21,3 +21,14 @@ export async function getTrendingVideos(addAlert) {
addAlert('error', 'Erreur lors du chargement des vidéos tendance');
}
}
export async function getTopCreators(addAlert) {
try {
const response = await fetch('/api/recommendations/creators');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
addAlert('error', 'Erreur lors du chargement des créateurs');
}
}
Loading…
Cancel
Save