feat: Añadir configuración de Nginx para API de OpenCCB y corregir URLs de producción
This commit is contained in:
+115
-62
@@ -66,7 +66,11 @@ El script preguntará:
|
|||||||
3. Contraseña (oculta)
|
3. Contraseña (oculta)
|
||||||
4. Nombre de la organización
|
4. Nombre de la organización
|
||||||
5. ¿Usar SSL? [y/N]
|
5. ¿Usar SSL? [y/N]
|
||||||
|
- **y**: Usará HTTPS (con o sin staging)
|
||||||
|
- **N**: Usará HTTP (recomendado para staging)
|
||||||
6. ¿Usar STAGING? [y/N] (solo si elegiste SSL)
|
6. ¿Usar STAGING? [y/N] (solo si elegiste SSL)
|
||||||
|
- **y**: Certificados de prueba (sin rate limits)
|
||||||
|
- **N**: Certificados reales (con rate limits)
|
||||||
|
|
||||||
### Conexión al servidor:
|
### Conexión al servidor:
|
||||||
|
|
||||||
@@ -77,41 +81,56 @@ cd /var/www/openccb
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ Problemas Conocidos (Para Resolver)
|
## ⚠️ Problemas Conocidos y Soluciones
|
||||||
|
|
||||||
### 1. Variables NEXT_PUBLIC en Studio
|
### 1. Login Pegado / Error de Conexión API
|
||||||
|
|
||||||
**Problema**: El contenedor `openccb-studio` no está recibiendo ambas variables `NEXT_PUBLIC`:
|
**Problema**: El botón de login se queda procesando infinitamente.
|
||||||
- ✅ `NEXT_PUBLIC_LMS_API_URL=http://learning.norteamericano.com`
|
|
||||||
- ❌ `NEXT_PUBLIC_CMS_API_URL` - **FALTA**
|
**Causa**: Las URLs de la API incluyen el puerto `:3001` incorrectamente.
|
||||||
|
|
||||||
**Solución Aplicada**:
|
**Solución Aplicada**:
|
||||||
1. Actualizado `web/studio/Dockerfile` para aceptar ambos ARG
|
- Actualizado `web/studio/src/lib/api.ts` para hardcodear las URLs de producción
|
||||||
2. Actualizado `docker-compose.yml` para pasar ambos argumentos
|
- El código ahora detecta el hostname y usa la URL sin puerto
|
||||||
3. Actualizado `deploy.sh` para verificar y reconstruir con `--no-cache`
|
|
||||||
|
|
||||||
**Comandos para Verificar**:
|
**Comandos para Solucionar**:
|
||||||
```bash
|
```bash
|
||||||
# En el servidor
|
# Conectarse al servidor
|
||||||
sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
ssh -i "ubuntu.pem" ubuntu@ec2-18-224-137-67.us-east-2.compute.amazonaws.com
|
||||||
|
|
||||||
# Debería mostrar:
|
|
||||||
# NEXT_PUBLIC_CMS_API_URL=http://studio.norteamericano.com
|
|
||||||
# NEXT_PUBLIC_LMS_API_URL=http://learning.norteamericano.com
|
|
||||||
```
|
|
||||||
|
|
||||||
**Si persiste el problema**:
|
|
||||||
```bash
|
|
||||||
# Reconstruir manualmente
|
|
||||||
cd /var/www/openccb
|
cd /var/www/openccb
|
||||||
|
|
||||||
|
# Detener todo
|
||||||
sudo docker compose down
|
sudo docker compose down
|
||||||
|
|
||||||
|
# Eliminar imágenes cacheadas
|
||||||
sudo docker rmi openccb-studio 2>/dev/null || true
|
sudo docker rmi openccb-studio 2>/dev/null || true
|
||||||
sudo docker builder prune -f
|
sudo docker images | grep openccb | awk '{print $3}' | xargs sudo docker rmi -f 2>/dev/null || true
|
||||||
|
|
||||||
|
# Limpiar caché de Docker
|
||||||
|
sudo docker builder prune -af
|
||||||
|
sudo docker system prune -af
|
||||||
|
|
||||||
|
# Reconstruir DESDE CERO (CRÍTICO usar --no-cache)
|
||||||
sudo docker compose build --no-cache studio
|
sudo docker compose build --no-cache studio
|
||||||
sudo docker compose up -d studio
|
|
||||||
sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
# Iniciar todo
|
||||||
|
sudo docker compose up -d
|
||||||
|
|
||||||
|
# Esperar 1 minuto
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# Verificar
|
||||||
|
sudo docker compose ps
|
||||||
|
docker logs openccb-studio --tail 20
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Verificación en el Navegador**:
|
||||||
|
1. Abrir ventana de incógnito (Ctrl+Shift+N)
|
||||||
|
2. Ir a `http://studio.norteamericano.com`
|
||||||
|
3. Abrir consola (F12) → Pestaña Network
|
||||||
|
4. Intentar loguearse
|
||||||
|
5. Verificar que la URL sea: `http://studio.norteamericano.com/auth/login` (SIN puerto)
|
||||||
|
|
||||||
### 2. Rate Limit de Let's Encrypt
|
### 2. Rate Limit de Let's Encrypt
|
||||||
|
|
||||||
**Problema**: Se alcanzó el límite de 5 certificados por semana.
|
**Problema**: Se alcanzó el límite de 5 certificados por semana.
|
||||||
@@ -129,13 +148,16 @@ sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
|||||||
### 1. `deploy.sh`
|
### 1. `deploy.sh`
|
||||||
- ✅ Pregunta datos del administrador
|
- ✅ Pregunta datos del administrador
|
||||||
- ✅ Pregunta sobre SSL y Staging
|
- ✅ Pregunta sobre SSL y Staging
|
||||||
- ✅ Actualiza docker-compose.yml según elección
|
- ✅ Actualiza docker-compose.yml según elección (HTTP/HTTPS)
|
||||||
- ✅ Reconstruye contenedores con `--no-cache`
|
- ✅ Reconstruye contenedores con `--no-cache`
|
||||||
- ✅ Verifica variables de entorno
|
- ✅ Verifica variables de entorno en los contenedores
|
||||||
|
- ✅ Muestra URLs correctas según protocolo elegido
|
||||||
|
|
||||||
### 2. `docker-compose.yml`
|
### 2. `docker-compose.yml`
|
||||||
- ✅ URLs en HTTP por defecto
|
- ✅ URLs en HTTP por defecto
|
||||||
- ✅ Ambos argumentos de build para studio
|
- ✅ Ambos argumentos de build para studio:
|
||||||
|
- `NEXT_PUBLIC_CMS_API_URL: http://studio.norteamericano.com`
|
||||||
|
- `NEXT_PUBLIC_LMS_API_URL: http://learning.norteamericano.com`
|
||||||
- ✅ Variables de entorno correctas
|
- ✅ Variables de entorno correctas
|
||||||
|
|
||||||
### 3. `web/studio/Dockerfile`
|
### 3. `web/studio/Dockerfile`
|
||||||
@@ -143,7 +165,9 @@ sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
|||||||
- ✅ Agrega `ENV NEXT_PUBLIC_LMS_API_URL`
|
- ✅ Agrega `ENV NEXT_PUBLIC_LMS_API_URL`
|
||||||
|
|
||||||
### 4. `web/studio/src/lib/api.ts`
|
### 4. `web/studio/src/lib/api.ts`
|
||||||
- ✅ Corrige función `getApiBaseUrl` para priorizar variable de entorno
|
- ✅ Corrige función `getApiBaseUrl` para producción
|
||||||
|
- ✅ Hardcodea URLs para `studio.norteamericano.com` y `learning.norteamericano.com`
|
||||||
|
- ✅ Elimina el puerto de las URLs en producción
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -186,6 +210,10 @@ sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
|||||||
|
|
||||||
# Experience
|
# Experience
|
||||||
sudo docker exec openccb-experience env | grep NEXT_PUBLIC
|
sudo docker exec openccb-experience env | grep NEXT_PUBLIC
|
||||||
|
|
||||||
|
# Debería mostrar:
|
||||||
|
# NEXT_PUBLIC_CMS_API_URL=http://studio.norteamericano.com
|
||||||
|
# NEXT_PUBLIC_LMS_API_URL=http://learning.norteamericano.com
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reconstruir contenedores
|
### Reconstruir contenedores
|
||||||
@@ -199,6 +227,15 @@ sudo docker compose build --no-cache studio
|
|||||||
sudo docker compose up -d studio
|
sudo docker compose up -d studio
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Limpiar caché de Docker
|
||||||
|
```bash
|
||||||
|
# Limpiar builder
|
||||||
|
sudo docker builder prune -af
|
||||||
|
|
||||||
|
# Limpiar sistema
|
||||||
|
sudo docker system prune -af
|
||||||
|
```
|
||||||
|
|
||||||
### Verificar certificados SSL
|
### Verificar certificados SSL
|
||||||
```bash
|
```bash
|
||||||
docker logs acme-companion --tail 50
|
docker logs acme-companion --tail 50
|
||||||
@@ -206,30 +243,37 @@ docker logs acme-companion --tail 50
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Próximos Pasos (Para Continuar Mañana)
|
## 🎯 Próximos Pasos
|
||||||
|
|
||||||
### 1. Verificar Variables NEXT_PUBLIC
|
### 1. Reconstruir Studio con --no-cache
|
||||||
```bash
|
```bash
|
||||||
ssh -i "ubuntu.pem" ubuntu@ec2-18-224-137-67.us-east-2.compute.amazonaws.com
|
ssh -i "ubuntu.pem" ubuntu@ec2-18-224-137-67.us-east-2.compute.amazonaws.com
|
||||||
cd /var/www/openccb
|
cd /var/www/openccb
|
||||||
|
|
||||||
# Verificar
|
|
||||||
sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
|
||||||
|
|
||||||
# Si falta CMS_API_URL, reconstruir:
|
|
||||||
sudo docker compose down
|
sudo docker compose down
|
||||||
sudo docker rmi openccb-studio 2>/dev/null || true
|
sudo docker rmi openccb-studio 2>/dev/null || true
|
||||||
sudo docker builder prune -f
|
sudo docker builder prune -af
|
||||||
sudo docker compose build --no-cache studio
|
sudo docker compose build --no-cache studio
|
||||||
sudo docker compose up -d studio
|
sudo docker compose up -d
|
||||||
|
sleep 60
|
||||||
|
sudo docker compose ps
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Probar Login
|
### 2. Probar Login
|
||||||
- Acceder a `http://studio.norteamericano.com`
|
- Abrir ventana de incógnito
|
||||||
- Intentar loguearse con las credenciales del admin
|
- Ir a `http://studio.norteamericano.com`
|
||||||
- Verificar que no haya errores de conexión
|
- Ver consola (F12) → Network
|
||||||
|
- Verificar URL: `http://studio.norteamericano.com/auth/login`
|
||||||
|
- Intentar loguearse
|
||||||
|
|
||||||
### 3. Cambiar a HTTPS (Después del Rate Limit)
|
### 3. Verificar Funcionalidades
|
||||||
|
- [ ] Login de administrador
|
||||||
|
- [ ] Creación de cursos
|
||||||
|
- [ ] Subida de archivos
|
||||||
|
- [ ] Integración con LMS
|
||||||
|
- [ ] Certificados SSL generados
|
||||||
|
|
||||||
|
### 4. Cambiar a HTTPS (Después del Rate Limit)
|
||||||
```bash
|
```bash
|
||||||
# Después del 2026-03-27
|
# Después del 2026-03-27
|
||||||
./deploy.sh
|
./deploy.sh
|
||||||
@@ -237,29 +281,21 @@ sudo docker compose up -d studio
|
|||||||
# Responder "n" a "¿Usar STAGING?"
|
# Responder "n" a "¿Usar STAGING?"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Verificar Funcionalidades
|
|
||||||
- [ ] Login de administrador
|
|
||||||
- [ ] Creación de cursos
|
|
||||||
- [ ] Subida de archivos
|
|
||||||
- [ ] Integración con LMS
|
|
||||||
- [ ] Certificados SSL generados
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Notas Importantes
|
|
||||||
|
|
||||||
1. **HTTP vs HTTPS**: Actualmente se usa HTTP porque los certificados de staging no son válidos para las llamadas API entre dominios.
|
|
||||||
|
|
||||||
2. **Rate Limit**: Let's Encrypt permite 5 certificados por semana por dominio. El límite se reinicia el 2026-03-27.
|
|
||||||
|
|
||||||
3. **Variables de Entorno**: Es crítico que ambos `NEXT_PUBLIC_*` estén presentes en el contenedor de Studio para que las llamadas API funcionen correctamente.
|
|
||||||
|
|
||||||
4. **Reconstrucción**: Siempre usar `--no-cache` al reconstruir para asegurar que los cambios en las variables de entorno se apliquen.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Solución de Problemas Comunes
|
## 🔧 Solución de Problemas Comunes
|
||||||
|
|
||||||
|
### Login se queda procesando
|
||||||
|
```bash
|
||||||
|
# Verificar URL en consola del navegador
|
||||||
|
# Debe ser: http://studio.norteamericano.com/auth/login
|
||||||
|
# NO debe tener :3001
|
||||||
|
|
||||||
|
# Si tiene puerto, reconstruir con --no-cache
|
||||||
|
sudo docker compose build --no-cache studio
|
||||||
|
sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
### Error 502 Bad Gateway
|
### Error 502 Bad Gateway
|
||||||
```bash
|
```bash
|
||||||
# Verificar que los servicios están corriendo
|
# Verificar que los servicios están corriendo
|
||||||
@@ -278,8 +314,8 @@ sudo docker compose restart
|
|||||||
# Ver docker-compose.yml
|
# Ver docker-compose.yml
|
||||||
cat docker-compose.yml | grep -A 10 "studio:"
|
cat docker-compose.yml | grep -A 10 "studio:"
|
||||||
|
|
||||||
# Verificar argumentos
|
# Verificar en contenedor
|
||||||
sudo docker compose config | grep NEXT_PUBLIC
|
sudo docker exec openccb-studio env | grep NEXT_PUBLIC
|
||||||
|
|
||||||
# Reconstruir
|
# Reconstruir
|
||||||
sudo docker compose build --no-cache studio
|
sudo docker compose build --no-cache studio
|
||||||
@@ -300,6 +336,22 @@ sudo netstat -tlnp | grep :80
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 📝 Notas Importantes
|
||||||
|
|
||||||
|
1. **HTTP vs HTTPS**: Actualmente se usa HTTP porque:
|
||||||
|
- Los certificados de staging no son válidos para producción
|
||||||
|
- Las llamadas API entre dominios requieren HTTP o certificados válidos
|
||||||
|
|
||||||
|
2. **Rate Limit**: Let's Encrypt permite 5 certificados por semana por dominio. El límite se reinicia el 2026-03-27.
|
||||||
|
|
||||||
|
3. **--no-cache es CRÍTICO**: Siempre usar `--no-cache` al reconstruir Studio para que los cambios en el código se apliquen. Docker usa caché por defecto.
|
||||||
|
|
||||||
|
4. **Ventana de Incógnito**: Después de reconstruir, siempre probar en ventana de incógnito para evitar caché del navegador.
|
||||||
|
|
||||||
|
5. **URLs Hardcodeadas**: El código ahora tiene las URLs de producción hardcodeadas para `studio.norteamericano.com` y `learning.norteamericano.com`. Esto evita problemas con variables de entorno.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📞 Contacto y Soporte
|
## 📞 Contacto y Soporte
|
||||||
|
|
||||||
**Documentación**:
|
**Documentación**:
|
||||||
@@ -311,9 +363,10 @@ sudo netstat -tlnp | grep :80
|
|||||||
- `/var/www/openccb/.env` - Variables de entorno
|
- `/var/www/openccb/.env` - Variables de entorno
|
||||||
- `/var/www/openccb/docker-compose.yml` - Servicios Docker
|
- `/var/www/openccb/docker-compose.yml` - Servicios Docker
|
||||||
- `web/studio/Dockerfile` - Build de Studio
|
- `web/studio/Dockerfile` - Build de Studio
|
||||||
|
- `web/studio/src/lib/api.ts` - Configuración de APIs
|
||||||
- `deploy.sh` - Script de despliegue
|
- `deploy.sh` - Script de despliegue
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Última Actualización**: 26 de Marzo de 2026
|
**Última Actualización**: 26 de Marzo de 2026
|
||||||
**Estado**: Pendiente verificar variables NEXT_PUBLIC en Studio
|
**Estado**: ✅ Solución aplicada - Pendiente reconstruir con --no-cache y probar login
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ mkdir -p "$PROD_DIR/nginx"
|
|||||||
if [ -f "nginx/proxy.conf" ]; then
|
if [ -f "nginx/proxy.conf" ]; then
|
||||||
cp nginx/proxy.conf "$PROD_DIR/nginx/"
|
cp nginx/proxy.conf "$PROD_DIR/nginx/"
|
||||||
fi
|
fi
|
||||||
|
if [ -f "nginx/studio.conf" ]; then
|
||||||
|
cp nginx/studio.conf "$PROD_DIR/nginx/"
|
||||||
|
fi
|
||||||
|
|
||||||
echo " ✅ Archivos esenciales copiados"
|
echo " ✅ Archivos esenciales copiados"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ services:
|
|||||||
- vhost:/etc/nginx/vhost.d
|
- vhost:/etc/nginx/vhost.d
|
||||||
- html:/usr/share/nginx/html
|
- html:/usr/share/nginx/html
|
||||||
- ./nginx/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro
|
- ./nginx/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro
|
||||||
- ./nginx/studio.norteamericano.com:/etc/nginx/vhost.d/studio.norteamericano.com:ro
|
- ./nginx/studio.conf:/etc/nginx/vhost.d/studio.norteamericano.com:ro
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- openccb-network
|
- openccb-network
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Custom nginx proxy configuration for OpenCCB
|
||||||
|
# This file configures routing for Studio (CMS) API endpoints
|
||||||
|
|
||||||
|
# Map to detect API requests that should go to port 3001
|
||||||
|
map $uri $api_backend {
|
||||||
|
default 0;
|
||||||
|
~/auth/ 1;
|
||||||
|
~/courses/ 1;
|
||||||
|
~/modules/ 1;
|
||||||
|
~/lessons/ 1;
|
||||||
|
~/assets/ 1;
|
||||||
|
~/organization/ 1;
|
||||||
|
~/users/ 1;
|
||||||
|
~/grading/ 1;
|
||||||
|
~/question-bank/ 1;
|
||||||
|
~/test-templates/ 1;
|
||||||
|
~/admin/ 1;
|
||||||
|
~/api/ 1;
|
||||||
|
~/branding 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Override upstream for API requests
|
||||||
|
map $api_backend $upstream_addr_override {
|
||||||
|
0 "";
|
||||||
|
1 "openccb-studio:3001";
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
# Custom nginx configuration for OpenCCB Studio
|
||||||
|
# This overrides the default location block to route API requests correctly
|
||||||
|
|
||||||
|
# API routes that need to go to port 3001
|
||||||
|
location = /auth/login {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /auth/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /organization {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /organization/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /branding {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /courses/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /modules/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /lessons/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /assets/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /users/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /grading/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /question-bank/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /admin/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://openccb-studio:3001;
|
||||||
|
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 $http_x_forwarded_proto;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
}
|
||||||
+40
-30
@@ -1,17 +1,27 @@
|
|||||||
const getApiBaseUrl = (defaultPort: string, envVar?: string) => {
|
const getApiBaseUrl = (defaultPort: string, envVar?: string) => {
|
||||||
// Si hay una variable de entorno definida, usarla siempre
|
// Producción - dominios específicos
|
||||||
if (envVar && envVar.trim() !== '') {
|
|
||||||
return envVar;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback para desarrollo local
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
const protocol = window.location.protocol;
|
const protocol = window.location.protocol;
|
||||||
|
|
||||||
|
// Forzar URLs correctas para producción (sin puerto)
|
||||||
|
if (hostname === 'studio.norteamericano.com') {
|
||||||
|
return `${protocol}//studio.norteamericano.com`;
|
||||||
|
}
|
||||||
|
if (hostname === 'learning.norteamericano.com') {
|
||||||
|
return `${protocol}//learning.norteamericano.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usar variable de entorno si está definida
|
||||||
|
if (envVar && envVar.trim() !== '') {
|
||||||
|
return envVar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desarrollo local
|
||||||
return `${protocol}//${hostname}:${defaultPort}`;
|
return `${protocol}//${hostname}:${defaultPort}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `http://localhost:${defaultPort}`;
|
return envVar || `http://localhost:${defaultPort}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const API_BASE_URL = getApiBaseUrl("3001", process.env.NEXT_PUBLIC_CMS_API_URL);
|
export const API_BASE_URL = getApiBaseUrl("3001", process.env.NEXT_PUBLIC_CMS_API_URL);
|
||||||
@@ -800,7 +810,7 @@ export const cmsApi = {
|
|||||||
getLesson: (id: string): Promise<Lesson> => apiFetch(`/lessons/${id}`),
|
getLesson: (id: string): Promise<Lesson> => apiFetch(`/lessons/${id}`),
|
||||||
updateLesson: (id: string, payload: Partial<Lesson>): Promise<Lesson> => apiFetch(`/lessons/${id}`, { method: 'PUT', body: JSON.stringify(payload) }),
|
updateLesson: (id: string, payload: Partial<Lesson>): Promise<Lesson> => apiFetch(`/lessons/${id}`, { method: 'PUT', body: JSON.stringify(payload) }),
|
||||||
summarizeLesson: (id: string): Promise<Lesson> => apiFetch(`/lessons/${id}/summarize`, { method: 'POST' }),
|
summarizeLesson: (id: string): Promise<Lesson> => apiFetch(`/lessons/${id}/summarize`, { method: 'POST' }),
|
||||||
async generateQuiz(lessonId: string, payload: { prompt_hint?: string, quiz_type?: string }): Promise<any> {
|
async generateQuiz(lessonId: string, payload: { prompt_hint?: string, quiz_type?: string }): Promise<{ questions: QuizQuestion[] }> {
|
||||||
return apiFetch(`/lessons/${lessonId}/generate-quiz`, {
|
return apiFetch(`/lessons/${lessonId}/generate-quiz`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
@@ -1019,7 +1029,7 @@ export const cmsApi = {
|
|||||||
|
|
||||||
// Test Templates
|
// Test Templates
|
||||||
listTestTemplates: (filters?: TestTemplateFilters): Promise<TestTemplate[]> =>
|
listTestTemplates: (filters?: TestTemplateFilters): Promise<TestTemplate[]> =>
|
||||||
apiFetch('/test-templates', { method: 'GET', query: filters as any }, false),
|
apiFetch('/test-templates', { method: 'GET', query: filters as Record<string, string | number | boolean | undefined | null> }, false),
|
||||||
getTestTemplate: (templateId: string): Promise<TestTemplateWithQuestions> =>
|
getTestTemplate: (templateId: string): Promise<TestTemplateWithQuestions> =>
|
||||||
apiFetch(`/test-templates/${templateId}`, {}, false),
|
apiFetch(`/test-templates/${templateId}`, {}, false),
|
||||||
createTestTemplate: (payload: CreateTestTemplatePayload): Promise<TestTemplate> =>
|
createTestTemplate: (payload: CreateTestTemplatePayload): Promise<TestTemplate> =>
|
||||||
@@ -1055,13 +1065,13 @@ export interface QuestionBank {
|
|||||||
organization_id: string;
|
organization_id: string;
|
||||||
question_text: string;
|
question_text: string;
|
||||||
question_type: QuestionBankType;
|
question_type: QuestionBankType;
|
||||||
options?: any;
|
options?: unknown;
|
||||||
correct_answer?: any;
|
correct_answer?: unknown;
|
||||||
explanation?: string;
|
explanation?: string;
|
||||||
audio_url?: string;
|
audio_url?: string;
|
||||||
audio_text?: string;
|
audio_text?: string;
|
||||||
audio_status?: 'pending' | 'generating' | 'ready' | 'failed';
|
audio_status?: 'pending' | 'generating' | 'ready' | 'failed';
|
||||||
audio_metadata?: any;
|
audio_metadata?: unknown;
|
||||||
media_url?: string;
|
media_url?: string;
|
||||||
media_type?: string;
|
media_type?: string;
|
||||||
points: number;
|
points: number;
|
||||||
@@ -1069,7 +1079,7 @@ export interface QuestionBank {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
skill_assessed?: 'reading' | 'listening' | 'speaking' | 'writing';
|
skill_assessed?: 'reading' | 'listening' | 'speaking' | 'writing';
|
||||||
source?: 'manual' | 'ai-generated' | 'imported-mysql' | 'imported-csv';
|
source?: 'manual' | 'ai-generated' | 'imported-mysql' | 'imported-csv';
|
||||||
source_metadata?: any;
|
source_metadata?: unknown;
|
||||||
usage_count?: number;
|
usage_count?: number;
|
||||||
last_used_at?: string;
|
last_used_at?: string;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
@@ -1091,8 +1101,8 @@ export interface QuestionBankFilters {
|
|||||||
export interface CreateQuestionBankPayload {
|
export interface CreateQuestionBankPayload {
|
||||||
question_text: string;
|
question_text: string;
|
||||||
question_type: QuestionBankType;
|
question_type: QuestionBankType;
|
||||||
options?: any;
|
options?: unknown;
|
||||||
correct_answer?: any;
|
correct_answer?: unknown;
|
||||||
explanation?: string;
|
explanation?: string;
|
||||||
points?: number;
|
points?: number;
|
||||||
difficulty?: string;
|
difficulty?: string;
|
||||||
@@ -1107,8 +1117,8 @@ export interface CreateQuestionBankPayload {
|
|||||||
export interface UpdateQuestionBankPayload {
|
export interface UpdateQuestionBankPayload {
|
||||||
question_text?: string;
|
question_text?: string;
|
||||||
question_type?: QuestionBankType;
|
question_type?: QuestionBankType;
|
||||||
options?: any;
|
options?: unknown;
|
||||||
correct_answer?: any;
|
correct_answer?: unknown;
|
||||||
explanation?: string;
|
explanation?: string;
|
||||||
points?: number;
|
points?: number;
|
||||||
difficulty?: string;
|
difficulty?: string;
|
||||||
@@ -1122,7 +1132,7 @@ export interface UpdateQuestionBankPayload {
|
|||||||
|
|
||||||
export const questionBankApi = {
|
export const questionBankApi = {
|
||||||
list: (filters?: QuestionBankFilters): Promise<QuestionBank[]> =>
|
list: (filters?: QuestionBankFilters): Promise<QuestionBank[]> =>
|
||||||
apiFetch('/question-bank', { method: 'GET', query: filters as any }, false),
|
apiFetch('/question-bank', { method: 'GET', query: filters as Record<string, string | number | boolean | undefined | null> }, false),
|
||||||
get: (id: string): Promise<QuestionBank> =>
|
get: (id: string): Promise<QuestionBank> =>
|
||||||
apiFetch(`/question-bank/${id}`, {}, false),
|
apiFetch(`/question-bank/${id}`, {}, false),
|
||||||
create: (payload: CreateQuestionBankPayload): Promise<QuestionBank> =>
|
create: (payload: CreateQuestionBankPayload): Promise<QuestionBank> =>
|
||||||
@@ -1265,7 +1275,7 @@ export interface Badge {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon_url: string;
|
icon_url: string;
|
||||||
criteria: any;
|
criteria: unknown;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1320,7 +1330,7 @@ export interface TestTemplate {
|
|||||||
passing_score: number;
|
passing_score: number;
|
||||||
total_points: number;
|
total_points: number;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
template_data: any;
|
template_data: unknown;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
usage_count: number;
|
usage_count: number;
|
||||||
@@ -1337,7 +1347,7 @@ export interface TestTemplateSection {
|
|||||||
section_order: number;
|
section_order: number;
|
||||||
points: number;
|
points: number;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
section_data?: any;
|
section_data?: unknown;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1348,11 +1358,11 @@ export interface TestTemplateQuestion {
|
|||||||
question_order: number;
|
question_order: number;
|
||||||
question_type: QuestionType;
|
question_type: QuestionType;
|
||||||
question_text: string;
|
question_text: string;
|
||||||
options?: any;
|
options?: unknown;
|
||||||
correct_answer?: any;
|
correct_answer?: unknown;
|
||||||
explanation?: string;
|
explanation?: string;
|
||||||
points: number;
|
points: number;
|
||||||
metadata?: any;
|
metadata?: unknown;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1373,7 +1383,7 @@ export interface CreateTestTemplatePayload {
|
|||||||
passing_score: number;
|
passing_score: number;
|
||||||
total_points: number;
|
total_points: number;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
template_data: any;
|
template_data: unknown;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1388,7 +1398,7 @@ export interface UpdateTestTemplatePayload {
|
|||||||
passing_score?: number;
|
passing_score?: number;
|
||||||
total_points?: number;
|
total_points?: number;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
template_data?: any;
|
template_data?: unknown;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
}
|
}
|
||||||
@@ -1398,11 +1408,11 @@ export interface CreateQuestionPayload {
|
|||||||
question_order: number;
|
question_order: number;
|
||||||
question_type: string;
|
question_type: string;
|
||||||
question_text: string;
|
question_text: string;
|
||||||
options?: any;
|
options?: unknown;
|
||||||
correct_answer?: any;
|
correct_answer?: unknown;
|
||||||
explanation?: string;
|
explanation?: string;
|
||||||
points: number;
|
points: number;
|
||||||
metadata?: any;
|
metadata?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateSectionPayload {
|
export interface CreateSectionPayload {
|
||||||
@@ -1411,7 +1421,7 @@ export interface CreateSectionPayload {
|
|||||||
section_order: number;
|
section_order: number;
|
||||||
points: number;
|
points: number;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
section_data?: any;
|
section_data?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TestTemplateFilters {
|
export interface TestTemplateFilters {
|
||||||
|
|||||||
Reference in New Issue
Block a user