Sposta avatar e documenti profilo su PocketBase (file field)
Sostituisce i due bucket Supabase Storage con campi file PocketBase:
- avatar-giocatori: nuova collection avatar_giocatori separata da
giocatori_squadra, perché la policy originale ("chiunque autenticato
carica/sostituisce/elimina qualsiasi avatar, nessun controllo
proprietario") è più permissiva delle regole di giocatori_squadra —
mescolarle avrebbe indebolito le une o bloccato l'altra. File pubblico,
come il bucket originale;
- profili-giocatore: campi file su profili_giocatore stesso, con
protected: true (vedi sotto).
Trovato e corretto un problema di sicurezza reale: PocketBase rende i
file pubblici di default (si affida solo alla casualità del nome file),
a meno di impostare esplicitamente protected: true sul campo — i
documenti d'identità e i certificati medici sarebbero stati raggiungibili
senza autenticazione, in violazione di DD-016 regola 4. Verificato che
ora servono un token valido (404 senza, 200 con).
La scrittura dei campi testuali del profilo e il caricamento dei file
sono due operazioni separate (PocketBase rifiuta una stringa dove si
aspetta un file): verificato che caricare un documento non cancella i
dati testuali già salvati.
Rimossi da profili-core.ts RigaProfilo/daRigaProfilo/aRigaProfilo
(shape Postgres non più usata da nessun modulo di produzione) e
rimuoviFile (mai chiamato da nessun componente). Avatar.tsx non usa più
un URL deterministico per giocatore: PocketBase genera nomi file
casuali, quindi legge la mappa avatar tramite una query React Query
condivisa tra tutte le istanze del componente.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,27 @@
|
||||
/**
|
||||
* Check dell'avatar giocatore: `bun test/unit/avatar-store.test.ts`.
|
||||
* `urlAvatar` è puro (il bucket è pubblico, nessuna richiesta di rete): qui
|
||||
* verifichiamo solo la forma dell'URL, non serve un database.
|
||||
* `urlAvatarDaRiga` è puro (il campo non è protetto, nessuna richiesta di rete): qui
|
||||
* verifichiamo solo la forma dell'URL a partire da una riga finta, non serve un database.
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import { urlAvatar } from "@/lib/avatar-store";
|
||||
import { urlAvatarDaRiga } from "@/lib/avatar-store";
|
||||
|
||||
const url = urlAvatar("g1");
|
||||
const riga = { id: "rec1", giocatore: "g1", foto: "avatar_abc123.jpg" };
|
||||
|
||||
assert.ok(url.includes("avatar-giocatori"), "punta al bucket degli avatar");
|
||||
assert.ok(url.includes("g1/avatar.jpg"), "il percorso è <id>/avatar.jpg");
|
||||
assert.equal(urlAvatar("g1"), url, "deterministico: nessuna chiamata di rete coinvolta");
|
||||
assert.notEqual(urlAvatar("g2"), url, "id diversi -> percorsi diversi");
|
||||
const url = urlAvatarDaRiga(riga);
|
||||
|
||||
assert.ok(url.includes("avatar_giocatori"), "punta alla collection degli avatar");
|
||||
assert.ok(url.includes("rec1"), "contiene l'id del record");
|
||||
assert.ok(url.includes("avatar_abc123.jpg"), "contiene il nome del file");
|
||||
assert.equal(
|
||||
urlAvatarDaRiga(riga),
|
||||
url,
|
||||
"deterministico a parità di riga: nessuna chiamata di rete",
|
||||
);
|
||||
assert.notEqual(
|
||||
urlAvatarDaRiga({ ...riga, foto: "altro.jpg" }),
|
||||
url,
|
||||
"file diverso -> URL diverso",
|
||||
);
|
||||
|
||||
console.log("avatar-store: ok");
|
||||
|
||||
Reference in New Issue
Block a user