Compare commits

...

1 Commits

Author SHA1 Message Date
astria 60985c9923 STAGING 3 months ago
  1. 4
      backend/app/controllers/playlist.controller.js
  2. 19
      backend/logs/access.log
  3. 13
      frontend/Dockerfile
  4. 68
      frontend/default.conf
  5. 23
      frontend/nginx-selfsigned.crt
  6. 28
      frontend/nginx-selfsigned.key
  7. 2
      frontend/src/components/SeeLater.jsx
  8. 8
      nginx/Dockerfile

4
backend/app/controllers/playlist.controller.js

@ -238,6 +238,7 @@ export async function getSeeLater(req, res) {
const client = await getClient(); const client = await getClient();
const query = ` const query = `
SELECT SELECT
COALESCE(
JSON_AGG( JSON_AGG(
json_build_object( json_build_object(
'video_id', videos.id, 'video_id', videos.id,
@ -258,6 +259,8 @@ export async function getSeeLater(req, res) {
'description', channels.description 'description', channels.description
) )
) )
) FILTER (WHERE videos.id IS NOT NULL),
'[]'::json
) AS videos ) AS videos
FROM FROM
public.playlists public.playlists
@ -290,6 +293,7 @@ export async function getSeeLater(req, res) {
res.status(404).json({ error: "'See Later' playlist not found" }); res.status(404).json({ error: "'See Later' playlist not found" });
return; return;
} }
logger.write("'See Later' playlist retrieved for user with id " + userId, 200); logger.write("'See Later' playlist retrieved for user with id " + userId, 200);
client.release(); client.release();
res.status(200).json(result.rows[0].videos); res.status(200).json(result.rows[0].videos);

19
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.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.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-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

13
frontend/Dockerfile

@ -1,9 +1,8 @@
FROM node:21-alpine3.20 FROM nginx:latest
WORKDIR /app COPY ./dist/ /usr/share/nginx/html
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev"]
COPY ./default.conf /etc/nginx/conf.d/
COPY ./nginx-* /etc/nginx/ssl/
EXPOSE 80:80

68
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;
}
}

23
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-----

28
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-----

2
frontend/src/components/SeeLater.jsx

@ -8,7 +8,7 @@ export default function SeeLater({videos}) {
<h2 className="text-3xl font-bold mb-4 text-white">A regarder plus tard</h2> <h2 className="text-3xl font-bold mb-4 text-white">A regarder plus tard</h2>
<div> <div>
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 mt-2"> <div className="grid grid-cols-1 lg:grid-cols-5 gap-8 mt-2">
{videos && videos.map((video, index) => ( {videos && videos.length > 0 && videos.map((video, index) => (
<VideoCard key={video.id || index} video={video} /> <VideoCard key={video.id || index} video={video} />
))} ))}
</div> </div>

8
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
Loading…
Cancel
Save