From cde24cbde97d596f4791a3547268e75d1487ece2 Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Thu, 16 Apr 2026 08:38:13 +0200 Subject: [PATCH 01/30] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..023fc56 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# orcid_system +Sistema en Python para la recolección automática de publicaciones desde ORCID y su exportación al protocolo SWORD con sincronización mensual programada. From bf35fcaaf3f6bb69592318ca1d468a191857fb7d Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Fri, 8 May 2026 13:41:49 +0200 Subject: [PATCH 02/30] Revise README with project overview and tech stack Updated project description, added tech stack badges, and detailed project overview. --- README.md | 336 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 334 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 023fc56..74828d3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,334 @@ -# orcid_system -Sistema en Python para la recolección automática de publicaciones desde ORCID y su exportación al protocolo SWORD con sincronización mensual programada. +# ORCID SWORD System + +
+ +![FastAPI](https://img.shields.io/badge/FastAPI-109989?style=for-the-badge&logo=fastapi&logoColor=white) +![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white) +![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB) +![Vite](https://img.shields.io/badge/Vite-646CFF?style=for-the-badge&logo=vite&logoColor=white) +![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white) +![Redis](https://img.shields.io/badge/Redis-D82C20?style=for-the-badge&logo=redis&logoColor=white) +![Docker](https://img.shields.io/badge/Docker-0db7ed?style=for-the-badge&logo=docker&logoColor=white) +![ORCID OAuth](https://img.shields.io/badge/OAuth2-ORCID-A6CE39?style=for-the-badge) + +
+ +
+ Full-stack platform for ORCID authentication, researcher synchronization, and publication export in SWORD XML / ZIP formats. +
+ +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Overview: What is this project meant + +`orcid-system` is designed for research workflows where ORCID data must be ingested, normalized, and exported. + +Core capabilities: + +- ORCID OAuth 3-legged login +- researcher search and synchronization against ORCID +- publication export by selection or by researcher +- export formats: **SWORD XML** and **ZIP** +- dual access model: user JWT or service API key (for export) + +> [!NOTE] +> The stack is local-first with Docker, but includes production-oriented hardening (CORS policy, trusted hosts, security headers, rate limiting, etc.). + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Tech Stack + +### Backend +- FastAPI +- SQLAlchemy +- PostgreSQL +- Redis +- python-jose (JWT) +- slowapi (rate limit) +- APScheduler +- httpx + +### Frontend +- React 19 +- Vite 8 +- React Router +- TailwindCSS 4 +- Sonner (notifications) + +### Infrastructure +- Docker / Docker Compose + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Quick Start + +From the project root: + +```bash +docker compose down +docker compose up --build +``` + +Default local URLs: + +- Frontend: `http://localhost:5173` +- Backend: `http://localhost:8000` + +> [!IMPORTANT] +> Current compose mapping uses loopback binding: +> - `127.0.0.1:5173:5173` +> - `127.0.0.1:8000:8000` +> This means services are reachable from the host machine, not exposed publicly by default. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Environment Configuration + +Backend: +- Main file: `backend/.env` +- Reference: `backend/.env.example` + +Frontend: +- Compose/dev file: `frontend/.env` +- Optional local override for host dev: `frontend/.env.local` + +Important backend variables: + +- `ORCID_CLIENT_ID` +- `ORCID_CLIENT_SECRET` +- `ORCID_REDIRECT_URI` +- `JWT_SECRET` +- `API_KEY_NAME` +- `API_KEY_VALUE` +- `CORS_ALLOWED_ORIGINS` +- `TRUSTED_HOSTS` +- `DATABASE_URL` +- `REDIS_URL` + +Important frontend variables: + +- `VITE_API_URL` (empty in Docker setup, so Vite proxy handles `/api`) +- `VITE_API_PROXY_TARGET` (defaults to `http://backend:8000` in compose network) +- `VITE_API_KEY` (must match backend `API_KEY_VALUE` when using API key mode) +- `VITE_USE_MOCKS` +- `VITE_ORCID_PUBLIC_API_BASE` (optional override) + +> [!WARNING] +> Never commit real production secrets. Rotate `JWT_SECRET`, `API_KEY_VALUE`, and `ORCID_CLIENT_SECRET` before deployment. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). ngrok Bridge for Local OAuth Callback + +To test OAuth callback from ORCID in local environments, compose can inject a public callback URL: + +```yaml +environment: + ORCID_REDIRECT_URI: https://jargon-supreme-palpable.ngrok-free.dev/callback +``` + +> [!NOTE] +> Values under `docker-compose.yml -> services.backend.environment` override `backend/.env` inside the container. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). API Endpoints + +Base backend URL: `http://localhost:8000` + +| Module | Method | Endpoint | Auth | Parameters | Body | Notes | +| :--- | :---: | :--- | :--- | :--- | :--- | ---: | +| Health | `GET` | `/health` | None | None | None | Liveness check | +| ORCID Auth | `GET` | `/api/auth/orcid/authorize` | None | None | None | Redirects to ORCID | +| ORCID Auth | `GET` | `/api/auth/orcid/callback` | None | `code`, `state` | None | Exchanges OAuth code for backend JWT | +| ORCID Auth | `GET` | `/callback` | None | `code`, `state` | None | Alias for callback flow | +| Researchers | `POST` | `/api/researchers/search` | Optional Bearer | None | `{"orcid_ids":[...]}` | Batch search/sync | +| Researchers | `POST` | `/api/researchers/{orcid_id}/sync` | Optional Bearer | `orcid_id` | None | Full sync of one researcher | +| Export SWORD | `POST` | `/api/export/sword/publications` | Bearer or API key | None | `["publication_uuid", ...]` | Export selected publications | +| Export SWORD | `GET` | `/api/export/sword/researcher/{orcid_id}` | Bearer or API key | `orcid_id` | None | Export all by researcher | +| Export ZIP | `POST` | `/api/export/zip/publications` | Bearer or API key | None | `["publication_uuid", ...]` | Export selected publications | +| Export ZIP | `GET` | `/api/export/zip/researcher/{orcid_id}` | Bearer or API key | `orcid_id` | None | Export all by researcher | + +> [!IMPORTANT] +> `.../publications` endpoints require **publication IDs**, not `researcher.id`. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Request Examples + +### Health +```bash +curl http://localhost:8000/health +``` + +### Search one or more researchers +```bash +curl -X POST "http://localhost:8000/api/researchers/search" \ + -H "Content-Type: application/json" \ + -d "{\"orcid_ids\":[\"0009-0000-0793-5376\"]}" +``` + +### Sync one researcher +```bash +curl -X POST "http://localhost:8000/api/researchers/0009-0000-0793-5376/sync" +``` + +### Export SWORD by researcher (API key mode) +```bash +curl "http://localhost:8000/api/export/sword/researcher/0009-0000-0793-5376" \ + -H "X-API-Key: YOUR_API_KEY" \ + -o sword.xml +``` + +### Export ZIP by publication IDs (Bearer mode) +```bash +curl -X POST "http://localhost:8000/api/export/zip/publications" \ + -H "Authorization: Bearer YOUR_JWT" \ + -H "Content-Type: application/json" \ + -d "[\"04f6a2a6-b753-4432-982b-b88160f627fe\"]" \ + -o export.zip +``` + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Security Controls + +Implemented controls in backend: + +- strict CORS allowlist +- trusted host filtering +- request body size limit +- OAuth `state` validation +- JWT validation with issuer/audience claims +- API key validation via constant-time comparison +- rate limiting +- security headers middleware +- non-root container and reduced privileges + +> [!WARNING] +> For production, enforce HTTPS behind a reverse proxy and set concrete values for `CORS_ALLOWED_ORIGINS` and `TRUSTED_HOSTS`. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Frontend Details + +The frontend is a React SPA using route-based navigation and a centralized API client. + +### Frontend routing + +- `/` → landing page +- `/dashboard/:orcid` → researcher dashboard +- `/group` → multi-researcher results +- `/callback` → OAuth callback handler + +### Frontend API behavior (`frontend/src/services/api.js`) + +- central HTTP wrapper with `ApiError` +- includes `X-API-Key` on requests when configured +- includes `Authorization: Bearer ` when token exists in `localStorage` +- supports mock mode through `VITE_USE_MOCKS` +- supports Vite proxy mode when `VITE_API_URL` is empty + +### Vite proxy (`frontend/vite.config.js`) + +- `/api` proxied to `VITE_API_PROXY_TARGET` (`http://backend:8000` in compose) +- `/health` proxied to same target +- dev host settings allow tunnel scenarios (ngrok callback testing) + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Project Structure + +```text +orcid-system/ +├── backend/ +│ ├── app/ +│ │ ├── api/ +│ │ │ ├── auth.py +│ │ │ ├── researchers.py +│ │ │ └── export.py +│ │ ├── core/ +│ │ │ ├── config.py +│ │ │ ├── rate_limit.py +│ │ │ ├── security_headers.py +│ │ │ ├── error_handlers.py +│ │ │ └── body_size.py +│ │ ├── db/ +│ │ │ ├── models.py +│ │ │ ├── session.py +│ │ │ └── repositories/ +│ │ ├── security/ +│ │ │ ├── jwt.py +│ │ │ ├── api_key.py +│ │ │ └── oauth_state.py +│ │ ├── services/ +│ │ │ ├── orcid_client.py +│ │ │ ├── sword_generator.py +│ │ │ ├── zip_generator.py +│ │ │ └── sync_service.py +│ │ ├── utils/ +│ │ └── main.py +│ ├── .env +│ ├── .env.example +│ ├── .env.production +│ ├── Dockerfile +│ └── requirements.txt +├── frontend/ +│ ├── src/ +│ │ ├── components/ +│ │ │ ├── dashboard/ +│ │ │ ├── layout/ +│ │ │ └── ui/ +│ │ ├── contexts/ +│ │ │ └── AuthContext.jsx +│ │ ├── pages/ +│ │ │ ├── LandingPage.jsx +│ │ │ ├── DashboardPage.jsx +│ │ │ ├── GroupResultsPage.jsx +│ │ │ └── AuthCallbackPage.jsx +│ │ ├── services/ +│ │ │ ├── api.js +│ │ │ └── mocks.js +│ │ ├── utils/ +│ │ ├── App.jsx +│ │ └── main.jsx +│ ├── .env +│ ├── package.json +│ ├── vite.config.js +│ └── eslint.config.js +├── docker-compose.yml +└── README.md +``` + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Production Checklist + +- [ ] `ENVIRONMENT=production` +- [ ] `DEBUG=false` +- [ ] rotate all secrets +- [ ] define strict `CORS_ALLOWED_ORIGINS` +- [ ] define strict `TRUSTED_HOSTS` +- [ ] enforce HTTPS via reverse proxy +- [ ] keep DB/Redis private +- [ ] configure monitoring and backups + +--- + +## ![github](https://www.readmecodegen.com/api/social-icon?name=github&size=20&color=%238b5cf6). Authors and Team + +This project is the result of the collaboration with the **University of Jaén**. + +| Role | Developer | GitHub | +| :--- | :--- | :--- | +| **Frontend** | Alexis López Moral | [@AlexisLopez-Dev](https://github.com/AlexisLopez-Dev) | +| **Backend** | Mireya Cueto Garrido | [@MireyaCueto](https://github.com/MireyaCueto) | + +### Direction +* **Proyect Supervisor:** Luis Martínez López + +--- + +

+ Built with professional care and ❤️ for secure research data workflows at the University of Jaén. +

From c190c96752869da27ade4cd22e8d1bfe1d607273 Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Fri, 8 May 2026 14:00:46 +0200 Subject: [PATCH 03/30] Remove color parameter from certificate icons --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 74828d3..1e6e5da 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Overview: What is this project meant +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Overview: What is this project meant `orcid-system` is designed for research workflows where ORCID data must be ingested, normalized, and exported. @@ -36,7 +36,7 @@ Core capabilities: --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Tech Stack +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Tech Stack ### Backend - FastAPI @@ -60,7 +60,7 @@ Core capabilities: --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Quick Start +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Quick Start From the project root: @@ -82,7 +82,7 @@ Default local URLs: --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Environment Configuration +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Environment Configuration Backend: - Main file: `backend/.env` @@ -118,7 +118,7 @@ Important frontend variables: --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). ngrok Bridge for Local OAuth Callback +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). ngrok Bridge for Local OAuth Callback To test OAuth callback from ORCID in local environments, compose can inject a public callback URL: @@ -132,7 +132,7 @@ environment: --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). API Endpoints +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). API Endpoints Base backend URL: `http://localhost:8000` @@ -154,7 +154,7 @@ Base backend URL: `http://localhost:8000` --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Request Examples +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Request Examples ### Health ```bash @@ -191,7 +191,7 @@ curl -X POST "http://localhost:8000/api/export/zip/publications" \ --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Security Controls +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Security Controls Implemented controls in backend: @@ -210,7 +210,7 @@ Implemented controls in backend: --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Frontend Details +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Frontend Details The frontend is a React SPA using route-based navigation and a centralized API client. @@ -237,7 +237,7 @@ The frontend is a React SPA using route-based navigation and a centralized API c --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Project Structure +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Project Structure ```text orcid-system/ @@ -302,7 +302,7 @@ orcid-system/ --- -## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20&color=%238b5cf6). Production Checklist +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Production Checklist - [ ] `ENVIRONMENT=production` - [ ] `DEBUG=false` From 35bfc889d032a917a06c0a7a86597aa6cfe36200 Mon Sep 17 00:00:00 2001 From: Mireya Date: Mon, 11 May 2026 09:25:39 +0000 Subject: [PATCH 04/30] Edit docker-compose.yml --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a0ea598..96075b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: container_name: orcid-backend restart: unless-stopped ports: - - "127.0.0.1:8000:8000" + - "127.0.0.1:8072:8000" env_file: - ./backend/.env environment: @@ -36,7 +36,7 @@ services: container_name: orcid-frontend restart: unless-stopped ports: - - "127.0.0.1:5173:5173" + - "127.0.0.1:8073:5173" depends_on: - backend env_file: From d0a3d445e14a2cd0ac59913294a6b8481c9b6131 Mon Sep 17 00:00:00 2001 From: Mireya Date: Tue, 12 May 2026 11:06:19 +0000 Subject: [PATCH 05/30] Edit docker-compose.yml --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 96075b0..cebc6da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: container_name: orcid-backend restart: unless-stopped ports: - - "127.0.0.1:8072:8000" + - "0.0.0.0:8072:8000" env_file: - ./backend/.env environment: @@ -25,7 +25,7 @@ services: security_opt: - no-new-privileges:true healthcheck: - test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"] + test: ["CMD", "curl", "-fsS", "http://0.0.0.0:8000/health"] interval: 30s timeout: 5s retries: 3 @@ -36,7 +36,7 @@ services: container_name: orcid-frontend restart: unless-stopped ports: - - "127.0.0.1:8073:5173" + - "0.0.0.0:8073:5173" depends_on: - backend env_file: From 9a43059b60b73cd06ca5d9e2e3d34b5374d39fea Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 10:16:34 +0200 Subject: [PATCH 06/30] dix: mapear puertos en docker compose --- docker-compose.yml | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index da14276..8122bab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,9 @@ services: backend: build: ./backend container_name: orcid-backend - restart: always + restart: unless-stopped ports: - - "8000:8000" + - "0.0.0.0:8072:8000" env_file: - ./backend/.env environment: @@ -17,28 +17,43 @@ services: condition: service_healthy redis: condition: service_started + read_only: true + tmpfs: + - /tmp + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s frontend: build: ./frontend container_name: orcid-frontend - restart: always + restart: unless-stopped ports: - - "5173:5173" + - "0.0.0.0:8073:5173" depends_on: - backend env_file: - ./frontend/.env + security_opt: + - no-new-privileges:true db: image: postgres:16 container_name: orcid-postgres - restart: always + restart: unless-stopped environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: orcid_db - ports: - - "5432:5432" + expose: + - "5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: @@ -46,13 +61,18 @@ services: interval: 2s timeout: 3s retries: 20 + security_opt: + - no-new-privileges:true redis: image: redis:7 container_name: orcid-redis - restart: always - ports: - - "6379:6379" + restart: unless-stopped + command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] + expose: + - "6379" + security_opt: + - no-new-privileges:true volumes: postgres_data: From 3573bec5721562b0f5a96a9c36f242f90f6b4612 Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Wed, 13 May 2026 10:19:44 +0200 Subject: [PATCH 07/30] Cambios finales --- .gitignore | 6 --- backend/.env.example | 85 ++++++++----------------------------------- docker-compose.yml | 8 ++-- frontend/.env.example | 49 ++----------------------- 4 files changed, 23 insertions(+), 125 deletions(-) diff --git a/.gitignore b/.gitignore index 6932fd8..fe9544d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ -# --- GLOBAL --- -.env -*.env -.env.* -!.env.example - # --- PYTHON BACKEND --- __pycache__/ diff --git a/backend/.env.example b/backend/.env.example index c6b9fe5..3e798fc 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,81 +1,26 @@ -# ============================================================ -# ENVIRONMENT -# ============================================================ -ENVIRONMENT=development +ENVIRONMENT=production DEBUG=false -# ============================================================ -# DATABASE / CACHE -# ============================================================ -DATABASE_URL=postgresql://postgres:postgres@db:5432/orcid_db +ORCID_CLIENT_ID=APP-XXXX +ORCID_CLIENT_SECRET= +ORCID_REDIRECT_URI=https://app.tudominio.com/callback +ORCID_OAUTH_STATE_ENABLED=true + +API_KEY_NAME=X-API-Key +API_KEY_VALUE= + +DATABASE_URL=postgresql://:@db:5432/orcid_db REDIS_URL=redis://redis:6379/0 -# ============================================================ -# BASE URL (uso interno del scheduler) -# ============================================================ -BASE_URL=http://localhost:8000/api +BASE_URL=https://api.tudominio.com/api -# ============================================================ -# CORS — lista blanca estricta separada por comas -# Nunca uses "*" si allow_credentials=true. -# ============================================================ -CORS_ALLOWED_ORIGINS=http://localhost:5173 +CORS_ALLOWED_ORIGINS=https://app.tudominio.com +TRUSTED_HOSTS=api.tudominio.com -# ============================================================ -# Trusted Hosts — anti Host-header injection (en prod, sé explícito) -# ============================================================ -TRUSTED_HOSTS=* - -# ============================================================ -# JWT (login ORCID) -# Genera un secreto fuerte: `openssl rand -base64 64` -# ============================================================ -JWT_SECRET=change_me_to_a_long_random_value_at_least_32_chars +JWT_SECRET= JWT_ALGORITHM=HS256 JWT_EXPIRES_MINUTES=720 JWT_ISSUER=orcid-sword-backend JWT_AUDIENCE=orcid-sword-frontend -# ============================================================ -# API key máquina-a-máquina (scheduler interno) -# Genera con: `python -c "import secrets;print(secrets.token_urlsafe(48))"` -# ============================================================ -API_KEY_NAME=X-API-Key -API_KEY_VALUE=replace_with_a_strong_random_value_min_24_chars - -# ============================================================ -# ORCID OAuth 3-legged (authorization code) -# ============================================================ -ORCID_CLIENT_ID=APP-XXXXXXXXXXXXXXXX -ORCID_CLIENT_SECRET=replace_me -ORCID_REDIRECT_URI=http://localhost:8000/api/auth/orcid/callback -ORCID_OAUTH_STATE_ENABLED=true - -# ============================================================ -# Rate limits (formato slowapi: "/") -# ============================================================ -RATE_LIMIT_DEFAULT=60/minute -RATE_LIMIT_AUTH=10/minute -RATE_LIMIT_SEARCH_ANON=5/minute -RATE_LIMIT_SEARCH_AUTH=30/minute -RATE_LIMIT_EXPORT=20/minute -RATE_LIMIT_SYNC=5/minute - -# ============================================================ -# Tope de tamaños (anti DoS) -# ============================================================ -MAX_ORCID_BATCH=25 -MAX_PUB_IDS_BATCH=500 -MAX_REQUEST_BODY_BYTES=1048576 - -# ============================================================ -# Documentación interactiva (deshabilita en producción si no es necesaria) -# ============================================================ -DOCS_ENABLED=true - -# ============================================================ -# HSTS -# ============================================================ -SECURITY_HSTS_SECONDS=31536000 -SECURITY_HSTS_INCLUDE_SUBDOMAINS=true -SECURITY_HSTS_PRELOAD=false +DOCS_ENABLED=false \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 96075b0..31f0586 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: container_name: orcid-backend restart: unless-stopped ports: - - "127.0.0.1:8072:8000" + - "0.0.0.0:8072:8000" env_file: - ./backend/.env environment: @@ -25,7 +25,7 @@ services: security_opt: - no-new-privileges:true healthcheck: - test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"] + test: ["CMD", "curl", "-fsS", "http://0.0.0.0:8000/health"] interval: 30s timeout: 5s retries: 3 @@ -36,7 +36,7 @@ services: container_name: orcid-frontend restart: unless-stopped ports: - - "127.0.0.1:8073:5173" + - "0.0.0.0:8073:5173" depends_on: - backend env_file: @@ -75,4 +75,4 @@ services: - no-new-privileges:true volumes: - postgres_data: + postgres_data: \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example index 159ff5a..f61b7bf 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,45 +1,4 @@ -# URL base del backend FastAPI (sin barra final). -# -# En desarrollo puedes dejarlo en blanco y el proxy de Vite -# (ver vite.config.js) reenviará todo lo que cuelgue de /api al -# destino indicado en VITE_API_PROXY_TARGET. Esto evita problemas -# de CORS sin exponer el host del backend al navegador. -VITE_API_URL=http://localhost:8000/api - -# Solo para dev: destino al que el proxy de Vite reenvía las peticiones -# que empiecen por /api. Cambia a http://backend:8000 si ejecutas el -# frontend dentro de docker-compose. -VITE_API_PROXY_TARGET=http://localhost:8000 - -# Clave compartida con el backend. Se inyecta como header `X-API-Key` -# en TODAS las peticiones salientes (ver src/services/api.js). Debe -# coincidir con `API_KEY_VALUE` del .env del backend. -VITE_API_KEY=12ao.9-8a7b-4c&d-9e,f-?89abc - -# Pon "true" SOLO si el backend no está disponible y quieres trabajar -# con los fixtures de src/services/mocks.js. En producción debe estar a "false". -VITE_USE_MOCKS=false - -# ── Autenticación OAuth ORCID ──────────────────────────────────────────────── -# -# El flujo real es: -# 1. Frontend abre popup → GET /api/auth/orcid/authorize -# 2. Backend redirige a sandbox.orcid.org (o pub.orcid.org en producción) -# 3. Usuario se autentica en ORCID -# 4. ORCID redirige a ORCID_REDIRECT_URI (debe apuntar a esta app) -# 5. /auth/callback extrae el code y llama al backend para obtener el JWT -# -# Para que el callback vuelva al frontend, el backend necesita: -# ORCID_REDIRECT_URI=http://localhost:5173/callback -# (en backend/.env — debe coincidir con el redirect URI del app ORCID sandbox) -# En producción con ngrok u otro túnel, el formato sería: -# ORCID_REDIRECT_URI=https:///callback -# -# ── Modo bypass (solo desarrollo sin credenciales OAuth configuradas) ───────── -# Cuando está a "true", el botón "Iniciar sesión" genera un token simulado -# a partir del ORCID introducido en el campo de texto, sin abrir popup ni -# contactar al backend de auth. Útil para probar la UI autenticada -# (badges "Nuevo", botón "Descargar lo nuevo") sin OAuth real. -# ADVERTENCIA: el token simulado NO es válido en el backend, por lo que -# downloaded_by_me siempre será null (sin datos reales de "novedad"). -VITE_AUTH_BYPASS=false +VITE_API_URL=https://api.tudominio.com/api +VITE_API_PROXY_TARGET= +VITE_API_KEY= +VITE_USE_MOCKS=false \ No newline at end of file From 410fd4a09e32b38235c845eea0ec0b4ad0b1558f Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Wed, 13 May 2026 10:34:32 +0200 Subject: [PATCH 08/30] Create .gilab-ci.yml --- .gilab-ci.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .gilab-ci.yml diff --git a/.gilab-ci.yml b/.gilab-ci.yml new file mode 100644 index 0000000..eae3f23 --- /dev/null +++ b/.gilab-ci.yml @@ -0,0 +1,37 @@ +stages: + - deploy + +variables: + APP_NAME: "orcid-system" + BACKEND_PORT: "8072" + FRONTEND_PORT: "8073" + +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 up --build -d + " + + - echo "Despliegue completado." + - echo "Backend -> http://$SSH_HOST:$BACKEND_PORT" + - echo "Frontend -> http://$SSH_HOST:$FRONTEND_PORT" + + only: + - branches From c945a59bfcc093ef10b73a9123c092c6d07690c3 Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Wed, 13 May 2026 10:34:45 +0200 Subject: [PATCH 09/30] Rename .gilab-ci.yml to .gitlab-ci.yml --- .gilab-ci.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .gilab-ci.yml diff --git a/.gilab-ci.yml b/.gilab-ci.yml deleted file mode 100644 index eae3f23..0000000 --- a/.gilab-ci.yml +++ /dev/null @@ -1,37 +0,0 @@ -stages: - - deploy - -variables: - APP_NAME: "orcid-system" - BACKEND_PORT: "8072" - FRONTEND_PORT: "8073" - -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 up --build -d - " - - - echo "Despliegue completado." - - echo "Backend -> http://$SSH_HOST:$BACKEND_PORT" - - echo "Frontend -> http://$SSH_HOST:$FRONTEND_PORT" - - only: - - branches From 9ea6f516ee0e40f0262f95ceab712253bcaa2a21 Mon Sep 17 00:00:00 2001 From: Mireya Date: Wed, 13 May 2026 08:41:49 +0000 Subject: [PATCH 10/30] Delete .gitlab-ci.yml --- frontend/.gitlab-ci.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 frontend/.gitlab-ci.yml diff --git a/frontend/.gitlab-ci.yml b/frontend/.gitlab-ci.yml deleted file mode 100644 index eae3f23..0000000 --- a/frontend/.gitlab-ci.yml +++ /dev/null @@ -1,37 +0,0 @@ -stages: - - deploy - -variables: - APP_NAME: "orcid-system" - BACKEND_PORT: "8072" - FRONTEND_PORT: "8073" - -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 up --build -d - " - - - echo "Despliegue completado." - - echo "Backend -> http://$SSH_HOST:$BACKEND_PORT" - - echo "Frontend -> http://$SSH_HOST:$FRONTEND_PORT" - - only: - - branches From 5b94b1a39a7ee00518c854fcea79a3fd5864e40d Mon Sep 17 00:00:00 2001 From: Mireya Date: Wed, 13 May 2026 08:47:03 +0000 Subject: [PATCH 11/30] Edit README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e6e5da..3a3e436 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ This project is the result of the collaboration with the **University of Jaén** | **Backend** | Mireya Cueto Garrido | [@MireyaCueto](https://github.com/MireyaCueto) | ### Direction -* **Proyect Supervisor:** Luis Martínez López +* **Project Supervisor:** Luis Martínez López --- From 1bee9fe74bd772b28e5fe8e1abcda6edc8f9abc1 Mon Sep 17 00:00:00 2001 From: Mireya Cueto Garrido Date: Wed, 13 May 2026 11:01:43 +0200 Subject: [PATCH 12/30] Delete frontend/README.md --- frontend/README.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 frontend/README.md diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index a36934d..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. From 2ccdcac4c83298f8b23c2dfa77865a2741298a3b Mon Sep 17 00:00:00 2001 From: Mireya Date: Wed, 13 May 2026 09:02:21 +0000 Subject: [PATCH 13/30] Delete README.md --- frontend/README.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 frontend/README.md diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index a36934d..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. From 1e973bf81c5880000941c8f4556dca4c6ca8d824 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 11:30:12 +0200 Subject: [PATCH 14/30] Update Vite configuration to use base path from environment and simplify proxy settings; adjust main entry point for BrowserRouter basename. --- frontend/src/main.jsx | 19 +++++++++---------- frontend/vite.config.js | 20 +++++--------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index d9e87bc..be0f423 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,14 +1,13 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { BrowserRouter } from "react-router-dom"; +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' +import App from './App.jsx' +import './index.css' -import "./index.css"; -import App from "./App.jsx"; - -createRoot(document.getElementById("root")).render( +createRoot(document.getElementById('root')).render( - + - , -); + +) \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8183396..ec5c921 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -2,32 +2,22 @@ import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' -// https://vite.dev/config/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, import.meta.dirname, '') const proxyTarget = env.VITE_API_PROXY_TARGET || 'http://localhost:8000' + const base = env.VITE_BASE_PATH || '/' return { + base, plugins: [react(), tailwindcss()], server: { host: true, - // Needed for HTTPS tunnels like ngrok during OAuth callback flows. - // We allow all hosts in dev to avoid host-blocking when ngrok URL rotates. allowedHosts: true, port: 5173, proxy: { - // El backend agrupa todo bajo /api (researchers, export, …). - // Con un único prefijo evitamos tener que mantener una entrada - // por router cada vez que se añada un endpoint nuevo. - '/api': { - target: proxyTarget, - changeOrigin: true, - }, - '/health': { - target: proxyTarget, - changeOrigin: true, - }, + '/api': { target: proxyTarget, changeOrigin: true }, + '/health': { target: proxyTarget, changeOrigin: true }, }, }, } -}) +}) \ No newline at end of file From e358f9cb495481df1492c31217927d9f7d213911 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 11:36:35 +0200 Subject: [PATCH 15/30] chore: add .gitattributes for LF on YAML (avoid EOL merge noise) --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..506e0f0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Detect text files and normalize line endings in the index (cross‑platform). +* text=auto + +# YAML / CI: always LF so Linux runners and merges match Windows checkouts. +*.yml text eol=lf +*.yaml text eol=lf From 10c91683702776691e767cf16742a8bd32feaca1 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 11:56:02 +0200 Subject: [PATCH 16/30] chore: update Dockerfile to implement multi-stage build for optimized production deployment --- frontend/Dockerfile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b3e5b32..dffaab2 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,10 +1,22 @@ -FROM node:20-alpine +# ── Build stage ──────────────────────────────────────────────── +FROM node:20-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ -RUN npm install +RUN npm ci COPY . . +RUN npm run build # genera dist/ con base=/orcid2words/ -CMD ["npm", "run", "dev", "--", "--host"] +# ── Serve stage ──────────────────────────────────────────────── +FROM node:20-alpine + +WORKDIR /app + +RUN npm install -g serve + +COPY --from=builder /app/dist ./dist + +EXPOSE 8073 +CMD ["serve", "-s", "dist", "-l", "8073"] \ No newline at end of file From a5321a6807672767e9e76b9ae89152a025018f81 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 12:38:21 +0200 Subject: [PATCH 17/30] fix: update Dockerfile to change the port from 8073 to 5173 for serving the application --- frontend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index dffaab2..773f605 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -19,4 +19,4 @@ RUN npm install -g serve COPY --from=builder /app/dist ./dist EXPOSE 8073 -CMD ["serve", "-s", "dist", "-l", "8073"] \ No newline at end of file +CMD ["serve", "-s", "dist", "-l", "5173"] \ No newline at end of file From cd9ad28a98add46bcfc37f1fd576c102d78fcf61 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 12:49:36 +0200 Subject: [PATCH 18/30] refactor: update Dockerfile to use Nginx for serving the application and adjust configuration for multi-stage build --- frontend/Dockerfile | 13 +++++-------- frontend/nginx.conf | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 frontend/nginx.conf diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 773f605..ce870c4 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -10,13 +10,10 @@ COPY . . RUN npm run build # genera dist/ con base=/orcid2words/ # ── Serve stage ──────────────────────────────────────────────── -FROM node:20-alpine +FROM nginx:alpine -WORKDIR /app +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist /app/dist -RUN npm install -g serve - -COPY --from=builder /app/dist ./dist - -EXPOSE 8073 -CMD ["serve", "-s", "dist", "-l", "5173"] \ No newline at end of file +EXPOSE 5173 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..6fa4421 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,18 @@ +server { + listen 5173; + listen [::]:5173; + server_name _; + + root /app/dist; + index index.html; + + # Apache forwards the full path; strip /orcid2words/ before resolving files under dist/. + location = /orcid2words { + return 301 /orcid2words/; + } + + location /orcid2words/ { + rewrite ^/orcid2words/(.*)$ /$1 break; + try_files $uri $uri/ /index.html; + } +} From a3b9082a71c29836f1800e61a2fce36e36a898cf Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 12:59:47 +0200 Subject: [PATCH 19/30] chore: update Dockerfile and .dockerignore to clarify environment variable handling and improve build context; adjust Footer component to use dynamic base URL for logo --- frontend/.dockerignore | 5 +++-- frontend/Dockerfile | 6 +++++- frontend/src/components/layout/Footer.jsx | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 3e328fd..c0fa193 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -16,8 +16,9 @@ build/ .vite/ *.timestamp-* -# Secretos: docker-compose ya inyecta las variables vía `env_file`, -# no necesitamos copiarlos al filesystem de la imagen. +# Secretos: no subir `.env` al contexto de `docker build` (evita capas con claves). +# `env_file` en compose aplica al proceso en ejecución del servicio, no al paso +# `npm run build` del Dockerfile; las `VITE_*` deben estar disponibles ahí (ENV/ARG). .env .env.* !.env.example diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ce870c4..a410a8a 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -7,7 +7,11 @@ COPY package.json package-lock.json ./ RUN npm ci COPY . . -RUN npm run build # genera dist/ con base=/orcid2words/ +# `env_file` en docker-compose solo inyecta variables en el contenedor al ARRANCAR +# (aquí nginx no usa Vite). `vite build` corre en esta fase y necesita `VITE_*` +# ya definidas: `.env` no entra en el contexto de build (.dockerignore). +ENV VITE_BASE_PATH=/orcid2words/ +RUN npm run build # ── Serve stage ──────────────────────────────────────────────── FROM nginx:alpine diff --git a/frontend/src/components/layout/Footer.jsx b/frontend/src/components/layout/Footer.jsx index c8b7654..eb9b709 100644 --- a/frontend/src/components/layout/Footer.jsx +++ b/frontend/src/components/layout/Footer.jsx @@ -55,7 +55,7 @@ export default function Footer() { de Jaén Logo UJA From 0265afad5b35e9ae6d9b3dcc5f6a9cea424a3b32 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 13:07:40 +0200 Subject: [PATCH 20/30] chore: enhance Nginx configuration for direct access and update main entry point to dynamically resolve Router basename based on environment settings --- frontend/nginx.conf | 5 +++++ frontend/src/main.jsx | 45 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 6fa4421..b53a317 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -6,6 +6,11 @@ server { root /app/dist; index index.html; + # Acceso directo al puerto (sin Apache): misma app bajo el prefijo del build. + location = / { + return 301 /orcid2words/; + } + # Apache forwards the full path; strip /orcid2words/ before resolving files under dist/. location = /orcid2words { return 301 /orcid2words/; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index be0f423..824e95e 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,13 +1,40 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import { BrowserRouter } from 'react-router-dom' -import App from './App.jsx' -import './index.css' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App.jsx"; +import "./index.css"; -createRoot(document.getElementById('root')).render( +/** + * `import.meta.env.BASE_URL` es fijo en build (p. ej. `/orcid2words/` en prod). + * Los assets siguen bajo ese prefijo, pero la URL del documento puede ser `/` + * (acceso directo `http://host:8073/`). React Router exige que el pathname + * empiece por el basename; si no, no renderiza nada. + */ +function resolveRouterBasename() { + const configured = import.meta.env.BASE_URL ?? "/"; + const withSlash = configured.endsWith("/") ? configured : `${configured}/`; + + if (withSlash === "/") { + return "/"; + } + + const { pathname } = window.location; + if (pathname === "/" || pathname === "") { + return "/"; + } + + const prefix = withSlash.replace(/\/$/, ""); + if (pathname === prefix || pathname.startsWith(`${prefix}/`)) { + return withSlash; + } + + return "/"; +} + +createRoot(document.getElementById("root")).render( - + - -) \ No newline at end of file + , +); From 5f3fd8aa3e48dc175a2f86191e96f31be37a357a Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 13:14:46 +0200 Subject: [PATCH 21/30] chore: simplify Nginx configuration for handling `/orcid2words` paths to improve URL resolution and prevent 404 errors --- frontend/nginx.conf | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index b53a317..dc9f102 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -11,13 +11,12 @@ server { return 301 /orcid2words/; } - # Apache forwards the full path; strip /orcid2words/ before resolving files under dist/. - location = /orcid2words { - return 301 /orcid2words/; - } - - location /orcid2words/ { - rewrite ^/orcid2words/(.*)$ /$1 break; + # Apache reenvía la URL completa (sin strip). Cubrimos `/orcid2words`, + # `/orcid2words/` y `/orcid2words/...` con un solo bloque para no depender + # de un `location = /orcid2words` exacto (que se ha visto caer en 404 según + # cómo Apache forme la URI reenviada). + location ~ ^/orcid2words(/.*)?$ { + rewrite ^/orcid2words/?(.*)$ /$1 break; try_files $uri $uri/ /index.html; } } From 926e59891d84db1d647d2303582dc2a648f5101d Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 13:27:40 +0200 Subject: [PATCH 22/30] fix(nginx): tolerar Apache con o sin strip y forzar rebuild en CI --- frontend/nginx.conf | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index dc9f102..34c0978 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -6,17 +6,17 @@ server { root /app/dist; index index.html; - # Acceso directo al puerto (sin Apache): misma app bajo el prefijo del build. - location = / { - return 301 /orcid2words/; - } + # Tolerante al comportamiento de Apache (con o sin strip del prefijo): + # - Si llega `/orcid2words[/...]` → strip y servir. + # - Si llega `/` o cualquier otra ruta → servir SPA igualmente. + # Combinado con el `basename` dinámico de `main.jsx`, React Router se adapta. - # Apache reenvía la URL completa (sin strip). Cubrimos `/orcid2words`, - # `/orcid2words/` y `/orcid2words/...` con un solo bloque para no depender - # de un `location = /orcid2words` exacto (que se ha visto caer en 404 según - # cómo Apache forme la URI reenviada). location ~ ^/orcid2words(/.*)?$ { rewrite ^/orcid2words/?(.*)$ /$1 break; try_files $uri $uri/ /index.html; } + + location / { + try_files $uri $uri/ /index.html; + } } From d5be910da65983f20b9c3930ae8c8c2b530c3172 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 13:39:53 +0200 Subject: [PATCH 23/30] fix(nginx): prefix ^~ /orcid2words/ + diagnostic headers --- frontend/nginx.conf | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 34c0978..73b67a2 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -7,16 +7,27 @@ server { index index.html; # Tolerante al comportamiento de Apache (con o sin strip del prefijo): - # - Si llega `/orcid2words[/...]` → strip y servir. + # - Si llega `/orcid2words/...` → strip y servir desde dist/. # - Si llega `/` o cualquier otra ruta → servir SPA igualmente. + # `^~` fuerza prioridad sobre cualquier regex y evita sorpresas de matching. # Combinado con el `basename` dinámico de `main.jsx`, React Router se adapta. - location ~ ^/orcid2words(/.*)?$ { - rewrite ^/orcid2words/?(.*)$ /$1 break; + location ^~ /orcid2words/ { + add_header X-Original-URI $request_uri always; + add_header X-Resolved-URI $uri always; + add_header X-Location-Hit "prefix-orcid2words" always; + rewrite ^/orcid2words/(.*)$ /$1 break; try_files $uri $uri/ /index.html; } + location = /orcid2words { + return 301 /orcid2words/; + } + location / { + add_header X-Original-URI $request_uri always; + add_header X-Resolved-URI $uri always; + add_header X-Location-Hit "root" always; try_files $uri $uri/ /index.html; } } From d520e4923ba58f995874ed5c96e7fc4ddc1f580e Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 13:47:17 +0200 Subject: [PATCH 24/30] fix(nginx): add handling for /flintstones prefix to improve URL resolution and maintain SPA functionality --- frontend/nginx.conf | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 73b67a2..d89e1a3 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -6,16 +6,21 @@ server { root /app/dist; index index.html; - # Tolerante al comportamiento de Apache (con o sin strip del prefijo): - # - Si llega `/orcid2words/...` → strip y servir desde dist/. - # - Si llega `/` o cualquier otra ruta → servir SPA igualmente. - # `^~` fuerza prioridad sobre cualquier regex y evita sorpresas de matching. - # Combinado con el `basename` dinámico de `main.jsx`, React Router se adapta. + # Apache en Sinbad2 reescribe la URL pública `/orcid2words/...` a un prefijo + # interno distinto (`/flintstones/...`) antes de llegar a este contenedor. + # Hay que quitar ese prefijo y servir desde dist/ igual que con `/orcid2words/` + # (acceso directo al puerto 8073 sin pasar por Apache). + + location ^~ /flintstones/ { + rewrite ^/flintstones/(.*)$ /$1 break; + try_files $uri $uri/ /index.html; + } + + location = /flintstones { + return 301 /flintstones/; + } location ^~ /orcid2words/ { - add_header X-Original-URI $request_uri always; - add_header X-Resolved-URI $uri always; - add_header X-Location-Hit "prefix-orcid2words" always; rewrite ^/orcid2words/(.*)$ /$1 break; try_files $uri $uri/ /index.html; } @@ -25,9 +30,6 @@ server { } location / { - add_header X-Original-URI $request_uri always; - add_header X-Resolved-URI $uri always; - add_header X-Location-Hit "root" always; try_files $uri $uri/ /index.html; } } From cd8efa12d0002930665857230e20d0bff4a759f8 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 13:59:21 +0200 Subject: [PATCH 25/30] fix(nginx): replace 301 redirects with rewrite rules for /flintstones and /orcid2words to prevent internal port exposure and improve URL handling --- frontend/nginx.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index d89e1a3..dc81a66 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -17,7 +17,8 @@ server { } location = /flintstones { - return 301 /flintstones/; + # Sin 301: Apache puede reescribir `Location` y exponer puerto interno (p. ej. :5173) y bucles. + rewrite ^ /flintstones/ last; } location ^~ /orcid2words/ { @@ -26,7 +27,7 @@ server { } location = /orcid2words { - return 301 /orcid2words/; + rewrite ^ /orcid2words/ last; } location / { From 4a6eeeae68ec994d265a1373665d21c716a26f53 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 14:07:23 +0200 Subject: [PATCH 26/30] fix(main): update resolveRouterBasename function to return prefix instead of withSlash for improved routing behavior --- frontend/src/main.jsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 824e95e..da21b70 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -4,12 +4,6 @@ import { BrowserRouter } from "react-router-dom"; import App from "./App.jsx"; import "./index.css"; -/** - * `import.meta.env.BASE_URL` es fijo en build (p. ej. `/orcid2words/` en prod). - * Los assets siguen bajo ese prefijo, pero la URL del documento puede ser `/` - * (acceso directo `http://host:8073/`). React Router exige que el pathname - * empiece por el basename; si no, no renderiza nada. - */ function resolveRouterBasename() { const configured = import.meta.env.BASE_URL ?? "/"; const withSlash = configured.endsWith("/") ? configured : `${configured}/`; @@ -25,7 +19,7 @@ function resolveRouterBasename() { const prefix = withSlash.replace(/\/$/, ""); if (pathname === prefix || pathname.startsWith(`${prefix}/`)) { - return withSlash; + return prefix; } return "/"; From 1b5308406234030cfba4c212d1babebf9e30b918 Mon Sep 17 00:00:00 2001 From: Alexis Date: Wed, 13 May 2026 14:16:43 +0200 Subject: [PATCH 27/30] fix(nginx): replace rewrite rules with try_files for /flintstones and /orcid2words to enhance SPA functionality and prevent 404 errors --- frontend/nginx.conf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index dc81a66..97e871a 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -17,8 +17,7 @@ server { } location = /flintstones { - # Sin 301: Apache puede reescribir `Location` y exponer puerto interno (p. ej. :5173) y bucles. - rewrite ^ /flintstones/ last; + try_files /index.html =404; } location ^~ /orcid2words/ { @@ -27,7 +26,7 @@ server { } location = /orcid2words { - rewrite ^ /orcid2words/ last; + try_files /index.html =404; } location / { From 7ac0eab3212e324fb2ff5016790ec3f2cd8e97c5 Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 14 May 2026 10:20:19 +0200 Subject: [PATCH 28/30] fix(nginx): enhance configuration to prevent absolute redirects and ensure index.html is served without cache for improved SPA performance --- frontend/nginx.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 97e871a..186ed01 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -3,9 +3,19 @@ server { listen [::]:5173; server_name _; + # Evita que nginx fabrique `Location` absoluto con su puerto interno (5173) + # si en algún momento se emitiera una redirección. + absolute_redirect off; + port_in_redirect off; + root /app/dist; index index.html; + # index.html sin caché → evita que persistan 301/HTML antiguos en clientes. + location = /index.html { + add_header Cache-Control "no-store" always; + } + # Apache en Sinbad2 reescribe la URL pública `/orcid2words/...` a un prefijo # interno distinto (`/flintstones/...`) antes de llegar a este contenedor. # Hay que quitar ese prefijo y servir desde dist/ igual que con `/orcid2words/` From 76f003377d904bbc4d235fede98f7763035bc0c7 Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 14 May 2026 10:50:52 +0200 Subject: [PATCH 29/30] fix: update base path from /orcid2words to /orcid2sword in .env, Dockerfile, and nginx.conf --- frontend/Dockerfile | 2 +- frontend/nginx.conf | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a410a8a..6b9fd82 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -10,7 +10,7 @@ COPY . . # `env_file` en docker-compose solo inyecta variables en el contenedor al ARRANCAR # (aquí nginx no usa Vite). `vite build` corre en esta fase y necesita `VITE_*` # ya definidas: `.env` no entra en el contexto de build (.dockerignore). -ENV VITE_BASE_PATH=/orcid2words/ +ENV VITE_BASE_PATH=/orcid2sword/ RUN npm run build # ── Serve stage ──────────────────────────────────────────────── diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 186ed01..f1b2bc2 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -16,9 +16,9 @@ server { add_header Cache-Control "no-store" always; } - # Apache en Sinbad2 reescribe la URL pública `/orcid2words/...` a un prefijo + # Apache en Sinbad2 reescribe la URL pública `/orcid2sword/...` a un prefijo # interno distinto (`/flintstones/...`) antes de llegar a este contenedor. - # Hay que quitar ese prefijo y servir desde dist/ igual que con `/orcid2words/` + # Hay que quitar ese prefijo y servir desde dist/ igual que con `/orcid2sword/` # (acceso directo al puerto 8073 sin pasar por Apache). location ^~ /flintstones/ { @@ -30,12 +30,12 @@ server { try_files /index.html =404; } - location ^~ /orcid2words/ { - rewrite ^/orcid2words/(.*)$ /$1 break; + location ^~ /orcid2sword/ { + rewrite ^/orcid2sword/(.*)$ /$1 break; try_files $uri $uri/ /index.html; } - location = /orcid2words { + location = /orcid2sword { try_files /index.html =404; } From 70dc397ac2bd4adfde3e0ce9587ede2096b8d7d1 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 15 May 2026 09:37:13 +0200 Subject: [PATCH 30/30] fix: typography consistency with native-first font stack and Inter webfont fallback --- frontend/index.html | 3 +++ frontend/src/index.css | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 5120c51..a0881b0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,6 +4,9 @@ + + + ORCID2SWORD diff --git a/frontend/src/index.css b/frontend/src/index.css index 704dfbe..de2e2b1 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -62,7 +62,7 @@ --color-error-text: #6E1111; /* Fonts */ - --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, "Inter", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif; --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; }