Skip to content

Commit 57b86cc

Browse files
committed
Add Laravel Boost integration and MCP server tools
1 parent 3abf9e3 commit 57b86cc

16 files changed

Lines changed: 1153 additions & 0 deletions

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
"plannr/laravel-fast-refresh-database": "^1.2"
3232
},
3333
"suggest": {
34+
"laravel/boost": "Required for AI-assisted development guidelines and skills",
35+
"laravel/mcp": "Required for MCP server tools integration",
3436
"spatie/laravel-activitylog": "Required for included activity log based stats"
3537
},
3638
"autoload": {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## Javaabu Stats
2+
3+
This package provides time-series statistics for Laravel applications, supporting aggregate count and sum queries with filters, formatters, date ranges, and CSV export.
4+
5+
### Architecture
6+
7+
Stats are managed through a central static registry `TimeSeriesStats` and follow a repository pattern:
8+
9+
- `CountStatsRepository` — counts rows grouped by time period. Implement `query(): Builder`, `getTable(): string`, `getAggregateFieldName(): string`.
10+
- `SumStatsRepository` — sums a numeric field grouped by time period. Same methods as count, plus `getFieldToSum(): string`.
11+
12+
Stat classes live in `App\Stats\TimeSeries\` and are registered via `TimeSeriesStats::register()` in a service provider's `boot()` method with snake_case metric names.
13+
14+
### Artisan Generator
15+
16+
Generate and auto-register a stat class: `php artisan stats:time-series {Name} {Model} --type={count|sum}`.
17+
18+
### Filters
19+
20+
Define allowed filters by returning `Filter` objects from `allowedFilters()` using the `StatsFilter` factory:
21+
22+
- `StatsFilter::exact('name', 'column')` — matches a column value exactly
23+
- `StatsFilter::scope('name', 'scopeMethod')` — calls an Eloquent query scope
24+
- `StatsFilter::closure('name', fn)` — applies arbitrary query logic
25+
26+
### Formatters
27+
28+
Five built-in formatters: `default`, `chartjs`, `sparkline`, `flot`, `combined`. Register custom formatters via `TimeSeriesStats::registerFormatters()`.
29+
30+
### Routes
31+
32+
- `TimeSeriesStats::registerApiRoute()` — registers a GET endpoint returning JSON stats data
33+
- `TimeSeriesStats::registerRoutes()` — registers GET (web view) and POST (CSV export) endpoints
34+
35+
### Authorization
36+
37+
Override `canView(?Authorizable $user)` on a stat repository to control access. The default checks for `view_stats` permission. The `stats.view-time-series` middleware alias is auto-registered.
38+
39+
### Configuration
40+
41+
Publish with `php artisan vendor:publish --tag=stats-config`. Other publish tags: `stats-views`, `stats-stubs`. Key options in `config/stats.php`: `week_starts_on_sunday`, `date_locale`, `date_formats`, `default_time_series_mode`, `default_date_range`, `framework`, `default_layout`.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
name: stats-development
3+
description: Create, register, and filter time-series stat repositories using javaabu/stats.
4+
---
5+
6+
# Stats Development
7+
8+
## When to use this skill
9+
10+
Use when creating stat repositories, registering metrics, adding filters, or setting up stats routes with `javaabu/stats`.
11+
12+
## Core Principle: Use What Exists
13+
14+
The package provides built-in controllers, routes, middleware, formatters, and export. Your job is to generate stat repository classes for each model — not to reimplement infrastructure. Specifically:
15+
16+
- **Use the Artisan generator** to scaffold stat classes — it auto-registers them too
17+
- **Use `registerApiRoute()` / `registerRoutes()`** — never write custom route handlers for stats
18+
- **Use built-in formatters** (`default`, `chartjs`, `sparkline`, `flot`, `combined`) before creating custom ones
19+
- **Use the `stats.view-time-series` middleware** — it's auto-registered, don't recreate auth logic
20+
- **Use `ExportsTimeSeriesStats` trait** in existing controllers for CSV export — don't build export from scratch
21+
22+
When refactoring existing stats code, check for: direct filter class instantiation (should use `StatsFilter` factory), manual route definitions (should use `registerApiRoute`/`registerRoutes`), and reimplemented formatting or export logic.
23+
24+
## Creating a Stat
25+
26+
Place stat classes in `app/Stats/TimeSeries/`. Extend `CountStatsRepository` for row counts or `SumStatsRepository` for numeric sums. One class per model/metric — each stat targets a single table.
27+
28+
**Quick path — Artisan generator (auto-registers in AppServiceProvider):**
29+
30+
```bash
31+
php artisan stats:time-series OrdersCount Order --type=count
32+
php artisan stats:time-series PaymentAmounts Payment --type=sum
33+
```
34+
35+
**Manual — Count stat with filters** (namespace `App\Stats\TimeSeries`, import `StatsFilter`, `CountStatsRepository`, `Builder`):
36+
37+
```php
38+
class OrdersCount extends CountStatsRepository
39+
{
40+
public function query(): Builder { return Order::query(); }
41+
public function getTable(): string { return 'orders'; }
42+
public function getAggregateFieldName(): string { return 'count'; }
43+
44+
public function allowedFilters(): array
45+
{
46+
return [
47+
StatsFilter::exact('customer', 'customer_id'),
48+
StatsFilter::exact('status', 'status'),
49+
];
50+
}
51+
}
52+
```
53+
54+
**For a sum stat**, extend `SumStatsRepository` instead and add one extra method:
55+
56+
```php
57+
public function getFieldToSum(): string
58+
{
59+
return 'amount';
60+
}
61+
```
62+
63+
## Registering Stats
64+
65+
Register in `AppServiceProvider::boot()`. Metric names must be snake_case.
66+
67+
```php
68+
use Javaabu\Stats\TimeSeriesStats;
69+
70+
public function boot(): void
71+
{
72+
TimeSeriesStats::register([
73+
'orders_count' => OrdersCount::class,
74+
'payment_amounts' => PaymentAmounts::class,
75+
]);
76+
}
77+
```
78+
79+
To suppress built-in user_signups/user_logins stats: `TimeSeriesStats::excludeDefaultStats();`
80+
81+
## Filters
82+
83+
Always use the `StatsFilter` factory. Never instantiate filter classes directly.
84+
85+
```php
86+
use Javaabu\Stats\Filters\StatsFilter;
87+
88+
public function allowedFilters(): array
89+
{
90+
return [
91+
// Exact column match
92+
StatsFilter::exact('customer', 'customer_id'),
93+
94+
// Eloquent query scope — calls $query->whereActive()
95+
StatsFilter::scope('active', 'whereActive'),
96+
97+
// Custom closure — receives ($query, $value, $stat)
98+
StatsFilter::closure('min_amount', function (Builder $query, $value, $stat) {
99+
return $query->where('amount', '>=', $value);
100+
}),
101+
];
102+
}
103+
```
104+
105+
Pass filters when creating a stat instance:
106+
107+
```php
108+
$stats = TimeSeriesStats::createFromMetric('orders_count', PresetDateRanges::THIS_YEAR, [
109+
'customer' => 5,
110+
'status' => 'completed',
111+
]);
112+
```
113+
114+
## Routes
115+
116+
**API route (JSON) — register in `routes/api.php`:**
117+
118+
```php
119+
use Javaabu\Stats\TimeSeriesStats;
120+
121+
// IMPORTANT: Do NOT include /api in the URL — routes/api.php adds it automatically.
122+
TimeSeriesStats::registerApiRoute('/stats/time-series', 'stats.time-series.index');
123+
```
124+
125+
**Admin routes (web view + CSV export):**
126+
127+
```php
128+
TimeSeriesStats::registerRoutes('/stats/time-series', 'stats.index', 'stats.export', ['auth', 'stats.view-time-series']);
129+
```
130+
131+
The `stats.view-time-series` middleware alias is auto-registered by the package.
132+
133+
## Quick Reference
134+
135+
| What | How |
136+
|------|-----|
137+
| Base classes | `CountStatsRepository`, `SumStatsRepository` |
138+
| Register metrics | `TimeSeriesStats::register(['name' => Class::class])` |
139+
| Create instance | `TimeSeriesStats::createFromMetric('name', $dateRange, $filters)` |
140+
| Time modes | `TimeSeriesModes::HOUR\|DAY\|WEEK\|MONTH\|YEAR` |
141+
| Date ranges | `PresetDateRanges::THIS_YEAR\|LAST_30_DAYS\|LAST_7_DAYS\|...` |
142+
| Custom range | `new ExactDateRange('2024-01-01', '2024-12-31')` |
143+
| Format output | `$stat->format('chartjs', TimeSeriesModes::DAY)` |
144+
| Built-in formats | `default`, `chartjs`, `sparkline`, `flot`, `combined` |
145+
| Get total | `$stat->total()` |
146+
| Custom date col | Override `getDateFieldName()` (default: `created_at`) |
147+
| Authorization | Override `canView(?Authorizable $user)` (default: `view_stats` permission) |
148+
149+
See `references/` for formatters, export, authorization, and advanced features.
150+
151+
## Verify Against Live State
152+
153+
If the app has `laravel/mcp` installed, use the MCP tools to cross-check before writing code:
154+
155+
- **ListMetrics** — confirm which metrics are registered, their filters, and aggregate fields
156+
- **ListFormatters** — confirm available formatters (including custom ones)
157+
- **QueryStat** — test a metric with real data before building on top of it
158+
159+
This avoids guessing metric names or filter keys — the MCP tools reflect the actual running application.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Advanced Features
2+
3+
## Authorization
4+
5+
Override `canView()` to customize per-stat access control. Default requires `view_stats` permission.
6+
7+
```php
8+
use Illuminate\Contracts\Auth\Access\Authorizable;
9+
10+
public function canView(?Authorizable $user = null): bool
11+
{
12+
return $user && $user->can('view_order_stats');
13+
}
14+
```
15+
16+
Check if any stat is viewable: `TimeSeriesStats::canViewAny($user)`
17+
18+
## Custom Date Field
19+
20+
Override `getDateFieldName()` to group by a column other than `created_at`:
21+
22+
```php
23+
public function getDateFieldName(): string
24+
{
25+
return 'completed_at';
26+
}
27+
```
28+
29+
## Login & Signup Repositories
30+
31+
Built-in abstract repos for tracking user auth activity. Extend and implement `userModelClass()`:
32+
33+
```php
34+
use Javaabu\Stats\Repositories\TimeSeries\SignupsRepository;
35+
36+
class CustomerSignups extends SignupsRepository
37+
{
38+
public function userModelClass(): string
39+
{
40+
return \App\Models\Customer::class;
41+
}
42+
}
43+
```
44+
45+
For logins (requires `spatie/laravel-activitylog`), extend `LoginsRepository` the same way.
46+
47+
The package auto-registers `user_signups` and `user_logins` for the default User model. Suppress with `TimeSeriesStats::excludeDefaultStats()`.
48+
49+
## Configuration
50+
51+
Publish: `php artisan vendor:publish --tag=stats-config`
52+
53+
Key options in `config/stats.php`:
54+
55+
| Option | Default | Description |
56+
|--------|---------|-------------|
57+
| `week_starts_on_sunday` | `true` | Week grouping start day |
58+
| `date_locale` | `en_GB` | Date format locale |
59+
| `default_time_series_mode` | `DAY` | Default grouping granularity |
60+
| `default_date_range` | `LAST_7_DAYS` | Default date range |
61+
| `framework` | `material-admin-26` | CSS framework for views |
62+
63+
Other publish tags: `stats-views`, `stats-stubs`.
64+
65+
## MCP Tools
66+
67+
Three read-only MCP tools for AI agent integration (requires `laravel/mcp`):
68+
69+
- **ListMetrics** — lists all registered metrics with classes, aggregate fields, and allowed filters
70+
- **QueryStat** — queries a metric with parameters (mode, date range, filters, format)
71+
- **ListFormatters** — lists available output formats
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Querying Stats & Formatting Output
2+
3+
## Querying Stats Programmatically
4+
5+
```php
6+
use Javaabu\Stats\TimeSeriesStats;
7+
use Javaabu\Stats\Enums\PresetDateRanges;
8+
use Javaabu\Stats\Enums\TimeSeriesModes;
9+
10+
// Create a stat instance
11+
$stat = TimeSeriesStats::createFromMetric('orders_count', PresetDateRanges::LAST_30_DAYS, [
12+
'status' => 'completed',
13+
]);
14+
15+
// Get results grouped by time mode
16+
$results = $stat->results(TimeSeriesModes::DAY); // Collection of [count, day]
17+
18+
// Get formatted output for a charting library
19+
$chartData = $stat->format('chartjs', TimeSeriesModes::DAY);
20+
21+
// Get aggregate total
22+
$total = $stat->total();
23+
```
24+
25+
## Preset Date Ranges
26+
27+
`PresetDateRanges` enum values: `TODAY`, `YESTERDAY`, `THIS_WEEK`, `LAST_WEEK`, `THIS_MONTH`, `LAST_MONTH`, `THIS_YEAR`, `LAST_YEAR`, `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`, `LAST_5_YEARS`, `LAST_10_YEARS`, `LIFETIME`.
28+
29+
For custom ranges:
30+
31+
```php
32+
use Javaabu\Stats\Support\ExactDateRange;
33+
34+
$range = new ExactDateRange('2024-01-01', '2024-12-31');
35+
$stat = TimeSeriesStats::createFromMetric('orders_count', $range);
36+
```
37+
38+
## Comparison Periods
39+
40+
```php
41+
$current = PresetDateRanges::THIS_MONTH;
42+
$previous = $current->getPreviousDateRange();
43+
44+
$currentStats = TimeSeriesStats::createFromMetric('orders_count', $current);
45+
$previousStats = TimeSeriesStats::createFromMetric('orders_count', $previous);
46+
```
47+
48+
## Time Series Modes
49+
50+
`TimeSeriesModes` enum: `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR`. Controls SQL grouping granularity.
51+
52+
```php
53+
$stat->results(TimeSeriesModes::MONTH); // Monthly aggregation
54+
$stat->format('chartjs', TimeSeriesModes::WEEK); // Weekly chart data
55+
```
56+
57+
## Built-in Formatters
58+
59+
`default`, `chartjs`, `sparkline`, `flot`, `combined`. Request via API with `?format=chartjs`.
60+
61+
## Creating a Custom Formatter
62+
63+
Extend `AbstractTimeSeriesStatsFormatter` and implement `format()`:
64+
65+
```php
66+
<?php
67+
68+
namespace App\Formatters;
69+
70+
use Javaabu\Stats\Contracts\TimeSeriesStatsRepository;
71+
use Javaabu\Stats\Enums\TimeSeriesModes;
72+
use Javaabu\Stats\Formatters\TimeSeries\AbstractTimeSeriesStatsFormatter;
73+
74+
class ReactFormatter extends AbstractTimeSeriesStatsFormatter
75+
{
76+
public function format(TimeSeriesModes $mode, TimeSeriesStatsRepository $stats, ?TimeSeriesStatsRepository $compare = null): array
77+
{
78+
$field = $stats->getAggregateFieldName();
79+
$results = $stats->results($mode);
80+
81+
return [
82+
'labels' => $results->pluck($mode->value)->toArray(),
83+
'values' => $results->pluck($field)->toArray(),
84+
'total' => $stats->total(),
85+
];
86+
}
87+
}
88+
```
89+
90+
Register in a service provider:
91+
92+
```php
93+
TimeSeriesStats::registerFormatters([
94+
'react' => \App\Formatters\ReactFormatter::class,
95+
]);
96+
```

0 commit comments

Comments
 (0)