Browse Source

Finish fixing services

fix/services
Astri4-4 4 months ago
parent
commit
e253665544
  1. 6
      backend/app/controllers/channel.controller.js
  2. 73
      backend/app/controllers/video.controller.js
  3. 1239
      backend/logs/access.log
  4. 2
      frontend/src/components/Alert.jsx
  5. 2
      frontend/src/components/AlertList.jsx
  6. 42
      frontend/src/components/Comment.jsx
  7. 6
      frontend/src/components/Navbar.jsx
  8. 4
      frontend/src/components/ProtectedRoute.jsx
  9. 21
      frontend/src/modals/CreateChannelModal.jsx
  10. 94
      frontend/src/pages/Account.jsx
  11. 82
      frontend/src/pages/AddVideo.jsx
  12. 15
      frontend/src/pages/Home.jsx
  13. 65
      frontend/src/pages/ManageChannel.jsx
  14. 105
      frontend/src/pages/ManageVideo.jsx
  15. 149
      frontend/src/pages/Video.jsx
  16. 61
      frontend/src/services/channel.service.js
  17. 67
      frontend/src/services/comment.service.js
  18. 23
      frontend/src/services/recommendation.service.js
  19. 87
      frontend/src/services/user.service.js
  20. 212
      frontend/src/services/video.service.js

6
backend/app/controllers/channel.controller.js

@ -55,11 +55,15 @@ export async function getById(req, res) {
channels.name AS name, channels.name AS name,
channels.description AS description, channels.description AS description,
users.picture AS profilePicture, users.picture AS profilePicture,
COUNT(h.id) AS views COUNT(h.id) AS views,
COUNT(likes.id) AS likes,
COUNT(c.id) AS comments
FROM public.videos FROM public.videos
LEFT JOIN public.channels ON videos.channel = channels.id LEFT JOIN public.channels ON videos.channel = channels.id
LEFT JOIN public.users ON channels.OWNER = users.id LEFT JOIN public.users ON channels.OWNER = users.id
LEFT JOIN public.history h ON h.video = videos.id LEFT JOIN public.history h ON h.video = videos.id
LEFT JOIN public.likes ON likes.video = videos.id
LEFT JOIN public.comments c ON c.video = videos.id
WHERE videos.channel = $1 WHERE videos.channel = $1
GROUP BY videos.id, channels.name, channels.description, users.username, users.picture GROUP BY videos.id, channels.name, channels.description, users.username, users.picture
`; `;

73
backend/app/controllers/video.controller.js

@ -1,10 +1,10 @@
import {getClient} from "../utils/database.js"; import { getClient } from "../utils/database.js";
import * as path from "node:path"; import * as path from "node:path";
import * as fs from "node:fs"; import * as fs from "node:fs";
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname } from 'path';
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import {query} from "express-validator"; import { query } from "express-validator";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
@ -54,7 +54,7 @@ export async function upload(req, res) {
const id = idResult.rows[0].id; const id = idResult.rows[0].id;
logger.write("successfully uploaded video", 200); logger.write("successfully uploaded video", 200);
await client.end() await client.end()
res.status(200).json({"message": "Successfully uploaded video", "id":id}); res.status(200).json({ "message": "Successfully uploaded video", "id": id });
} }
@ -78,7 +78,7 @@ export async function uploadThumbnail(req, res) {
await client.query(updateQuery, [file, req.body.video]); await client.query(updateQuery, [file, req.body.video]);
logger.write("successfully uploaded thumbnail", 200); logger.write("successfully uploaded thumbnail", 200);
await client.end(); await client.end();
res.status(200).json({"message": "Successfully uploaded thumbnail"}); res.status(200).json({ "message": "Successfully uploaded thumbnail" });
} }
export async function getById(req, res) { export async function getById(req, res) {
@ -148,9 +148,48 @@ export async function update(req, res) {
const client = await getClient(); const client = await getClient();
const query = `UPDATE videos SET title = $1, description = $2, visibility = $3 WHERE id = $4`; 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]); await client.query(query, [req.body.title, req.body.description, req.body.visibility, id]);
const resultQuery = `
SELECT
videos.id,
videos.title,
videos.thumbnail,
videos.channel,
videos.file,
videos.description,
videos.visibility,
videos.release_date,
COUNT(DISTINCT l.id) AS likes_count,
COUNT(DISTINCT h.id) AS history_count,
JSON_AGG(
JSON_BUILD_OBJECT(
'id', c.id,
'content', c.content,
'username', u.username,
'video', c.video,
'created_at', c.created_at,
'picture', u.picture
)
) FILTER (
WHERE
c.id IS NOT NULL
) AS comments,
JSON_AGG(DISTINCT t.name) FILTER (WHERE t.name IS NOT NULL) AS tags
FROM public.videos
LEFT JOIN public.likes l ON l.video = videos.id
LEFT JOIN public.history h ON h.video = videos.id
LEFT JOIN public.comments c ON c.video = videos.id
LEFT JOIN public.video_tags vt ON vt.video = videos.id
LEFT JOIN public.tags t ON vt.tag = t.id
LEFT JOIN public.users u ON u.id = c.author
WHERE
videos.id = $1
GROUP BY public.videos.id
`;
const result = await client.query(resultQuery, [id]);
logger.write("successfully updated video", 200); logger.write("successfully updated video", 200);
client.end() client.end()
res.status(200).json({"message": "Successfully updated video"}); res.status(200).json(result.rows[0]);
} }
export async function updateVideo(req, res) { export async function updateVideo(req, res) {
@ -163,15 +202,15 @@ export async function updateVideo(req, res) {
const video = videoResult.rows[0]; const video = videoResult.rows[0];
const slug = video.slug; const slug = video.slug;
const format = video.format; const format = video.format;
const pathToDelete = path.join(__dirname, "../uploads/videos/", slug + "." + format ); const pathToDelete = path.join(__dirname, "../uploads/videos/", slug + "." + format);
fs.unlink(pathToDelete, (error) => { fs.unlink(pathToDelete, (error) => {
if (error) { if (error) {
logger.write(error, 500); logger.write(error, 500);
client.end() client.end()
res.status(500).json({"message": "Failed to delete video"}); res.status(500).json({ "message": "Failed to delete video" });
return return
} }
logger.action("successfully deleted video " + slug + "." + format ); logger.action("successfully deleted video " + slug + "." + format);
const fileBuffer = req.file.buffer; const fileBuffer = req.file.buffer;
const finalName = slug + "." + format; const finalName = slug + "." + format;
const destinationPath = path.join(__dirname, "../uploads/videos/" + finalName) const destinationPath = path.join(__dirname, "../uploads/videos/" + finalName)
@ -180,7 +219,7 @@ export async function updateVideo(req, res) {
logger.write("successfully updated video", 200); logger.write("successfully updated video", 200);
client.end() client.end()
res.status(200).json({"message": "Successfully updated video"}); res.status(200).json({ "message": "Successfully updated video" });
}) })
@ -202,7 +241,7 @@ export async function del(req, res) {
if (error) { if (error) {
logger.write(error, 500); logger.write(error, 500);
client.end() client.end()
res.status(500).json({"message": "Failed to delete video"}); res.status(500).json({ "message": "Failed to delete video" });
return return
} }
@ -210,14 +249,14 @@ export async function del(req, res) {
fs.unlink(pathToDelete, async (error) => { fs.unlink(pathToDelete, async (error) => {
if (error) { if (error) {
logger.write(error, 500); logger.write(error, 500);
res.status(500).json({"message": "Failed to delete video"}); res.status(500).json({ "message": "Failed to delete video" });
return return
} }
const query = `DELETE FROM videos WHERE id = $1`; const query = `DELETE FROM videos WHERE id = $1`;
await client.query(query, [id]); await client.query(query, [id]);
logger.write("successfully deleted video", 200); logger.write("successfully deleted video", 200);
client.end() client.end()
res.status(200).json({"message": "Successfully deleted video"}); res.status(200).json({ "message": "Successfully deleted video" });
}) })
}) })
@ -248,7 +287,7 @@ export async function toggleLike(req, res) {
logger.write("no likes found adding likes for video " + id, 200); logger.write("no likes found adding likes for video " + id, 200);
client.end(); client.end();
res.status(200).json({"message": "Successfully added like", "likes": likesCount}); res.status(200).json({ "message": "Successfully added like", "likes": likesCount });
} else { } else {
const query = `DELETE FROM likes WHERE owner = $1 AND video = $2`; const query = `DELETE FROM likes WHERE owner = $1 AND video = $2`;
await client.query(query, [userId, id]); await client.query(query, [userId, id]);
@ -260,7 +299,7 @@ export async function toggleLike(req, res) {
logger.write("likes found, removing like for video " + id, 200); logger.write("likes found, removing like for video " + id, 200);
client.end(); client.end();
res.status(200).json({"message": "Successfully removed like", "likes": likesCount}); res.status(200).json({ "message": "Successfully removed like", "likes": likesCount });
} }
@ -315,7 +354,7 @@ export async function addTags(req, res) {
logger.write("successfully added tags to video " + videoId, 200); logger.write("successfully added tags to video " + videoId, 200);
await client.end(); await client.end();
res.status(200).json({"message": "Successfully added tags to video", "tags" : updatedTags.map(tag => tag.name)}); res.status(200).json({ "message": "Successfully added tags to video", "tags": updatedTags.map(tag => tag.name) });
} }
@ -333,7 +372,7 @@ export async function getSimilarVideos(req, res) {
if (tags.length === 0) { if (tags.length === 0) {
logger.write("No tags found for video " + id, 404); logger.write("No tags found for video " + id, 404);
res.status(404).json({"message": "No similar videos found"}); res.status(404).json({ "message": "No similar videos found" });
return; return;
} }
@ -449,5 +488,5 @@ export async function addViews(req, res) {
logger.write("successfully added views for video " + id, 200); logger.write("successfully added views for video " + id, 200);
await client.end(); await client.end();
res.status(200).json({"message": "Successfully added views"}); res.status(200).json({ "message": "Successfully added views" });
} }

1239
backend/logs/access.log

File diff suppressed because it is too large

2
frontend/src/components/Alert.jsx

@ -6,7 +6,7 @@ export default function Alert({ type, message, onClose }) {
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
onClose(); onClose();
}, 15000); // 15 seconds }, 5000); // 5 seconds
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [onClose]); }, [onClose]);

2
frontend/src/components/AlertList.jsx

@ -3,7 +3,7 @@ import Alert from "./Alert.jsx";
export default function AlertList({ alerts, onCloseAlert }) { export default function AlertList({ alerts, onCloseAlert }) {
return ( return (
<div className="absolute top-1/1 right-0 flex flex-col gap-2 mt-2 mr-4 z-40"> <div className="fixed bottom-2.5 right-0 flex flex-col gap-2 mt-2 mr-4 z-40">
{alerts.map((alert, index) => ( {alerts.map((alert, index) => (
<Alert <Alert
key={index} key={index}

42
frontend/src/components/Comment.jsx

@ -1,8 +1,9 @@
import {useAuth} from "../contexts/AuthContext.jsx"; import {useAuth} from "../contexts/AuthContext.jsx";
import {useRef, useState} from "react"; import {useRef, useState} from "react";
import { updateComment, deleteComment } from "../services/comment.service.js";
export default function Comment({ comment, index, videoId, refetchVideo, doShowCommands=true }) { export default function Comment({ comment, index, videoId, refetchVideo, doShowCommands=true, addAlert }) {
let {user, isAuthenticated} = useAuth(); let {user, isAuthenticated} = useAuth();
let commentRef = useRef(); let commentRef = useRef();
@ -17,23 +18,11 @@ export default function Comment({ comment, index, videoId, refetchVideo, doShowC
} }
const handleDelete = async (id) => { const handleDelete = async (id) => {
try {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const response = await fetch(`/api/comments/${id}`, { const response = await deleteComment(id, token, addAlert);
method: 'DELETE', if (response) {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
// Refresh the video data to update the comments list
refetchVideo(); refetchVideo();
} }
} catch (error) {
console.error('Error deleting comment:', error);
}
} }
const handleEditSubmit = async (id, content) => { const handleEditSubmit = async (id, content) => {
@ -41,8 +30,6 @@ export default function Comment({ comment, index, videoId, refetchVideo, doShowC
alert("Comment cannot be empty"); alert("Comment cannot be empty");
return; return;
} }
try {
// Retrieve the token from localStorage // Retrieve the token from localStorage
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
@ -51,26 +38,17 @@ export default function Comment({ comment, index, videoId, refetchVideo, doShowC
return; return;
} }
const response = await fetch(`/api/comments/${comment.id}`, { const body = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
content: content, content: content,
video: videoId video: videoId
}) };
});
if (!response.ok) { const response = await updateComment(id, body, token, addAlert);
throw new Error('Failed to post comment');
}
setEditMode(false);
} catch (error) { if (response) {
console.error('Error posting comment:', error); setEditMode(false);
} }
} }
return ( return (

6
frontend/src/components/Navbar.jsx

@ -39,7 +39,8 @@ export default function Navbar({ isSearchPage = false, alerts = [], onCloseAlert
}; };
return ( return (
<nav className="flex justify-between items-center p-4 text-white absolute top-0 left-0 w-screen relative"> <>
<nav className="flex justify-between items-center p-4 text-white absolute top-0 left-0 w-screen">
<div> <div>
<h1 className="font-montserrat text-5xl font-black"> <h1 className="font-montserrat text-5xl font-black">
<a href="/">FreeTube</a> <a href="/">FreeTube</a>
@ -101,8 +102,9 @@ export default function Navbar({ isSearchPage = false, alerts = [], onCloseAlert
</ul> </ul>
</div> </div>
<AlertList alerts={allAlerts} onCloseAlert={handleCloseAlert} />
</nav> </nav>
<AlertList alerts={allAlerts} onCloseAlert={handleCloseAlert} />
</>
) )
} }

4
frontend/src/components/ProtectedRoute.jsx

@ -17,10 +17,6 @@ const ProtectedRoute = ({ children, requireAuth = true }) => {
return <Navigate to="/login" replace />; return <Navigate to="/login" replace />;
} }
if (!requireAuth && isAuthenticated) {
return <Navigate to="/" replace />;
}
return children; return children;

21
frontend/src/modals/CreateChannelModal.jsx

@ -1,7 +1,8 @@
import {useState} from "react"; import {useState} from "react";
import { createChannel } from "../services/channel.service.js";
export default function CreateChannelModal({isOpen, onClose}) { export default function CreateChannelModal({isOpen, onClose, addAlert}) {
const [name, setName] = useState(''); const [name, setName] = useState('');
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
@ -13,27 +14,17 @@ export default function CreateChannelModal({isOpen, onClose}) {
const onSubmit = async (e) => { const onSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
const request = await fetch(`/api/channels/`, { const body = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
"name": name, "name": name,
"description": description, "description": description,
"owner": user.id "owner": user.id
})
})
if (!request.ok) {
console.error("Not able to create channel");
return; // Prevent further execution if the request failed
} }
const data = await request.json(); const data = await createChannel(body, token, addAlert);
console.log(data); console.log(data);
onClose();
} }
return isOpen && ( return isOpen && (

94
frontend/src/pages/Account.jsx

@ -4,6 +4,7 @@ import PlaylistCard from "../components/PlaylistCard.jsx";
import VideoCard from "../components/VideoCard.jsx"; import VideoCard from "../components/VideoCard.jsx";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
import CreateChannelModal from "../modals/CreateChannelModal.jsx"; import CreateChannelModal from "../modals/CreateChannelModal.jsx";
import { getChannel, getUserHistory, getPlaylists, updateUser } from "../services/user.service.js";
export default function Account() { export default function Account() {
@ -25,67 +26,13 @@ export default function Account() {
const navigation = useNavigate(); const navigation = useNavigate();
const fetchUserChannel = async () => { const fetchUserChannel = async () => {
try { setUserChannel(await getChannel(user.id, token, addAlert)); // Reset before fetching
const response = await fetch(`/api/users/${user.id}/channel`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user data");
}
const data = await response.json();
setUserChannel(data);
} catch (error) {
console.error("Error fetching user channel:", error);
addAlert('error', "Erreur lors de la récupération des données de l'utilisateur.");
return null;
}
} }
const fetchUserHistory = async () => { const fetchUserHistory = async () => {
if (!user.id || !token) { setUserHistory(await getUserHistory(user.id, token, addAlert));
console.warn("User ID or token missing, skipping history fetch");
return;
}
try {
const response = await fetch(`/api/users/${user.id}/history`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user history");
}
const data = await response.json();
setUserHistory(data);
} catch (error) {
console.error("Error fetching user history:", error);
addAlert('error', "Erreur lors de la récupération de l'historique de l'utilisateur.");
}
} }
const fetchUserPlaylists = async () => { const fetchUserPlaylists = async () => {
if (!user.id || !token) { setUserPlaylists(await getPlaylists(user.id, token, addAlert));
console.warn("User ID or token missing, skipping playlists fetch");
return;
}
try {
const response = await fetch(`/api/playlists/user/${user.id}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user playlists");
}
const data = await response.json();
setUserPlaylists(data);
} catch (error) {
console.error("Error fetching user playlists:", error);
addAlert('error', "Erreur lors de la récupération des playlists de l'utilisateur.");
}
} }
useEffect(() => { useEffect(() => {
@ -116,27 +63,11 @@ export default function Account() {
password: password || undefined, // Only send password if it's not empty password: password || undefined, // Only send password if it's not empty
}; };
try { const result = await updateUser(user.id, token, updatedUser, addAlert);
const response = await fetch(`/api/users/${user.id}`, { if (result) {
method: "PUT", localStorage.setItem("user", JSON.stringify(result));
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify(updatedUser),
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
localStorage.setItem("user", JSON.stringify(data));
setEditMode(false); setEditMode(false);
addAlert('success', "Profil mis à jour avec succès."); addAlert('success', "Profil mis à jour avec succès.");
} catch (error) {
console.error("Error updating user:", error);
addAlert('error', error.message || "Erreur lors de la mise à jour du profil.");
} }
} }
@ -149,6 +80,11 @@ export default function Account() {
setAlerts(alerts.filter(alert => alert !== alertToRemove)); setAlerts(alerts.filter(alert => alert !== alertToRemove));
}; };
const closeModal = () => {
setIsModalOpen(false);
fetchUserChannel();
}
return ( return (
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
@ -290,7 +226,7 @@ export default function Account() {
<h2 className="font-montserrat font-bold text-3xl text-white mt-10" >Playlists</h2> <h2 className="font-montserrat font-bold text-3xl text-white mt-10" >Playlists</h2>
<div className="w-full mt-5 flex flex-wrap" > <div className="w-full mt-5 flex flex-wrap" >
{ {
userPlaylists.map((playlist, index) => ( userPlaylists && userPlaylists.map((playlist, index) => (
<PlaylistCard playlist={playlist} key={index} onClick={handlePlaylistClick} /> <PlaylistCard playlist={playlist} key={index} onClick={handlePlaylistClick} />
)) ))
} }
@ -299,7 +235,7 @@ export default function Account() {
<h2 className="font-montserrat font-bold text-3xl text-white mt-10" >Historique</h2> <h2 className="font-montserrat font-bold text-3xl text-white mt-10" >Historique</h2>
<div className="w-full mt-5 flex flex-wrap gap-2" > <div className="w-full mt-5 flex flex-wrap gap-2" >
{ {
userHistory.map((video, index) => ( userHistory && userHistory.map((video, index) => (
<div className="w-1/3" key={index}> <div className="w-1/3" key={index}>
<VideoCard video={video}/> <VideoCard video={video}/>
</div> </div>
@ -309,7 +245,7 @@ export default function Account() {
</div> </div>
</main> </main>
<CreateChannelModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} /> <CreateChannelModal isOpen={isModalOpen} onClose={() => closeModal()} addAlert={addAlert} />
</div> </div>
) )

82
frontend/src/pages/AddVideo.jsx

@ -1,6 +1,8 @@
import Navbar from "../components/Navbar.jsx"; import Navbar from "../components/Navbar.jsx";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import Tag from "../components/Tag.jsx"; import Tag from "../components/Tag.jsx";
import { getChannel } from "../services/user.service.js";
import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js";
export default function AddVideo() { export default function AddVideo() {
@ -23,21 +25,9 @@ export default function AddVideo() {
}, []) }, [])
const fetchChannel = async () => { const fetchChannel = async () => {
try { const fetchedChannel = await getChannel(user.id, token, addAlert);
const response = await fetch(`/api/users/${user.id}/channel`, { setChannel(fetchedChannel.channel);
headers: { console.log(fetchedChannel.channel);
"Authorization": `Bearer ${token}` // Assuming you have a token for authentication
},
});
if (!response.ok) {
throw new Error("Erreur lors de la récupération de la chaîne");
}
const data = await response.json();
setChannel(data.channel);
} catch (error) {
console.error("Erreur lors de la récupération de la chaîne :", error);
addAlert('error', 'Erreur lors de la récupération de la chaîne');
}
} }
const handleTagKeyDown = (e) => { const handleTagKeyDown = (e) => {
@ -58,6 +48,8 @@ export default function AddVideo() {
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
console.log(channel)
if (!videoTitle || !videoDescription || !videoThumbnail || !videoFile) { if (!videoTitle || !videoDescription || !videoThumbnail || !videoFile) {
addAlert('error', 'Veuillez remplir tous les champs requis.'); addAlert('error', 'Veuillez remplir tous les champs requis.');
return; return;
@ -74,32 +66,11 @@ export default function AddVideo() {
const formData = new FormData(); const formData = new FormData();
formData.append("title", videoTitle); formData.append("title", videoTitle);
formData.append("description", videoDescription); formData.append("description", videoDescription);
formData.append("channel", channel.id.toString()); // Ensure it's a string formData.append("channel", channel.id.toString());
formData.append("visibility", visibility); formData.append("visibility", visibility);
formData.append("file", videoFile); formData.append("file", videoFile);
const request = await fetch("/api/videos", { const request = await uploadVideo(formData, token, addAlert);
method: "POST",
headers: {
"Authorization": `Bearer ${token}` // Assuming you have a token for authentication
},
body: formData
});
if (!request.ok) {
const errorData = await request.json();
console.error("Backend validation errors:", errorData);
// Display specific validation errors if available
if (errorData.errors && errorData.errors.length > 0) {
const errorMessages = errorData.errors.map(error =>
`${error.path}: ${error.msg}`
).join('\n');
addAlert('error', `Erreurs de validation:\n${errorMessages}`);
} else {
addAlert('error', 'Erreurs inconnues');
}
return;
}
// If the video was successfully created, we can now upload the thumbnail // If the video was successfully created, we can now upload the thumbnail
const response = await request.json(); const response = await request.json();
@ -108,35 +79,14 @@ export default function AddVideo() {
thumbnailFormData.append("video", videoId); thumbnailFormData.append("video", videoId);
thumbnailFormData.append("file", videoThumbnail); thumbnailFormData.append("file", videoThumbnail);
thumbnailFormData.append("channel", channel.id.toString()); thumbnailFormData.append("channel", channel.id.toString());
const thumbnailRequest = await fetch("/api/videos/thumbnail", { await uploadThumbnail(thumbnailFormData, token, addAlert);
method: "POST",
headers: {
"Authorization": `Bearer ${token}` // Assuming you have a token for authentication
},
body: thumbnailFormData
});
if (!thumbnailRequest.ok) {
const errorData = await thumbnailRequest.json();
console.error("Backend validation errors:", errorData);
addAlert('error', 'Erreur lors de l\'envoie d la miniature');
return;
}
// if the thumbnail was successfully uploaded, we can send the tags // if the thumbnail was successfully uploaded, we can send the tags
const tagsRequest = await fetch(`/api/videos/${videoId}/tags`, { const body = {
method: "PUT", tags: videoTags,
headers: { channel: channel.id.toString()
"Content-Type": "application/json", };
"Authorization": `Bearer ${token}` // Assuming you have a token for authentication await uploadTags(body, videoId, token, addAlert);
},
body: JSON.stringify({ tags: videoTags, channel: channel.id.toString() }) // Ensure channel ID is a string
});
if (!tagsRequest.ok) {
const errorData = await tagsRequest.json();
console.error("Backend validation errors:", errorData);
addAlert('error', 'Erreur lors de l\'ajout des tags');
return;
}
// If everything is successful, redirect to the video management page // If everything is successful, redirect to the video management page
addAlert('success', 'Vidéo ajoutée avec succès !'); addAlert('success', 'Vidéo ajoutée avec succès !');

15
frontend/src/pages/Home.jsx

@ -5,6 +5,7 @@ import {useState, useEffect} from "react";
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import TopCreators from "../components/TopCreators.jsx"; import TopCreators from "../components/TopCreators.jsx";
import TrendingVideos from "../components/TrendingVideos.jsx"; import TrendingVideos from "../components/TrendingVideos.jsx";
import { getRecommendations, getTrendingVideos } from '../services/recommendation.service.js';
export default function Home() { export default function Home() {
const { isAuthenticated, user } = useAuth(); const { isAuthenticated, user } = useAuth();
@ -18,21 +19,13 @@ export default function Home() {
// Fetch recommendations, top creators, and trending videos // Fetch recommendations, top creators, and trending videos
const fetchData = async () => { const fetchData = async () => {
try { try {
const response = await fetch('/api/recommendations'); setRecommendations(await getRecommendations(addAlert));
const data = await response.json();
setRecommendations(data.recommendations);
} catch (error) {
console.error('Error fetching data:', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
try { try {
const trendingResponse = await fetch('/api/recommendations/trending'); setTrendingVideos(await getTrendingVideos(addAlert));
const trendingData = await trendingResponse.json();
setTrendingVideos(trendingData);
} catch (error) {
addAlert('error', 'Erreur lors du chargement des vidéos tendance');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -42,7 +35,7 @@ export default function Home() {
}, []); }, []);
const addAlert = (type, message) => { const addAlert = (type, message) => {
const newAlert = { type, message, id: Date.now() }; // Add unique ID const newAlert = { type, message, id: Date.now() };
setAlerts([...alerts, newAlert]); setAlerts([...alerts, newAlert]);
}; };

65
frontend/src/pages/ManageChannel.jsx

@ -3,6 +3,7 @@ import {useEffect, useState} from "react";
import {useNavigate, useParams} from "react-router-dom"; import {useNavigate, useParams} from "react-router-dom";
import {useAuth} from "../contexts/AuthContext.jsx"; import {useAuth} from "../contexts/AuthContext.jsx";
import VideoStatListElement from "../components/VideoStatListElement.jsx"; import VideoStatListElement from "../components/VideoStatListElement.jsx";
import {fetchChannelDetails, fetchChannelStats, updateChannel} from "../services/channel.service.js";
export default function ManageChannel() { export default function ManageChannel() {
@ -27,69 +28,31 @@ export default function ManageChannel() {
useEffect(() => { useEffect(() => {
fetchChannelData() fetchChannelData()
fetchChannelStats() fetchStats()
}, []); }, []);
const fetchChannelData = async () => { const fetchChannelData = async () => {
try { setChannel(await fetchChannelDetails(id, addAlert));
const request = await fetch(`/api/channels/${id}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
})
const result = await request.json();
setChannel(result);
} catch (error) {
console.error("Error fetching channel data:", error);
}
} }
const fetchChannelStats = async () => { const fetchStats = async () => {
try { setChannelStats(await fetchChannelStats(id, token, addAlert));
const request = await fetch(`/api/channels/${id}/stats`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
})
const result = await request.json();
setChannelStats(result);
} catch (error) {
console.error("Error fetching channel stats", error);
}
} }
const handleUpdateChannel = async () => { const handleUpdateChannel = async () => {
if (!editMode) return; if (!editMode) return;
try { const data = {
const response = await fetch(`/api/channels/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({
name: channelName || channel.name, name: channelName || channel.name,
description: description || channel.description, description: description || channel.description,
}) };
});
const response = await updateChannel(id, data, token, addAlert);
if (response.ok) { if (response) {
setEditMode(false); setEditMode(false);
addAlert('success', 'Chaîne mise à jour avec succès');
fetchChannelData(); // Refresh channel data after update
} else {
console.error("Failed to update channel");
const errorData = await response.json();
addAlert('error', errorData.message || 'Erreur lors de la mise à jour de la chaîne');
}
} catch (error) {
console.error("Error updating channel:", error);
addAlert('error', 'Erreur lors de la mise à jour de la chaîne');
} }
} }
const onCloseAlert = (alertToRemove) => { const onCloseAlert = (alertToRemove) => {
@ -105,10 +68,10 @@ export default function ManageChannel() {
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> <div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient">
<Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} /> <Navbar isSearchPage={false} alerts={alerts} onCloseAlert={onCloseAlert} />
<main className="pt-[118px] px-36 flex"> <main className="pt-[118px] px-36 flex pb-10">
{/* LEFT SIDE */} {/* LEFT SIDE */}
<form className="glassmorphism w-1/3 h-screen py-10 px-4"> <form className="glassmorphism w-1/3 py-10 px-4 h-max">
<img src={user.picture} className="w-1/3 aspect-square object-cover rounded-full mx-auto" alt=""/> <img src={user.picture} className="w-1/3 aspect-square object-cover rounded-full mx-auto" alt=""/>
<label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat ${editMode ? "block" : "hidden"} `}> <label htmlFor="name" className={`text-2xl text-white mb-1 block font-montserrat ${editMode ? "block" : "hidden"} `}>
Nom de chaine Nom de chaine
@ -168,7 +131,7 @@ export default function ManageChannel() {
</form> </form>
{/* RIGHT SIDE */} {/* RIGHT SIDE */}
<div className="w-2/3 h-screen pl-10" > <div className="w-2/3 pl-10" >
{/* VIEW / SUBSCRIBERS STATS */} {/* VIEW / SUBSCRIBERS STATS */}
<div className="flex gap-4" > <div className="flex gap-4" >

105
frontend/src/pages/ManageVideo.jsx

@ -5,6 +5,7 @@ import {useEffect, useState} from "react";
import LinearGraph from "../components/LinearGraph.jsx"; import LinearGraph from "../components/LinearGraph.jsx";
import Comment from "../components/Comment.jsx"; import Comment from "../components/Comment.jsx";
import Tag from "../components/Tag.jsx"; import Tag from "../components/Tag.jsx";
import {getVideoById, getLikesPerDay, updateVideo, updateVideoFile, uploadThumbnail, uploadTags} from "../services/video.service.js";
export default function ManageVideo() { export default function ManageVideo() {
@ -31,34 +32,16 @@ export default function ManageVideo() {
const editModeClassesTextArea = nonEditModeClassesTextArea + " glassmorphism h-48"; const editModeClassesTextArea = nonEditModeClassesTextArea + " glassmorphism h-48";
const fetchVideo = async () => { const fetchVideo = async () => {
const request = await fetch(`/api/videos/${id}`, { const data = await getVideoById(id, addAlert);
method: 'GET', if (!data) return;
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!request.ok) {
addAlert('error', 'Erreur lors de la récupération de la vidéo');
}
const data = await request.json();
setVideo(data); setVideo(data);
setVisibility(data.visibility) setVisibility(data.visibility)
setVideoTitle(data.title); setVideoTitle(data.title);
setDescription(data.description); setDescription(data.description);
} }
const fetchLikesPerDay = async () => { const fetchLikesPerDay = async () => {
const request = await fetch(`/api/videos/${id}/likes/day`, { const data = await getLikesPerDay(id, token, addAlert);
method: 'GET', if (!data) return;
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!request.ok) {
addAlert('error', 'Erreur lors de la récupération des likes par jour');
}
const data = await request.json();
setLikesPerDay(data.likes); setLikesPerDay(data.likes);
setViewsPerDay(data.views); setViewsPerDay(data.views);
} }
@ -74,24 +57,14 @@ export default function ManageVideo() {
e.preventDefault(); e.preventDefault();
if (!editMode) return; if (!editMode) return;
const request = await fetch(`/api/videos/${id}`, { const body = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
title: videoTitle, title: videoTitle,
description: description, description: description,
visibility: visibility, visibility: visibility,
channel: video.channel channel: video.channel
}) };
});
if (!request.ok) { const request = await updateVideo(id, body, token, addAlert);
addAlert('error', 'Erreur lors de la mise à jour des détails de la vidéo');
return;
}
const form = new FormData(); const form = new FormData();
if (videoFile) { if (videoFile) {
@ -99,18 +72,7 @@ export default function ManageVideo() {
form.append('video', id); form.append('video', id);
form.append('channel', video.channel); form.append('channel', video.channel);
const videoRequest = await fetch(`/api/videos/${id}/video`, { await updateVideoFile(id, form, token, addAlert);
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`
},
body: form
});
if (!videoRequest.ok) {
addAlert('error', 'Erreur lors de la mise à jour de la vidéo');
return;
}
} }
const data = await request.json(); const data = await request.json();
@ -134,21 +96,7 @@ export default function ManageVideo() {
formData.append('video', id); formData.append('video', id);
formData.append('channel', video.channel); formData.append('channel', video.channel);
const request = await uploadThumbnail(formData, token, addAlert);
const request = await fetch(`/api/videos/thumbnail`, {
"method": 'POST',
"headers": {
"Authorization": `Bearer ${token}`
},
body: formData
})
if (!request.ok) {
addAlert('error', 'Erreur lors de l\'envoi de la miniature');
return;
}
const data = await request.json();
addAlert('success', 'Miniature mise à jour avec succès');
}; };
const onAddTag = async (e) => { const onAddTag = async (e) => {
@ -156,23 +104,12 @@ export default function ManageVideo() {
const newTag = e.target.value.trim(); const newTag = e.target.value.trim();
e.target.value = ""; e.target.value = "";
const body = {
const request = await fetch(`/api/videos/${id}/tags`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
tags: [...video.tags, newTag], tags: [...video.tags, newTag],
channel: video.channel channel: video.channel
})
});
if (!request.ok) {
addAlert('error', 'Erreur lors de l\'ajout du tag');
return;
} }
const data = await request.json();
const request = await uploadTags(body, id, token, addAlert);
setVideo({ setVideo({
...video, ...video,
tags: [...video.tags, newTag] tags: [...video.tags, newTag]
@ -181,22 +118,12 @@ export default function ManageVideo() {
const onSuppressTag = async (tag) => { const onSuppressTag = async (tag) => {
const request = await fetch(`/api/videos/${id}/tags`, { const body = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
tags: video.tags.filter(t => t !== tag), tags: video.tags.filter(t => t !== tag),
channel: video.channel channel: video.channel
})
});
if (!request.ok) {
addAlert('error', 'Erreur lors de la suppression du tag');
return;
} }
const data = await request.json();
const request = await uploadTags(body, id, token, addAlert);
const newTags = video.tags.filter(t => t !== tag); const newTags = video.tags.filter(t => t !== tag);
setVideo({ setVideo({
...video, ...video,

149
frontend/src/pages/Video.jsx

@ -5,10 +5,12 @@ import { useAuth } from "../contexts/AuthContext.jsx";
import Comment from "../components/Comment.jsx"; import Comment from "../components/Comment.jsx";
import VideoCard from "../components/VideoCard.jsx"; import VideoCard from "../components/VideoCard.jsx";
import Tag from "../components/Tag.jsx"; import Tag from "../components/Tag.jsx";
import {addView, getSimilarVideos, getVideoById, toggleLike} from "../services/video.service.js";
import {subscribe} from "../services/channel.service.js";
import {addComment} from "../services/comment.service.js";
export default function Video() { export default function Video() {
// This component can be used to display a video player or video-related content.
const {id} = useParams(); const {id} = useParams();
const { user, isAuthenticated } = useAuth(); const { user, isAuthenticated } = useAuth();
const videoRef = useRef(null); const videoRef = useRef(null);
@ -26,61 +28,24 @@ export default function Video() {
const fetchVideo = useCallback(async () => { const fetchVideo = useCallback(async () => {
// Fetch video data and similar videos based on the video ID from the URL // Fetch video data and similar videos based on the video ID from the URL
try { if (!id) {
const response = await fetch(`/api/videos/${id}`); addAlert('error', 'Vidéo introuvable');
if (!response.ok) { navigation('/');
throw new Error('Network response was not ok'); return;
}
const videoData = await response.json();
setVideo(videoData);
} catch (error) {
addAlert('error', 'Erreur lors de la récupération de la vidéo');
}
try {
const response = await fetch(`/api/videos/${id}/similar`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const similarVideosData = await response.json();
setSimilarVideos(similarVideosData);
} catch (error) {
addAlert('error', 'Erreur lors de la récupération des vidéos similaires');
} }
// Add views to the video const data = await getVideoById(id, addAlert);
try { if (!data) return;
const token = localStorage.getItem('token'); setVideo(data);
if (token) {
await fetch(`/api/videos/${id}/views`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
}
} catch (error) { const similarVideosResponse = await getSimilarVideos(id, addAlert);
addAlert('error', 'Erreur lors de l\'ajout des vues à la vidéo'); if (similarVideosResponse) {
setSimilarVideos(similarVideosResponse);
} }
}, [id, navigation]);
const fetchComments = useCallback(async () => { // Add views to the video
// Fetch comments for the video await addView(id, addAlert);
try { }, [id, navigation]);
const response = await fetch(`/api/comments/video/${id}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const commentsData = await response.json();
setVideo((prevVideo) => ({
...prevVideo,
comments: commentsData
}));
} catch (error) {
addAlert('error', 'Erreur lors de la récupération des commentaires');
}
}, [id]);
useEffect(() => { useEffect(() => {
fetchVideo(); fetchVideo();
@ -171,34 +136,11 @@ export default function Video() {
return; return;
} }
try { const response = await subscribe(video.creator.id, addAlert);
// Retrieve the token from localStorage
const token = localStorage.getItem('token');
if (!token) { console.log('Subscription successful:', response);
navigation('/login');
return;
}
const response = await fetch(`/api/channels/${video.creator.id}/subscribe`, { const subscriptionCount = response.subscriptions || 0;
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
userId: user.id
})
});
if (!response.ok) {
throw new Error('Failed to subscribe');
}
const data = await response.json();
console.log('Subscription successful:', data);
const subscriptionCount = data.subscriptions || 0;
setVideo((prevVideo) => { setVideo((prevVideo) => {
return { return {
...prevVideo, ...prevVideo,
@ -208,10 +150,6 @@ export default function Video() {
} }
}; };
}) })
} catch (error) {
addAlert('error', 'Erreur lors de l\'abonnement');
}
}; };
const handleLike = async () => { const handleLike = async () => {
@ -220,7 +158,6 @@ export default function Video() {
return; return;
} }
try {
// Retrieve the token from localStorage // Retrieve the token from localStorage
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
@ -229,19 +166,7 @@ export default function Video() {
return; return;
} }
const response = await fetch(`/api/videos/${id}/like`, { const data = await toggleLike(id, token, addAlert);
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to like video');
}
const data = await response.json();
setVideo((prevVideo) => { setVideo((prevVideo) => {
return { return {
@ -249,10 +174,6 @@ export default function Video() {
likes: data.likes || prevVideo.likes + 1 // Update likes count likes: data.likes || prevVideo.likes + 1 // Update likes count
}; };
}) })
} catch (error) {
addAlert('error', 'Erreur lors de l\'ajout du like');
}
}; };
const handleComment = async () => { const handleComment = async () => {
@ -265,8 +186,6 @@ export default function Video() {
alert("Comment cannot be empty"); alert("Comment cannot be empty");
return; return;
} }
try {
// Retrieve the token from localStorage // Retrieve the token from localStorage
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
@ -275,35 +194,13 @@ export default function Video() {
return; return;
} }
const response = await fetch(`/api/comments/`, { const data = await addComment(video.id, comment, token, addAlert);
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
content: comment,
video: id
})
});
if (!response.ok) {
throw new Error('Failed to post comment');
}
const data = await response.json();
console.log('Comment posted successfully:', data);
setComment(""); // Clear the comment input setComment(""); // Clear the comment input
setVideo((prevVideo) => ({ setVideo((prevVideo) => ({
...prevVideo, ...prevVideo,
comments: [...(prevVideo.comments || []), data] // Add the new comment to the existing comments comments: [...(prevVideo.comments || []), data]
})); }));
} catch (error) {
addAlert('error', 'Erreur lors de la publication du commentaire');
}
} }
const addAlert = (type, message) => { const addAlert = (type, message) => {
@ -432,7 +329,7 @@ export default function Video() {
<div className="mt-4"> <div className="mt-4">
{video.comments && video.comments.length > 0 ? ( {video.comments && video.comments.length > 0 ? (
video.comments.map((comment, index) => ( video.comments.map((comment, index) => (
<Comment comment={comment} index={index} videoId={id} key={index} refetchVideo={fetchVideo} /> <Comment comment={comment} index={index} videoId={id} key={index} refetchVideo={fetchVideo} addAlert={addAlert} />
)) ))
) : ( ) : (
<p className="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à en publier !</p> <p className="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à en publier !</p>

61
frontend/src/services/channel.service.js

@ -12,6 +12,47 @@ export function fetchChannelDetails(channelId, addAlert) {
}); });
} }
export async function fetchChannelStats(channelId, token, addAlert) {
try {
const request = await fetch(`/api/channels/${channelId}/stats`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
})
const result = await request.json();
return result;
} catch (error) {
console.error("Error fetching channel stats", error);
addAlert('error', "Erreur lors de la récupération des statistiques de la chaîne");
}
}
export async function updateChannel(channelId, data, token, addAlert) {
try {
const response = await fetch(`/api/channels/${channelId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify(data)
});
if (response.ok) {
addAlert('success', 'Chaîne mise à jour avec succès');
return response.json();
} else {
console.error("Failed to update channel");
const errorData = await response.json();
addAlert('error', errorData.message || 'Erreur lors de la mise à jour de la chaîne');
}
} catch (error) {
console.error("Error updating channel:", error);
addAlert('error', 'Erreur lors de la mise à jour de la chaîne');
}
}
export async function subscribe(channelId, addAlert) { export async function subscribe(channelId, addAlert) {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const user = JSON.parse(localStorage.getItem('user')); const user = JSON.parse(localStorage.getItem('user'));
@ -37,3 +78,23 @@ export async function subscribe(channelId, addAlert) {
throw error; throw error;
}); });
} }
export async function createChannel(body, token, addAlert) {
const request = await fetch(`/api/channels/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(body)
})
if (!request.ok) {
console.error("Not able to create channel");
return; // Prevent further execution if the request failed
}
addAlert('success', 'Chaîne créée avec succès');
const data = await request.json();
return data;
}

67
frontend/src/services/comment.service.js

@ -0,0 +1,67 @@
export async function getCommentByVideo(id, addAlert) {
try {
const response = await fetch(`/api/comments/video/${id}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const commentsData = await response.json();
return commentsData;
} catch (error) {
addAlert('error', 'Erreur lors de la récupération des commentaires');
}
}
export async function addComment(videoId, commentData, token, addAlert) {
try {
const response = await fetch(`/api/comments/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
content: commentData,
video: videoId
})
});
if (!response.ok) {
throw new Error('Failed to post comment');
}
const data = await response.json();
return data;
} catch (error) {
addAlert('error', 'Erreur lors de l\'ajout du commentaire');
}
}
export async function updateComment(id, body, token, addAlert) {
const response = await fetch(`/api/comments/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(body)
});
if (!response.ok) {
addAlert('error', 'Erreur lors de la mise à jour du commentaire');
}
}
export async function deleteComment(id, token, addAlert) {
const response = await fetch(`/api/comments/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
addAlert('error', 'Erreur lors de la suppression du commentaire');
}
return response;
}

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

@ -0,0 +1,23 @@
export async function getRecommendations(addAlert) {
try {
const response = await fetch('/api/recommendations');
const data = await response.json();
return data.recommendations;
} catch (error) {
console.error('Error fetching data:', error);
addAlert('error', 'Erreur lors du chargement des recommandations');
}
}
export async function getTrendingVideos(addAlert) {
try {
const response = await fetch('/api/recommendations/trending');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
addAlert('error', 'Erreur lors du chargement des vidéos tendance');
}
}

87
frontend/src/services/user.service.js

@ -14,3 +14,90 @@ export async function isSubscribed(channelId, addAlert) {
console.log("Subscription status for channel ID", channelId, ":", result); console.log("Subscription status for channel ID", channelId, ":", result);
return result.subscribed; return result.subscribed;
} }
export async function getChannel(userId, token, addAlert) {
try {
const response = await fetch(`/api/users/${userId}/channel`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user data");
}
const data = await response.json();
return data
} catch (error) {
console.error("Error fetching user channel:", error);
addAlert('error', "Erreur lors de la récupération des données de l'utilisateur.");
return null;
}
}
export async function getUserHistory(userId, token, addAlert) {
if (!userId || !token) {
console.warn("User ID or token missing, skipping history fetch");
return;
}
try {
const response = await fetch(`/api/users/${userId}/history`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user history");
}
const data = await response.json();
return data
} catch (error) {
console.error("Error fetching user history:", error);
addAlert('error', "Erreur lors de la récupération de l'historique de l'utilisateur.");
}
}
export async function getPlaylists(userId, token, addAlert) {
if (!userId || !token) {
console.warn("User ID or token missing, skipping playlists fetch");
return;
}
try {
const response = await fetch(`/api/playlists/user/${userId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
});
if (!response.ok) {
throw new Error("Failed to fetch user playlists");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching user playlists:", error);
addAlert('error', "Erreur lors de la récupération des playlists de l'utilisateur.");
}
}
export async function updateUser(userId, token, userData, addAlert) {
try {
const response = await fetch(`/api/users/${userId}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error("Failed to update user");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error updating user:", error);
addAlert('error', "Erreur lors de la mise à jour des données de l'utilisateur.");
}
}

212
frontend/src/services/video.service.js

@ -0,0 +1,212 @@
export async function uploadVideo(formData, token, addAlert) {
const request = await fetch("/api/videos", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}` // Assuming you have a token for authentication
},
body: formData
});
if (!request.ok) {
const errorData = await request.json();
console.error("Backend validation errors:", errorData);
// Display specific validation errors if available
if (errorData.errors && errorData.errors.length > 0) {
const errorMessages = errorData.errors.map(error =>
`${error.path}: ${error.msg}`
).join('\n');
addAlert('error', `Erreurs de validation:\n${errorMessages}`);
} else {
addAlert('error', 'Erreurs inconnues');
}
return;
}
return request;
}
export async function uploadThumbnail(formData, token, addAlert) {
try {
const thumbnailRequest = await fetch("/api/videos/thumbnail", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`
},
body: formData
});
if (!thumbnailRequest.ok) {
const errorData = await thumbnailRequest.json();
console.error("Backend validation errors:", errorData);
addAlert('error', 'Erreur lors de l\'envoie d la miniature');
return;
}
addAlert('success', 'Miniature envoyée avec succès');
return thumbnailRequest;
} catch (error) {
console.error("Error uploading thumbnail:", error);
addAlert('error', 'Erreur lors de l\'envoie de la miniature');
}
}
export async function uploadTags(body, videoId, token, addAlert) {
const tagsRequest = await fetch(`/api/videos/${videoId}/tags`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}` // Assuming you have a token for authentication
},
body: JSON.stringify(body) // Ensure channel ID is a string
});
addAlert('success', 'Tags mis à jour avec succès');
if (!tagsRequest.ok) {
const errorData = await tagsRequest.json();
console.error("Backend validation errors:", errorData);
addAlert('error', 'Erreur lors de l\'ajout des tags');
return;
}
}
export async function getVideoById(id, addAlert) {
try {
const request = await fetch(`/api/videos/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!request.ok) {
addAlert('error', 'Erreur lors de la récupération de la vidéo');
}
const data = await request.json();
return data;
} catch (error) {
console.error("Error fetching video:", error);
addAlert('error', 'Erreur lors de la récupération de la vidéo');
}
}
export async function getLikesPerDay(id, token, addAlert) {
try {
const request = await fetch(`/api/videos/${id}/likes/day`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!request.ok) {
addAlert('error', 'Erreur lors de la récupération des likes par jour');
}
const data = await request.json();
return data;
} catch (error) {
console.error("Error fetching likes per day:", error);
addAlert('error', 'Erreur lors de la récupération des likes par jour');
}
}
export async function updateVideo(id, data, token, addAlert) {
try {
const request = await fetch(`/api/videos/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
if (!request.ok) {
const errorData = await request.json();
console.error("Backend validation errors:", errorData);
addAlert('error', 'Erreur lors de la mise à jour de la vidéo');
return;
}
return request;
} catch (error) {
console.error("Error updating video:", error);
addAlert('error', 'Erreur lors de la mise à jour de la vidéo');
}
}
export async function updateVideoFile(id, formData, token, addAlert) {
try {
const videoRequest = await fetch(`/api/videos/${id}/video`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
if (!videoRequest.ok) {
addAlert('error', 'Erreur lors de la mise à jour de la vidéo');
return;
}
return videoRequest.json();
} catch (error) {
console.error("Error updating video file:", error);
addAlert('error', 'Erreur lors de la mise à jour de la vidéo');
}
}
export async function getSimilarVideos(id, addAlert) {
try {
const request = await fetch(`/api/videos/${id}/similar`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!request.ok) {
addAlert('error', 'Erreur lors de la récupération des vidéos similaires');
return [];
}
const data = await request.json();
return data;
} catch (error) {
console.error("Error fetching similar videos:", error);
addAlert('error', 'Erreur lors de la récupération des vidéos similaires');
}
}
export async function addView(id, addAlert) {
try {
const token = localStorage.getItem('token');
if (token) {
await fetch(`/api/videos/${id}/views`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
}
} catch (error) {
addAlert('error', 'Erreur lors de l\'ajout des vues à la vidéo');
}
}
export async function toggleLike(id, token, addAlert) {
try {
const response = await fetch(`/api/videos/${id}/like`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to like video');
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error toggling like:", error);
addAlert('error', 'Erreur lors du changement de like');
}
}
Loading…
Cancel
Save