Ho iniziato a ricostruire MariaValenciaTrumpet.com come migrazione di un sito. Il progetto è cambiato quando prenotazioni, MariaFiles e lezioni video hanno dovuto condividere account, permessi e regole operative. Quello che sembrava un nuovo sito pubblico è diventato una piattaforma utilizzata dagli studenti di Maria. La pagina dei crediti del progetto documenta il mio ruolo nella progettazione e nello sviluppo.
Le pagine pubbliche continuano a presentare biografia, eventi, corsi e contatti. Dopo l’accesso, gli studenti possono anche prenotare e pagare le lezioni, consultare materiale didattico riservato, entrare nelle stanze video individuali e ricevere le registrazioni. Per costruire questi flussi ho dovuto stabilire quali controlli potessero restare nel browser, quali dovessero essere eseguiti sul server e quali dovessero resistere al retry di un provider o a una richiesta interrotta.
Quando la migrazione è diventata una piattaforma
Ho mantenuto il sito pubblico su Astro e l’ho distribuito attraverso Cloudflare Workers. Le pagine editoriali arrivano così come HTML con contenuti localizzati, senza trasformare l’intero sito in un’applicazione eseguita nel browser. Il JavaScript viene caricato dove serve davvero: moduli, area personale e stanza video.
MariaFiles ha cambiato la struttura del progetto. La vecchia area membri proveniva da un plugin WordPress; la nuova versione richiedeva rendering autenticato e controllo degli accessi sul server. Ho separato i dati in base al loro utilizzo:
- Cloudflare D1 conserva utenti, ruoli, lezioni, ordini, permessi e registri di audit.
- Cloudflare R2 contiene PDF, audio, video, allegati e registrazioni private.
- Sessioni Worker e stato temporaneo mantengono autenticazione e coordinamento di breve durata fuori dallo storage del browser.
La stanza individuale usa Cloudflare RealtimeKit per il trasporto audio e video, dietro un’interfaccia personalizzata. Stripe fornisce il checkout ospitato, Resend consegna le email transazionali e un Worker separato esegue il bot Telegram per le operazioni. Ho tenuto il bot fuori dal runtime del sito per ridurre la superficie esposta dal suo token e dalle azioni interne.
Per le prenotazioni non bastava un calendario
Nel primo prototipo, la disponibilità sembrava soprattutto un problema di interfaccia. Collegando lezioni, pacchetti, coupon e pagamenti è diventata un problema di stato. Uno slot può essere libero, bloccato temporaneamente, pagato, cancellato, rilasciato o perso. Un coupon che azzera il totale deve assegnare il diritto corretto senza contattare il provider di pagamento.
Ho quindi modellato ordine, tentativo di pagamento, lezione e registro dei crediti come record collegati. I webhook vengono elaborati in modo idempotente, così lo stesso evento non può accreditare due volte un acquisto. Prima della conferma, il server controlla nuovamente lo slot: la casella verde selezionata pochi secondi prima rappresenta soltanto una fotografia della disponibilità .
Un bug di pianificazione ha chiarito il problema. L’interfaccia controllava l’ora d’inizio della lezione, mentre il calendario doveva verificarne anche l’ora di fine. Vicino a un periodo bloccato poteva quindi comparire uno slot che la lezione completa avrebbe oltrepassato. Ora la disponibilità copre l’intero intervallo nel fuso orario didattico e viene memorizzata in UTC.
Poi otto byte mancanti hanno bloccato MariaFiles
Durante la migrazione dell’area membri ho conservato i concetti già familiari agli studenti: file, categorie, ruoli e accessi personali. Non ho mantenuto il modello di sicurezza del vecchio plugin PHP. Le query del catalogo vengono filtrate sul server; le route di anteprima e download ricontrollano sessione e permessi. I download privati usano firme di breve durata legate all’utente.
Il primo import sembrava corretto nel database, ma i PDF si aprivano come pagine grigie e le anteprime audio o video non funzionavano. La sorgente era un backup WordPress .wpress, che non è un archivio ZIP o TAR. Il mio primo passaggio di estrazione aveva rimosso otto byte all’inizio di ogni file multimediale.
Ho estratto nuovamente i file dall’offset corretto, calcolato gli hash, confrontato le dimensioni con i record dell’archivio e verificato le firme reali: %PDF, marcatori JPEG, ID3, ftyp degli MP4 e gli altri header previsti. Solo dopo ho aggiornato R2 e D1. Da quell’incidente, nome del file, etichetta MIME e riga del database non bastano più per approvare un file importato.
Le lezioni reali hanno riscritto la checklist video
Ho costruito la stanza didattica sul Core SDK di RealtimeKit, mantenendo il controllo dell’interfaccia per videocamera, microfono, condivisione schermo, chat e registrazione. Il sito crea l’accesso dopo avere verificato l’utente, la proprietà della lezione, il ruolo e la finestra oraria consentita. Un URL copiato non può sostituire questi controlli.
I test desktop coprivano il percorso principale, ma non riproducevano i problemi incontrati dagli studenti. Le sessioni su Windows e Android hanno mostrato comportamenti diversi nell’instradamento audio. I dispositivi Android meno recenti richiedevano più tempo durante il cambio della videocamera. Sugli schermi stretti, il pannello della chat entrava in competizione con l’area video, pur funzionando bene su un monitor desktop.
Da quelle sessioni sono nati il test audio prima dell’ingresso, fallback multimediali prudenti, la selezione dell’altoparlante soltanto sui browser compatibili, un’attesa prima di riacquisire la videocamera e un pannello chat separato dall’area video. Continuo a considerare le prove su dispositivi fisici una parte dello sviluppo, perché l’emulazione non riproduce ogni percorso WebRTC o di uscita audio.
La registrazione ha richiesto un flusso di consenso distinto. Il server rifiuta l’avvio quando lo stato adulto/minore o l’autorizzazione necessaria non sono noti. Il consenso viene associato alla lezione e all’evento di registrazione, senza estenderlo automaticamente alle sessioni future. Il rifiuto non impedisce lo svolgimento della lezione; la revoca avvia il tentativo di fermare una registrazione attiva. I file completati vengono trasferiti nello storage R2 privato e ricevono le regole di accesso MariaFiles dello studente.
I problemi minori hanno cambiato il rilascio
Alcune delle indagini più lunghe sono partite da dettagli apparentemente lontani dall’applicazione principale. Il pannello dei cookie, per esempio, funzionava nei test ordinari ma spariva per alcuni utenti desktop. La cache era una delle cause. Alcuni content blocker reagivano ai nomi riconoscibili delle classi, il focus automatico intercettava un Invio residuo e certe estensioni inviavano click sintetici.
Ora il pannello è presente prima dell’esecuzione del JavaScript, non riceve il focus iniziale, conserva una scelta versionata e accetta la prima decisione soltanto da un evento affidabile del browser dopo un breve periodo di stabilizzazione. Gli analytics restano disattivati fino a quella decisione.
I client email hanno prodotto sorprese diverse. I marchi SVG e ICO, corretti sul sito, non venivano mostrati in modo affidabile nei messaggi; i template transazionali usano quindi un asset PNG dedicato. Anche i link privati delle lezioni richiedevano un trattamento diverso dagli URL pubblicitari.
Un deploy mi ha insegnato a controllare un livello in più. Il Worker restituiva HTML valido mentre mancava uno degli asset CSS indicati nella pagina. Gli smoke test ora scaricano l’HTML live e richiedono esattamente i file CSS e JavaScript presenti nel markup. Il successo del solo comando di deploy non basta più per chiudere un rilascio.
Le verifiche decisive sono rimaste sul server
Ogni endpoint privato valida la richiesta sul server. Le operazioni che modificano dati controllano i segnali di origine; i body JSON e multipart hanno limiti espliciti; gli upload usano allowlist per estensione, MIME e firma del file. Limiti persistenti alle richieste proteggono login, caricamenti, reset della password e azioni massive.
I token di sessione sono opachi e vengono conservati come hash. I cookie usano HttpOnly e SameSite, mentre le route amministrative verificano il ruolo a ogni richiesta. Le risposte private includono no-store e noindex, ma l’accesso dipende comunque da autenticazione e autorizzazione. I bucket R2 restano privati, il recupero remoto passa attraverso una protezione SSRF e gli eventi di audit escludono indirizzi IP in chiaro, credenziali, token dei provider e chiavi degli oggetti.
La stessa regola vale per i pagamenti. Prezzi e diritti vengono calcolati sul server, gli eventi ripetuti possono essere rielaborati in sicurezza e il checkout ospitato lascia a Stripe la gestione dei dati della carta. I secret sono separati per ambiente e le modifiche alle dipendenze vengono esaminate prima di accettare una correzione automatica dell’audit.
Cosa controllo oggi prima di un rilascio
Il percorso di release comprende test unitari e di sicurezza, suite browser, migrazioni su staging, smoke test remoti e un controllo manuale. Chromium e WebKit coprono i flussi ripetibili. Cambio della videocamera, uscita audio, WebRTC e Safari mobile richiedono ancora dispositivi fisici e sessioni con utenti effettivi.
Anche i controlli operativi derivano dagli incidenti incontrati: prove di ripristino D1, inventario R2, log strutturati senza dati personali, job di recupero per import di registrazioni interrotti e procedure per la rotazione dei secret o un deploy fallito. Il rollback di un Worker non annulla una migrazione del database e non può annullare l’invio di un’email; ogni effetto richiede una propria procedura di recupero.
Alcune decisioni restano fuori dal codice. L’estensione del commercio richiede verifiche indipendenti legali, privacy, fiscali e di sicurezza. La copertura dei dispositivi migliora soltanto continuando le lezioni su hardware reale. I backup cifrati a lunga conservazione fuori dall’account Cloudflare richiedono ancora un responsabile e una decisione sulla gestione delle chiavi.
Quando oggi rileggo la checklist di rilascio, quasi ogni riga rimanda a un problema concreto: otto byte mancanti, un percorso audio silenzioso, un pannello dei cookie scomparso o un foglio di stile non trovato. Quella storia descrive la piattaforma meglio del suo diagramma architetturale.
