Adapt CI/CD and endpoint defaults for Sinbad2 production.

Configure GitLab deploy pipeline for this app and switch backend/frontend endpoint defaults from localhost to sinbad2.ujaen.es, including Docker, env files, API client, CORS, and docs.
This commit is contained in:
Mireya Cueto Garrido
2026-06-02 10:25:53 +02:00
parent 98dedfb6b8
commit 7f32380e0a
11 changed files with 58 additions and 20 deletions
+38
View File
@@ -0,0 +1,38 @@
stages:
- deploy
variables:
APP_NAME: "generadorexamenesllms"
BACKEND_PORT: "8074"
FRONTEND_PORT: "8075"
deploy_to_sinbad2:
stage: deploy
before_script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts
script:
- echo "Enviando código a Sinbad2..."
- ssh $REMOTE_USER@$SSH_HOST "mkdir -p ~/deploy_$APP_NAME"
- scp -r ./* $REMOTE_USER@$SSH_HOST:~/deploy_$APP_NAME/
- echo "Levantando contenedores con Docker Compose..."
- ssh $REMOTE_USER@$SSH_HOST "
cd ~/deploy_$APP_NAME &&
docker compose down --remove-orphans &&
docker compose build --no-cache frontend backend &&
docker compose up -d
"
- echo "Despliegue completado."
- echo "Backend -> http://sinbad2.ujaen.es:$BACKEND_PORT"
- echo "Frontend -> http://sinbad2.ujaen.es:$FRONTEND_PORT"
only:
- branches
+6 -6
View File
@@ -2,7 +2,7 @@
Documento resumen para entender **qué hace el usuario en cada paso**, **qué endpoint usar**, **cabeceras**, **cuerpos**, **ejemplos de respuesta** y **errores típicos**. Documento resumen para entender **qué hace el usuario en cada paso**, **qué endpoint usar**, **cabeceras**, **cuerpos**, **ejemplos de respuesta** y **errores típicos**.
**Base URL de ejemplo:** `http://localhost:8074` **Base URL de ejemplo:** `http://sinbad2.ujaen.es:8074`
--- ---
@@ -131,7 +131,7 @@ Detalle de cuerpos, respuestas y errores: **sección 4** de esta guía.
**Ejemplo:** **Ejemplo:**
```bash ```bash
curl -s http://localhost:8074/health curl -s http://sinbad2.ujaen.es:8074/health
``` ```
**Respuesta OK (200):** **Respuesta OK (200):**
@@ -165,7 +165,7 @@ curl -s http://localhost:8074/health
**Ejemplo:** **Ejemplo:**
```bash ```bash
curl -s -X POST http://localhost:8074/auth/register \ curl -s -X POST http://sinbad2.ujaen.es:8074/auth/register \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"email":"profesor@ejemplo.com","password":"ClaveSegura1","full_name":"María"}' -d '{"email":"profesor@ejemplo.com","password":"ClaveSegura1","full_name":"María"}'
``` ```
@@ -303,7 +303,7 @@ Todas requieren: `Authorization: Bearer <token>`
**Ejemplo mínimo:** **Ejemplo mínimo:**
```bash ```bash
curl -s -X POST http://localhost:8074/exam/templates \ curl -s -X POST http://sinbad2.ujaen.es:8074/exam/templates \
-H "Authorization: Bearer $TOKEN" \ -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
@@ -415,7 +415,7 @@ Sirven para **texto** que la IA puede usar al generar (PDF, DOCX, TXT, MD; imág
**Ejemplo:** **Ejemplo:**
```bash ```bash
curl -s -X POST "http://localhost:8074/exam/templates/TEMPLATE_UUID/materials" \ curl -s -X POST "http://sinbad2.ujaen.es:8074/exam/templates/TEMPLATE_UUID/materials" \
-H "Authorization: Bearer $TOKEN" \ -H "Authorization: Bearer $TOKEN" \
-F "file=@./apuntes.pdf" -F "file=@./apuntes.pdf"
``` ```
@@ -468,7 +468,7 @@ Solo para **mostrar en la pregunta** (Moodle); no rellenan el contexto de texto
**Ejemplo:** **Ejemplo:**
```bash ```bash
curl -s -X POST "http://localhost:8074/exam/templates/TEMPLATE_UUID/images" \ curl -s -X POST "http://sinbad2.ujaen.es:8074/exam/templates/TEMPLATE_UUID/images" \
-H "Authorization: Bearer $TOKEN" \ -H "Authorization: Bearer $TOKEN" \
-F "file=@./diagrama.png" \ -F "file=@./diagrama.png" \
-F "caption=Diagrama del modelo ER" -F "caption=Diagrama del modelo ER"
+1 -1
View File
@@ -31,7 +31,7 @@ docker compose up --build
La API queda disponible en: La API queda disponible en:
```text ```text
http://localhost:8074 http://sinbad2.ujaen.es:8074
``` ```
## Configuración ## Configuración
+1 -1
View File
@@ -6,7 +6,7 @@ API_KEY=change-me-in-production-min-16-chars
# --- Base de datos (Docker: host "db") --- # --- Base de datos (Docker: host "db") ---
DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes
# --- CORS (orígenes del frontend, separados por coma) --- # --- CORS (orígenes del frontend, separados por coma) ---
ALLOWED_ORIGINS=http://localhost:8075 ALLOWED_ORIGINS=http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075
# --- Rate limiting y tamaño de petición --- # --- Rate limiting y tamaño de petición ---
RATE_LIMIT_REQUESTS=60 RATE_LIMIT_REQUESTS=60
RATE_LIMIT_WINDOW_SECONDS=60 RATE_LIMIT_WINDOW_SECONDS=60
+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 DATABASE_URL=postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes
# --- CORS (orígenes del frontend, separados por coma) --- # --- CORS (orígenes del frontend, separados por coma) ---
ALLOWED_ORIGINS=http://localhost:8075 ALLOWED_ORIGINS=http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075
# --- Rate limiting y tamaño de petición --- # --- Rate limiting y tamaño de petición ---
RATE_LIMIT_REQUESTS=60 RATE_LIMIT_REQUESTS=60
+1 -1
View File
@@ -10,7 +10,7 @@ class Settings(BaseSettings):
api_prefix: str = "" api_prefix: str = ""
api_key: str = Field(min_length=16) api_key: str = Field(min_length=16)
database_url: str = "postgresql+psycopg://genexamenes:genexamenes@localhost:5432/genexamenes" database_url: str = "postgresql+psycopg://genexamenes:genexamenes@localhost:5432/genexamenes"
allowed_origins: str = "http://localhost:8075" allowed_origins: str = "http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075"
rate_limit_requests: int = Field(default=60, ge=1) rate_limit_requests: int = Field(default=60, ge=1)
rate_limit_window_seconds: 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) max_request_bytes: int = Field(default=1_048_576, ge=1_024)
+2 -2
View File
@@ -7,7 +7,7 @@ services:
environment: environment:
DATABASE_URL: postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes DATABASE_URL: postgresql+psycopg://genexamenes:genexamenes@db:5432/genexamenes
# Sobrescribe backend/.env con el puerto actual del frontend. # Sobrescribe backend/.env con el puerto actual del frontend.
ALLOWED_ORIGINS: http://localhost:8075 ALLOWED_ORIGINS: http://sinbad2.ujaen.es,http://sinbad2.ujaen.es:8075
LLM_BASE_URL: LLM_BASE_URL:
LLM_MODEL: qwen3.5:35b LLM_MODEL: qwen3.5:35b
LLM_TIMEOUT_SECONDS: "180" LLM_TIMEOUT_SECONDS: "180"
@@ -24,7 +24,7 @@ services:
build: build:
context: ./frontend context: ./frontend
args: args:
VITE_API_URL: ${VITE_API_URL:-http://localhost:8074} VITE_API_URL: ${VITE_API_URL:-http://sinbad2.ujaen.es:8074}
VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-} VITE_GOOGLE_CLIENT_ID: ${VITE_GOOGLE_CLIENT_ID:-}
ports: ports:
- "8075:80" - "8075:80"
+1 -1
View File
@@ -1,5 +1,5 @@
# URL base del backend (accesible desde el navegador) # URL base del backend (accesible desde el navegador)
VITE_API_URL=http://localhost:8074 VITE_API_URL=http://sinbad2.ujaen.es:8074
# (Opcional) Client ID de Google para "Iniciar sesión con Google". # (Opcional) Client ID de Google para "Iniciar sesión con Google".
# Debe coincidir con GOOGLE_CLIENT_ID del backend. # Debe coincidir con GOOGLE_CLIENT_ID del backend.
+1 -1
View File
@@ -2,7 +2,7 @@
FROM node:20-alpine AS build FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
ARG VITE_API_URL=http://localhost:8074 ARG VITE_API_URL=http://sinbad2.ujaen.es:8074
ARG VITE_GOOGLE_CLIENT_ID= ARG VITE_GOOGLE_CLIENT_ID=
ENV VITE_API_URL=$VITE_API_URL ENV VITE_API_URL=$VITE_API_URL
ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID
+4 -4
View File
@@ -25,20 +25,20 @@ src/
## Desarrollo local ## Desarrollo local
Requisitos: Node 20+ y el backend corriendo en `http://localhost:8074`. Requisitos: Node 20+ y el backend corriendo en `http://sinbad2.ujaen.es:8074`.
```bash ```bash
cd frontend cd frontend
cp .env.example .env # ajusta VITE_API_URL si es necesario cp .env.example .env # ajusta VITE_API_URL si es necesario
npm install npm install
npm run dev # http://localhost:8075 npm run dev # http://sinbad2.ujaen.es:8075
``` ```
## Variables de entorno ## Variables de entorno
| Variable | Descripción | | Variable | Descripción |
| ----------------------- | -------------------------------------------------------- | | ----------------------- | -------------------------------------------------------- |
| `VITE_API_URL` | URL base del backend (por defecto `http://localhost:8074`). | | `VITE_API_URL` | URL base del backend (por defecto `http://sinbad2.ujaen.es:8074`). |
| `VITE_GOOGLE_CLIENT_ID` | (Opcional) Client ID de Google. Si está vacío, se oculta el botón de Google. | | `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 > Las variables `VITE_*` se incrustan en el build, por lo que apuntan al backend
@@ -54,7 +54,7 @@ npm run preview # sirve el build localmente
## Docker ## Docker
El `docker-compose.yml` de la raíz construye el frontend con un build multi-stage El `docker-compose.yml` de la raíz construye el frontend con un build multi-stage
(Node → Nginx) y lo publica en `http://localhost:8075`: (Node → Nginx) y lo publica en `http://sinbad2.ujaen.es:8075`:
```bash ```bash
docker compose up --build docker compose up --build
+1 -1
View File
@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
export const API_URL = export const API_URL =
import.meta.env.VITE_API_URL?.replace(/\/$/, "") || "http://localhost:8074"; import.meta.env.VITE_API_URL?.replace(/\/$/, "") || "http://sinbad2.ujaen.es:8074";
const TOKEN_KEY = "genex_token"; const TOKEN_KEY = "genex_token";