From 859fed601acaf5a92f4c95743cd0364902796e21 Mon Sep 17 00:00:00 2001 From: Astri4-4 Date: Thu, 14 Aug 2025 08:49:28 +0000 Subject: [PATCH 1/2] Fix channel merge --- backend/app/controllers/channel.controller.js | 50 +++++++++++++++---- backend/app/controllers/user.controller.js | 24 +++++++++ backend/app/routes/channel.route.js | 2 +- backend/app/routes/user.route.js | 7 ++- frontend/src/components/ChannelLastVideos.jsx | 20 ++++++++ frontend/src/components/TabLayout.jsx | 43 ++++++++++++++++ frontend/src/components/VideoCard.jsx | 25 ++++++++++ frontend/src/index.css | 16 ++++++ frontend/src/routes/routes.jsx | 19 +++++-- frontend/src/services/channel.service.js | 39 +++++++++++++++ frontend/src/services/user.service.js | 16 ++++++ 11 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/ChannelLastVideos.jsx create mode 100644 frontend/src/components/TabLayout.jsx create mode 100644 frontend/src/services/channel.service.js create mode 100644 frontend/src/services/user.service.js diff --git a/backend/app/controllers/channel.controller.js b/backend/app/controllers/channel.controller.js index f1e51ca..7995726 100644 --- a/backend/app/controllers/channel.controller.js +++ b/backend/app/controllers/channel.controller.js @@ -30,25 +30,57 @@ export async function getById(req, res) { const client = await getClient(); const query = ` - SELECT * + SELECT channels.*, u.username, u.picture, COUNT(s.id) as subscriptions FROM channels + JOIN public.users u ON channels.owner = u.id + LEFT JOIN public.subscriptions s ON channels.id = s.channel WHERE channels.id = $1 + GROUP BY channels.id, name, description, channels.owner, u.username, u.picture `; const result = await client.query(query, [id]); const videoQuery = ` - SELECT v.*, COUNT(h.video) as views, COUNT(l.id) as likes, COUNT(c.id) as comments - FROM videos v - LEFT JOIN history h ON v.id = h.video - LEFT JOIN likes l ON v.id = l.video - LEFT JOIN comments c ON v.id = c.video - WHERE v.channel = $1 - GROUP BY v.id, title, thumbnail, description, channel, visibility, file, slug, format, release_date + SELECT + videos.id, + videos.title, + videos.description AS video_description, + videos.thumbnail, + videos.channel, + videos.visibility, + videos.file, + videos.slug, + videos.format, + videos.release_date, + channels.name AS name, + channels.description AS description, + users.picture AS profilePicture, + COUNT(h.id) AS views + FROM public.videos + LEFT JOIN public.channels ON videos.channel = channels.id + LEFT JOIN public.users ON channels.OWNER = users.id + LEFT JOIN public.history h ON h.video = videos.id + WHERE videos.channel = $1 + GROUP BY videos.id, channels.name, channels.description, users.username, users.picture `; + const videoResult = await client.query(videoQuery, [id]); - result.rows[0].videos = videoResult.rows; + const videoReturn = []; + + for (const video of videoResult.rows) { + video.creator = { + name: video.name, + profilePicture: video.profilepicture, + description: video.video_description + }; + delete video.name; + delete video.profilepicture; + delete video.video_description; + videoReturn.push(video); + } + + result.rows[0].videos = videoReturn; logger.write("Successfully get channel with id " + id, 200); client.end(); diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 4243899..a065b6c 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -307,4 +307,28 @@ export async function getHistory(req, res) { logger.write("successfully retrieved history of user " + id, 200); client.end(); 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.end(); + return res.status(200).json({subscribed: true}); + } else { + logger.write(`user ${userId} is not subscribed to channel ${channelId}`, 200); + client.end(); + return res.status(200).json({subscribed: false}); + } } \ No newline at end of file diff --git a/backend/app/routes/channel.route.js b/backend/app/routes/channel.route.js index 4876c6a..3663063 100644 --- a/backend/app/routes/channel.route.js +++ b/backend/app/routes/channel.route.js @@ -18,7 +18,7 @@ const router = Router(); router.post("/", [addLogger, isTokenValid, ChannelCreate.name, ChannelCreate.description, ChannelCreate.owner, validator, doUserExistsBody, doUserHaveChannel, isOwnerBody, doChannelNameExists], create); // GET CHANNEL BY ID -router.get("/:id", [addLogger, isTokenValid, Channel.id, validator, doChannelExists], getById); +router.get("/:id", [addLogger, Channel.id, validator, doChannelExists], getById); // GET ALL CHANNEL router.get("/", [addLogger, isTokenValid], getAll); diff --git a/backend/app/routes/user.route.js b/backend/app/routes/user.route.js index c3a5652..814cd37 100644 --- a/backend/app/routes/user.route.js +++ b/backend/app/routes/user.route.js @@ -6,7 +6,8 @@ import { getByUsername, update, deleteUser, - getChannel, getHistory + getChannel, getHistory, + isSubscribed } from "../controllers/user.controller.js"; import { UserRegister, @@ -22,6 +23,7 @@ import validator from "../middlewares/error.middleware.js"; import {isTokenValid} from "../middlewares/jwt.middleware.js"; import {addLogger} from "../middlewares/logger.middleware.js"; import {profileUpload} from "../middlewares/file.middleware.js"; +import {Channel} from "../middlewares/channel.middleware.js"; const router = Router(); @@ -49,4 +51,7 @@ router.get("/:id/channel", [addLogger, isTokenValid, User.id, validator], getCha // GET USER HISTORY router.get("/:id/history", [addLogger, isTokenValid, User.id, validator], getHistory); +// CHECK IF SUBSCRIBED TO CHANNEL +router.get("/:id/channel/subscribed", [addLogger, isTokenValid, User.id, Channel.id, validator], isSubscribed) + export default router; \ No newline at end of file diff --git a/frontend/src/components/ChannelLastVideos.jsx b/frontend/src/components/ChannelLastVideos.jsx new file mode 100644 index 0000000..7fdf6ba --- /dev/null +++ b/frontend/src/components/ChannelLastVideos.jsx @@ -0,0 +1,20 @@ +import VideoCard from "./VideoCard.jsx"; + +export default function ChannelLastVideos({ videos }) { + + return ( +
+ { + videos && videos.length > 0 ? ( + videos.map((video) => ( + + ) + ) + ) : ( +

Aucune vidéo trouvée

+ ) + } +
+ ) + +} \ No newline at end of file diff --git a/frontend/src/components/TabLayout.jsx b/frontend/src/components/TabLayout.jsx new file mode 100644 index 0000000..b92a0db --- /dev/null +++ b/frontend/src/components/TabLayout.jsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; + +export default function TabLayout({ tabs }) { + + const [activeTab, setActiveTab] = useState(tabs[0].id); + const onTabChange = (tabId) => { + setActiveTab(tabId); + }; + + return ( + +
+ + {/* TABS */} +
+ { + tabs.map((tab) => ( + + )) + } +
+ + {/* ELEMENT */} + +
+ {tabs.map((tab) => ( +
+ {tab.element()} +
+ ))} +
+ +
+ + ) + +} \ No newline at end of file diff --git a/frontend/src/components/VideoCard.jsx b/frontend/src/components/VideoCard.jsx index 7926efb..88f5855 100644 --- a/frontend/src/components/VideoCard.jsx +++ b/frontend/src/components/VideoCard.jsx @@ -1,5 +1,30 @@ import { useNavigate } from 'react-router-dom'; +// SUPPORTED JSON FORMAT +// [ +// { +// "id": 1, +// "title": "Video minecraft", +// "thumbnail": "/api/media/thumbnail/78438E11ABA5D0C8.webp", +// "video_description": "Cest une super video minecraft", +// "channel": 1, +// "visibility": "public", +// "file": "/api/media/video/78438E11ABA5D0C8.mp4", +// "slug": "78438E11ABA5D0C8", +// "format": "mp4", +// "release_date": "2025-08-11T11:14:01.357Z", +// "channel_id": 1, +// "owner": 2, +// "views": "2", +// "creator": { +// "name": "astria", +// "profilePicture": "/api/media/profile/sacha.jpg", +// "description": "salut tout le monde" +// }, +// "type": "video" +// } +// ] + export default function VideoCard({ video }) { const navigation = useNavigate(); const handleClick = () => { diff --git a/frontend/src/index.css b/frontend/src/index.css index 3a29bab..cc39166 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -38,6 +38,22 @@ backdrop-filter: blur(27.5px); } +.glassmorphism-top-round { + border-top-left-radius: 15px; + border-top-right-radius: 15px; + border: 1px solid rgba(239, 239, 239, 0.60); + background: linear-gradient(93deg, rgba(239, 239, 239, 0.06) 0%, rgba(239, 239, 239, 0.01) 100%); + backdrop-filter: blur(27.5px); +} + +.glassmorphism-bottom-round { + border-bottom-left-radius: 15px; + border-bottom-right-radius: 15px; + border: 1px solid rgba(239, 239, 239, 0.60); + background: linear-gradient(93deg, rgba(239, 239, 239, 0.06) 0%, rgba(239, 239, 239, 0.01) 100%); + backdrop-filter: blur(27.5px); +} + .resizable-none { resize: none; } diff --git a/frontend/src/routes/routes.jsx b/frontend/src/routes/routes.jsx index 5f1e7e0..74735bf 100644 --- a/frontend/src/routes/routes.jsx +++ b/frontend/src/routes/routes.jsx @@ -8,6 +8,7 @@ import ManageChannel from "../pages/ManageChannel.jsx"; import ManageVideo from "../pages/ManageVideo.jsx"; import AddVideo from "../pages/AddVideo.jsx"; import Search from "../pages/Search.jsx"; +import Channel from "../pages/Channel.jsx"; const routes = [ { path: "/", element: }, @@ -29,7 +30,11 @@ const routes = [ }, { path: "/video/:id", - element: