| .. | ||
| css | ||
| js | ||
| photos | ||
| a | ||
| admin.html | ||
| admin.html.old | ||
| index.html | ||
| login.html | ||
| logout.png | ||
| README.md | ||
| scan.log | ||
Certo Fabio — ora che tutto il backend e il frontend sono finalmente coerenti, ti preparo un README tecnico completo, scritto come si deve, chiaro, professionale e utile sia per te che per chiunque metterà mano al progetto in futuro.
È pensato per essere messo direttamente in README.md alla radice del progetto.
📸 PhotoGallery — README Tecnico Completo
Una piattaforma completa per la gestione di foto multi‑utente, con:
- autenticazione JWT
- scansione automatica delle cartelle
- thumbnails generate lato server
- sincronizzazione incrementale
- WebSocket real‑time
- frontend modulare e reattivo
- mappa globale stile Google Photos
- pannello info EXIF + geolocalizzazione
- bottom sheet multi‑foto
- modal fullscreen con navigazione
🚀 1. Architettura generale
` backend/ server.js api_v1/ scanner/ config.js ... db/ knex.js routes/ photos.js
public/ index.html login.html css/ js/ auth.js login.js logout.js config.js api.js state.js sync.js gallery.js modal.js infoPanel.js mapGlobal.js bottomSheet.js optionsSheet.js `
🔐 2. Autenticazione
Login POST /auth/login
Richiede:
json { "email": "...", "password": "..." }
Risponde:
json { "token": "JWT...", "name": "Fabio" }
Il token contiene:
json { "id": 1, "email": "fabio@example.com", "name": "Fabio", "exp": 1234567890 }
Logout POST /auth/logout
Il token viene inserito in denylist.
Middleware Tutte le rotte (eccetto /auth/*) richiedono:
Authorization: Bearer <token>
Il middleware aggiunge automaticamente:
req.query.user = [req.user.name, "Common"]
Quindi il frontend non deve mai passare user=.
🗂️ 3. Configurazione
GET /config restituisce:
json { "baseUrl": "https://…", "pathFull": false, "galleryRefreshSeconds": 30 }
Il frontend usa:
- baseUrl per costruire URL assoluti
- pathFull per capire se i path sono già completi
- galleryRefreshSeconds per il polling
🖼️ 4. Foto: API principali
Tutte le foto
GET /photos?user[]=Fabio&user[]=Common
(aggiunto automaticamente dal middleware)
Foto per ID GET /photos/byIds?id=123
Cambiamenti incrementali GET /photos/changes?since=
Restituisce:
json { "changes": [ { "photoid": 123, "changetype": "added", "timestamp": "..." }, { "photoid": 456, "changetype": "removed", "timestamp": "..." } ] }
🔄 5. Sincronizzazione
Il frontend implementa:
Full load
- chiamato al primo avvio
- scarica tutte le foto
- salva in localStorage
- aggiorna lastSync
Incremental sync
- usa /photos/changes
- applica solo differenze
- aggiorna la gallery
- aggiorna lastSync
WebSocket real‑time Il server invia:
{ type: "added", id } { type: "removed", id } { type: "add_dir", folder } { type: "del_dir", folder }
Il frontend aggiorna:
- stato locale
- gallery
- mappa
🗺️ 6. Mappa globale
Basata su Leaflet + MarkerCluster.
- clustering automatico
- collage thumbnails nei cluster
- click cluster → bottom sheet
- click foto → modal
- refresh automatico quando cambia la gallery
🪟 7. Modal foto/video
Funzionalità:
- navigazione ← →
- click ai bordi
- tastiera
- preload ±3 foto
- supporto video (mp4/webm/mov)
- pannello info integrato
ℹ️ 8. Info Panel
Mostra:
- nome
- data
- dimensioni
- peso
- MIME
- cartella
- EXIF GPS
- reverse geocoding (se presente)
- mappa dedicata
Auto‑refresh quando cambi foto nel modal.
📚 9. Bottom Sheet
Usato per:
- mostrare gruppi di foto (cluster mappa)
- navigazione rapida
- apertura modal
⚙️ 10. Options Sheet
Gestisce:
- ordinamento (asc/desc)
- raggruppamento (auto/day/month/year)
- filtri (folder/location/type)
Aggiorna la gallery in tempo reale.
🧠 11. Stato locale
Gestito in state.js:
- localPhotos[]
- lastSync
- cache in localStorage
- funzioni: add/remove/update
🧩 12. Costruzione URL media
Tutto passa da:
js mediaUrlsFromPhoto(photo)
Restituisce:
js { original, preview }
Supporta:
- path relativi
- path assoluti
- baseUrl dinamico
- cartella utente
🧪 13. Test end‑to‑end consigliato
- Vai su /login
- Inserisci email/password
- Redirect a /
- Full load → gallery appare
- Apri una foto → modal
- Apri info → mappa + EXIF
- Apri mappa globale → cluster
- Click cluster → bottom sheet
- Click foto → modal
- Cancella una foto dal filesystem → WS → frontend aggiorna
- Logout → redirect a login
🛠️ 14. Requisiti
- Node.js 18+
- SQLite3
- ffmpeg (per thumbnails video)
- ImageMagick (per thumbnails foto)
▶️ 15. Avvio
bash npm install node server.js
Server su:
http://localhost:4000
🎯 16. TODO / miglioramenti futuri
- virtualizzazione gallery (migliaia di foto)
- ricerca per testo
- timeline orizzontale
- upload drag&drop
- editing EXIF
- preferiti / album
💬 17. Contatti
Progetto sviluppato da Fabio
Backend + frontend completamente integrati.
Perfetto Fabio — ti preparo il diagramma architetturale completo, chiaro, leggibile e fedele al tuo backend e al tuo frontend attuale.
Niente fronzoli, niente astrazioni inutili: solo ciò che serve davvero per capire come gira tutto.
🧩 DIAGRAMMA ARCHITETTURALE — PhotoGallery System
- Vista generale (High‑Level)
┌──────────────────────────┐ │ FRONTEND │ │ (public/, JS modules) │ └─────────────┬────────────┘ │ HTTP + WS ┌─────────────▼────────────┐ │ BACKEND │ │ (Node + Express) │ └─────────────┬────────────┘ │ DB queries ┌─────────────▼────────────┐ │ SQLite DB │ │ (tabella photos) │ └───────────────────────────┘
- Dettaglio Frontend (moduli JS)
┌──────────────────────────────────────────────────────────────┐ │ FRONTEND │ │ │ │ index.html / login.html │ │ │ │ │ ▼ │ │ auth.js ←→ login.js ←→ logout.js │ │ │ │ │ ▼ │ │ config.js → api.js → state.js → sync.js │ │ │ │ │ │ │ │ │ └── WebSocket │ │ ▼ ▼ │ │ gallery.js ←→ modal.js ←→ infoPanel.js │ │ │ │ │ │ ▼ ▼ │ │ mapGlobal.js ←→ bottomSheet.js ←→ optionsSheet.js │ └──────────────────────────────────────────────────────────────┘
Flusso principale:
- auth.js controlla token → se valido, mostra app
- config.js carica /config
- sync.js fa full load → incremental sync → WebSocket
- state.js mantiene foto locali
- gallery.js renderizza
- modal.js apre foto/video
- infoPanel.js mostra EXIF + mappa
- mapGlobal.js mostra mappa globale
- bottomSheet.js mostra strip foto
- optionsSheet.js gestisce filtri/ordinamento
- Dettaglio Backend (server.js)
┌──────────────────────────────────────────────────────────────┐ │ BACKEND │ │ │ │ server.js │ │ │ │ │ ├── STATIC: serve public/ │ │ ├── /config │ │ ├── /auth/login │ │ ├── /auth/logout │ │ │ │ │ ├── JWT middleware │ │ │ ├── verify token │ │ │ ├── denylist │ │ │ └── req.user = { id, email, name } │ │ │ │ │ ├── GET middleware (user filtering) │ │ │ └── req.query.user = [req.user.name, "Common"] │ │ │ │ │ ├── /scan (Admin → tutti, User → solo se stesso) │ │ ├── /apiv1/autoscan (ADD, DEL, ADDDIR, DELDIR) │ │ │ └── WebSocket broadcast │ │ │ │ │ ├── /photos (router SQLite) │ │ │ ├── /photos │ │ │ ├── /photos/byIds │ │ │ └── /photos/changes │ │ │ │ │ ├── /files (serve file statici sicuri) │ │ ├── /initDB /initDBuser │ │ │ │ │ └── ws-server.js (WebSocket) │ └──────────────────────────────────────────────────────────────┘
- Flusso completo di una foto (end‑to‑end)
[1] File aggiunto nel filesystem │ ▼ [2] scan_auto → type="ADD" │ ├── scanFile() ├── scanPhotoSingle() ├── INSERT in DB ├── genera thumbnails └── WS: { type:"added", id } │ ▼ [3] Frontend riceve WS │ ├── getPhotoById(id) ├── addPhotoLocal() ├── refreshGallery() └── redrawPhotoMarkers()
- Flusso login → gallery
login.html │ ▼ login.js → AppAuth.login(email, password) │ ▼ POST /auth/login │ ▼ token JWT salvato │ ▼ redirect → / │ ▼ index.html │ ▼ auth.js.isLoggedIn() → OK │ ▼ config.js → GET /config │ ▼ sync.js.fullLoad() │ ▼ GET /photos │ ▼ state.js.setLocalPhotos() │ ▼ gallery.js.renderGallery() │ ▼ mapGlobal.js.redrawPhotoMarkers()
- Diagramma WebSocket
┌──────────────┐ WS ┌──────────────┐ │ Backend │ ───────────→ │ Frontend │ └──────────────┘ └──────────────┘ │ │ │ added → { id } │ │────────────────────────────────▶│ addPhotoLocal() │ │ refreshGallery() │ │ redrawPhotoMarkers() │ │ │ removed → { id } │ │────────────────────────────────▶│ removePhotoLocal() │ │ refreshGallery() │ │ redrawPhotoMarkers() │ │ │ adddir / deldir │ │────────────────────────────────▶│ incrementalSync()
- Diagramma Database
┌──────────────────────────────┐ │ photos │ ├──────────────────────────────┤ │ id (PK) │ │ user │ │ name │ │ path │ │ cartella │ │ mime_type │ │ width │ │ height │ │ size_bytes │ │ taken_at │ │ gps_lat │ │ gps_lng │ │ gps_alt │ │ location_json │ │ created_at │ └──────────────────────────────┘
- Diagramma URL media
` BASE_URL/photos////
Esempi:
original: https://server/photos/Fabio/original/2024/IMG_001.jpg
thumbs: https://server/photos/Fabio/thumbs/2024/IMG001thub2.jpg `
- Diagramma dei moduli JS (dipendenze)
auth.js ↑ login.js → logout.js ↑ index.html ↓ config.js → api.js → state.js → sync.js → WebSocket ↓ ↓ ↓ gallery.js ← modal.js ← infoPanel.js ↓ mapGlobal.js ← bottomSheet.js ← optionsSheet.js
Utente Browser/Frontend Backend (Express) DB (SQLite) │ │ │ │ │ 1. Apre /login │ │ │ ├──────────────────────────▶│ │ │ │ │ │ │ │ │ 2. Inserisce email/password │ │ │ ├────────────────────────────────▶│ POST /auth/login │ │ │ │ │ │ │ │ 3. Verifica utente │ │ │ │ bcrypt.compare() │ │ │ ├────────────────────────▶│ │ │ │ │ │ │ │ 4. Genera JWT │ │ │ │ createToken() │ │ │ │ │ │ │ 5. Riceve token │ │ │ ◀─────────────────────────────────┤ │ │ │ │ │ │ │ 6. Salva token (localStorage) │ │ │ │ 7. Redirect → / │ │ ├──────────────────────────▶│ │ │ │ │ │ │ │ │ 8. GET /config │ │ │ ├────────────────────────────────▶│ │ │ │ │ │ │ │ 9. Riceve config │ │ │ ◀─────────────────────────────────┤ │ │ │ │ │ │ │ 10. Full Sync │ │ │ │ GET /photos │ │ │ ├────────────────────────────────▶│ │ │ │ │ 11. Middleware JWT │ │ │ │ req.user = {name,...} │ │ │ │ │ │ │ │ 12. Filtra per utente │ │ │ │ req.query.user=[Fabio,Common] │ │ │ │ │ │ ├────────────────────────▶│ SELECT * FROM photos WHERE user IN (...) │ │ │ │ │ │ 13. Riceve lista foto │ │ │ ◀─────────────────────────────────┤ │ │ │ │ │ │ │ 14. state.setLocalPhotos() │ │ │ │ 15. gallery.render() │ │ │ │ 16. mapGlobal.redrawMarkers() │ │ │ │ │ │ │ │ 17. Apre WebSocket │ │ │ ├────────────────────────────────▶│ ws-server │ │ │ │ │ │ │ │ │ │ │ 18. Incremental Sync (polling) │ │ │ │ GET /photos/changes?since=... │ │ │ ├────────────────────────────────▶│ │ │ │ │ │ │ │ 19. Riceve changes │ │ │ ◀─────────────────────────────────┤ │ │ │ │ │ │ │ 20. Applica differenze │ │ │ │ state.add/remove/update │ │ │ │ gallery.refresh() │ │ │ │ mapGlobal.redrawMarkers() │ │ │ │ │ │ │ │ │ │ │ │ 21. Evento reale: file aggiunto │ │ │ │ │ scan_auto → ADD │ │ │ │ INSERT in DB │ │ │ ├────────────────────────▶│ │ │ │ │ │ │ │ 22. WS broadcast │ │ ◀─────────────────────────────────┤ {type:"added", id} │ │ │ │ │ │ │ 23. getPhotoById(id) │ │ │ ├────────────────────────────────▶│ /photos/byIds │ │ │ │ │ │ │ 24. Riceve foto │ │ │ ◀─────────────────────────────────┤ │ │ │ │ │ │ │ 25. state.addPhotoLocal() │ │ │ │ 26. gallery.refresh() │ │ │ │ 27. mapGlobal.redrawMarkers() │ │ │ │ │ │ │ │ │ │ │ 28. Clic su foto │ │ │ ├──────────────────────────▶│ modal.open(photo) │ │ │ │ infoPanel.render(photo) │ │ │ │ │ │ │ │ │ │ │ 29. Logout │ │ │ ├──────────────────────────▶│ AppAuth.logout() │ │ │ ├────────────────────────────────▶│ POST /auth/logout │ │ │ │ addToDenylist(token) │ │ │ │ │ │ │ 30. clearTokens() │ │ │ │ 31. redirect → /login │ │ └───────────────────────────┴─────────────────────────────────┴─────────────────────────┘