From e666ff434ec5374a7ee5dd0a1c2732a5220cc813 Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Fri, 13 Mar 2026 21:44:54 +0100 Subject: [PATCH] update --- sync/src/index.ts | 26 +++++++++++++++++++++++++- sync/src/scraper.ts | 9 ++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/sync/src/index.ts b/sync/src/index.ts index f33c1c4..c476858 100644 --- a/sync/src/index.ts +++ b/sync/src/index.ts @@ -66,7 +66,31 @@ async function runSync(force = false): Promise { try { if (force) log('Force mode: ON'); log('Starting FDISK sync'); - const { members, ausbildungen, befoerderungen, untersuchungen, fahrgenehmigungen } = await scrapeAll(username, password); + + // Query dashboard accounts to limit detail scraping to linked members + const stNrResult = await pool.query<{ fdisk_standesbuch_nr: string }>( + `SELECT mp.fdisk_standesbuch_nr + FROM mitglieder_profile mp + JOIN users u ON u.id = mp.user_id + WHERE mp.fdisk_standesbuch_nr IS NOT NULL + AND u.last_login_at IS NOT NULL` + ); + const knownStNrs = new Set(stNrResult.rows.map(r => r.fdisk_standesbuch_nr)); + + // Also fetch names for users without standesbuchNr yet (for first-time linking) + const nameResult = await pool.query<{ given_name: string; family_name: string }>( + `SELECT u.given_name, u.family_name + FROM users u + JOIN mitglieder_profile mp ON mp.user_id = u.id + WHERE mp.fdisk_standesbuch_nr IS NULL + AND u.given_name IS NOT NULL AND u.family_name IS NOT NULL + AND u.last_login_at IS NOT NULL` + ); + const knownNames = new Set(nameResult.rows.map(r => `${r.given_name.toLowerCase()}::${r.family_name.toLowerCase()}`)); + + log(`Detail scraping for ${knownStNrs.size} linked + ${knownNames.size} name-matchable accounts`); + + const { members, ausbildungen, befoerderungen, untersuchungen, fahrgenehmigungen } = await scrapeAll(username, password, knownStNrs, knownNames); await syncToDatabase(pool, members, ausbildungen, befoerderungen, untersuchungen, fahrgenehmigungen, force); log(`Sync complete — ${members.length} members, ${ausbildungen.length} Ausbildungen, ${befoerderungen.length} Beförderungen, ${untersuchungen.length} Untersuchungen, ${fahrgenehmigungen.length} Fahrgenehmigungen`); } finally { diff --git a/sync/src/scraper.ts b/sync/src/scraper.ts index 382e6b2..666f39a 100644 --- a/sync/src/scraper.ts +++ b/sync/src/scraper.ts @@ -39,7 +39,7 @@ function cellText(text: string | undefined | null): string | null { return t || null; } -export async function scrapeAll(username: string, password: string): Promise<{ +export async function scrapeAll(username: string, password: string, knownStNrs: Set, knownNames: Set): Promise<{ members: FdiskMember[]; ausbildungen: FdiskAusbildung[]; befoerderungen: FdiskBefoerderung[]; @@ -73,6 +73,13 @@ export async function scrapeAll(username: string, password: string): Promise<{ const fahrgenehmigungen: FdiskFahrgenehmigung[] = []; for (const member of members) { + // Only scrape detail pages for members with a dashboard account + // (matched by standesbuchNr or by name for first-time linking) + const nameKey = `${member.vorname.toLowerCase()}::${member.zuname.toLowerCase()}`; + if (!knownStNrs.has(member.standesbuchNr) && !knownNames.has(nameKey)) { + continue; + } + try { // Navigate to member detail page — use direct URL if available, else search+click fallback const onDetail = member.detailUrl