|
|
|
@@ -1,41 +1,272 @@ |
|
|
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). |
|
|
|
# Captive Portal CMS — Casa della Scuola |
|
|
|
|
|
|
|
## Getting Started |
|
|
|
CMS per portali captive: gestione di card informative, gallerie, flip-book e contenuti kiosk a schermo intero, con un'area di amministrazione locale. Stack: Next.js 16 (App Router, Turbopack), React 19, TypeScript, Tailwind v4. Persistenza su file (nessun database). Pensato per girare su **server Ubuntu offline**. |
|
|
|
|
|
|
|
First, run the development server: |
|
|
|
--- |
|
|
|
|
|
|
|
## Indice |
|
|
|
|
|
|
|
1. [Avvio](#avvio) |
|
|
|
2. [Configurazione (`lib/config.ts`)](#configurazione-libconfigts) |
|
|
|
3. [Tipi di card](#tipi-di-card) |
|
|
|
4. [File consentiti negli upload](#file-consentiti-negli-upload) |
|
|
|
5. [Limiti di testo](#limiti-di-testo) |
|
|
|
6. [Sicurezza degli input](#sicurezza-degli-input) |
|
|
|
7. [Struttura dei dati (`data/`)](#struttura-dei-dati-data) |
|
|
|
8. [Backup e ripristino](#backup-e-ripristino) |
|
|
|
9. [Factory Preset (developer)](#factory-preset-developer) |
|
|
|
10. [Font](#font) |
|
|
|
11. [Prerequisiti di sistema](#prerequisiti-di-sistema) |
|
|
|
12. [Risoluzione problemi](#risoluzione-problemi) |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Avvio |
|
|
|
|
|
|
|
**Sviluppo:** |
|
|
|
```bash |
|
|
|
npm install |
|
|
|
npm run dev |
|
|
|
# or |
|
|
|
yarn dev |
|
|
|
# or |
|
|
|
pnpm dev |
|
|
|
# or |
|
|
|
bun dev |
|
|
|
``` |
|
|
|
Apri [http://localhost:3000](http://localhost:3000) (portale pubblico) e [http://localhost:3000/admin](http://localhost:3000/admin) (amministrazione). |
|
|
|
|
|
|
|
**Produzione:** |
|
|
|
```bash |
|
|
|
npm run build |
|
|
|
npm start |
|
|
|
``` |
|
|
|
|
|
|
|
> **Server offline:** la macchina di produzione non ha accesso a internet. NON eseguire `npm install` lì. Installa le dipendenze su una macchina con internet (stesso OS, Linux), poi copia l'intera cartella `node_modules` sul server insieme al progetto buildato. Su Ubuntu basta `npm run build` (se `node_modules` è presente) + `npm start`. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Configurazione (`lib/config.ts`) |
|
|
|
|
|
|
|
Tutte le impostazioni globali sono **flag build-time** nel file [`lib/config.ts`](lib/config.ts). Dopo ogni modifica serve ricostruire: `npm run build`. |
|
|
|
|
|
|
|
| Variabile | Default | Descrizione | |
|
|
|
|---|---|---| |
|
|
|
| `EXTERNAL_LINK_ENABLED` | `true` | Mostra il tipo di card "External Link" nel menu dell'admin. Le card di quel tipo già esistenti restano comunque visibili e cliccabili anche se messo a `false`. | |
|
|
|
| `FACTORY_RESET_ENABLED` | `false` | Mostra la sezione "Factory Preset" nell'admin (funzione developer). Vedi [Factory Preset](#factory-preset-developer). Gli endpoint API restano attivi a prescindere. | |
|
|
|
| `DEFAULT_FONT` | `''` | Font di default se il portale non ne ha impostato uno. Stringa vuota = font di sistema (Arial). Altrimenti il nome esatto di un file in `data/fonts/` (es. `"Geist-Variable.woff2"`). | |
|
|
|
| `TEXT_LIMITS` | vedi sotto | Limiti caratteri di tutti i campi testuali. | |
|
|
|
| `UPLOAD_LIMITS` | vedi sotto | Dimensioni massime upload per famiglia di file. | |
|
|
|
|
|
|
|
### Limiti testo (`TEXT_LIMITS`) |
|
|
|
```ts |
|
|
|
card: { |
|
|
|
title: 200, |
|
|
|
shortDescription: 500, |
|
|
|
fullContent: 20_000, |
|
|
|
actionUrl: 2000, |
|
|
|
}, |
|
|
|
portal: { |
|
|
|
title: 200, |
|
|
|
welcomeText: 1000, |
|
|
|
}, |
|
|
|
``` |
|
|
|
|
|
|
|
### Limiti upload (`UPLOAD_LIMITS`) |
|
|
|
```ts |
|
|
|
image: 25 MB, |
|
|
|
pdf: 20 MB, |
|
|
|
video: 1 GB, |
|
|
|
``` |
|
|
|
|
|
|
|
Per cambiare un qualunque limite: modifica il numero in `lib/config.ts` e ricostruisci. Il valore è condiviso tra interfaccia (contatore / `maxLength`), validazione server e check di upload — un solo punto di verità. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Tipi di card |
|
|
|
|
|
|
|
| Tipo | Nome in admin | Comportamento | |
|
|
|
|---|---|---| |
|
|
|
| `INFO_PAGE` | Info Page | Pagina informativa: solo cover, niente galleria. | |
|
|
|
| `IMAGE_GALLERY` | Image Gallery | Galleria di immagini/video/PDF sfogliabile a schermo intero. | |
|
|
|
| `BOOK` | Flip-Book | Sfoglialibro in formato A4 (due pagine affiancate). | |
|
|
|
| `FULLSCREEN_LOCK` | Fullscreen Lock (kiosk) | **Takeover totale**: se presente, il portale pubblico mostra SOLO il suo contenuto (immagine o video) a tutto schermo, senza hero, griglia o pulsanti di chiusura. Le altre card vengono nascoste. Utile per redirect/segnaletica kiosk. | |
|
|
|
| `EXTERNAL_LINK` | External Link | Apre un URL esterno. Visibile nel menu solo se `EXTERNAL_LINK_ENABLED = true`. | |
|
|
|
|
|
|
|
**Note sulla Fullscreen Lock:** |
|
|
|
- Se ci sono più card lock, viene usata la prima per ordine di visualizzazione. |
|
|
|
- `/admin` resta sempre accessibile anche con una lock attiva. |
|
|
|
- Per tornare al portale normale: elimina (o cambia tipo a) la card lock. |
|
|
|
|
|
|
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. |
|
|
|
--- |
|
|
|
|
|
|
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. |
|
|
|
## File consentiti negli upload |
|
|
|
|
|
|
|
Gli upload passano per tre controlli in cascata. Se uno fallisce, **nessun file viene salvato** e l'admin riceve un messaggio d'errore. |
|
|
|
|
|
|
|
### 1. Whitelist estensioni |
|
|
|
| Famiglia | Estensioni | Limite | |
|
|
|
|---|---|---| |
|
|
|
| Immagini | `png` `jpg` `jpeg` `gif` `webp` | 25 MB | |
|
|
|
| Video | `mp4` `m4v` `webm` `mov` `ogv` `ogg` | 1 GB | |
|
|
|
| Documenti | `pdf` | 20 MB | |
|
|
|
|
|
|
|
Tutto il resto (es. `svg`, `heic`, `bmp`, `avi`, `exe`) viene rifiutato. **SVG è escluso di proposito** (può contenere `<script>`). |
|
|
|
|
|
|
|
### 2. Controllo "magic-bytes" |
|
|
|
Il contenuto reale del file viene confrontato con l'estensione dichiarata. Esempi rifiutati: |
|
|
|
- Un PDF rinominato `.jpg` → rifiutato (contenuto ≠ estensione). |
|
|
|
- Un eseguibile rinominato `.png` → rifiutato (tipo non riconosciuto). |
|
|
|
- Un PNG rinominato `.jpg` → rifiutato (mismatch interno alla famiglia immagini). |
|
|
|
|
|
|
|
`mov` e `mp4` sono intercambiabili (entrambi container ISO BMFF). |
|
|
|
|
|
|
|
### 3. Transcodifica video automatica |
|
|
|
Solo i video possono essere ricodificati. Alla ricezione il server sonda i codec: |
|
|
|
- **Video già compatibile** (H.264 + AAC/MP3) → nessuna ricodifica, salvataggio immediato. |
|
|
|
- **Video non compatibile** (HEVC iPhone, VP9, AV1, audio Opus/Vorbis…) → messo in coda e ricodificato in background con `ffmpeg` verso **MP4 H.264 + AAC, max 720p**. L'admin vede un badge "Transcoding XX%" sulla miniatura; quando finisce il file diventa riproducibile su tutti i browser. |
|
|
|
|
|
|
|
> Le immagini e i PDF **non** vengono mai trasformati: sono salvati identici al file caricato. |
|
|
|
|
|
|
|
La transcodifica richiede `ffmpeg`/`ffprobe` sul server — vedi [Prerequisiti](#prerequisiti-di-sistema). Se mancano, gli upload di video che richiedono ricodifica rispondono `503`. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Limiti di testo |
|
|
|
|
|
|
|
Ogni campo compilabile dall'admin ha un limite (vedi [`TEXT_LIMITS`](#limiti-testo-text_limits)). L'interfaccia mostra un contatore quando ci si avvicina al limite (rosso se superato) e blocca l'inserimento oltre il massimo. Lato server, una richiesta che sfora viene rifiutata con `400` e l'elenco dei campi fuori limite — nessun troncamento silenzioso. |
|
|
|
|
|
|
|
Gli URL (`actionUrl`) accettano solo gli schemi `http`, `https`, `mailto`, `tel`. Schemi come `javascript:` vengono rifiutati. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Sicurezza degli input |
|
|
|
|
|
|
|
- **HTML delle card (`fullContent`)**: sanificato in scrittura con una whitelist (`p, br, strong, em, b, i, u, ul, ol, li, a, h1–h6, blockquote, span`). I link ricevono automaticamente `rel="noopener noreferrer" target="_blank"`. Script e attributi pericolosi vengono rimossi. |
|
|
|
- **Welcome text del portale**: sanificato con whitelist ridotta (`b, i, strong, em, br, p, div, span`) — solo grassetto, corsivo e a-capo, nessun link. Si modifica con il mini-editor (pulsanti **B**/**I**) nelle impostazioni. |
|
|
|
- **Colore tema (`themeColor`)**: accettato solo nel formato `#RRGGBB`. |
|
|
|
- **Nomi file**: normalizzati (accenti rimossi, niente caratteri speciali, niente path traversal) e resi univoci con timestamp + stringa casuale. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Struttura dei dati (`data/`) |
|
|
|
|
|
|
|
Tutto lo stato applicativo vive nella cartella `data/` alla radice del progetto. Non contiene codice, solo dati caricati dagli utenti: |
|
|
|
|
|
|
|
``` |
|
|
|
data/ |
|
|
|
├── cards.txt ← tutte le card (JSON array) |
|
|
|
├── portals.txt ← configurazione del portale (JSON array) |
|
|
|
├── fonts/ ← font caricati (.woff2/.woff/.ttf/.otf) |
|
|
|
├── uploads/ ← media caricati (immagini, video, pdf) |
|
|
|
│ └── .tmp/ ← buffer temporaneo upload/transcoding (svuotabile) |
|
|
|
└── transcode-jobs.json ← stato della coda di transcodifica (creato all'occorrenza) |
|
|
|
``` |
|
|
|
|
|
|
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. |
|
|
|
Copiare via questa cartella = backup completo. Sostituirla = ripristino completo. Nessun database, nessuna migrazione. |
|
|
|
|
|
|
|
## Learn More |
|
|
|
--- |
|
|
|
|
|
|
|
To learn more about Next.js, take a look at the following resources: |
|
|
|
## Backup e ripristino |
|
|
|
|
|
|
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. |
|
|
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. |
|
|
|
Disponibile dall'admin in **Settings → Backup & Restore**. |
|
|
|
|
|
|
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! |
|
|
|
### Dall'interfaccia |
|
|
|
- **⬇ Scarica backup ZIP** — scarica `interceptop-backup-<data>.zip` con card, configurazione, media e font (esclude i file temporanei). |
|
|
|
- **⤴ Ripristina da ZIP…** — carica uno zip; dopo conferma sovrascrive lo stato attuale e ricarica la pagina. La cartella `data/` precedente viene conservata come `data.bak-<timestamp>/` come rete di sicurezza. |
|
|
|
|
|
|
|
## Deploy on Vercel |
|
|
|
### Da riga di comando (Linux) |
|
|
|
|
|
|
|
**Creare un backup** (il `cd data` è essenziale: mette i file alla radice dello zip): |
|
|
|
```bash |
|
|
|
cd /percorso/del/progetto/data |
|
|
|
zip -r ~/backup-$(date +%Y%m%d-%H%M%S).zip cards.txt portals.txt uploads fonts -x "uploads/.tmp/*" |
|
|
|
``` |
|
|
|
|
|
|
|
**Verificare la struttura** (devi vedere `cards.txt` e `portals.txt` in cima, NON una cartella `data/`): |
|
|
|
```bash |
|
|
|
unzip -l ~/backup-*.zip |
|
|
|
``` |
|
|
|
|
|
|
|
Lo zip così prodotto è caricabile direttamente dal pulsante "Ripristina da ZIP…". |
|
|
|
|
|
|
|
> **Struttura obbligatoria:** i file devono stare alla radice dello zip. Uno zip con tutto dentro una cartella `data/` verrà rifiutato con "cards.txt assente". Deve essere uno **ZIP**, non un `.tar`. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Factory Preset (developer) |
|
|
|
|
|
|
|
Stato "di fabbrica" ripristinabile con un click. Pensato per preparare preset standard (es. banner + icona + una card di redirect) da distribuire a più macchine MajorNet. |
|
|
|
|
|
|
|
**Visibile nell'admin solo se** `FACTORY_RESET_ENABLED = true` in `lib/config.ts`. Gli endpoint API restano comunque attivi anche con il flag a `false`. |
|
|
|
|
|
|
|
Il preset è un file fisso: **`factory/preset.zip`** alla radice del progetto (fuori da `data/`, quindi non viene toccato dai reset; fuori da `public/`, quindi non scaricabile via web). |
|
|
|
|
|
|
|
### Dall'interfaccia (con flag attivo) |
|
|
|
- **💾 Salva stato attuale come Factory Preset** — congela lo stato corrente in `factory/preset.zip`. |
|
|
|
- **🏭 Factory Reset** — ripristina tutto al preset (disabilitato se il preset non esiste). La `data/` precedente resta come `data.bak-<timestamp>/`. |
|
|
|
|
|
|
|
### Da riga di comando / API |
|
|
|
```bash |
|
|
|
# salva lo stato corrente come preset |
|
|
|
curl -X POST http://localhost:3000/api/admin/factory-preset |
|
|
|
|
|
|
|
# esegui il factory reset |
|
|
|
curl -X POST http://localhost:3000/api/admin/factory-reset |
|
|
|
|
|
|
|
# verifica se il preset esiste |
|
|
|
curl http://localhost:3000/api/admin/factory-preset |
|
|
|
``` |
|
|
|
|
|
|
|
### Creare il preset a mano (Linux) |
|
|
|
```bash |
|
|
|
cd /percorso/del/progetto/data |
|
|
|
mkdir -p ../factory |
|
|
|
zip -r ../factory/preset.zip cards.txt portals.txt uploads fonts -x "uploads/.tmp/*" |
|
|
|
``` |
|
|
|
|
|
|
|
### Distribuzione su altre macchine |
|
|
|
Copia `factory/preset.zip` sulle altre installazioni: l'admin (con flag attivo) vedrà il preset e potrà fare Factory Reset per allinearsi allo stato standard. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Font |
|
|
|
|
|
|
|
I font vanno collocati in `data/fonts/` nei formati `.woff2`, `.woff`, `.ttf`, `.otf`. Vengono inclusi automaticamente nei backup. |
|
|
|
|
|
|
|
- L'elenco mostrato in admin esclude i file con `italic`/`bold` nel nome (si usa la variante "regular"; i pesi vengono gestiti dal browser). |
|
|
|
- Si seleziona il font del portale dalle impostazioni; in alternativa si imposta `DEFAULT_FONT` in `lib/config.ts`. |
|
|
|
- Il welcome text formattato (grassetto/corsivo) eredita il font selezionato. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Prerequisiti di sistema |
|
|
|
|
|
|
|
Sul server servono alcuni binari di sistema (richiamati direttamente, non via npm): |
|
|
|
|
|
|
|
| Binario | A cosa serve | Se manca | |
|
|
|
|---|---|---| |
|
|
|
| `ffmpeg` | Transcodifica video non compatibili | Upload video che richiedono ricodifica → `503` | |
|
|
|
| `ffprobe` | Riconoscimento codec video | Come sopra | |
|
|
|
| `zip` | Creazione backup / preset | Pulsante "Scarica backup" e "Salva preset" → `503` | |
|
|
|
| `unzip` | Ripristino backup / factory reset | Pulsanti di ripristino → `503` | |
|
|
|
|
|
|
|
Verifica su Ubuntu: |
|
|
|
```bash |
|
|
|
which ffmpeg ffprobe zip unzip |
|
|
|
``` |
|
|
|
Se mancano: |
|
|
|
```bash |
|
|
|
sudo apt install ffmpeg zip unzip |
|
|
|
``` |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Risoluzione problemi |
|
|
|
|
|
|
|
**`npm run build` → "Module not found: file-type / sanitize-html"** anche se sono installati: cache Turbopack corrotta. Soluzione: |
|
|
|
```bash |
|
|
|
rm -rf .next && npm run build |
|
|
|
``` |
|
|
|
|
|
|
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. |
|
|
|
**Un video caricato non parte nel browser**: probabilmente la transcodifica è fallita o `ffmpeg` non è installato. Controlla `which ffmpeg ffprobe` e i log del server. |
|
|
|
|
|
|
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. |
|
|
|
**Il ripristino dice "cards.txt assente"**: lo zip ha una cartella `data/` di troppo al suo interno. Ricrealo facendo `cd data` PRIMA del comando `zip` (vedi [Backup](#backup-e-ripristino)). |
|
|
|
|
|
|
|
## Configurazione |
|
|
|
**Recupero dopo un ripristino sbagliato**: lo stato precedente è in `data.bak-<timestamp>/`. Ferma il server, rinomina quella cartella in `data/` e riavvia. |
|
|
|
|
|
|
|
Per configruare usare il file lib/config.ts |
|
|
|
Per disattivare l'utilizzo di external link, settare a false la variabile EXTERNAL_LINK_ENABLED a false; |
|
|
|
**La sezione Factory Preset non compare**: è dietro il flag `FACTORY_RESET_ENABLED` in `lib/config.ts`. Mettilo a `true` e ricostruisci. |