(retentionDays)
| 581 | } |
| 582 | |
| 583 | async function cleanupExpiredRuns(retentionDays) { |
| 584 | if (!db.UpdateRun || !db.UpdateRunEvent) { |
| 585 | return { deletedRuns: 0, deletedEvents: 0 }; |
| 586 | } |
| 587 | |
| 588 | const parsedRetentionDays = Number.parseInt(retentionDays, 10); |
| 589 | const effectiveRetentionDays = Number.isInteger(parsedRetentionDays) && parsedRetentionDays > 0 |
| 590 | ? parsedRetentionDays |
| 591 | : 30; |
| 592 | const cutoff = new Date(Date.now() - (effectiveRetentionDays * 24 * 60 * 60 * 1000)); |
| 593 | |
| 594 | try { |
| 595 | const expiredRuns = await db.UpdateRun.findAll({ |
| 596 | where: { |
| 597 | startedAt: { |
| 598 | [Op.lt]: cutoff, |
| 599 | }, |
| 600 | }, |
| 601 | attributes: ["id"], |
| 602 | }); |
| 603 | const runIds = expiredRuns.map((run) => run.id); |
| 604 | |
| 605 | if (runIds.length === 0) { |
| 606 | return { deletedRuns: 0, deletedEvents: 0 }; |
| 607 | } |
| 608 | |
| 609 | const deletedEvents = await db.UpdateRunEvent.destroy({ |
| 610 | where: { |
| 611 | runId: runIds, |
| 612 | }, |
| 613 | }); |
| 614 | const deletedRuns = await db.UpdateRun.destroy({ |
| 615 | where: { |
| 616 | id: runIds, |
| 617 | }, |
| 618 | }); |
| 619 | |
| 620 | emitStructuredAuditLog("cleanup_completed", { |
| 621 | cutoff: cutoff.toISOString(), |
| 622 | retentionDays: effectiveRetentionDays, |
| 623 | deletedRuns, |
| 624 | deletedEvents, |
| 625 | }); |
| 626 | |
| 627 | return { deletedRuns, deletedEvents }; |
| 628 | } catch (error) { |
| 629 | emitAuditInternalError("[updateAudit] failed to cleanup expired runs", error); |
| 630 | return { deletedRuns: 0, deletedEvents: 0 }; |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | module.exports = { |
| 635 | createHash, |
no test coverage detected