( db: DatabaseAdapter, payload: PushImportPayload )
| 306 | } |
| 307 | |
| 308 | function incrementalImport( |
| 309 | db: DatabaseAdapter, |
| 310 | payload: PushImportPayload |
| 311 | ): { |
| 312 | writtenCount: number |
| 313 | duplicateCount: number |
| 314 | metaUpdated: boolean |
| 315 | membersAdded: number |
| 316 | membersUpdated: number |
| 317 | } { |
| 318 | const metaUpdateMode = payload.options?.metaUpdateMode ?? 'patch' |
| 319 | const memberUpdateMode = payload.options?.memberUpdateMode ?? 'upsert' |
| 320 | |
| 321 | // Load dedup keys |
| 322 | const existingPmids = new Set<string>() |
| 323 | const existingKeys = new Set<string>() |
| 324 | ;( |
| 325 | db.prepare('SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL').all() as Array<{ |
| 326 | platform_message_id: string |
| 327 | }> |
| 328 | ).forEach((r) => existingPmids.add(r.platform_message_id)) |
| 329 | // Load hashes for ALL existing messages (not just those without pmid) so that |
| 330 | // messages previously imported with a platformMessageId are still caught by |
| 331 | // content-hash dedup when the same content arrives without one. |
| 332 | ;( |
| 333 | db |
| 334 | .prepare(`SELECT msg.ts, m.platform_id, msg.content FROM message msg JOIN member m ON msg.sender_id = m.id`) |
| 335 | .all() as Array<{ ts: number; platform_id: string; content: string | null }> |
| 336 | ).forEach((r) => existingKeys.add(generateMessageKey(r.ts, r.platform_id, r.content))) |
| 337 | |
| 338 | let metaUpdated = false |
| 339 | let membersAdded = 0 |
| 340 | let membersUpdated = 0 |
| 341 | |
| 342 | if (payload.meta && metaUpdateMode === 'patch') { |
| 343 | const m = payload.meta |
| 344 | db.prepare( |
| 345 | `UPDATE meta SET name = COALESCE(NULLIF(?, ''), name), group_id = COALESCE(NULLIF(?, ''), group_id), group_avatar = COALESCE(NULLIF(?, ''), group_avatar), owner_id = COALESCE(NULLIF(?, ''), owner_id), imported_at = ?` |
| 346 | ).run(m.name || '', m.groupId || '', m.groupAvatar || '', m.ownerId || '', Math.floor(Date.now() / 1000)) |
| 347 | metaUpdated = true |
| 348 | } |
| 349 | |
| 350 | if (payload.members && memberUpdateMode === 'upsert') { |
| 351 | const upsertMember = db.prepare( |
| 352 | `INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles) VALUES (?, ?, ?, ?, ?) |
| 353 | ON CONFLICT(platform_id) DO UPDATE SET |
| 354 | account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name), |
| 355 | group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname), |
| 356 | avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar), |
| 357 | roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END` |
| 358 | ) |
| 359 | const getMemberId = db.prepare('SELECT id FROM member WHERE platform_id = ?') |
| 360 | const existingMemberIds = new Set( |
| 361 | (db.prepare('SELECT platform_id FROM member').all() as Array<{ platform_id: string }>).map((r) => r.platform_id) |
| 362 | ) |
| 363 | |
| 364 | db.transaction(() => { |
| 365 | for (const m of payload.members!) { |
no test coverage detected