# VRP B100 - Motto, avatar precedenti, playlist del JukeBox

**Stato:** 06/09/2026 - ✅ **FATTO E COLLAUDATO** in giornata. DDL lanciata da
vitti come root; codice, prove e resoconto in
[VRP_B300](VRP_B300_diario_coding.md) alla voce del 06/09.
Questo foglio resta come il DISEGNO: il perche' delle scelte, che nel B300 non
si ripete.
Riferimenti: [VRP_B200_architettura](VRP_B200_architettura.md),
[VRP_DB_catalogo](VRP_DB_catalogo.md).

---

## 1. Cosa ha chiesto vitti

1. **JukeBox - continuare**: la *scelta dei brani* e le *playing list*,
   *"quelle solo per i loggati!"* (detto il 05/09 andando in spiaggia).
2. **Avatar - conservare i precedenti**, non solo quello corrente.
3. **Motto per utente**, anche quello con lo storico. Il suo:

       Vitti, Uto, Ubi, Elena, Seneca & Frida, nihil separatum est

Tre idee diverse che pero' fanno **una domanda sola**: dove si mettono le cose
di un utente che cambiano nel tempo.

---

## 2. Cosa dice il codice di OGGI (verificato il 06/09, non ricordato)

### 2.1 La tabella `vrp_auth.users` - 13 righe

    id  username  email  pass_hash  display_name  avatar  is_active
    created_at  last_login_at

Non c'e' nessuna colonna per il motto e **non c'e' nessuna tabella di storico**
(le tabelle di `vrp_auth` sono: `users`, `user_services`, `sessions`,
`sessions_h`, `vrw_import`).

### 2.2 L'avatar oggi si SOVRASCRIVE, e il vecchio si butta

Il nome del file e' `u<id>.<ext>` - cioe' **uno solo per persona**:

- `platform/pages/profilo.php:77-83` - carica, e prima fa `@unlink` dei file
  dello stesso utente con **altra** estensione;
- `platform/pages/users.php:114-120` - identico, dal lato amministratore;
- `platform/pages/users.php:161` - alla cancellazione dell'utente fa `@unlink`
  dell'avatar.

Quindi **oggi lo storico degli avatar non e' possibile**: caricando un `.jpg`
sopra un `.jpg` il precedente sparisce dal disco. Non e' un baco, e' una scelta
di allora; ma e' la prima cosa da cambiare, ed e' piccola.

Sul disco (`lab/public/avatars/`) ci sono **6 file** per 13 utenti:
`u1.jpg`, `u2.jpg`, `u5.jpg`, `u9.jpeg`, `u15.jpeg`, `u17.jpg`.

### 2.3 Il motto viaggerebbe GRATIS

`platform/lib/vrp_auth.php:69` (`vrp_current_user`) fa gia':

    SELECT id, username, display_name, email, avatar, is_active FROM users ...

ed e' memoizzata. Aggiungere `motto` a quella SELECT costa **una parola**, e da
li' il motto e' in mano a chiunque: badge del VRV, profilo, pagina Utenti, i
post. Stessa strada gia' fatta il 05/09 per l'avatar del badge.

### 2.4 Il JukeBox e' PUBBLICO, e resta pubblico

`platform/pages/jukebox.php` non ha guardia e la voce e' `'lan' => false`:
ci si ascolta la musica dalla spiaggia. Le playlist invece sono roba di chi ha
un nome: **compaiono solo se `vrp_current_user()` c'e'**. Non si chiude la
pagina per proteggere una funzione.

La pagina e' un cappello su `vrp_explorer` con `ui.select = 'none'` e
`ui.picker = true` (un clic accoda). Per "scegliere i brani" il motore ha gia'
la selezione multipla: si accende `ui.select` e si aggiunge un bottone.

### 2.5 Il separatore `|` e' gia' di casa

`post.musica` tiene piu' brani separati da `|`
(`visualizza_post_draft.php:151-164` fa `explode('|')`). Una playlist e'
esattamente quella cosa: un elenco ordinato di brani. Non serve inventare un
formato nuovo, e nemmeno una seconda tabella di righe.

### 2.6 La DDL la deve lanciare ROOT

`vrp_auth_app` ha solo SELECT/INSERT/UPDATE/DELETE
([VRP_DB_catalogo](VRP_DB_catalogo.md)). Ogni CREATE/ALTER qui sotto e' **una
riga che deve lanciare vitti da root**, e va lanciata quando Elena non sta
lavorando (vedi la lezione del 27/07: un ALTER le inchiodo' la pagina).

---

## 3. La forma proposta

### 3.1 Il CORRENTE sta in `users`, lo STORICO in una tabella sola

Non due tabelle (una per gli avatar, una per i motti): **una sola**, con una
colonna che dice di che cosa si sta parlando. Sono la stessa cosa - "un valore
che questa persona portava fino a un certo giorno" - e separarle prima di
sapere che divergono e' una separazione prematura.

    users.avatar   il file di adesso     <- nessun lettore cambia
    users.motto    il motto di adesso    <- colonna nuova
    user_storia    quelli di prima       <- tabella nuova

Cosi' **niente di quello che esiste si accorge del cambiamento**: chi legge
`users.avatar` continua a leggere l'avatar buono. Lo storico e' un registro che
si appende e non si rilegge mai per sapere "chi sono io adesso" - quindi non
puo' nascere una seconda verita'.

    CREATE TABLE user_storia (
      id           INT UNSIGNED NOT NULL AUTO_INCREMENT,
      user_id      INT UNSIGNED NOT NULL,
      cosa         ENUM('avatar','motto') NOT NULL,
      valore       VARCHAR(255) NOT NULL,
      ritirato_il  DATETIME NOT NULL,
      ritirato_da  INT UNSIGNED NULL,
      PRIMARY KEY (id),
      KEY k_chi (user_id, cosa, ritirato_il),
      CONSTRAINT fk_storia_user FOREIGN KEY (user_id)
        REFERENCES users(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

- `valore` = il nome del file (avatar) oppure il testo (motto). Una colonna per
  due mestieri perche' la domanda e' la stessa: *cosa portavi*.
- **una sola data**, `ritirato_il`. Il "da quando" e' il `ritirato_il` della
  riga precedente: metterlo due volte vuol dire poterlo scrivere in due modi
  diversi.
- `ritirato_da` = chi ha fatto il cambio (l'utente stesso, o l'amministratore
  che gliel'ha cambiato dalla pagina Utenti). NULL per le righe vecchie.
- `ON DELETE CASCADE`: se l'utente sparisce, sparisce il suo passato.

### 3.2 I file degli avatar smettono di sovrascriversi

Nome nuovo: `u<id>_<AAAAMMGGHHMMSS>.<ext>` (esempio `u1_20260906_101500.jpg`).

- i file vecchi `u<id>.<ext>` **restano validi come sono**: il nome sta scritto
  in `users.avatar`, nessuno lo ricalcola. Niente migrazione.
- si toglie l'`@unlink` del cambio (in **tutti e due** i punti: `profilo.php` e
  `users.php`). Resta quello della cancellazione dell'utente, che pero' va
  esteso: cancella l'avatar corrente **e i suoi precedenti**.
- "torna a un avatar di prima" = un solo UPDATE di `users.avatar`, zero lavoro
  sui file. E il ritirato di adesso finisce nello storico. Uno scambio, non una
  copia.

Nel profilo: sotto "Foto" una fila di miniature dei precedenti; un clic lo
rimette in carica; il visore `vrp_zoom` (gia' in casa dal 05/09) per guardarlo
intero. Nessuna cancellazione a mano in prima battuta - se un giorno la
cartella diventa grossa se ne riparla, ma 13 persone che cambiano foto due
volte l'anno fanno 26 file all'anno.

### 3.3 Il motto

    ALTER TABLE users ADD COLUMN motto VARCHAR(255) NULL AFTER display_name;

- si scrive nel **profilo** (self-service) e nella pagina **Utenti** (per conto
  di un altro), esattamente come `display_name`;
- nel form il campo si limita a ~160 caratteri: **un motto che non sta nel
  badge non e' un motto**;
- il precedente va in `user_storia` con `cosa='motto'`.

Quello di vitti sta in 63 caratteri, sta comodo.

### 3.4 Le playlist del JukeBox

    CREATE TABLE jukebox_playlist (
      id           INT UNSIGNED NOT NULL AUTO_INCREMENT,
      user_id      INT UNSIGNED NOT NULL,
      nome         VARCHAR(120) NOT NULL,
      brani        TEXT NOT NULL,
      creata_il    DATETIME NOT NULL,
      cambiata_il  DATETIME NOT NULL,
      PRIMARY KEY (id),
      UNIQUE KEY uq_pl (user_id, nome),
      CONSTRAINT fk_pl_user FOREIGN KEY (user_id)
        REFERENCES users(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

- `brani` = le chiavi `<scaffale>/<file>` separate da `|`, **nell'ordine**:
  `mus/Ci vuole un fiore.mp3|mp3/Radio.mp3`. Stesso idioma di `post.musica`.
  La chiave dice gia' lo scaffale, quindi una playlist puo' pescare da tutti e
  due (Radio VV e Musica dei post) senza che serva altro.
- sta in `vrp_auth` perche' e' appesa a un utente e il JukeBox non ha un DB
  suo. Il nome della tabella dice il servizio, come `vrw_import`.
- **la regola di chi puo' cosa e' gia' scritta e gia' decisa** (05/09, i
  permessi sui post): *l'autore comanda la sua roba, l'admin comanda tutto*.
  Qui vuol dire: le playlist si **vedono** fra loggati di casa, ma le
  **cambia** solo chi le ha fatte (e l'admin). Da confermare - punto 4.3.

### 3.5 Cosa cambia nella pagina JukeBox

1. si accende `ui.select` (il motore la sa gia' fare) -> le mattonelle si
   spuntano. **Il clic semplice deve restare "accoda"**: la spunta e' un gesto
   in piu', non al posto di quello che c'e'. Da provare a video, e' l'unico
   punto in cui posso rovinare qualcosa che gia' funziona.
2. un bottone "Salva la coda come playlist" - **si salva la CODA, non la
   selezione**: la coda e' gia' l'elenco ordinato che si sta ascoltando, ed e'
   il gesto naturale ("questa sequenza mi piace, tienimela").
3. a sinistra della console, un elenco delle playlist: un clic la mette in coda.
4. tutto il blocco compare solo se c'e' un utente. Chi non e' loggato vede il
   JukeBox di oggi, identico.

### 3.6 Un solo script, una volta sola

Le tre DDL (una ALTER + due CREATE) vanno in **un file solo** da lanciare da
root in un momento solo, non tre volte in tre momenti diversi.

---

## 4. Le decisioni, come le ha prese vitti il 06/09

1. **Il motto si vede in HOVER**: sul badge del VRV e sulla barra di
   piattaforma, *"anche nella lista utenti"*. Non sotto il nome a caratteri
   cubitali: e' una cosa che si scopre, non un'insegna.
2. **Le playing list**: *"ogni loggato avrebbe le sue playing list, gli ospiti
   potrebbero suonare la list di vitti, o di Elena, o farne una propria, ma non
   salvarla!"*. Cioe': si LEGGONO tutte, anche da ospite; a mancare all'ospite
   e' il solo bottone Salva. La cancella il padrone (o un admin).
   📌 Il 05/09 aveva detto *"quelle solo per i loggati!"* e il 06/09 l'ha
   precisato: e' il **salvataggio** a volere un nome, non l'ascolto.
3. **La lista utenti**: *"gli admin possono tutto, modificare se stessi e gli
   altri; i loggati possono modificarsi, ma potrebbero vedere gli altri"*.
   Da qui le DUE FACCE della pagina Utenti (vedi il B300).
4. **La DDL**: lanciata da lui la mattina del 06/09 (*"Elena non sta
   lavorando, sta preparando da mangiare"*).
   🐞 Ci si e' inciampati una volta: gli avevo suggerito `sudo mysql`, ma il
   root di MariaDB qui **non e' unix_socket** - vuole la sua password, e il
   sudo non c'entra. Comando giusto: `mariadb -u root -p vrp_auth < file.sql`.

## 4bis. Quello che il disegno non aveva previsto, e si e' visto facendo

- **Riprendere una foto e' un buco di sicurezza, se non si guarda.** Il nome
  del file arriva dal form: senza ricontrollarlo contro la soffitta VERA di
  quell'utente, uno si prendeva l'avatar di un altro scrivendone il nome.
  Provato: `riprendi=u1.jpg` (la foto di vitti) -> negato.
- **Cancellare un utente e' un'operazione a due tempi.** Il cascade si porta
  via le righe di `user_storia`, e con loro l'unico posto dove c'e' scritto
  come si chiamavano le sue foto di prima: i nomi vanno raccolti PRIMA della
  DELETE, se no restano file orfani per sempre.
- **Il lucchetto sta sul POST, non sui bottoni.** Nascondere il form della
  pagina Utenti a chi non e' admin non e' una guardia: un form nascosto non e'
  un form che non si puo' mandare.

---

## 5. Cosa NON si tocca

- La **Radio VV** e `shared/mp3`: due scaffali, e restano due (deciso il 05/09).
- `vrv.utenti` (il legacy) e i suoi avatar: sono in ritiro, non si estendono.
- `/srv/http/maps/img.php`: legacy, non si tocca piu' (vitti, 05/09).
- Il JukeBox **resta pubblico**.
