From 31dc8310552fa072741c4eceeeb489767d17ce5e Mon Sep 17 00:00:00 2001 From: DanMat Date: Sun, 16 Aug 2026 10:59:53 -0400 Subject: [PATCH] Retention: prune page views older than ANALYTICS_RETENTION_DAYS Registers a `nimbus prune` maintenance task (the new plugin maintenance capability) that deletes analytics_hits rows older than the retention window (ANALYTICS_RETENTION_DAYS, default 90; 0 keeps everything). Boundary test asserts the task is registered on load. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 2 ++ src/AnalyticsPlugin.php | 14 ++++++++++++++ tests/PackageIntegrationTest.php | 12 ++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd1482..66522ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,3 +15,5 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Third-party agent injection: a head contributor emits the snippet for a configured provider (Plausible, Fathom, or Google Analytics) from environment variables. +- Retention: a `nimbus prune` maintenance task drops page views older than + `ANALYTICS_RETENTION_DAYS` (default 90; `0` keeps everything). diff --git a/src/AnalyticsPlugin.php b/src/AnalyticsPlugin.php index 4622420..f30928e 100644 --- a/src/AnalyticsPlugin.php +++ b/src/AnalyticsPlugin.php @@ -53,6 +53,20 @@ static function (mixed $payload) use ($recorder): void { static fn (Request $request): string => $dashboard->render(), ); + // Retention: `nimbus prune` drops page views older than the window + // (ANALYTICS_RETENTION_DAYS, default 90; 0 keeps everything). + $context->maintenance()->register('prune-hits', static function () use ($storage): int { + $days = (int) (getenv('ANALYTICS_RETENTION_DAYS') ?: '90'); + if ($days <= 0) { + return 0; + } + + return $storage()->execute( + 'DELETE FROM ' . Schema::TABLE . ' WHERE occurred_at < :cutoff', + ['cutoff' => date('Y-m-d H:i:s', (int) strtotime("-{$days} days"))], + ); + }); + $agent = Agent::fromEnv(); if ($agent !== null) { $context->head()->register(new AgentContributor($agent)); diff --git a/tests/PackageIntegrationTest.php b/tests/PackageIntegrationTest.php index 428cbb2..439af30 100644 --- a/tests/PackageIntegrationTest.php +++ b/tests/PackageIntegrationTest.php @@ -10,6 +10,7 @@ use Nimbus\Plugin\PluginLoader; use Nimbus\Support\CoreEvents; use Nimbus\Support\EventDispatcher; +use Nimbus\Support\MaintenanceRegistry; use NimbusCMS\Analytics\AnalyticsPlugin; use PHPUnit\Framework\TestCase; @@ -69,17 +70,19 @@ public function test_the_package_is_typed_as_a_nimbus_plugin(): void self::assertSame('nimbuscms-plugin', $this->manifest()['type']); } - public function test_discovery_registers_the_migration_listener_and_admin_page(): void + public function test_discovery_registers_the_migration_listener_admin_page_and_retention(): void { - $migrations = new MigrationRegistry(); - $events = new EventDispatcher(); - $adminPages = new AdminPageRegistry(); + $migrations = new MigrationRegistry(); + $events = new EventDispatcher(); + $adminPages = new AdminPageRegistry(); + $maintenance = new MaintenanceRegistry(); $loader = new PluginLoader($this->installedAs()); $diagnostics = $loader->load(new PluginCapabilities( migrations: $migrations, events: $events, adminPages: $adminPages, + maintenance: $maintenance, )); self::assertSame([], $diagnostics, 'a correctly installed package must load cleanly'); @@ -88,6 +91,7 @@ public function test_discovery_registers_the_migration_listener_and_admin_page() self::assertSame(['nimbuscms.analytics:001_hits'], array_column($migrations->all(), 'name'), 'its migration'); self::assertTrue($events->hasListeners(CoreEvents::REQUEST_HANDLED), 'its page-view listener'); self::assertSame(['analytics'], array_column($adminPages->all(), 'slug'), 'its admin page'); + self::assertSame(['nimbuscms.analytics:prune-hits'], array_column($maintenance->all(), 'name'), 'its retention task'); } public function test_disabling_the_package_registers_nothing(): void