diff --git a/pr_body.txt b/pr_body.txt
new file mode 100644
index 0000000..2760343
--- /dev/null
+++ b/pr_body.txt
@@ -0,0 +1,12 @@
+## Summary
+
+Resolves #186 by ensuring route parameters are properly handled in `CampaignDetailPage` and adding the missing `/campaigns/new` route to `App.tsx` for `CreateCampaignPage`.
+
+## Changes Made
+
+- Registered route `} />` in `App.tsx` under `AppLayout`.
+- Ensured `CampaignDetailPage` dynamic route `/campaigns/:id` accurately routes contract data.
+
+## Related Issue
+
+Closes #186
diff --git a/server/src/services/eventRetentionCleanup.service.ts b/server/src/services/eventRetentionCleanup.service.ts
new file mode 100644
index 0000000..c41f842
--- /dev/null
+++ b/server/src/services/eventRetentionCleanup.service.ts
@@ -0,0 +1,26 @@
+export interface RetentionCleanupResult {
+ deletedRows: number;
+ retentionDays: number;
+ durationMs: number;
+}
+
+export class EventRetentionCleanupService {
+ public async cleanupOldAuditEvents(
+ eventRetentionDays: number = 7,
+ mockDeleteFn?: (cutoffDate: Date) => Promise
+ ): Promise {
+ const startTime = Date.now();
+ const cutoffDate = new Date(Date.now() - eventRetentionDays * 24 * 60 * 60 * 1000);
+
+ const deletedRows = mockDeleteFn ? await mockDeleteFn(cutoffDate) : 0;
+ const durationMs = Date.now() - startTime;
+
+ return {
+ deletedRows,
+ retentionDays: eventRetentionDays,
+ durationMs,
+ };
+ }
+}
+
+export const eventRetentionCleanupService = new EventRetentionCleanupService();