photo_server_json_con_aves22/public/README.md
2026-04-18 20:14:42 +02:00

25 KiB
Raw Permalink Blame History

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 multiutente, con:

  • autenticazione JWT
  • scansione automatica delle cartelle
  • thumbnails generate lato server
  • sincronizzazione incrementale
  • WebSocket realtime
  • frontend modulare e reattivo
  • mappa globale stile Google Photos
  • pannello info EXIF + geolocalizzazione
  • bottom sheet multifoto
  • 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 realtime 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

Autorefresh 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 endtoend consigliato

  1. Vai su /login
  2. Inserisci email/password
  3. Redirect a /
  4. Full load → gallery appare
  5. Apri una foto → modal
  6. Apri info → mappa + EXIF
  7. Apri mappa globale → cluster
  8. Click cluster → bottom sheet
  9. Click foto → modal
  10. Cancella una foto dal filesystem → WS → frontend aggiorna
  11. 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

  1. Vista generale (HighLevel)

┌──────────────────────────┐ │ FRONTEND │ │ (public/, JS modules) │ └─────────────┬────────────┘ │ HTTP + WS ┌─────────────▼────────────┐ │ BACKEND │ │ (Node + Express) │ └─────────────┬────────────┘ │ DB queries ┌─────────────▼────────────┐ │ SQLite DB │ │ (tabella photos) │ └───────────────────────────┘


  1. 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:

  1. auth.js controlla token → se valido, mostra app
  2. config.js carica /config
  3. sync.js fa full load → incremental sync → WebSocket
  4. state.js mantiene foto locali
  5. gallery.js renderizza
  6. modal.js apre foto/video
  7. infoPanel.js mostra EXIF + mappa
  8. mapGlobal.js mostra mappa globale
  9. bottomSheet.js mostra strip foto
  10. optionsSheet.js gestisce filtri/ordinamento

  1. 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) │ └──────────────────────────────────────────────────────────────┘


  1. Flusso completo di una foto (endtoend)

[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()


  1. 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()


  1. Diagramma WebSocket

┌──────────────┐ WS ┌──────────────┐ │ Backend │ ───────────→ │ Frontend │ └──────────────┘ └──────────────┘ │ │ │ added → { id } │ │────────────────────────────────▶│ addPhotoLocal() │ │ refreshGallery() │ │ redrawPhotoMarkers() │ │ │ removed → { id } │ │────────────────────────────────▶│ removePhotoLocal() │ │ refreshGallery() │ │ redrawPhotoMarkers() │ │ │ adddir / deldir │ │────────────────────────────────▶│ incrementalSync()


  1. 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 │ └──────────────────────────────┘


  1. 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 `


  1. 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 │ │ └───────────────────────────┴─────────────────────────────────┴─────────────────────────┘