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.
This commit is contained in:
Mireya Cueto Garrido
2026-06-02 13:10:52 +02:00
parent d7f9ae8841
commit 944482b96c
10 changed files with 76 additions and 18 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+3 -2
View File
@@ -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"
+7 -2
View File
@@ -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.
+3 -1
View File
@@ -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
+21 -2
View File
@@ -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
+25
View File
@@ -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;
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -8,7 +8,7 @@ import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<BrowserRouter basename={import.meta.env.BASE_URL}>
<ToastProvider>
<AuthProvider>
<App />
+13 -7
View File
@@ -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,
},
};
});