Toglie la foto tessera dal profilo (DD-044)

Il Profilo non chiede più la foto tessera e la scheda admin non la mostra.
Il completamento diventa dati personali 34%, documento 33%, certificato 33%.
La migration m29 elimina la colonna profili_giocatore.foto_path; le 4 foto
già caricate sono state cancellate dal bucket profili-giocatore. Migration
applicata in produzione.

Test: pesi e sezioni di profili-core, rendering di CampiProfilo, colonna
rimossa sul database locale. Documentati DD-044, modulo profilo-giocatore,
DATABASE, ROADMAP e changelog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-01 15:03:04 +02:00
1 parent e822e4574b
commit de78431090
21 files changed
+164 -79

No files matched your search

+6 -6
View File
@@ -24,12 +24,12 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push
## Struttura
| Cartella | Cosa verifica | Serve rete? |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No |
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26 e M27, DD-040 e DD-042: palloni un solo avviso a 3 ore) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
| Cartella | Cosa verifica | Serve rete? |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No |
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26 e M27, DD-040 e DD-042: palloni un solo avviso a 3 ore) la colonna `foto_path` eliminata (`foto-tessera-rimossa`, M29, DD-044) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
## Server di sviluppo con Node vecchio
@@ -0,0 +1,37 @@
/**
* Via la foto tessera dal profilo (M29, DD-044): `bun test/integration/foto-tessera-rimossa.test.ts`.
*
* Gira solo sullo stack locale (`npx supabase start`): verifica che la colonna `foto_path` non ci
* sia più e che le colonne lette dall'app (`COLONNE_PROFILO`) esistano tutte. Non guarda il
* database di `.env`, che può essere ancora quello di produzione prima di applicare la M29.
*/
import assert from "node:assert/strict";
import { COLONNE_PROFILO } from "@/lib/profili-core";
import { statoLocale } from "../helpers/locale";
import { prova, riepilogo, salta } from "../helpers/prova";
const locale = statoLocale();
if (!locale) {
salta("foto tessera rimossa", "stack locale non attivo (npx supabase start)");
riepilogo("foto-tessera-rimossa");
} else {
const leggi = (colonne: string) =>
fetch(`${locale.url}/rest/v1/profili_giocatore?select=${encodeURIComponent(colonne)}&limit=1`, {
headers: { apikey: locale.servizio, Authorization: `Bearer ${locale.servizio}` },
});
await prova("la colonna foto_path non esiste più", async () => {
const res = await leggi("foto_path");
assert.equal(res.status, 400, "PostgREST rifiuta una colonna che non c'è");
assert.match(await res.text(), /foto_path/);
});
await prova("le colonne lette dall'app esistono tutte", async () => {
assert.ok(!COLONNE_PROFILO.includes("foto"), "COLONNE_PROFILO non nomina la foto");
const res = await leggi(COLONNE_PROFILO);
assert.equal(res.status, 200, await res.text());
});
riepilogo("foto-tessera-rimossa");
}
+10 -6
View File
@@ -8,6 +8,7 @@ import {
completamento,
completamentoAllenatore,
csvTesseramento,
PESI,
daRigaProfilo,
formatDataBreve,
giorniAllaScadenza,
@@ -41,7 +42,6 @@ const vuoto: Profilo = {
documentoRetroPath: null,
certificatoScadenza: null,
certificatoPath: null,
fotoPath: null,
};
const completo: Profilo = {
@@ -60,18 +60,22 @@ const completo: Profilo = {
documentoRetroPath: "g1/documento-retro.jpg",
certificatoScadenza: "2027-06-30",
certificatoPath: "g1/certificato.pdf",
fotoPath: "g1/foto.jpg",
};
// --- completamento -----------------------------------------------------------
assert.equal(completamento(null), 0, "profilo inesistente = 0%");
assert.equal(completamento(vuoto), 0);
assert.equal(completamento(completo), 100, "tutte le sezioni piene = 100%");
assert.equal(completamento({ ...completo, fotoPath: null }), 90, "la foto pesa 10");
assert.equal(completamento({ ...completo, certificatoPath: null }), 70, "il certificato pesa 30");
assert.equal(
Object.values(PESI).reduce((somma, peso) => somma + peso, 0),
100,
"senza foto tessera (DD-044) i pesi di dati, documento e certificato fanno 100",
);
assert.deepEqual(Object.keys(sezioniComplete(completo)), ["dati", "documento", "certificato"]);
assert.equal(completamento({ ...completo, certificatoPath: null }), 67, "il certificato pesa 33");
assert.equal(
completamento({ ...completo, email: null }),
70,
66,
"i dati personali sono completi solo tutti insieme",
);
@@ -216,7 +220,7 @@ assert.equal(
email: "a@b.it",
}),
100,
"bastano i quattro dati personali: niente indirizzo, documento, certificato, foto",
"bastano i quattro dati personali: niente indirizzo, documento, certificato",
);
assert.equal(completamentoAllenatore({ ...vuoto, telefono: " " }), 0, "spazi soli non contano");
+2 -2
View File
@@ -14,11 +14,11 @@ await assert.rejects(
);
// --- file troppo grande: rifiutato anche con un formato valido ---------------
const troppoGrande = new File([new Uint8Array(8 * 1024 * 1024 + 1)], "foto.jpg", {
const troppoGrande = new File([new Uint8Array(8 * 1024 * 1024 + 1)], "certificato.jpg", {
type: "image/jpeg",
});
await assert.rejects(
() => caricaFile("g1", "foto", troppoGrande),
() => caricaFile("g1", "certificato", troppoGrande),
/File troppo grande: massimo 8 MB\./,
);
+28
View File
@@ -0,0 +1,28 @@
/** Check dei campi del profilo: `bun test/unit/profilo-campi.test.ts` (DD-044: niente foto tessera). */
import assert from "node:assert/strict";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { CampiProfilo } from "@/components/crapp/ProfiloAmministrativo";
import { profiloVuoto, sezioniComplete } from "@/lib/profili-core";
const corrente = profiloVuoto("g1");
const html = renderToStaticMarkup(
createElement(CampiProfilo, {
corrente,
aggiorna: () => {},
sezioni: sezioniComplete(corrente),
fileDocumento: createElement("p", null, "slot-documento"),
fileCertificato: createElement("p", null, "slot-certificato"),
}),
);
// --- le sezioni rimaste ci sono ----------------------------------------------
assert.ok(html.includes("Dati personali"));
assert.ok(html.includes("slot-documento"), "lo slot del documento è mostrato");
assert.ok(html.includes("slot-certificato"), "lo slot del certificato è mostrato");
// --- la foto tessera non c'è più, né come titolo né come campo ----------------
assert.ok(!/foto tessera/i.test(html), "nessun riferimento alla foto tessera");
assert.ok(!html.includes("fotoPath"));
console.log("profilo-campi: ok");