Add production Docker setup and update backend/frontend configs

Introduces .dockerignore, production Dockerfile and nginx config for frontend, and refactors docker-compose.yml for multi-service deployment. Updates backend and frontend code to support public API tagging, improves refund handling, adds test email endpoint, and migrates Orval config to TypeScript. Removes unused frontend Dockerfile and updates dependencies for React Query and Orval.
This commit is contained in:
David Bruno Vontor
2025-12-05 18:22:35 +01:00
parent d94ad93222
commit 4cbebff43b
20 changed files with 3752 additions and 144 deletions

View File

@@ -1,11 +0,0 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev"]

16
frontend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,16 @@
# Step 1: Build React (Vite) app
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
# If package-lock.json exists, npm ci is faster and reproducible
RUN npm ci || npm install
COPY . .
ENV NODE_ENV=production
RUN npm run build
# Step 2: Nginx runtime
FROM nginx:1.27-alpine
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

75
frontend/nginx/nginx.conf Normal file
View File

@@ -0,0 +1,75 @@
# nginx.conf
worker_processes auto;
user nginx;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
client_max_body_size 50m;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name _;
# -------------------------
# React frontend
# -------------------------
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
# Ensure CSP is present on SPA document responses too
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://api.paylibo.com; connect-src 'self' http://127.0.0.1:8000 http://localhost:8000 ws: wss: https://api.paylibo.com; font-src 'self' data:" always;
}
# -------------------------
# Django backend API
# -------------------------
# Serve Django static and media volumes mounted into the container
location /static/ {
alias /app/collectedstaticfiles/;
}
location /media/ {
alias /app/media/;
}
# Same-origin proxy for API -> avoids CORS and allows cookies
location /api {
return 301 /api/;
}
location /api/ {
proxy_pass http://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_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
client_max_body_size 50m;
# Ensure CSP is also present on proxied responses
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://api.paylibo.com; connect-src 'self' http://127.0.0.1:8000 http://localhost:8000 ws: wss: https://api.paylibo.com; font-src 'self' data:" always;
}
# -------------------------
# Security headers
# -------------------------
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Minimal, valid CSP for development (apply on all responses)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://api.paylibo.com; connect-src 'self' http://127.0.0.1:8000 http://localhost:8000 ws: wss: https://api.paylibo.com; font-src 'self' data:" always;
}
}

View File

@@ -1,30 +0,0 @@
module.exports = {
public: {
input: { target: "http://localhost:8000/api/schema/" },
output: {
target: "src/api/generated/public.ts",
schemas: "src/api/generated/models",
client: "axios",
override: {
mutator: {
path: "src/api/publicClient.ts",
name: "publicApi",
},
},
},
},
private: {
input: { target: "http://localhost:8000/api/schema/" },
output: {
target: "src/api/generated/private.ts",
schemas: "src/api/generated/models",
client: "axios",
override: {
mutator: {
path: "src/api/privateClient.ts",
name: "privateApi",
},
},
},
},
};

59
frontend/orval.config.ts Normal file
View File

@@ -0,0 +1,59 @@
import { defineConfig } from "orval";
import "dotenv/config";
const backendUrl = process.env.VITE_API_BASE_URL || "http://localhost:8000";
// může se hodit pokud nechceme při buildu generovat klienta (nechat false pro produkci nebo vynechat)
const SKIP_ORVAL = process.env.SKIP_ORVAL === "true";
if (SKIP_ORVAL){
}
export default defineConfig({
public: {
input: {
target: `${backendUrl}/api/schema/`,
filters: {
mode: "include",
tags: ["public"],
},
},
output: {
target: "src/api/generated/public.ts",
schemas: "src/api/generated/public/models",
client: "react-query",
httpClient: "axios",
override: {
mutator: {
path: "src/api/publicClient.ts", //IMPORTANTE
name: "publicApi",
},
},
},
},
private: {
input: {
target: `${backendUrl}/api/schema/`
// No filters, include all endpoints
},
output: {
target: "src/api/generated/private.ts", //IMPORTANTE
schemas: "src/api/generated/private/models",
client: "react-query",
httpClient: "axios",
override: {
mutator: {
path: "src/api/privateClient.ts",
name: "privateApi",
},
},
},
},
});

File diff suppressed because it is too large Load Diff

View File

@@ -5,15 +5,19 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build": "tsc -b && tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"api:gen": "orval --config orval.config.js"
"api:gen": "orval --config orval.config.ts"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.16",
"@tanstack/react-query": "^5.90.12",
"@types/react-router": "^5.1.20",
"axios": "^1.13.0",
"dotenv": "^17.2.3",
"orval": "^7.13.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
@@ -34,7 +38,6 @@
"globals": "^16.3.0",
"typescript": "~5.8.3",
"typescript-eslint": "^8.39.1",
"vite": "^7.1.2",
"openapi-generator-cli": "^2.9.0"
"vite": "^7.1.2"
}
}

View File

@@ -2,7 +2,7 @@
// User API model for searching users by username
// Structure matches other model files (see order.js for reference)
import Client from '../legacy/Client';
import Client from '../Client';
const API_BASE_URL = "/account/users";

View File

@@ -1,6 +1,6 @@
import React, { createContext, useState, useEffect } from 'react';
import userAPI from '../api/models/User';
import userAPI from '../api/legacy/models/User';
// definice uživatele
export interface User {