Fix contatti + rinomina servizi + orario sabato
- Contatti: rate-limit per IP reale (X-Forwarded-For) invece dell'IP del proxy Traefik, condiviso da tutti; controllo spostato dopo la validazione così bot/honeypot non consumano la quota. Honeypot rinominato da "website" a "hp_field" per non farlo riempire dall'autofill del browser (falsi 400). - Servizi: "Performance Class" -> "Class" (titolo/card/H1/footer); migrazione DB invertita e guardata. - One to One: migrazione titolo prod "Personal training" -> "One to One". - Footer: orario sabato 8-17 (non 8-19), seed + migrazione guardata. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+4
-3
@@ -1,6 +1,6 @@
|
||||
export interface ContactData {
|
||||
firstName: string; lastName: string; phone: string;
|
||||
email: string; message: string; website: string;
|
||||
email: string; message: string; hpField: string;
|
||||
}
|
||||
|
||||
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
@@ -20,9 +20,10 @@ export function validateContact(data: unknown):
|
||||
phone: str(d.phone, 40),
|
||||
email: str(d.email, 200),
|
||||
message: str(d.message, 5000),
|
||||
website: str(d.website, 200),
|
||||
// Honeypot: nome neutro per non farlo riempire dall'autofill del browser.
|
||||
hpField: str(d.hp_field, 200),
|
||||
};
|
||||
if (value.website) return { ok: false, error: 'Richiesta non valida.' };
|
||||
if (value.hpField) return { ok: false, error: 'Richiesta non valida.' };
|
||||
if (!value.firstName) return { ok: false, error: 'Il nome è obbligatorio.' };
|
||||
if (!value.lastName) return { ok: false, error: 'Il cognome è obbligatorio.' };
|
||||
if (!value.phone) return { ok: false, error: 'Il telefono è obbligatorio.' };
|
||||
|
||||
+16
-5
@@ -177,11 +177,18 @@ export function createDb(path?: string): Database.Database {
|
||||
const updPromoTx = db.transaction(() => { for (const [tag, oldV, newV] of PROMO_FRECCE) upd02.run(newV, tag, oldV); });
|
||||
updPromoTx();
|
||||
|
||||
// Fix: il titolo del servizio Performance Class era rimasto troncato a "Class" nei DB esistenti
|
||||
// (valore vecchio mai allineato dal seed INSERT OR IGNORE), mostrando "CLASS" come H1 della
|
||||
// pagina /services/performance-class e nelle card di /services e home. Guardato dal vecchio
|
||||
// valore → idempotente, non tocca eventuali modifiche del pannello.
|
||||
db.prepare("UPDATE content_blocks SET value = 'Performance Class' WHERE tag = 'service.performance-class.title' AND value = 'Class'").run();
|
||||
// Il servizio "Performance Class" torna a chiamarsi solo "Class" (titolo card /services,
|
||||
// H1 pagina dettaglio, link footer). Inverte la precedente normalizzazione Class→Performance
|
||||
// Class. Guardato dal vecchio valore → idempotente, non tocca eventuali modifiche del pannello.
|
||||
db.prepare("UPDATE content_blocks SET value = 'Class' WHERE tag = 'service.performance-class.title' AND value = 'Performance Class'").run();
|
||||
db.prepare("UPDATE content_blocks SET value = 'Class' WHERE tag = 'footer.services.2.label' AND value = 'Performance Class'").run();
|
||||
|
||||
// Titolo del servizio One to One: in prod era stato editato da pannello a "Personal training"
|
||||
// (t minuscola → la vecchia migrazione, guardata su 'Personal Training', non l'agganciava),
|
||||
// così l'hero e l'H2 di /services/one-to-one mostravano "PERSONAL TRAINING". Riportiamo il
|
||||
// nome canonico "One to One" (già nel seed). Guardato sul valore reale corrente → idempotente;
|
||||
// la descrizione lunga editata da pannello NON viene toccata.
|
||||
db.prepare("UPDATE content_blocks SET value = 'One to One' WHERE tag = 'service.one-to-one.title' AND value = 'Personal training'").run();
|
||||
|
||||
// Pulizia tag orfani: il riquadro training "performance-class" è stato rimosso dal seed
|
||||
// (commit 9966ec3, "Training: 6 riquadri"), ma i suoi tag restano nei DB esistenti — orfani,
|
||||
@@ -194,6 +201,10 @@ export function createDb(path?: string): Database.Database {
|
||||
// già esistente, se non modificato dal pannello. Idempotente.
|
||||
db.prepare("UPDATE content_blocks SET value = 'Via Leandro Alberti 76 e Via De Mattiolo 5' WHERE tag = 'global.contact.address' AND value = 'Via Leandro Alberti, 76'").run();
|
||||
|
||||
// Orario sabato corretto: 8–17, non 8–19. Guardato dal vecchio valore di default →
|
||||
// idempotente, non tocca eventuali modifiche del pannello.
|
||||
db.prepare("UPDATE content_blocks SET value = 'Sab 8.00 – 17.00 · Dom chiuso' WHERE tag = 'global.hours.2' AND value = 'Sab 8.00 – 19.00 · Dom chiuso'").run();
|
||||
|
||||
// Modifiche 02 (pagina Insanitylab/about): testi riscritti. Guardia via frammento LIKE
|
||||
// distintivo del vecchio testo → robusto agli apostrofi tipografici, idempotente e
|
||||
// rispettoso delle eventuali riscritture da pannello (se il frammento non c'è più, salta).
|
||||
|
||||
@@ -15,3 +15,10 @@ export function rateLimit(key: string, max: number, windowMs: number): boolean {
|
||||
export function _resetBuckets(): void {
|
||||
buckets.clear();
|
||||
}
|
||||
|
||||
// Dietro Traefik `clientAddress` è l'IP interno del proxy, uguale per tutti:
|
||||
// usiamo il primo IP di X-Forwarded-For (client reale) come chiave rate-limit.
|
||||
export function clientIp(request: Request, fallback: string): string {
|
||||
const first = request.headers.get('x-forwarded-for')?.split(',')[0].trim();
|
||||
return first || fallback;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user