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:
1 parent
e822e4574b
commit
de78431090
21 files changed
+164
-79
No files matched your search
+6
-6
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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\./,
|
||||
);
|
||||
|
||||
|
||||
@@ -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");
|
||||
Reference in new issue
Block a user