diff --git a/src/app/Http/Controllers/API/V4/MetricsController.php b/src/app/Http/Controllers/API/V4/MetricsController.php new file mode 100644 --- /dev/null +++ b/src/app/Http/Controllers/API/V4/MetricsController.php @@ -0,0 +1,117 @@ +selectRaw('distinct wallet_id') + ->where('status', Payment::STATUS_PAID); + + // A subquery to get users' wallets (by entitlement) - one record per user + $wallets = DB::table('entitlements') + ->selectRaw("min(wallet_id) as id, entitleable_id as user_id") + ->where('entitleable_type', User::class) + ->groupBy('entitleable_id'); + + // Count all non-degraded and non-deleted users with any successful payment + $count = DB::table('users') + ->joinSub($wallets, 'wallets', function ($join) { + $join->on('users.id', '=', 'wallets.user_id'); + }) + ->joinSub($payments, 'payments', function ($join) { + $join->on('wallets.id', '=', 'payments.wallet_id'); + }) + ->whereNull('users.deleted_at') + ->whereNot('users.status', '&', User::STATUS_DEGRADED) + ->whereNot('users.status', '&', User::STATUS_SUSPENDED); + + if ($tenant_id) { + $count->where('users.tenant_id', $tenant_id); + } else { + $count->whereNull('users.tenant_id'); + } + + return $count->count(); + } + + /** + * Collect number of wallets that require topup + */ + protected function numberOfWalletsWithBalanceBelowManadate(): int + { + return Wallet::select('wallets.id') + ->join('users', 'users.id', '=', 'wallets.user_id') + ->join('wallet_settings', function (\Illuminate\Database\Query\JoinClause $join) { + $join->on('wallet_settings.wallet_id', '=', 'wallets.id') + ->where('wallet_settings.key', '=', 'mandate_balance'); + }) + ->whereNull('users.deleted_at') + ->whereRaw('wallets.balance < (wallet_settings.value * 100)') + ->whereNot('users.status', '&', User::STATUS_DEGRADED | User::STATUS_SUSPENDED | User::STATUS_DELETED) + ->count(); + } + + /** + * Expose kolab metrics + * + * @return \Illuminate\Http\JsonResponse The response + */ + public function metrics() + { + $appDomain = \config('app.domain'); + $tenantId = \config('app.tenant_id'); + //TODO just get this from the stats table instead? + $numberOfPayingUsers = $this->collectPayersCount(); + + $numberOfUsers = User::count(); + $numberOfDeletedUsers = User::withTrashed()->whereNotNull('deleted_at')->count(); + $numberOfSuspendedUsers = User::where('status', '&', User::STATUS_SUSPENDED)->count(); + $numberOfRestrictedUsers = User::where('status', '&', User::STATUS_RESTRICTED)->count(); + $numberOfWalletsWithBalanceBelowManadate = $this->numberOfWalletsWithBalanceBelowManadate(); + + $text = << "text/plain", + ] + ); + } +} diff --git a/src/routes/api.php b/src/routes/api.php --- a/src/routes/api.php +++ b/src/routes/api.php @@ -227,6 +227,7 @@ Route::post('policy/ratelimit', [API\V4\PolicyController::class, 'ratelimit']); Route::post('policy/spf', [API\V4\PolicyController::class, 'senderPolicyFramework']); Route::get('health/status', [API\V4\HealthController::class, 'status']); + Route::get('metrics', [API\V4\MetricsController::class, 'metrics']); } ); } diff --git a/src/tests/Feature/Controller/MetricsTest.php b/src/tests/Feature/Controller/MetricsTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/Controller/MetricsTest.php @@ -0,0 +1,40 @@ +useServicesUrl(); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * Test webhook + */ + public function testStatus(): void + { + $response = $this->get("api/webhooks/metrics"); + $response->assertStatus(200); + + $body = $response->content(); + $this->assertTrue(str_contains($body, 'kolab')); + } +}