Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
PlanAnalyticsController
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
3 / 3
6
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 usersByPlan
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 usageByPlan
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3namespace App\Http\Controllers\v2\Admin\SystemDashboard;
4
5use App\Http\Controllers\Controller;
6use App\Http\Requests\v2\Admin\SystemDashboard\UsageByPlanRequest;
7use App\Http\Requests\v2\Admin\SystemDashboard\UsersByPlanRequest;
8use App\Http\Resources\v2\Admin\SystemDashboard\UsageByPlanResource;
9use App\Http\Resources\v2\Admin\SystemDashboard\UsersByPlanResource;
10use App\Http\Services\Admin\SystemDashboard\PlanAnalyticsService;
11use Carbon\Carbon;
12use Illuminate\Http\JsonResponse;
13use Illuminate\Support\Facades\Log;
14
15/**
16 * Admin plan analytics: user counts by plan and average usage by plan/period.
17 *
18 * Both endpoints are CMC-only, gated by VENGRESO_ADMIN role via FormRequest.
19 */
20class PlanAnalyticsController extends Controller
21{
22    public function __construct(
23        private PlanAnalyticsService $service
24    ) {}
25
26    /**
27     * GET /api/v2/admin/system/users-by-plan
28     *
29     * @response 200 {"result": {"plans": [{"identifier": "freemium", "title": "Freemium", "user_count": 1234}]}}
30     */
31    public function usersByPlan(UsersByPlanRequest $request): JsonResponse
32    {
33        $rows = $this->service->usersByPlan();
34        $resource = new UsersByPlanResource($rows);
35
36        return response()->json($resource->toArray($request));
37    }
38
39    /**
40     * GET /api/v2/admin/system/usage-by-plan?period=daily|weekly|monthly&from=YYYY-MM-DD&to=YYYY-MM-DD
41     *
42     * @response 200 {"result": {"period": "daily", "plan_resolution": "current", "data": [...]}}
43     * @response 422 {"message": "The period field is required."}
44     * @response 500 {"error": {"code": "USAGE_AGGREGATION_FAILED", "message": "..."}}
45     */
46    public function usageByPlan(UsageByPlanRequest $request): JsonResponse
47    {
48        $period = $request->input('period');
49        $from = $request->input('from')
50            ? Carbon::parse($request->input('from'))->startOfDay()
51            : Carbon::now()->subDays(30)->startOfDay();
52        $to = $request->input('to')
53            ? Carbon::parse($request->input('to'))->endOfDay()
54            : Carbon::now()->endOfDay();
55
56        try {
57            $rows = $this->service->usageByPlan($period, $from, $to);
58            $resource = new UsageByPlanResource($rows, $period);
59
60            return response()->json($resource->toArray($request));
61        } catch (\Throwable $e) {
62            Log::error('[SystemDashboard] usageByPlan controller caught exception', [
63                'period'    => $period,
64                'exception' => $e->getMessage(),
65            ]);
66
67            return response()->json([
68                'error' => [
69                    'code'    => 'USAGE_AGGREGATION_FAILED',
70                    'message' => 'Usage data is currently unavailable. The team has been notified.',
71                ],
72            ], 500);
73        }
74    }
75}