Il peso mutato prima era l'unico sotto-dominio del suo asse (questionario_lifestyle, 1.00 in Stile di Vita & Sonno): in una media pesata a un solo elemento disponibile il peso si semplifica sia al numeratore sia al denominatore, quindi il test restava verde anche se refertoDi avesse ricalcolato invece di leggere gli score congelati. Sostituita con il peso di un sotto-dominio ASSENTE in un asse a piu' elementi con copertura parziale non nulla (hrv in Recupero & Sistema Nervoso): li' un ricalcolo sposta davvero la copertura oltre soglia. Verificato rosso col ricalcolo forzato, verde ripristinato.
InsanityLab Website
Sito ufficiale di InsanityLab: vetrina aziendale, blog integrato, form di contatti, piattaforme riservate ai clienti abilitati e sistema di contenuti modificabili senza toccare il codice. In produzione su https://insanitylab.it.
Descrizione
Il sito è costruito su Astro 7 in modalità server (SSR) con adapter Node.js standalone, servito in un container Docker dietro Traefik (TLS Let's Encrypt automatico). I contenuti testuali e le immagini della parte vetrina sono "taggati" e salvati su SQLite: si modificano da un pannello di amministrazione o direttamente in pagina, senza deploy. Il blog è gestito internamente con editor rich‑text, e il form di contatti invia email tramite SMTP.
Accanto alla vetrina vivono due sezioni riservate, raccolte sotto /piattaforme e visibili solo agli utenti abilitati: il Biohacking Campus (guide di studio generate da un progetto esterno) e Stress Index (piattaforma di monitoraggio HRV). Quest'ultima è l'unica parte del sito che usa React, sotto forma di isole limitate ai grafici e alle viste interattive.
Stack tecnologico
- Framework: Astro 7 (output
server,prerender = falsesulle pagine dinamiche) - Runtime: Node.js 22 (adapter
@astrojs/nodestandalone) - Database: SQLite via
better-sqlite3 - Autenticazione: sessioni con password hashate (
bcryptjs) - Email: Nodemailer (SMTP)
- Editor articoli: TipTap (rich text)
- Isole interattive: React 19 via
@astrojs/react, solo nella piattaforma Stress Index - Grafici: Recharts (caricati unicamente sulle pagine della piattaforma)
- Font:
@fontsource(Montserrat, Open Sans) — nessun CDN esterno - Sitemap: endpoint runtime
/sitemap.xml(route vetrina + articoli pubblicati) - Test: Vitest
- Linguaggio: TypeScript
- Deploy: Docker (multi‑stage,
node:22-slim) + Traefik su VPS
Funzionalità principali
- Vetrina: home, chi siamo, training, servizi (con schede di dettaglio), blog, contatti, landing "Promo ed eventi".
- Contenuti taggati: ogni testo/immagine ha un tag (es.
home.hero.title-line-1) modificabile dal pannello o in pagina. - Modifica inline: chi ha i permessi può attivare l'overlay dei tag e modificare i contenuti direttamente mentre naviga il sito.
- Blog con editor TipTap, immagini caricate, firma autore, proprietà degli articoli per ruolo, filtro per tematica senza ricaricare la pagina e conteggio degli articoli per categoria.
- Cestino degli articoli: l'eliminazione sposta in
/admin/cestino, da dove si ripristina o si cancella in via definitiva. - Gestione utenti con quattro ruoli (
admin,superuser,user,piattaforme), creazione su pagina dedicata e password impostabile a mano o generata. - Piattaforme riservate (
/piattaforme): indice delle sezioni accessibili ai soli utenti abilitati — Biohacking Campus e Stress Index. - Form contatti con validazione, honeypot anti‑spam, rate‑limit e invio email via SMTP.
Installazione (sviluppo)
Prerequisiti
- Node.js 18+ e npm
Setup
npm install
cp .env.example .env # poi compila i valori reali
npm run create-user -- admin <password> admin
npm run dev # http://localhost:4321
Il database data/insanitylab.db viene creato al primo avvio ed è popolato automaticamente con i contenuti di default (seed) e le migrazioni idempotenti.
Comandi
| Comando | Descrizione |
|---|---|
npm run dev |
Server di sviluppo (localhost:4321) |
npm run build |
Build di produzione |
npm run preview |
Anteprima locale della build |
npm run start |
Server di produzione (node --env-file=.env) |
npm run test |
Test con Vitest |
npm run create-user |
Crea/aggiorna utente: -- <nome> <password> [admin|superuser|user|piattaforme] |
npm run sync-campus |
Rigenera i contenuti del Campus: -- <path-progetto-biohacking-campus> |
npm run seed-blog |
Carica i venti articoli divulgativi tratti dal corso base del Campus (salta quelli già presenti) |
Gestione contenuti
I testi e le immagini della vetrina sono blocchi taggati salvati nella tabella content_blocks. Ogni blocco ha un tag stabile (es. home.hero.title-line-1), un valore, eventuali stili predefiniti, un tipo (text / html / image) e un GUID univoco.
- Pannello
/admin/content: i blocchi sono raggruppati per pagina, con ricerca per tag o contenuto, upload delle immagini e ripristino all'originale. - Modifica inline: attivando "Mostra tag" dal menu utente, ogni blocco mostra un badge (nome tag + matita) che apre una finestra di modifica direttamente in pagina.
- Componenti
<T>e<TImg>: rendono i blocchi con fallback automatico al seed quando un tag non è ancora nel database.
Il seed dei contenuti è in src/data/content-seed.ts; le variazioni sui contenuti già esistenti (rinomine, testi aggiornati) si applicano tramite migrazioni idempotenti in src/lib/db.ts, così da non sovrascrivere le modifiche fatte dal pannello.
Nota tecnica: gli elementi resi dai componenti
<T>/<TImg>non ereditano ildata-astro-ciddel componente che li usa. Uno stile scoped basato solo sulla classe (es..hero__line) non li raggiunge: va ancorato a un contenitore che ha il cid usando:global()(es..hero__text :global(.hero__line)).
Blog
Gli articoli stanno nella tabella posts e si gestiscono da /admin. La pagina pubblica /blog filtra per tematica e pagina senza ricaricare — si scarica la stessa pagina e si sostituiscono soltanto griglia e filtri — mantenendo indirizzi veri e cronologia del browser: senza JavaScript la navigazione funziona come prima.
L'eliminazione di un articolo non cancella nulla: valorizza deleted_at e lo sposta nel cestino (/admin/cestino), da dove si ripristina o si elimina davvero. Ogni lettura pubblica — elenco, recenti, archivio per mese, pagina del singolo articolo — esclude il cestino, e chi non ha ruolo di gestione vede nel cestino soltanto i propri articoli.
Lo script npm run seed-blog carica venti articoli divulgativi ricavati dal corso base del Campus, dieci tecnici e dieci educativi. È versionato apposta: il database di produzione è un volume del VPS e il deploy non lo tocca, quindi articoli creati a mano in locale resterebbero in locale. Lo script salta gli slug già presenti e crea, se mancano, i due utenti che firmano gli articoli — la firma pubblica è lo username dell'autore.
Ruoli e accessi
Quattro ruoli, con autorizzazione a matrice di prefissi in src/lib/auth.ts:
| Ruolo | Può fare |
|---|---|
admin |
Tutto: contenuti, blog, upload, gestione utenti (/admin/users), piattaforme |
superuser |
Pannello contenuti + blog |
user |
Solo i propri articoli del blog |
piattaforme |
Solo le sezioni riservate (/piattaforme, /campus); nessun accesso al pannello |
Login pubblico su /login (?next sanitizzato contro open redirect). Dopo l'autenticazione ogni ruolo viene indirizzato alla propria pagina iniziale: il pannello per admin e user, i contenuti per superuser, l'indice delle piattaforme per il ruolo piattaforme. La gestione utenti (/admin/users, solo admin) consente creazione su pagina dedicata (/admin/users/new), cambio password — scelta a mano o generata, mostrata una sola volta perché nel database resta il solo hash — cambio ruolo ed eliminazione, con guardie contro l'auto‑eliminazione e la rimozione dell'ultimo admin.
Il ruolo
campusè stato rinominato inpiattaformequando le sezioni riservate sono diventate due. Una migrazione insrc/lib/db.tsaggiorna da sé gli utenti esistenti.
Piattaforme riservate
Le sezioni riservate sono raccolte sotto /piattaforme, che è anche la pagina di atterraggio del ruolo omonimo. L'elenco vive in src/data/piattaforme.ts: aggiungerne una è una riga, e il menu dell'header la mostra da sé a chi ha i permessi.
Biohacking Campus (/campus)
La sezione /campus raccoglie le guide di studio del corso Biohacking Campus. È protetta dal middleware come il pannello: un visitatore anonimo viene mandato al login, un utente con ruolo non abilitato torna alla propria pagina iniziale. Le pagine non montano l'header e il footer del sito — la navigazione avviene attraverso la barra laterale delle aree, con un unico richiamo per tornare all'indice delle piattaforme — ma usano i font, i colori e il CSS globale di InsanityLab.
I contenuti non vengono scritti a mano: lo script npm run sync-campus legge le guide sorgente (*.studio.md) dal progetto Biohacking Campus, le converte in frammenti HTML e produce la cartella campus-content/ (indice nav.json, pagine e immagini delle slide), che va committata perché il deploy la copia nell'immagine Docker. Lo script è idempotente: rigenerarlo dopo l'aggiunta di nuove lezioni aggiorna indice e pagine e rimuove quelle non più presenti. Le nuove guide vanno prima dichiarate nella struttura dei capitoli in scripts/campus/structure.mjs.
Le immagini delle slide sono servite dalla route /campus/assets/..., anch'essa protetta: non sono raggiungibili senza una sessione valida.
Il progetto sorgente sta in
/home/adriano/Wasabi-Adp-Work/AI-OS/projects/personale/biohacking-campus(percorso predefinito dello script). In sviluppo, dopo un sync, il server va riavviato:src/lib/campus.tstiene l'indice in cache in memoria e le guide nuove darebbero 404.
Stress Index (/piattaforme/stress-index)
Piattaforma di monitoraggio HRV portata dentro il sito da un prototipo Next.js esterno: nove viste — cruscotto giornaliero, elenco clienti, scheda cliente, analytics di studio, modulo sport con sessioni e atleti, team live, impostazioni, organizzazione. Il design system del prototipo (Tailwind, palette teal) è stato sostituito con quello del sito: lo stile sta in src/styles/stress-index.css, un foglio globale prefissato .si perché le stesse classi servono sia ai componenti Astro sia alle isole React, che lo scope di Astro non raggiungerebbe.
React è limitato a ciò che ha davvero bisogno di stato: la scheda cliente, la vista Analytics, il modulo Sport e i grafici Recharts. Il resto è Astro con qualche riga di JavaScript. I dati sono dimostrativi e stanno in src/lib/stress-index/data.ts; le aggregazioni con una logica propria (finestre temporali, distribuzioni, riepiloghi atleta) vivono in analytics.ts e sport.ts accanto, coperte da test.
Struttura del progetto
insanitylab-website/
├── src/
│ ├── pages/ # Route Astro (vetrina, /admin, /api, /campus, /piattaforme)
│ ├── components/ # Componenti (home/, content/, stress-index/, Header, Footer, …)
│ ├── layouts/ # Layout Base, Admin, Campus, StressIndex
│ ├── data/ # Dati statici + seed contenuti (services, trainings, site, piattaforme, content-seed)
│ ├── lib/ # db, auth, content, mailer, rate-limit, safe-next, env, stress-index/
│ ├── assets/img/ # Immagini ottimizzate da Astro
│ └── styles/ # global.css, stress-index.css
├── data/ # SQLite (volume in produzione)
├── uploads/ # Upload immagini (volume in produzione)
├── campus-content/ # Contenuti del Campus generati da sync-campus
├── scripts/ # create-user, sync-campus, seed-blog-biohacking, smoke test
├── astro.config.mjs # Config Astro (site, allowedDomains, redirects)
├── compose.yaml # Stack Docker + label Traefik
├── Dockerfile # Build multi-stage
├── .env.example # Template variabili d'ambiente
└── README.md
Variabili d'ambiente
Il file .env (non versionato) richiede:
| Variabile | Descrizione |
|---|---|
SMTP_HOST |
Host SMTP (produzione: smtps.aruba.it) |
SMTP_PORT |
Porta SMTP (465 con SSL) |
SMTP_USER |
Casella di invio (deve coincidere con CONTACT_FROM) |
SMTP_PASS |
Password della casella |
CONTACT_FROM |
Mittente delle email (= SMTP_USER) |
CONTACT_TO |
Destinatario dei messaggi del form |
DB_PATH |
Percorso del database SQLite |
UPLOADS_DIR |
Cartella degli upload |
CAMPUS_DIR |
Cartella dei contenuti del Campus (default ./campus-content) |
La posta del dominio @insanitylab.it è ospitata su Aruba; l'invio del form usa l'SMTP autenticato Aruba (smtps.aruba.it:465). Aruba impone che il mittente sia la casella autenticata, quindi CONTACT_FROM deve essere uguale a SMTP_USER.
Deploy in produzione
Il sito gira in Docker su un VPS, dietro Traefik. Lo stack è in /opt/docker/insanitylab/ (repo in src/, volumi data/ e uploads/). Aggiornamento:
cd /opt/docker/insanitylab/src
sudo git -c safe.directory="$PWD" pull origin main # il repo è di root: serve sudo, con password
docker compose up -d --build # docker gira senza sudo
Le migrazioni del database (in src/lib/db.ts) sono idempotenti e girano all'avvio del container: aggiornano lo schema e i contenuti senza perdere le modifiche fatte dal pannello. Le migrazioni che cambiano un valore sono sempre guardate dal valore precedente, quindi non sovrascrivono ciò che è stato modificato dal pannello — con la conseguenza, da tenere presente, che un contenuto già ritoccato a mano non riceve il nuovo default: va corretto dal pannello.
Prima di ogni deploy conviene fare il backup del database, che è in modalità WAL: un cp del solo .db perderebbe le modifiche non ancora consolidate.
sqlite3 -cmd ".timeout 10000" /opt/docker/insanitylab/data/insanitylab.db ".backup '/dest/insanitylab.db'"
sqlite3 /dest/insanitylab.db "PRAGMA integrity_check;" # deve rispondere 'ok'
Gli articoli del blog non viaggiano con il codice: per caricarli in produzione, una volta sola, docker exec insanitylab node scripts/seed-blog-biohacking.mjs.
Dominio e DNS
- Dominio ufficiale: insanitylab.it (canonico l'apex;
wwwfa redirect 301 all'apex tramite Traefik). - I record A di
@ewwwpuntano al VPS; la posta resta su Aruba (MX, SPF, webmail non vengono toccati). - TLS Let's Encrypt automatico via Traefik (certresolver
mytlschallenge).
Attenzione dietro Traefik:
allowedDomainsinastro.config.mjsdeve elencare i domini serviti, altrimenti Astro ignoraX-Forwarded-Proto/Hoste il controllo CSRF respinge i POST con 403.
Problemi comuni
better-sqlite3 non compila su npm install — servono i build tools:
- Linux:
sudo apt-get install build-essential python3 - macOS:
xcode-select --install - Windows: Visual Studio Build Tools
Modifiche allo <style> scoped di [slug].astro non compaiono in dev — l'HMR di Astro 7 non sempre ricompila lo stile scoped delle route dinamiche: riavviare il dev server (astro dev stop + npm run dev).
Database "locked" durante i test — assicurarsi che nessun processo stia accedendo a data/insanitylab.db.
Le guide nuove del Campus danno 404 in sviluppo — src/lib/campus.ts tiene l'indice in cache in memoria: dopo npm run sync-campus va riavviato il server.
Il database di produzione sembra non aggiornato dopo il deploy — sqlite3 -readonly non legge il file -wal, dove stanno le modifiche non ancora consolidate, e mostra valori vecchi. Per verificare, interrogare il database in lettura‑scrittura oppure — meglio — guardare direttamente le pagine del sito.
Licenza
Proprietà di InsanityLab.