From 944482b96ccffc36903536d8b63df479e4f5446e Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Tue, 2 Jun 2026 13:10:52 +0200 Subject: [PATCH] Support HTTPS deployment under /generadorexamenesllm behind reverse proxy. This updates frontend base-path routing, same-origin API proxying, and deployment defaults/docs so the app works correctly through the Sinbad2 Apache ProxyPass setup. --- backend/.env.example | 2 +- backend/app/core/config.py | 2 +- docker-compose.yml | 5 +++-- frontend/.env.example | 9 +++++++-- frontend/Dockerfile | 4 +++- frontend/README.md | 23 +++++++++++++++++++++-- frontend/nginx.conf | 25 +++++++++++++++++++++++++ frontend/src/api/client.js | 2 +- frontend/src/main.jsx | 2 +- frontend/vite.config.js | 20 +++++++++++++------- 10 files changed, 76 insertions(+), 18 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 87db385..00357a5 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -9,7 +9,7 @@ API_KEY=change-me-in-production-min-16-chars DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes # --- CORS (orígenes del frontend, separados por coma) --- -ALLOWED_ORIGINS=http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075 +ALLOWED_ORIGINS=https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075 # --- Rate limiting y tamaño de petición --- RATE_LIMIT_REQUESTS=60 diff --git a/backend/app/core/config.py b/backend/app/core/config.py index daaa2aa..1e869d5 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -10,7 +10,7 @@ class Settings(BaseSettings): api_prefix: str = "" api_key: str = Field(min_length=16) database_url: str = "postgresql+psycopg://genexamenes:genexamenes@localhost:5432/genexamenes" - allowed_origins: str = "http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075" + allowed_origins: str = "https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075" rate_limit_requests: int = Field(default=60, ge=1) rate_limit_window_seconds: int = Field(default=60, ge=1) max_request_bytes: int = Field(default=1_048_576, ge=1_024) diff --git a/docker-compose.yml b/docker-compose.yml index c6d0941..e1e6854 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: DATABASE_URL: postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes # Sobrescribe backend/.env con el origen público del frontend en despliegue. - ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075} + ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://sinbad2.ujaen.es,http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075} LLM_BASE_URL: LLM_MODEL: qwen3.5:35b LLM_TIMEOUT_SECONDS: "180" @@ -24,7 +24,8 @@ services: build: context: ./frontend args: - VITE_API_URL: ${VITE_API_URL:-http://sinbad2.ujaen.es:8074} + VITE_APP_BASE_PATH: ${VITE_APP_BASE_PATH:-/generadorexamenesllm/} + VITE_API_URL: ${VITE_API_URL:-} VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-} ports: - "${FRONTEND_PORT:-8075}:80" diff --git a/frontend/.env.example b/frontend/.env.example index c77d877..3197851 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,5 +1,10 @@ -# URL base del backend (accesible desde el navegador) -VITE_API_URL=http://sinbad2.ujaen.es:8074 +# Base pública de la app cuando va tras proxy (debe terminar en /) +# Ejemplo producción UJA: /generadorexamenesllm/ +VITE_APP_BASE_PATH=/generadorexamenesllm/ + +# URL base del backend (accesible desde el navegador). +# Si se deja vacía, usa la misma base de la app (recomendado tras proxy HTTPS). +VITE_API_URL= # (Opcional) Client ID de Google para "Iniciar sesión con Google". # Debe coincidir con GOOGLE_CLIENT_ID del backend. diff --git a/frontend/Dockerfile b/frontend/Dockerfile index db038d7..62b6cc6 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -2,8 +2,10 @@ FROM node:20-alpine AS build WORKDIR /app -ARG VITE_API_URL=http://sinbad2.ujaen.es:8074 +ARG VITE_APP_BASE_PATH=/generadorexamenesllm/ +ARG VITE_API_URL= ARG VITE_GOOGLE_CLIENT_ID= +ENV VITE_APP_BASE_PATH=$VITE_APP_BASE_PATH ENV VITE_API_URL=$VITE_API_URL ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID diff --git a/frontend/README.md b/frontend/README.md index 23561c4..2ab1f90 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -38,7 +38,8 @@ npm run dev # http://sinbad2.ujaen.es:8075 | Variable | Descripción | | ----------------------- | -------------------------------------------------------- | -| `VITE_API_URL` | URL base del backend (por defecto `http://sinbad2.ujaen.es:8074`). | +| `VITE_APP_BASE_PATH` | Base pública de la SPA (debe terminar en `/`). En producción UJA: `/generadorexamenesllm/`. | +| `VITE_API_URL` | URL base de la API. Si se deja vacía, usa la misma base pública de la app. | | `VITE_GOOGLE_CLIENT_ID` | (Opcional) Client ID de Google. Si está vacío, se oculta el botón de Google. | > Las variables `VITE_*` se incrustan en el build, por lo que apuntan al backend @@ -60,9 +61,27 @@ El `docker-compose.yml` de la raíz construye el frontend con un build multi-sta docker compose up --build ``` -Las variables `VITE_API_URL` y `VITE_GOOGLE_CLIENT_ID` pueden pasarse como +Las variables `VITE_APP_BASE_PATH`, `VITE_API_URL` y `VITE_GOOGLE_CLIENT_ID` pueden pasarse como variables de entorno al ejecutar `docker compose`. +## Despliegue HTTPS en subruta (Sinbad2) + +Para ejecutarlo detrás de Apache en `https://sinbad2.ujaen.es/deckofcars` con: + +```apache +ProxyPass /generadorexamenesllm http://host.docker.internal:8075/ +ProxyPassReverse /generadorexamenesllm http://host.docker.internal:8075/ +``` + +usa estos valores de build: + +```env +VITE_APP_BASE_PATH=/generadorexamenesllm/ +VITE_API_URL= +``` + +Con esto, el frontend sirve assets/rutas bajo `/generadorexamenesllm` y consume la API por HTTPS en la misma base (sin mixed content). + ## Manejo de errores Todas las respuestas de error del backend siguen el formato diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 23a100c..346187a 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -4,6 +4,31 @@ server { root /usr/share/nginx/html; index index.html; + # Backend API dentro de la misma base HTTPS (evita mixed content). + location /auth/ { + proxy_pass http://backend:8074/auth/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /exam/ { + proxy_pass http://backend:8074/exam/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location = /health { + proxy_pass http://backend:8074/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + # SPA: cualquier ruta desconocida sirve index.html (React Router). location / { try_files $uri $uri/ /index.html; diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index a91a29f..c3d898a 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -1,7 +1,7 @@ import axios from "axios"; export const API_URL = - import.meta.env.VITE_API_URL?.replace(/\/$/, "") || "http://sinbad2.ujaen.es:8074"; + import.meta.env.VITE_API_URL?.replace(/\/$/, "") || import.meta.env.BASE_URL.replace(/\/$/, ""); const TOKEN_KEY = "genex_token"; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 01a6221..0ea2e26 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -8,7 +8,7 @@ import "./index.css"; ReactDOM.createRoot(document.getElementById("root")).render( - + diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 6ee4104..98f3bf5 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,10 +1,16 @@ -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; import react from "@vitejs/plugin-react"; -export default defineConfig({ - plugins: [react()], - server: { - host: true, - port: 8075, - }, +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const base = env.VITE_APP_BASE_PATH || "/"; + + return { + base, + plugins: [react()], + server: { + host: true, + port: 8075, + }, + }; });