Primi 20 download (e una lezione di moderazione)
Il primo contenuto pubblicato su Oceano di Versi era volgare e a sfondo nazista. Da quell'episodio è nato un video TikTok da 11k view — e una patch seria alla pipeline di moderazione.
Il primo utente, il primo problema
Quando ho aperto Oceano di Versi al pubblico, il primissimo contenuto pubblicato da un utente estraneo era una “poesia” volgare e a sfondo nazista. Non è passata inosservata: l’ho vista in feed pochi minuti dopo.
Due reazioni simultanee:
- Fastidio — l’app si chiama Oceano di Versi, non ci sta.
- Sollievo tecnico — era la prima volta che qualcuno usava davvero il sistema, quindi finalmente avevo un caso reale su cui testare la moderazione.
Cosa stavo sbagliando
Il controllo esisteva, ma era tutto lato client, prima dell’upload:
- Immagini: usavo il framework Sensitive Content Analysis di Apple direttamente nell’app. Quando un utente selezionava una foto profilo o una cover esplicita (nudità), l’app rifiutava l’upload prima che la richiesta partisse. Funzionava, ma copriva solo nudità esplicita — niente violenza, simboli d’odio, contenuti disturbanti non sessuali.
- Testi: una semplice blacklist di parole proibite. Aggirabile in due secondi scrivendo
n@zi,n4zi,n a z icon spazi, emoji al posto delle lettere, eccetera. La “poesia” incriminata era passata esattamente così.
In entrambi i casi il controllo era bypassabile dal client.
Il fix: pipeline server-side con Gemini
Quella stessa notte ho spostato tutto lato server, sulla pipeline descritta in dettaglio nella documentazione di Oceano di Versi. In sintesi: ogni poesia finisce prima nella collezione di staging pendingPoems, una Cloud Function Gen 2 chiama Gemini 2.5 Flash per analizzare testo e immagini, e solo se il verdict è clean il contenuto viene promosso su poems.
exports.moderatePendingPoem = onDocumentCreated(
{ document: "pendingPoems/{poemId}", secrets: [GEMINI_API_KEY] },
async (event) => {
const verdict = await runGeminiModeration(event.data.data());
if (verdict === "block") return quarantinePoem(event);
return promotePoemFromPending(event);
}
);
Lo stesso pattern si applica a commenti e foto profilo. Il verdict è fail-open (se Gemini va in errore il contenuto passa ma viene marcato per review umana), così un outage dell’API non ferma l’app — ma il controllo non è più bypassabile dal client e riconosce molto più che nudità o parole da blacklist.
E TikTok?
Tra una Cloud Function e l’altra ho filmato di sfuggita lo schermo mentre lavoravo e l’ho pubblicato su TikTok con una caption onesta:
Quando finalmente qualcuno usa la tua app ma pubblica un contenuto volgare e passi tutta la notte a migliorare il sistema di moderazione.
Risultato a distanza di qualche giorno:
| Metrica | Valore |
|---|---|
| Visualizzazioni | 10.636 |
| Like | 322 |
| Commenti | 13 |
| Condivisioni | 11 |
| Salvataggi | 31 |
| Tempo medio di visualizzazione | 6.8 s (su 5.57 s di durata) |
| Completion rate | 58.04 % |
| Nuovi follower | 11 |
| Download app (48 h) | ~20 |
Quei ~20 sono stati i primi veri download organici di Oceano di Versi.
Cosa porto via
- Client-side non basta. Un filtro prima dell’upload è utile come prima barriera UX (evita sprechi di banda, dà feedback immediato), ma non è mai una policy di moderazione: è bypassabile e copre solo i casi che il framework locale riconosce.
- Le blacklist di parole sono teatro. Qualunque utente leggermente motivato le aggira con simboli, numeri o spazi. Serve un classificatore semantico.
- Il primo utente reale è il miglior pen-tester gratuito. Nessun test scritto da me avrebbe trovato questo caso d’uso così rapidamente.
- I contenuti onesti funzionano. Il video ha performato perché raccontava un momento vero di sviluppo, non un teaser patinato.