Struttura: il sito esistente si sposta sotto apps/sito

Primo passo della separazione sito / piattaforme decisa da Adriano il 03/09:
due applicazioni autosufficienti, senza workspace di root, cosi' che ognuna
resti un progetto Astro completo ed estraibile.

Qui non cambia niente del codice: solo la radice. I pattern del .gitignore
che avevano una barra erano ancorati alla radice e dopo lo spostamento non
avrebbero piu' coperto niente (data/*.db*, uploads/*): resi validi a
qualunque profondita'.

Suite: 45 file, 347 test verdi prima e dopo lo spostamento.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
This commit is contained in:
AdrianoDev
2026-09-03 11:05:54 +00:00
parent c03edccd8e
commit 510d7ca4ec
551 changed files with 4 additions and 4 deletions
@@ -0,0 +1,110 @@
# Sezione Campus — design
Data: 2026-07-25 · Branch: `feature/campus` · Stato: approvato (approccio A)
## Obiettivo
Integrare le guide di studio del corso Biohacking Campus (progetto esterno
`/data/Sorgenti/AI-OS/projects/personale/biohacking-campus`) come sezione
riservata del sito InsanityLab, visibile soltanto agli utenti autenticati con
un ruolo dedicato. Le pagine devono adottare lo stile grafico del sito
(font, colori, CSS globale), non quello del generatore statico originale.
## Requisiti
1. **Nuovo ruolo `campus`**: vede soltanto la sezione `/campus`; nessun
accesso al pannello admin. `admin` accede a tutto. `superuser` e `user`
non accedono alla sezione campus.
2. **Generazione contenuti**: uno script legge le guide sorgente
(`*.studio.md`) dal progetto biohacking-campus e produce i contenuti nel
repo del sito. Rigenerabile a ogni aggiornamento del corso.
3. **Gestione utenti**: la pagina `/admin/users` esistente deve permettere di
creare/assegnare il ruolo `campus`.
4. **Navigazione dedicata**: dentro `/campus` non compaiono Header e Footer
del sito; navigazione propria (sidebar aree/capitoli) più un solo
richiamo "← Torna alla home" verso `/`.
5. **Protezione totale**: pagine e immagini (assets slide) mai servite a
utenti non autorizzati.
## Architettura
### Ruoli e accessi (`src/lib/auth.ts`, `src/middleware.ts`)
- `Role = 'admin' | 'superuser' | 'user' | 'campus'`.
- `RULES` esteso: `/campus/*``['campus', 'admin']` (admin passa sempre già
oggi per short-circuit).
- Middleware: `/campus/*` diventa percorso protetto (oltre a `/admin`,
`/api/admin`). Non autenticato → redirect a `/admin/login`.
- `landingFor('campus')``/campus`. La pagina di login rimandava sempre a
`/admin`: ora usa `landingFor` sul ruolo dell'utente appena autenticato,
così ogni ruolo atterra su una pagina che può davvero aprire.
- Il ruolo `campus` NON deve accedere ai percorsi comuni dei loggati del
pannello (blog admin, upload): `canAccessAdminPath` ritorna `false` per
`campus` su `/admin/*` e `/api/admin/*` non esplicitamente consentiti.
- `/admin/users`: aggiunta option `campus` nelle due select (nuovo utente,
cambio ruolo).
### Sync contenuti (`scripts/sync-campus.mjs`)
- Input: root del progetto biohacking-campus (arg CLI, default il path locale
di sviluppo).
- Porta la struttura dati del generatore Python (`CHAPTERS`, `AREAS`, regex
intestazioni) in JS. Legge i `*.studio.md` da `studio/`, `qa/`, `slide/`,
`accelerator/`, `lezioni-live/`, `speaker/`, `consulenze/`, `masterclass/`.
- Converte markdown → HTML con `marked` (nuova devDependency) e riscrive i
riferimenti alle immagini slide verso `/campus/assets/...`.
- Output in `campus-content/` (root repo, committato):
- `nav.json` — aree, capitoli, lezioni ordinate (slug, titolo, sottotitolo,
docente, prev/next). La catena prev/next segue l'ordine di navigazione
aree → capitoli → guide, non la numerazione dei capitoli: le aree li
raggruppano in un ordine diverso;
- `pages/<slug>.html` — frammento HTML del corpo di ogni guida;
- `assets/` — immagini slide copiate (~37 MB, commit una tantum).
- Idempotente: rigenera tutto e rimuove i file orfani.
### Rendering (`src/pages/campus/`, `src/layouts/Campus.astro`)
- `Campus.astro`: usa `global.css` e i font del sito; nessun `Header`/`Footer`;
sidebar con aree/capitoli/lezioni (voce corrente evidenziata) e link
"← Torna alla home"; responsive con menu mobile.
- Route SSR (runtime, non prerender — servono i file in `campus-content/`):
- `/campus` — portale con le card delle aree (da `nav.json`);
- `/campus/[...slug]` — pagina area (indice capitoli) o pagina guida
(frammento HTML + breadcrumb + prev/next). Slug non trovato → 404.
- `/campus/assets/[...path]` — endpoint che serve le immagini da
`campus-content/assets/` (protetto dal middleware come tutto `/campus`);
path traversal rifiutato; `Content-Type` da estensione; cache privata.
- Lettura file a runtime da `CAMPUS_DIR` (env, default `./campus-content`),
con cache in memoria di `nav.json`.
### Deploy (`Dockerfile`)
- Lo stage runtime copia solo `dist` e `scripts`; si aggiunge
`COPY --from=build /app/campus-content ./campus-content`.
- `/app/data` e `/app/uploads` sono volumi: `campus-content/` resta fuori da
entrambi, dentro l'immagine. Nessuna modifica a `compose.yaml`.
## Error handling
- `campus-content/` assente (sync mai eseguito): `/campus` risponde 404 con
messaggio esplicito nei log; il resto del sito non è impattato.
- Slug o asset inesistente → 404. Path traversal su assets → 400.
- Sync: file `.studio.md` con intestazione non riconosciuta → warning e skip,
exit code ≠ 0 solo per errori I/O.
## Testing
- `tests/auth-campus.test.ts`: matrice ruoli × percorsi (`campus` su
`/campus` sì, su `/admin/content` no; `user` su `/campus` no; ecc.),
`landingFor`, `isRole('campus')`.
- `tests/sync-campus.test.ts`: parsing intestazioni (lezione, Q&A, slide,
accelerator, consulenza, masterclass, speaker), generazione nav e
frammenti su fixture minima, riscrittura path immagini.
- Verifica manuale: login con utente `campus` (vede solo campus), `user`
(non vede campus), navigazione sidebar, immagini slide, mobile.
## Fuori scope
- Nessun tracciamento progressi/completamento lezioni.
- Nessuna ricerca full-text.
- Nessuna migrazione automatica degli utenti esistenti al nuovo ruolo.