From 6b1c3227fbd19111693017dbf24df867d4eb6725 Mon Sep 17 00:00:00 2001 From: astria Date: Sun, 31 Aug 2025 12:16:09 +0200 Subject: [PATCH 1/8] Added branch features/loading --- documentation.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 documentation.md diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000..3796d59 --- /dev/null +++ b/documentation.md @@ -0,0 +1,49 @@ +# 3RESIT Freetube + +## Sommaire + +1) Introduction au projet +2) Organisation +2) Les technologies utilisées + 1) Le serveur + 2) Le site web + 3) La base de données +3) Le serveur + 1) Les dépendances + 2) Le fonctionnement +4) Le site web + 1) Les dépendances + 2) Le fonctionnement +5) La base de données + 1) Diagramme des tables +6) Installation + 1) Docker Compose + 2) Script Shell + 3) Manuelle + +## Introduction + +Pour ce projet il nous a été demandé de recréer une plateforme similaire a Youtube nommée Freetube. Cette application web devait être gratuite et sans publicités. Nous avions la main libre sur le choix de la pile technologique utilisée et sur l'organisation du projet. + +## Organisation + +J'ai commencé par faire un plan détaillés de toute les routes et vérifications a effectué pour chaque fonctionnalité, puis faire un plan de la base de données. Ceci allait définir toute la structure du projet. + +Pour ce qui est du développement, j'ai choisis de procéder fonctionnalité par fonctionnalité en commençant par le serveur et la base de données pour les intégrer ensuite dans le site web. Pour séparer toute ces parties j'ai utilisé **git** en créant plusieurs branches, une par fonctionnalité. + +## Les technologies utilisées + +### Le serveur +Le serveur utilise **NodeJS**, j'ai choisis ce langage car il permet d'implementer une **API REST** efficacement grâce a son système d'asynchronisation natif très performant. Etant créer a partir du **Javascript** il est aussi plus simple a comprendre et a écrire. Même si NodeJS n'est pas le plus connu en terme de rapidité d'éxecution ce n'est pas un problème dans notre situation car nous travaillons avec une **API** qui ajoutera un temps de latence supplémentaire. + +### Le site web +Le site web est programmé en **ReactJS** avec **Vite** ce qui donne un **backend** et un **frontend** dans le même langage ce qui permet une maintenance et des mises à jour plus simple. Tout comme NodeJS, ReactJS et créé a partir de Javascript, il bénéficie donc de la même intégration de l'asynchrone natif. Le système de **composant** de ReactJS permet aussi une gestion de mise à jour de l'interface en temps réel plus simple a mettre en place et évite la duplication de code. + +### La base données +La base de données et une base **PostgreSQL**, un système basé sur le langage SQL largement connu. PostgreSQL est une alternative **OpenSource** à **MySQL**. Il possède une très bonne intégration du **JSON** très utilisé comme moyen d'envoyer des données via **requête HTTP** utilisé dans les API REST. + +## Le serveur + +### Les dépendances + +Pour l'API REST j'ai choisis d'utiliser **ExpressJS** couplé avec **express-validator** \ No newline at end of file From 3ec09e404bfe94bc57713c26da03bd80818e5740 Mon Sep 17 00:00:00 2001 From: astria Date: Sun, 31 Aug 2025 12:30:35 +0200 Subject: [PATCH 2/8] Added upload video loading --- backend/logs/access.log | 59 +++++++++++++++++++++++ frontend/src/modals/LoadingVideoModal.jsx | 17 +++++++ frontend/src/pages/AddVideo.jsx | 14 +++++- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 frontend/src/modals/LoadingVideoModal.jsx diff --git a/backend/logs/access.log b/backend/logs/access.log index 3fcfa30..552e604 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -11402,3 +11402,62 @@ [2025-08-27 14:02:56.751] [undefined] GET(/:id/history): successfully retrieved history of user 4 with status 200 [2025-08-27 14:02:56.755] [undefined] GET(/:id/channel): successfully retrieved channel of user 4 with status 200 [2025-08-27 14:02:56.761] [undefined] GET(/user/:id): Playlists retrieved for user with id 4 with status 200 +[2025-08-31 10:18:51.355] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-08-31 10:18:51.361] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:18:51.367] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:18:51.373] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-08-31 10:18:51.384] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-08-31 10:18:55.307] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-31 10:18:55.354] [undefined] GET(/:id/stats): try to get stats +[2025-08-31 10:18:55.360] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-31 10:18:55.371] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-31 10:19:10.594] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:19:10.599] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:22:07.050] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:22:07.056] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:22:30.580] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:22:30.586] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:22:48.827] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:22:48.834] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:22:56.007] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:22:56.012] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:23:10.939] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:23:10.945] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:23:11.668] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:23:11.673] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:23:47.510] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:23:47.516] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:23:53.769] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:23:53.775] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:25:15.205] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:25:15.210] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:25:35.308] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:25:35.313] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:25:52.991] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:25:52.997] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:26:48.298] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:26:48.304] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:26:55.482] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:26:55.488] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:27:21.463] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:27:21.469] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:28:55.696] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:28:55.701] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:29:12.334] [undefined] POST(/): try to upload video with status undefined +[2025-08-31 10:29:12.342] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-31 10:29:12.457] [undefined] POST(/thumbnail): try to add thumbnail to video 4 +[2025-08-31 10:29:12.464] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-31 10:29:12.492] [undefined] PUT(/:id/tags): try to add tags to video 4 +[2025-08-31 10:29:12.505] [undefined] PUT(/:id/tags): successfully added tags to video 4 with status 200 +[2025-08-31 10:29:45.158] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-08-31 10:29:45.164] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-08-31 10:29:58.657] [undefined] POST(/): try to upload video with status undefined +[2025-08-31 10:29:58.663] [undefined] POST(/): successfully uploaded video with status 200 +[2025-08-31 10:29:58.774] [undefined] POST(/thumbnail): try to add thumbnail to video 5 +[2025-08-31 10:29:58.780] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-08-31 10:29:58.805] [undefined] PUT(/:id/tags): try to add tags to video 5 +[2025-08-31 10:29:58.820] [undefined] PUT(/:id/tags): successfully added tags to video 5 with status 200 +[2025-08-31 10:29:58.856] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-31 10:29:58.869] [undefined] GET(/:id/stats): try to get stats +[2025-08-31 10:29:58.875] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-31 10:29:58.885] [undefined] GET(/:id/stats): Successfully get stats with status 200 diff --git a/frontend/src/modals/LoadingVideoModal.jsx b/frontend/src/modals/LoadingVideoModal.jsx new file mode 100644 index 0000000..3416584 --- /dev/null +++ b/frontend/src/modals/LoadingVideoModal.jsx @@ -0,0 +1,17 @@ + + +export default function LoadingVideoModal({state, message}) { + return state === "loading" && ( +
+
+ {/* Spinner */} +
+
+
+
+

{message}

+
+
+ ); + +} \ No newline at end of file diff --git a/frontend/src/pages/AddVideo.jsx b/frontend/src/pages/AddVideo.jsx index 9802935..511d06e 100644 --- a/frontend/src/pages/AddVideo.jsx +++ b/frontend/src/pages/AddVideo.jsx @@ -4,13 +4,15 @@ import Tag from "../components/Tag.jsx"; import { getChannel, searchByUsername } from "../services/user.service.js"; import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js"; import UserCard from "../components/UserCard.jsx"; - +import LoadingVideoModal from "../modals/LoadingVideoModal.jsx"; +import { useNavigate } from "react-router-dom"; export default function AddVideo() { const storedUser = localStorage.getItem("user"); const user = storedUser ? JSON.parse(storedUser) : null; const token = localStorage.getItem("token"); + const navigation = useNavigate(); const [videoTitle, setVideoTitle] = useState(""); const [videoDescription, setVideoDescription] = useState(""); @@ -23,6 +25,8 @@ export default function AddVideo() { const [authorizedUsers, setAuthorizedUsers] = useState([]); const [searchResults, setSearchResults] = useState([]); const [alerts, setAlerts] = useState([]); + const [loadingState, setLoadingState] = useState("none"); + const [loadingMessage, setLoadingMessage] = useState(""); useEffect(() => { fetchChannel(); @@ -53,6 +57,8 @@ export default function AddVideo() { e.preventDefault(); console.log(channel) + setLoadingState("loading"); + setLoadingMessage("Envoie de la vidéo..."); if (!videoTitle || !videoDescription || !videoThumbnail || !videoFile) { addAlert('error', 'Veuillez remplir tous les champs requis.'); @@ -77,6 +83,8 @@ export default function AddVideo() { const request = await uploadVideo(formData, token, addAlert); + setLoadingMessage("Envoie de la miniature..."); + // If the video was successfully created, we can now upload the thumbnail const response = await request.json(); const videoId = response.id; @@ -86,6 +94,7 @@ export default function AddVideo() { thumbnailFormData.append("channel", channel.id.toString()); await uploadThumbnail(thumbnailFormData, token, addAlert); + setLoadingMessage("Envoie des tags..."); // if the thumbnail was successfully uploaded, we can send the tags const body = { tags: videoTags, @@ -93,6 +102,7 @@ export default function AddVideo() { }; await uploadTags(body, videoId, token, addAlert); // If everything is successful, redirect to the video management page + navigation("/manage-channel/" + channel.id); addAlert('success', 'Vidéo ajoutée avec succès !'); @@ -345,7 +355,7 @@ export default function AddVideo() { - + ); From 298d49aecb44fc5e564c43f138696068f7841e3e Mon Sep 17 00:00:00 2001 From: astria Date: Wed, 3 Sep 2025 19:36:17 +0200 Subject: [PATCH 3/8] Fix See Later artefacts --- .../app/controllers/playlist.controller.js | 47 ++++++++++--------- backend/logs/access.log | 23 +++++++++ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/backend/app/controllers/playlist.controller.js b/backend/app/controllers/playlist.controller.js index ace7ba7..c85186a 100644 --- a/backend/app/controllers/playlist.controller.js +++ b/backend/app/controllers/playlist.controller.js @@ -238,26 +238,29 @@ export async function getSeeLater(req, res) { const client = await getClient(); const query = ` SELECT - JSON_AGG( - json_build_object( - 'video_id', videos.id, - 'title', videos.title, - 'thumbnail', videos.thumbnail, - 'video_decscription', videos.description, - 'channel', videos.channel, - 'visibility', videos.visibility, - 'file', videos.file, - 'format', videos.format, - 'release_date', videos.release_date, - 'channel_id', channels.id, - 'owner', channels.owner, - 'views', COALESCE(video_views.view_count, 0), - 'creator', json_build_object( - 'name', channels.name, - 'profilePicture', users.picture, - 'description', channels.description + COALESCE( + JSON_AGG( + json_build_object( + 'video_id', videos.id, + 'title', videos.title, + 'thumbnail', videos.thumbnail, + 'video_decscription', videos.description, + 'channel', videos.channel, + 'visibility', videos.visibility, + 'file', videos.file, + 'format', videos.format, + 'release_date', videos.release_date, + 'channel_id', channels.id, + 'owner', channels.owner, + 'views', COALESCE(video_views.view_count, 0), + 'creator', json_build_object( + 'name', channels.name, + 'profilePicture', users.picture, + 'description', channels.description + ) ) - ) + ) FILTER (WHERE videos.id IS NOT NULL), + '[]'::json ) AS videos FROM public.playlists @@ -285,14 +288,14 @@ export async function getSeeLater(req, res) { try { const result = await client.query(query, [userId]); if (result.rows.length === 0) { - logger.write("No 'See Later' playlist found for user with id " + userId, 404); + logger.write("No 'See Later' playlist found for user with id " + userId, 200); client.release(); - res.status(404).json({ error: "'See Later' playlist not found" }); + res.status(200).json([]); return; } logger.write("'See Later' playlist retrieved for user with id " + userId, 200); client.release(); - res.status(200).json(result.rows[0].videos); + res.status(200).json(result.rows[0].videos || []); } catch (error) { logger.write("Error retrieving 'See Later' playlist: " + error.message, 500); client.release(); diff --git a/backend/logs/access.log b/backend/logs/access.log index 552e604..fd02723 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -11461,3 +11461,26 @@ [2025-08-31 10:29:58.869] [undefined] GET(/:id/stats): try to get stats [2025-08-31 10:29:58.875] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 [2025-08-31 10:29:58.885] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-31 10:30:58.389] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:34:12.979] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:34:15.647] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-09-03 17:34:15.660] [undefined] GET(/:id/similar): failed due to invalid values with status 400 +[2025-09-03 17:34:15.671] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-03 17:34:15.736] [undefined] GET(/:id/views): failed due to invalid values with status 400 +[2025-09-03 17:34:24.176] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:34:26.470] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-09-03 17:34:26.478] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-03 17:34:26.485] [undefined] GET(/:id/similar): failed due to invalid values with status 400 +[2025-09-03 17:34:26.494] [undefined] GET(/:id/views): failed due to invalid values with status 400 +[2025-09-03 17:34:26.941] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:34:29.468] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-09-03 17:34:29.523] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-09-03 17:34:29.535] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-09-03 17:34:29.541] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-09-03 17:34:29.550] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-03 17:34:30.404] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200 +[2025-09-03 17:34:31.607] [undefined] DELETE(/:id/video/:videoId): Video deleted from playlist with id 1 with status 200 +[2025-09-03 17:34:31.690] [undefined] GET(/:id): Playlist retrieved with id 1 with status 200 +[2025-09-03 17:34:32.741] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:34:41.091] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:35:57.790] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 From 529a590cce2fb2675b99ca23d52d3f46e4452a8d Mon Sep 17 00:00:00 2001 From: astria Date: Wed, 3 Sep 2025 19:53:05 +0200 Subject: [PATCH 4/8] Fix docker issues --- backend/Dockerfile | 18 +++------- backend/logs/access.log | 3 ++ docker-compose.yaml | 33 +++++++---------- frontend/Dockerfile | 14 ++++++-- frontend/default.conf | 68 +++++++++++++++++++++++++++++++++++ frontend/nginx-selfsigned.crt | 23 ++++++++++++ frontend/nginx-selfsigned.key | 28 +++++++++++++++ frontend/package.json | 3 +- 8 files changed, 152 insertions(+), 38 deletions(-) create mode 100644 frontend/default.conf create mode 100644 frontend/nginx-selfsigned.crt create mode 100644 frontend/nginx-selfsigned.key diff --git a/backend/Dockerfile b/backend/Dockerfile index 1470f39..ea47ba6 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,22 +1,14 @@ -FROM node:20-alpine -# Set the working directory +FROM node:22-alpine + WORKDIR /app -# Copy package.json and package-lock.json + COPY package*.json ./ -# Install dependencies RUN npm install --production -# Copy the rest of the application code -COPY . . -# Expose the port the app runs on -EXPOSE 8000 -# Install netcat for health checks -RUN apk add --no-cache netcat-openbsd +COPY . . -# Install the cli tools -RUN chmod +x ./freetube.sh -RUN cp ./freetube.sh /usr/local/bin/freetube +EXPOSE 8000 # Start the application CMD ["npm", "start"] \ No newline at end of file diff --git a/backend/logs/access.log b/backend/logs/access.log index fd02723..4264c87 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -11484,3 +11484,6 @@ [2025-09-03 17:34:32.741] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 [2025-09-03 17:34:41.091] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 [2025-09-03 17:35:57.790] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:44:19.296] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:44:20.911] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 17:50:33.774] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 diff --git a/docker-compose.yaml b/docker-compose.yaml index 9daba49..d75b89e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,7 +24,8 @@ services: - ./backend/logs:/var/log/freetube - ./backend:/app depends_on: - - db + db: + condition: service_healthy db: image: postgres:latest @@ -36,34 +37,24 @@ services: POSTGRES_DB: ${POSTGRES_DB} volumes: - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s frontend: - image: nginx:latest + build: + context: ./frontend + dockerfile: Dockerfile + container_name: resit_frontend ports: - "80:80" - "443:443" - volumes: - - ./frontend/dist:/usr/share/nginx/html - - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - - ./nginx/nginx-selfsigned.crt:/etc/nginx/ssl/nginx-selfsigned.crt - - ./nginx/nginx-selfsigned.key:/etc/nginx/ssl/nginx-selfsigned.key depends_on: - resit_backend - - mailpit: - image: axllent/mailpit:latest - ports: - - "8025:8025" # Web UI - - "1025:1025" # SMTP - volumes: - - mailpit-data:/data - environment: - # set where to store the database - MP_DATABASE: /data/mailpit.db - restart: unless-stopped volumes: db_data: - driver: local - mailpit-data: driver: local \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 5b114a4..e0a2a74 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,17 @@ -FROM node:21-alpine3.20 +# Build stage +FROM node:22-alpine3.20 as build-stage WORKDIR /app COPY package*.json ./ RUN npm install COPY . . -EXPOSE 5173 -CMD ["npm", "run", "dev"] +RUN npm run build +# Production stage +FROM nginx:alpine +RUN mkdir -p /etc/nginx/ssl +COPY --from=build-stage /app/dist /usr/share/nginx/html +COPY default.conf /etc/nginx/conf.d/default.conf +COPY nginx-selfsigned.crt nginx-selfsigned.key /etc/nginx/ssl/ +EXPOSE 80 443 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/default.conf b/frontend/default.conf new file mode 100644 index 0000000..314181e --- /dev/null +++ b/frontend/default.conf @@ -0,0 +1,68 @@ +server { + server_name localhost; + listen 80; + + return 301 https://$host$request_uri; +} + +server { + server_name localhost; + listen 443 ssl; + + root /usr/share/nginx/html; + index index.html index.htm; + + # Allow large file uploads for videos (up to 500MB) + client_max_body_size 500M; + + ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt; + ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + # API routes - proxy to backend (MUST come before static file rules) + location /api/ { + # Handle preflight OPTIONS requests + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '$http_origin' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Max-Age' 1728000 always; + add_header 'Content-Type' 'text/plain; charset=utf-8' always; + add_header 'Content-Length' 0 always; + return 204; + } + + proxy_pass http://resit_backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Origin $http_origin; + proxy_buffering off; + + # CORS headers for actual requests + add_header 'Access-Control-Allow-Origin' '$http_origin' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + + # Also set timeout for large uploads + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + # Static assets - NO CACHING for development + location ~* ^/(?!api/).*\.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "0"; + try_files $uri =404; + } + + # Handle React Router - all other routes should serve index.html + location / { + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file diff --git a/frontend/nginx-selfsigned.crt b/frontend/nginx-selfsigned.crt new file mode 100644 index 0000000..29fac8a --- /dev/null +++ b/frontend/nginx-selfsigned.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5zCCAs+gAwIBAgIUXzNzqa/12lyIcoxXf+v371J3fWkwDQYJKoZIhvcNAQEL +BQAwgYIxCzAJBgNVBAYTAkZSMREwDwYDVQQIDAhOb3JtYW5keTENMAsGA1UEBwwE +Q2FlbjERMA8GA1UECgwIRnJhbWluZm8xFTATBgNVBAMMDFNhY2hhIEdVRVJJTjEn +MCUGCSqGSIb3DQEJARYYc2FjaGEuZ3VlcmluQHN1cGluZm8uY29tMB4XDTI1MDcy +MTEzMzgwMVoXDTI2MDcyMTEzMzgwMVowgYIxCzAJBgNVBAYTAkZSMREwDwYDVQQI +DAhOb3JtYW5keTENMAsGA1UEBwwEQ2FlbjERMA8GA1UECgwIRnJhbWluZm8xFTAT +BgNVBAMMDFNhY2hhIEdVRVJJTjEnMCUGCSqGSIb3DQEJARYYc2FjaGEuZ3Vlcmlu +QHN1cGluZm8uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvLg7 +nR0UqRZ7UadhI8jrUjRMV1SZj+ljxEnV6tDOVMsvafsym1MhDZHb+cyv8769yqPv +CKtIOQKhMH0PkSqau8szNlF1Tg/1UzT+Mkd4zvLvGE5+aW/oDMg7E2LMJZuCyO4X +9SzWDVA5+b1QFIw6vvb3mCkUOtVDkOFreBBwryZKcWJ0b8o1hT60oB2wr18P14j0 +0C2/TmHMtim0o4r3gKGvpatqt1fXJo0UlYOwTvfMrYhu2VHqsQ2qP7ocazXEWt5u +Alf1vNPkAenF0ZV/2UiaL41Q8GMoV1enDP7k7/qfgXvta/hOeYnLtmv5Qpi4XiWz +xKjSukTUD2sRtSX+YQIDAQABo1MwUTAdBgNVHQ4EFgQUVj9KtmjLFy4xWzkNI9Kq +NAxNsfUwHwYDVR0jBBgwFoAUVj9KtmjLFy4xWzkNI9KqNAxNsfUwDwYDVR0TAQH/ +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGpUPMoF/ASIqOOfX5anDtaPvnslj +DuEVbU7Uoyc4EuSD343DPV7iMUKoFvVLPxJJqMLhSo3aEGJyqOF6q3fvq/vX2VE7 +9MhwS1t2DBGb5foWRosnT1EuqFU1/S0RJ/Y+GNcoY1PrUES4+r7zqqJJjwKOzneV +ktUVCdKl0C1gtw6W4Ajxse3fm9DNLxnZZXbyNqn+KbI8QdO0xSEl+gyiycvPu/NT ++EesdlFoYjO7gdA8dXkmu+Z7R61MYhE9Zvyop5KVMqgU8/Ym04UUWjWQYWWLMyuu +bxngE4XNEI5fhg+0e/I25xJJ9wVV/ZNAF4+XOylHz/CmU8V/SPKuGXBGHg== +-----END CERTIFICATE----- diff --git a/frontend/nginx-selfsigned.key b/frontend/nginx-selfsigned.key new file mode 100644 index 0000000..0ac5345 --- /dev/null +++ b/frontend/nginx-selfsigned.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC8uDudHRSpFntR +p2EjyOtSNExXVJmP6WPESdXq0M5Uyy9p+zKbUyENkdv5zK/zvr3Ko+8Iq0g5AqEw +fQ+RKpq7yzM2UXVOD/VTNP4yR3jO8u8YTn5pb+gMyDsTYswlm4LI7hf1LNYNUDn5 +vVAUjDq+9veYKRQ61UOQ4Wt4EHCvJkpxYnRvyjWFPrSgHbCvXw/XiPTQLb9OYcy2 +KbSjiveAoa+lq2q3V9cmjRSVg7BO98ytiG7ZUeqxDao/uhxrNcRa3m4CV/W80+QB +6cXRlX/ZSJovjVDwYyhXV6cM/uTv+p+Be+1r+E55icu2a/lCmLheJbPEqNK6RNQP +axG1Jf5hAgMBAAECggEAAj+hmDRx6jafAAf67sqi3ZgEGEmBkXNeeLGBTPc/qhxd +ip6krTELnz8TE26RG5LYXzslasUNrn42nIImvBT5ZkcjcosKpWfEqQEAjc1PQovC +9eyKnKfw4TpUvvmiveT4T98vCYEOOqHE0/WTdlOoaBY/f+sZKQYu+1NMtAjFcg2r +vVqwsZb5vGyh7CKmIHZnz3UP8P+7G5digiNRne18pGnE2oTnSoQ3/QIqUWBs69DS +k5ew+CSyTLiUFFnMnE4adwyg6wAud5fBlzowF6UF2agToX7pxEaGxGvpBGG034kk +1UXaB/d5YwcsBeH+x5cNMLKZy4zqjoxEEW31Q466NQKBgQDtKk1R/slpTpRqvtBT +NC7InvjcCBXkXttylQHJRN9glqhmflEOe8iMW1/qRwBPlQgK1wq/sXySanVv2+gO +JGq8XNRLbHyG3YRyshdnJHP1HoWQE0uedD/rfqgkNaW5S1IvHrD7Q7tOvCrF+KbS +612pmIgNVzn+inafDXPhMZc4pQKBgQDLtQGAu2eK58ewndyL8+7+VHZSTEtKpt+h +G/U/ccv+6NGqdxI5YUkrJ7k6vV81VeRMvmN9uUS/i8znORFQmm6noRVkhXytwW5B +HXq2co4WRvv9b/XqcqS0GSYVPJ1u4YNH6lvtDZ4UWPyBzYl700GdHrGa+erT44yL +tnibHx9GDQKBgFW1J+Qt85O+9hvtgVPQU+fkq4K42VCCh0PNXavi2+cICyufEqPt +T/iJPQxpRE9+SD3CoPvNpHs1ReN60U3rEzenRIFNX2NNwoPAoHyBy/YVZac/keBd +mov8Zb9QM+fWtIiaytLDE3nMvph017T5ogucN+66SxcV6vBn6CzFwySRAoGAcUf2 +Tv1ohkGAtgIDrLx5cmvL5NZSpHAKOpDOoHqLA/W66v4OX2RviRUtF7JJ6OIb9GWH +9Fl8Fr0KtKbyrw1CbevRdrYY8JN52bIoFJ+9zjupVHXXnookd5boq7SqpAe6ttpo +RnplJ1GZEiIXy4lemp6AC/vhD/YhqWxOw4zaGl0CgYBslhqVt5F0EHf94p7NrCuY +hNHKHaNaULYP0VXKefQamt/ssDuktqb6DNSIvx2rbbB5+33nTlLTya67gimY1lKt +WeNB33/yBkCjfSP/J5UDD9mE/oPLt3vAOkOUgMCfp2IpC2Wez1QGqLHS260zpotP +VpgalHuSWtn8D4nO2pk1hg== +-----END PRIVATE KEY----- diff --git a/frontend/package.json b/frontend/package.json index adc5c94..2af0127 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite --host", - "build": "vite build --watch", + "build": "vite build", + "build:watch": "vite build --watch", "lint": "eslint .", "preview": "vite preview" }, From 8cbc6f0e4339ea93e274cde083e9657e359b7ea6 Mon Sep 17 00:00:00 2001 From: astria Date: Thu, 4 Sep 2025 23:26:39 +0200 Subject: [PATCH 5/8] Added fullscreen mode --- backend/logs/access.log | 267 ++++++++++++++++++++ developpement.yaml | 51 ++-- docker-compose.yaml | 2 + frontend/Docker.Development | 7 + frontend/src/assets/svg/exit_fullscreen.svg | 1 + frontend/src/assets/svg/fullscreen.svg | 1 + frontend/src/pages/Video.jsx | 75 +++++- 7 files changed, 378 insertions(+), 26 deletions(-) create mode 100644 frontend/Docker.Development create mode 100644 frontend/src/assets/svg/exit_fullscreen.svg create mode 100644 frontend/src/assets/svg/fullscreen.svg diff --git a/backend/logs/access.log b/backend/logs/access.log index 4264c87..647a6f5 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -11487,3 +11487,270 @@ [2025-09-03 17:44:19.296] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 [2025-09-03 17:44:20.911] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 [2025-09-03 17:50:33.774] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 20:04:12.031] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 20:04:13.537] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-09-03 20:04:13.544] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404 +[2025-09-03 20:04:13.556] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-09-03 20:04:13.561] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404 +[2025-09-03 20:04:13.566] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-03 20:04:18.757] [undefined] POST(/): try to create new channel with owner 1 and name c2lamerde +[2025-09-03 20:04:18.762] [undefined] POST(/): Successfully created new channel with name c2lamerde with status 200 +[2025-09-03 20:04:18.857] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-09-03 20:04:18.862] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-09-03 20:04:20.055] [undefined] GET(/:id): try to get channel with id 1 +[2025-09-03 20:04:20.060] [undefined] GET(/:id/stats): try to get stats +[2025-09-03 20:04:20.065] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-09-03 20:04:20.070] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-09-03 20:04:20.845] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-09-03 20:04:20.849] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-09-03 20:04:33.523] [undefined] POST(/): try to upload video with status undefined +[2025-09-03 20:04:33.528] [undefined] POST(/): successfully uploaded video with status 200 +[2025-09-03 20:04:33.621] [undefined] POST(/thumbnail): try to add thumbnail to video 1 +[2025-09-03 20:04:33.627] [undefined] POST(/thumbnail): successfully uploaded thumbnail with status 200 +[2025-09-03 20:04:33.638] [undefined] PUT(/:id/tags): try to add tags to video 1 +[2025-09-03 20:04:33.646] [undefined] PUT(/:id/tags): successfully added tags to video 1 with status 200 +[2025-09-03 20:04:33.671] [undefined] GET(/:id): try to get channel with id 1 +[2025-09-03 20:04:33.675] [undefined] GET(/:id/stats): try to get stats +[2025-09-03 20:04:33.679] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-09-03 20:04:33.685] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-09-03 20:04:35.555] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-03 20:04:36.856] [undefined] GET(/:id): try to get video 1 +[2025-09-03 20:04:36.860] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-03 20:04:36.867] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-03 20:04:36.877] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-03 20:04:36.882] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-03 20:04:36.893] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-03 20:04:36.900] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:32:34.492] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:32:36.797] [undefined] GET(/:id/channel): try to retrieve channel of user 1 +[2025-09-04 20:32:36.803] [undefined] GET(/:id/channel): successfully retrieved channel of user 1 with status 200 +[2025-09-04 20:32:36.815] [undefined] GET(/:id/history): try to retrieve history of user 1 +[2025-09-04 20:32:36.822] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:32:36.826] [undefined] GET(/:id/history): successfully retrieved history of user 1 with status 200 +[2025-09-04 20:32:47.165] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:34:30.396] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:34:31.179] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:35:34.522] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:36:03.193] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:37:37.442] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:37:42.936] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:42:20.620] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:42:31.482] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:43:51.613] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 1 with status 200 +[2025-09-04 20:44:03.089] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:44:03.095] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:44:03.102] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:44:03.112] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:44:03.119] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:44:03.131] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:44:03.136] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:44:42.468] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:44:42.523] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:44:42.534] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:44:42.544] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:44:42.553] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:44:42.569] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:44:42.573] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:45:08.656] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:45:08.660] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:45:08.667] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:45:08.677] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:45:08.682] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:45:08.711] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:45:08.718] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:45:27.395] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:45:27.403] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:45:27.413] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:45:27.418] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:45:27.424] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:45:27.455] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:45:27.465] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:45:40.775] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:45:40.780] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:45:40.787] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:45:40.799] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:45:40.805] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:45:40.887] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:45:40.894] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:48:09.425] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:48:09.433] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:48:09.442] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:48:09.454] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:48:09.461] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:48:09.487] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:48:09.495] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:48:14.450] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:48:14.455] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:48:14.461] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:48:14.479] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:48:14.485] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:48:14.516] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:48:14.524] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:48:23.210] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:48:23.215] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:48:23.221] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:48:23.232] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:48:23.237] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:48:23.268] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:48:23.277] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:48:28.466] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:48:28.471] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:48:28.478] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:48:28.491] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:48:28.498] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:48:28.562] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:48:28.567] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:48:55.091] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:48:55.099] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:48:55.109] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:48:55.116] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:48:55.122] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:48:55.142] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:48:55.150] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:49:01.822] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:49:01.827] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:49:01.834] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:49:01.844] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:49:01.850] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:49:01.906] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:49:01.912] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:49:37.215] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:49:37.223] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:49:37.230] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:49:37.241] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:49:37.248] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:49:37.271] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:49:37.277] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:50:00.804] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:50:00.808] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:50:00.815] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:50:00.826] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:50:00.832] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:50:00.888] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:50:00.894] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:50:16.435] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:50:16.445] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:50:16.451] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:50:16.462] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:50:16.468] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:50:16.539] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:50:16.545] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:51:09.495] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:51:09.502] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:51:09.509] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:51:09.519] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:51:09.526] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:51:09.581] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:51:09.588] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:51:20.516] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:51:20.520] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:51:20.527] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:51:20.539] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:51:20.545] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:51:20.597] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:51:20.604] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:51:38.559] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:51:38.563] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:51:38.570] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:51:38.583] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:51:38.589] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:51:38.660] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:51:38.666] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:51:59.262] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:51:59.269] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:51:59.278] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:51:59.284] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:51:59.290] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:51:59.312] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:51:59.320] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:52:08.947] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:52:08.951] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:52:08.957] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:52:08.968] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:52:08.974] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:52:09.029] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:52:09.033] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:53:15.575] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:53:15.583] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:53:15.590] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:53:15.596] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:53:15.602] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:53:15.694] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:53:15.699] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:53:40.778] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:53:40.782] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:53:40.789] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:53:40.799] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:53:40.805] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:53:40.859] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:53:40.866] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:53:53.650] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:53:53.658] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:53:53.666] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:53:53.675] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:53:53.681] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:53:53.748] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:53:53.754] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:57:38.645] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:57:38.652] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:57:38.659] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:57:38.666] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:57:38.673] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:57:38.707] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:57:38.716] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:57:47.609] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:57:47.614] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:57:47.621] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:57:47.635] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:57:47.641] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:57:47.693] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:57:47.699] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:57:54.157] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:57:54.162] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:57:54.168] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:57:54.181] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:57:54.187] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:57:54.260] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:57:54.266] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:58:40.327] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:58:40.335] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:58:40.342] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:58:40.353] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:58:40.359] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:58:40.380] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:58:40.389] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:59:06.564] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:59:06.568] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:59:06.574] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:59:06.583] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:59:06.588] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:59:06.609] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:59:06.618] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 20:59:50.463] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 20:59:50.474] [undefined] GET(/:id): try to get video 1 +[2025-09-04 20:59:50.481] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 20:59:50.495] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 20:59:50.501] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 20:59:50.530] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 20:59:50.537] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 21:01:19.527] [undefined] GET(/:id): try to get video 1 +[2025-09-04 21:01:19.535] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 21:01:19.541] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 21:01:19.547] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 21:01:19.554] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 21:01:19.571] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 21:01:19.579] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 21:01:28.790] [undefined] GET(/:id): try to get video 1 +[2025-09-04 21:01:28.846] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 21:01:28.853] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 21:01:28.882] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 21:01:28.888] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 21:01:28.935] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 21:01:28.941] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 +[2025-09-04 21:01:30.079] [undefined] GET(/:id): try to get video 1 +[2025-09-04 21:01:30.084] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200 +[2025-09-04 21:01:30.091] [undefined] GET(/:id): successfully get video 1 with status 200 +[2025-09-04 21:01:30.110] [undefined] GET(/:id/similar): try to get similar videos for video 1 +[2025-09-04 21:01:30.115] [undefined] GET(/:id/similar): successfully get similar videos for video 1 with status 200 +[2025-09-04 21:01:30.138] [undefined] GET(/:id/views): try to add views for video 1 +[2025-09-04 21:01:30.145] [undefined] GET(/:id/views): successfully added views for video 1 with status 200 diff --git a/developpement.yaml b/developpement.yaml index 5b12e0b..dd7c7b5 100644 --- a/developpement.yaml +++ b/developpement.yaml @@ -1,32 +1,34 @@ services: - backend: + resit_backend: build: context: ./backend dockerfile: Dockerfile network: host container_name: resit_backend ports: - - "${BACKEND_PORT}:${BACKEND_PORT}" + - "8000:8000" environment: - DB_USER: ${POSTGRES_USER} - DB_NAME: ${POSTGRES_DB} - DB_HOST: ${POSTGRES_HOST} - DB_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_HOST: db + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} JWT_SECRET: ${JWT_SECRET} LOG_FILE: ${LOG_FILE} PORT: ${BACKEND_PORT} + GMAIL_USER: ${GMAIL_USER} + GMAIL_PASSWORD: ${GMAIL_PASSWORD} + GITHUB_ID: ${GITHUB_ID} + GITHUB_SECRET: ${GITHUB_SECRET} volumes: - ./backend/logs:/var/log/freetube - - ./backend/app/uploads:/app/app/uploads - - ./backend/app/utils/wait-for-it.sh:/wait-for-it.sh + - ./backend:/app depends_on: - - db - command: ["/wait-for-it.sh", "${POSTGRES_HOST}:5432", "--", "npm", "start"] + db: + condition: service_healthy db: image: postgres:latest - container_name: resit_db ports: - "5432:5432" environment: @@ -35,20 +37,29 @@ services: POSTGRES_DB: ${POSTGRES_DB} volumes: - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s frontend: - build: - context: ./frontend - dockerfile: Dockerfile - network: host + image: nginx:alpine + container_name: resit_frontend ports: - - "5173:5173" + - "80:80" + - "443:443" volumes: - - ./frontend/:/app - - /app/node_modules + - ./frontend/dist:/usr/share/nginx/html + - ./frontend/nginx-selfsigned.crt:/etc/nginx/ssl/nginx-selfsigned.crt + - ./frontend/nginx-selfsigned.key:/etc/nginx/ssl/nginx-selfsigned.key + - ./frontend/default.conf:/etc/nginx/conf.d/default.conf + environment: + - VITE_API_BASE_URL=https://localhost/api depends_on: - - backend + - resit_backend volumes: db_data: - driver: local + driver: local \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index d75b89e..d0246dd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -52,6 +52,8 @@ services: ports: - "80:80" - "443:443" + environment: + - VITE_API_BASE_URL=https://localhost/api depends_on: - resit_backend diff --git a/frontend/Docker.Development b/frontend/Docker.Development new file mode 100644 index 0000000..2bc8951 --- /dev/null +++ b/frontend/Docker.Development @@ -0,0 +1,7 @@ +# Build stage +FROM node:22-alpine3.20 as build-stage + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . diff --git a/frontend/src/assets/svg/exit_fullscreen.svg b/frontend/src/assets/svg/exit_fullscreen.svg new file mode 100644 index 0000000..d6f4973 --- /dev/null +++ b/frontend/src/assets/svg/exit_fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/svg/fullscreen.svg b/frontend/src/assets/svg/fullscreen.svg new file mode 100644 index 0000000..a316690 --- /dev/null +++ b/frontend/src/assets/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/pages/Video.jsx b/frontend/src/pages/Video.jsx index 780d617..462295a 100644 --- a/frontend/src/pages/Video.jsx +++ b/frontend/src/pages/Video.jsx @@ -18,6 +18,7 @@ export default function Video() { const { user, isAuthenticated } = useAuth(); const videoRef = useRef(null); const controllerRef = useRef(null); + const hideControlsTimeoutRef = useRef(null); const navigation = useNavigate(); const [searchParams] = useSearchParams(); const playlistId = searchParams.get("playlistId"); @@ -35,6 +36,7 @@ export default function Video() { const [playlists, setPlaylists] = useState([]); const [isAddToPlaylistOpen, setIsAddToPlaylistOpen] = useState(false); const [currentPlaylist, setCurrentPlaylist] = useState(null); + const [isFullscreen, setIsFullscreen] = useState(false); const [isCommentVisible, setIsCommentVisible] = useState(window.innerWidth >= 1024); // Show comments by default on large screens const fetchVideo = useCallback(async () => { @@ -127,6 +129,19 @@ export default function Video() { fetchNextVideo(); }, [currentPlaylist]); + // Handle fullscreen state changes + useEffect(() => { + if (!isFullscreen) { + // Clear timeout when exiting fullscreen + if (hideControlsTimeoutRef.current) { + clearTimeout(hideControlsTimeoutRef.current); + hideControlsTimeoutRef.current = null; + } + // Reset controls visibility for normal mode + setShowControls(false); + } + }, [isFullscreen]); + const handlePlayPause = () => { if (videoRef.current) { if (videoRef.current.paused) { @@ -199,11 +214,31 @@ export default function Video() { } const handleMouseEnter = () => { - setShowControls(true); + if (!isFullscreen) { + setShowControls(true); + } }; const handleMouseLeave = () => { - setShowControls(false); + if (!isFullscreen) { + setShowControls(false); + } + }; + + const handleMouseMove = () => { + if (isFullscreen) { + setShowControls(true); + + // Clear existing timeout + if (hideControlsTimeoutRef.current) { + clearTimeout(hideControlsTimeoutRef.current); + } + + // Hide controls after 3 seconds of no mouse movement + hideControlsTimeoutRef.current = setTimeout(() => { + setShowControls(false); + }, 3000); + } }; const handleSubscribe = async () => { @@ -318,6 +353,7 @@ export default function Video() { className="relative w-full aspect-video mx-auto rounded-lg overflow-hidden" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} + onMouseMove={handleMouseMove} >