Merge pull request #21 from uja-dev-practices/fix/typography-consistency
Fix/typography consistency
This commit is contained in:
@@ -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
|
||||||
@@ -1,9 +1,3 @@
|
|||||||
# --- GLOBAL ---
|
|
||||||
.env
|
|
||||||
*.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
|
|
||||||
# --- PYTHON BACKEND ---
|
# --- PYTHON BACKEND ---
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -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) |
|
| **Backend** | Mireya Cueto Garrido | [@MireyaCueto](https://github.com/MireyaCueto) |
|
||||||
|
|
||||||
### Direction
|
### Direction
|
||||||
* **Proyect Supervisor:** Luis Martínez López
|
* **Project Supervisor:** Luis Martínez López
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+15
-70
@@ -1,81 +1,26 @@
|
|||||||
# ============================================================
|
ENVIRONMENT=production
|
||||||
# ENVIRONMENT
|
|
||||||
# ============================================================
|
|
||||||
ENVIRONMENT=development
|
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
|
|
||||||
# ============================================================
|
ORCID_CLIENT_ID=APP-XXXX
|
||||||
# DATABASE / CACHE
|
ORCID_CLIENT_SECRET=<secreto-fuerte>
|
||||||
# ============================================================
|
ORCID_REDIRECT_URI=https://app.tudominio.com/callback
|
||||||
DATABASE_URL=postgresql://postgres:postgres@db:5432/orcid_db
|
ORCID_OAUTH_STATE_ENABLED=true
|
||||||
|
|
||||||
|
API_KEY_NAME=X-API-Key
|
||||||
|
API_KEY_VALUE=<random-largo-48+>
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql://<user>:<pass>@db:5432/orcid_db
|
||||||
REDIS_URL=redis://redis:6379/0
|
REDIS_URL=redis://redis:6379/0
|
||||||
|
|
||||||
# ============================================================
|
BASE_URL=https://api.tudominio.com/api
|
||||||
# BASE URL (uso interno del scheduler)
|
|
||||||
# ============================================================
|
|
||||||
BASE_URL=http://localhost:8000/api
|
|
||||||
|
|
||||||
# ============================================================
|
CORS_ALLOWED_ORIGINS=https://app.tudominio.com
|
||||||
# CORS — lista blanca estricta separada por comas
|
TRUSTED_HOSTS=api.tudominio.com
|
||||||
# Nunca uses "*" si allow_credentials=true.
|
|
||||||
# ============================================================
|
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:5173
|
|
||||||
|
|
||||||
# ============================================================
|
JWT_SECRET=<random-largo-64+>
|
||||||
# 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_ALGORITHM=HS256
|
JWT_ALGORITHM=HS256
|
||||||
JWT_EXPIRES_MINUTES=720
|
JWT_EXPIRES_MINUTES=720
|
||||||
JWT_ISSUER=orcid-sword-backend
|
JWT_ISSUER=orcid-sword-backend
|
||||||
JWT_AUDIENCE=orcid-sword-frontend
|
JWT_AUDIENCE=orcid-sword-frontend
|
||||||
|
|
||||||
# ============================================================
|
DOCS_ENABLED=false
|
||||||
# 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: "<n>/<window>")
|
|
||||||
# ============================================================
|
|
||||||
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
|
|
||||||
+3
-3
@@ -5,7 +5,7 @@ services:
|
|||||||
container_name: orcid-backend
|
container_name: orcid-backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8072:8000"
|
- "0.0.0.0:8072:8000"
|
||||||
env_file:
|
env_file:
|
||||||
- ./backend/.env
|
- ./backend/.env
|
||||||
environment:
|
environment:
|
||||||
@@ -36,7 +36,7 @@ services:
|
|||||||
container_name: orcid-frontend
|
container_name: orcid-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8073:5173"
|
- "0.0.0.0:8073:5173"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
env_file:
|
env_file:
|
||||||
@@ -75,4 +75,4 @@ services:
|
|||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
@@ -16,8 +16,9 @@ build/
|
|||||||
.vite/
|
.vite/
|
||||||
*.timestamp-*
|
*.timestamp-*
|
||||||
|
|
||||||
# Secretos: docker-compose ya inyecta las variables vía `env_file`,
|
# Secretos: no subir `.env` al contexto de `docker build` (evita capas con claves).
|
||||||
# no necesitamos copiarlos al filesystem de la imagen.
|
# `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.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|||||||
+4
-45
@@ -1,45 +1,4 @@
|
|||||||
# URL base del backend FastAPI (sin barra final).
|
VITE_API_URL=https://api.tudominio.com/api
|
||||||
#
|
VITE_API_PROXY_TARGET=
|
||||||
# En desarrollo puedes dejarlo en blanco y el proxy de Vite
|
VITE_API_KEY=<misma API_KEY_VALUE backend si la usas desde frontend>
|
||||||
# (ver vite.config.js) reenviará todo lo que cuelgue de /api al
|
VITE_USE_MOCKS=false
|
||||||
# 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://<tu-dominio>/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
|
|
||||||
+16
-3
@@ -1,10 +1,23 @@
|
|||||||
FROM node:20-alpine
|
# ── Build stage ────────────────────────────────────────────────
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
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=/orcid2sword/
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
CMD ["npm", "run", "dev", "--", "--host"]
|
# ── Serve stage ────────────────────────────────────────────────
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=builder /app/dist /app/dist
|
||||||
|
|
||||||
|
EXPOSE 5173
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -4,6 +4,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
<title>ORCID2SWORD</title>
|
<title>ORCID2SWORD</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
server {
|
||||||
|
listen 5173;
|
||||||
|
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 `/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 `/orcid2sword/`
|
||||||
|
# (acceso directo al puerto 8073 sin pasar por Apache).
|
||||||
|
|
||||||
|
location ^~ /flintstones/ {
|
||||||
|
rewrite ^/flintstones/(.*)$ /$1 break;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /flintstones {
|
||||||
|
try_files /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /orcid2sword/ {
|
||||||
|
rewrite ^/orcid2sword/(.*)$ /$1 break;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /orcid2sword {
|
||||||
|
try_files /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,7 +55,7 @@ export default function Footer() {
|
|||||||
<span className="text-[10px] font-medium uppercase leading-none tracking-[0.22em] text-ink-tertiary">de Jaén</span>
|
<span className="text-[10px] font-medium uppercase leading-none tracking-[0.22em] text-ink-tertiary">de Jaén</span>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
src="/uja-logo.png"
|
src={`${import.meta.env.BASE_URL}uja-logo.png`}
|
||||||
alt="Logo UJA"
|
alt="Logo UJA"
|
||||||
className="h-7 w-7 object-contain grayscale opacity-80 transition-all group-hover:grayscale-0 group-hover:opacity-100"
|
className="h-7 w-7 object-contain grayscale opacity-80 transition-all group-hover:grayscale-0 group-hover:opacity-100"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
--color-error-text: #6E1111;
|
--color-error-text: #6E1111;
|
||||||
|
|
||||||
/* Fonts */
|
/* 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;
|
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-3
@@ -1,13 +1,33 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App.jsx";
|
import App from "./App.jsx";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
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 prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter basename={resolveRouterBasename()}>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
|
|||||||
+5
-15
@@ -2,32 +2,22 @@ import { defineConfig, loadEnv } from 'vite'
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, import.meta.dirname, '')
|
const env = loadEnv(mode, import.meta.dirname, '')
|
||||||
const proxyTarget = env.VITE_API_PROXY_TARGET || 'http://localhost:8000'
|
const proxyTarget = env.VITE_API_PROXY_TARGET || 'http://localhost:8000'
|
||||||
|
const base = env.VITE_BASE_PATH || '/'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
base,
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
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,
|
allowedHosts: true,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
// El backend agrupa todo bajo /api (researchers, export, …).
|
'/api': { target: proxyTarget, changeOrigin: true },
|
||||||
// Con un único prefijo evitamos tener que mantener una entrada
|
'/health': { target: proxyTarget, changeOrigin: true },
|
||||||
// por router cada vez que se añada un endpoint nuevo.
|
|
||||||
'/api': {
|
|
||||||
target: proxyTarget,
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/health': {
|
|
||||||
target: proxyTarget,
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user