diff --git a/backend/app/controllers/playlist.controller.js b/backend/app/controllers/playlist.controller.js index ace7ba7..a80df6a 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 @@ -290,6 +293,7 @@ export async function getSeeLater(req, res) { res.status(404).json({ error: "'See Later' playlist not found" }); return; } + logger.write("'See Later' playlist retrieved for user with id " + userId, 200); client.release(); res.status(200).json(result.rows[0].videos); diff --git a/backend/logs/access.log b/backend/logs/access.log index 3fcfa30..2c75f44 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -11402,3 +11402,22 @@ [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-09-03 15:14:11.066] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 +[2025-09-03 15:14:15.953] [undefined] GET(/:id/subscriptions): try to retrieve all subscriptions of user 4 +[2025-09-03 15:14:15.958] [undefined] GET(/:id/subscriptions/videos): try to retrieve all subscriptions of user 4 +[2025-09-03 15:14:15.970] [undefined] GET(/:id/subscriptions): successfully retrieved all subscriptions of user 4 with status 200 +[2025-09-03 15:14:15.974] [undefined] GET(/:id/subscriptions/videos): successfully retrieved all subscriptions of user 4 with status 200 +[2025-09-03 15:14:23.133] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 +[2025-09-03 15:26:51.925] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 +[2025-09-03 16:50:33.212] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 +[2025-09-03 16:50:41.355] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 +[2025-09-03 16:52:22.149] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 +[2025-09-03 16:52:27.861] [undefined] GET(/:id): try to get video 8 +[2025-09-03 16:52:27.866] [undefined] GET(/user/:id): Playlists retrieved for user with id 4 with status 200 +[2025-09-03 16:52:27.874] [undefined] GET(/:id): successfully get video 8 with status 200 +[2025-09-03 16:52:27.883] [undefined] GET(/:id/similar): try to get similar videos for video 8 +[2025-09-03 16:52:27.889] [undefined] GET(/:id/similar): successfully get similar videos for video 8 with status 200 +[2025-09-03 16:52:27.908] [undefined] GET(/:id/views): try to add views for video 8 +[2025-09-03 16:52:27.912] [undefined] GET(/:id/views): successfully added views for video 8 with status 200 +[2025-09-03 16:52:31.701] [undefined] POST(/:id): Video added to playlist with id 4 with status 200 +[2025-09-03 16:52:34.102] [undefined] GET(/see-later): 'See Later' playlist retrieved for user with id 4 with status 200 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 5b114a4..c75cd1c 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,8 @@ -FROM node:21-alpine3.20 +FROM nginx:latest -WORKDIR /app -COPY package*.json ./ -RUN npm install -COPY . . -EXPOSE 5173 -CMD ["npm", "run", "dev"] +COPY ./dist/ /usr/share/nginx/html +COPY ./default.conf /etc/nginx/conf.d/ +COPY ./nginx-* /etc/nginx/ssl/ + +EXPOSE 80:80 \ No newline at end of file diff --git a/frontend/default.conf b/frontend/default.conf new file mode 100644 index 0000000..65c5154 --- /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://localhost: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/src/components/SeeLater.jsx b/frontend/src/components/SeeLater.jsx index 05b2d8d..dcb24cd 100644 --- a/frontend/src/components/SeeLater.jsx +++ b/frontend/src/components/SeeLater.jsx @@ -2,13 +2,13 @@ import VideoCard from "./VideoCard.jsx"; export default function SeeLater({videos}) { - + return (

A regarder plus tard

- {videos && videos.map((video, index) => ( + {videos && videos.length > 0 && videos.map((video, index) => ( ))}
diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..f73a4e7 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,8 @@ +FROM nginx:latest + +COPY ../frontend/dist/ /usr/share/nginx/html + +COPY ./default.conf /etc/nginx/conf.d + +EXPOSE 80:80 +