MethodSteps passa a una sola colonna con step in orizzontale, PartnersStrip riduce loghi e spaziature, TrainingCards ridimensiona testi e padding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.