fix: critical perf & security — rate limiting, DB indexes, N+1 query, image lazy loading
- Add in-memory rate limiter (src/lib/rateLimit.ts) to public registration endpoints - Add DB migration #9 with 8 performance indexes on booking/registration tables - Fix N+1 query in getUpcomingReminders() — single IN() query instead of per-title - Add loading="lazy" to all non-hero images (MasterClasses, News, Classes, Team) - Add sizes attribute to Classes images for better responsive loading Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -184,6 +184,22 @@ const migrations: Migration[] = [
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 9,
|
||||
name: "add_performance_indexes",
|
||||
up: (db) => {
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_mc_registrations_title ON mc_registrations(master_class_title);
|
||||
CREATE INDEX IF NOT EXISTS idx_mc_registrations_created ON mc_registrations(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_group_bookings_created ON group_bookings(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_open_day_bookings_event ON open_day_bookings(event_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_open_day_bookings_class ON open_day_bookings(class_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_open_day_bookings_phone ON open_day_bookings(event_id, phone);
|
||||
CREATE INDEX IF NOT EXISTS idx_open_day_bookings_class_phone ON open_day_bookings(class_id, phone);
|
||||
CREATE INDEX IF NOT EXISTS idx_open_day_classes_event ON open_day_classes(event_id);
|
||||
`);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function runMigrations(db: Database.Database) {
|
||||
@@ -703,11 +719,22 @@ export function getUpcomingReminders(): ReminderItem[] {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const { title, date, time } of upcomingTitles) {
|
||||
if (upcomingTitles.length > 0) {
|
||||
const uniqueTitles = [...new Set(upcomingTitles.map((t) => t.title))];
|
||||
const placeholders = uniqueTitles.map(() => "?").join(", ");
|
||||
const rows = db.prepare(
|
||||
"SELECT * FROM mc_registrations WHERE master_class_title = ?"
|
||||
).all(title) as McRegistrationRow[];
|
||||
`SELECT * FROM mc_registrations WHERE master_class_title IN (${placeholders})`
|
||||
).all(...uniqueTitles) as McRegistrationRow[];
|
||||
|
||||
// Build a lookup: title → { date, time }
|
||||
const titleInfo = new Map<string, { date: string; time?: string }>();
|
||||
for (const t of upcomingTitles) {
|
||||
titleInfo.set(t.title, { date: t.date, time: t.time });
|
||||
}
|
||||
|
||||
for (const r of rows) {
|
||||
const info = titleInfo.get(r.master_class_title);
|
||||
if (!info) continue;
|
||||
items.push({
|
||||
id: r.id,
|
||||
type: "master-class",
|
||||
@@ -717,8 +744,8 @@ export function getUpcomingReminders(): ReminderItem[] {
|
||||
instagram: r.instagram ?? undefined,
|
||||
telegram: r.telegram ?? undefined,
|
||||
reminderStatus: r.reminder_status ?? undefined,
|
||||
eventLabel: `${title}${time ? ` · ${time}` : ""}`,
|
||||
eventDate: date,
|
||||
eventLabel: `${r.master_class_title}${info.time ? ` · ${info.time}` : ""}`,
|
||||
eventDate: info.date,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user