symfonia-ai-tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +489 -0
  2. package/bin/cli.mjs +35 -0
  3. package/lib/installer.mjs +495 -0
  4. package/lib/questions.mjs +332 -0
  5. package/lib/ui.mjs +76 -0
  6. package/lib/utils.mjs +231 -0
  7. package/package.json +26 -0
  8. package/templates/base/CLAUDE.md +34 -0
  9. package/templates/base/_ai/_guidelines_header.md +70 -0
  10. package/templates/base/_ai/context/README.md +20 -0
  11. package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
  12. package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
  13. package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
  14. package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
  15. package/templates/base/_ai/skills/README.md +80 -0
  16. package/templates/base/_ai/skills/TEMPLATE.md +106 -0
  17. package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
  18. package/templates/base/_ai/skills/debug/SKILL.md +93 -0
  19. package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
  20. package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
  21. package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
  22. package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
  23. package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
  24. package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
  25. package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
  26. package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
  27. package/templates/base/_claude/settings.local.json +37 -0
  28. package/templates/base/_cursor/rules/global.mdc +7 -0
  29. package/templates/base/_editorconfig +18 -0
  30. package/templates/base/_gemini/settings.json +3 -0
  31. package/templates/base/_github/copilot-instructions.md +1 -0
  32. package/templates/base/_github/pull_request_template.md +23 -0
  33. package/templates/base/_gitignore +22 -0
  34. package/templates/base/_junie/guidelines.md +1 -0
  35. package/templates/base/commit-instructions.md +92 -0
  36. package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
  37. package/templates/packs/docker/_guidelines.md +10 -0
  38. package/templates/packs/docker/pack.json +8 -0
  39. package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
  40. package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
  41. package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
  42. package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
  43. package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
  44. package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
  45. package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
  46. package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
  47. package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
  48. package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
  49. package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
  50. package/templates/packs/laravel/_guidelines.md +25 -0
  51. package/templates/packs/laravel/pack.json +6 -0
  52. package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
  53. package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
  54. package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
  55. package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
  56. package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
  57. package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
  58. package/templates/packs/playwright/_guidelines.md +12 -0
  59. package/templates/packs/playwright/pack.json +9 -0
  60. package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
  61. package/templates/packs/storybook/pack.json +6 -0
  62. package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
  63. package/templates/packs/vitest/pack.json +6 -0
  64. package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
  65. package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
  66. package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
  67. package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
  68. package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
  69. package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
  70. package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
  71. package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
  72. package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
  73. package/templates/packs/vue3/_guidelines.md +45 -0
  74. package/templates/packs/vue3/pack.json +11 -0
@@ -0,0 +1,22 @@
1
+ # AI tool configs with tokens/secrets
2
+ .claude/settings.local.json
3
+ .cursor/mcp.json
4
+ .mcp.json
5
+
6
+ # GSD working files
7
+ .planning/
8
+
9
+ # Environment
10
+ .env
11
+ .env.local
12
+ .env.*.local
13
+
14
+ # OS
15
+ .DS_Store
16
+ Thumbs.db
17
+
18
+ # IDE
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
@@ -0,0 +1 @@
1
+ Read and follow .ai/guidelines.md as mandatory context.
@@ -0,0 +1,92 @@
1
+ # Commit Instructions
2
+
3
+ ## Issue Number from Branch
4
+
5
+ Extract the issue number from the current branch name and use it as prefix:
6
+
7
+ ```
8
+ feature/{{JIRA_PREFIX}}-1234-feature-name → {{JIRA_PREFIX}}-1234: description
9
+ bugfix/{{JIRA_PREFIX}}-567-fix-name → {{JIRA_PREFIX}}-567: description
10
+ ```
11
+
12
+ ## Format
13
+
14
+ ```
15
+ {{JIRA_PREFIX}}-XXXX: <type>(<scope>): <short description>
16
+
17
+ [optional longer description — explain WHY, not WHAT]
18
+ ```
19
+
20
+ ## Types
21
+
22
+ - `feat` - new feature
23
+ - `fix` - bug fix
24
+ - `refactor` - code restructuring (no behavior change)
25
+ - `test` - adding/changing tests
26
+ - `docs` - documentation
27
+ - `style` - formatting (no logic change)
28
+ - `chore` - build/CI/tooling changes
29
+
30
+ ## Scope
31
+
32
+ Module or component name: `auth`, `api`, `ui`, `store`, `config`, `db`
33
+
34
+ ## Rules
35
+
36
+ 1. **Explain WHY, not WHAT** — don't repeat what's visible in the diff
37
+ 2. **Max 72 chars** in the first line
38
+ 3. **One commit = one logical change** — split unrelated changes into separate commits
39
+ 4. **Never add `Co-Authored-By`, `Generated by`, or any AI attribution**
40
+ 5. **Do not commit without developer approval**
41
+ 6. **Language**: English
42
+
43
+ ## Examples
44
+
45
+ ### Good
46
+
47
+ ```
48
+ {{JIRA_PREFIX}}-456: feat(auth): add JWT refresh token rotation
49
+
50
+ Implement automatic token rotation on each refresh to prevent
51
+ token replay attacks. Tokens are invalidated after single use.
52
+ ```
53
+
54
+ ```
55
+ {{JIRA_PREFIX}}-789: fix(api): prevent race condition in concurrent updates
56
+
57
+ Multiple simultaneous PATCH requests could overwrite each other.
58
+ Added optimistic locking with version field comparison.
59
+ ```
60
+
61
+ ### Bad
62
+
63
+ ```
64
+ # Too vague — no WHY
65
+ fix(auth): fixed bug
66
+
67
+ # Repeats the diff — no WHY
68
+ feat(users): added email field to form and regex validation
69
+
70
+ # Two unrelated changes in one commit
71
+ feat(users): add validation and change form layout
72
+ ```
73
+
74
+ ## Branch Naming
75
+
76
+ | Type | Pattern |
77
+ |------|---------|
78
+ | Feature | `feature/{{JIRA_PREFIX}}-XXXX-short-name` |
79
+ | Bugfix | `bugfix/{{JIRA_PREFIX}}-XXXX-short-name` |
80
+ | Hotfix | `hotfix/{{JIRA_PREFIX}}-XXXX-short-name` |
81
+ | Release | `release/vX.Y.Z` |
82
+
83
+ ## Breaking Changes
84
+
85
+ Add `BREAKING CHANGE:` in footer when breaking backward compatibility:
86
+
87
+ ```
88
+ {{JIRA_PREFIX}}-321: refactor(api)!: change auth endpoint response format
89
+
90
+ BREAKING CHANGE: /api/auth/login now returns { token, expiresAt }
91
+ instead of { access_token, expires_in }. All clients must update.
92
+ ```
@@ -0,0 +1,193 @@
1
+ ---
2
+ applyTo: "**/Dockerfile,**/docker-compose*,**/docker/**"
3
+ ---
4
+
5
+ # Docker & Deployment - Instrukcje
6
+
7
+ ## Zasada nadrzedna
8
+
9
+ **Wszystkie komendy PHP/Artisan/Composer uruchamiaj wewnatrz kontenera Docker.**
10
+
11
+ ```bash
12
+ # DOBRZE
13
+ docker exec {{DOCKER_CONTAINER}} php artisan migrate
14
+ docker exec {{DOCKER_CONTAINER}} composer install
15
+ docker exec {{DOCKER_CONTAINER}} php artisan test
16
+
17
+ # ZLE - nigdy na hoscie
18
+ php artisan migrate
19
+ composer install
20
+ ```
21
+
22
+ ## Podstawowe komendy
23
+
24
+ ```bash
25
+ # Start srodowiska
26
+ docker compose up -d
27
+
28
+ # Stop
29
+ docker compose down
30
+
31
+ # Rebuild
32
+ docker compose up -d --build
33
+
34
+ # Logi
35
+ docker compose logs -f app
36
+
37
+ # Shell wewnatrz kontenera
38
+ docker exec -it {{DOCKER_CONTAINER}} bash
39
+
40
+ # Lista kontenerow
41
+ docker compose ps
42
+ ```
43
+
44
+ ## Artisan w Docker
45
+
46
+ ```bash
47
+ # Migracje
48
+ docker exec {{DOCKER_CONTAINER}} php artisan migrate
49
+ docker exec {{DOCKER_CONTAINER}} php artisan migrate:rollback
50
+ docker exec {{DOCKER_CONTAINER}} php artisan migrate:fresh --seed
51
+
52
+ # Cache
53
+ docker exec {{DOCKER_CONTAINER}} php artisan optimize:clear
54
+ docker exec {{DOCKER_CONTAINER}} php artisan config:cache
55
+ docker exec {{DOCKER_CONTAINER}} php artisan route:cache
56
+
57
+ # Queue
58
+ docker exec {{DOCKER_CONTAINER}} php artisan queue:work --tries=3
59
+ docker exec {{DOCKER_CONTAINER}} php artisan horizon
60
+
61
+ # Generowanie
62
+ docker exec {{DOCKER_CONTAINER}} php artisan make:model Feature -mfcr
63
+ docker exec {{DOCKER_CONTAINER}} php artisan make:test FeatureTest --unit
64
+ docker exec {{DOCKER_CONTAINER}} php artisan make:request StoreFeatureRequest
65
+
66
+ # Testy
67
+ docker exec {{DOCKER_CONTAINER}} php artisan test
68
+ docker exec {{DOCKER_CONTAINER}} php artisan test --filter=FeatureTest
69
+ docker exec {{DOCKER_CONTAINER}} php artisan test --coverage
70
+ ```
71
+
72
+ ## docker-compose.yml - typowa struktura
73
+
74
+ ```yaml
75
+ services:
76
+ app:
77
+ build:
78
+ context: .
79
+ dockerfile: docker/app/Dockerfile
80
+ volumes:
81
+ - ./:/var/www/html
82
+ - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
83
+ ports:
84
+ - "${APP_PORT:-80}:80"
85
+ depends_on:
86
+ - postgres
87
+ - redis
88
+ environment:
89
+ PHP_IDE_CONFIG: "serverName=localhost"
90
+
91
+ horizon:
92
+ build:
93
+ context: .
94
+ dockerfile: docker/app/Dockerfile
95
+ command: php artisan horizon
96
+ depends_on:
97
+ - postgres
98
+ - redis
99
+ restart: unless-stopped
100
+
101
+ postgres:
102
+ image: postgres:16-alpine
103
+ environment:
104
+ POSTGRES_DB: "${DB_DATABASE}"
105
+ POSTGRES_USER: "${DB_USERNAME}"
106
+ POSTGRES_PASSWORD: "${DB_PASSWORD}"
107
+ ports:
108
+ - "${DB_PORT:-5432}:5432"
109
+ volumes:
110
+ - postgres-data:/var/lib/postgresql/data
111
+
112
+ redis:
113
+ image: redis:alpine
114
+ ports:
115
+ - "${REDIS_PORT:-6379}:6379"
116
+
117
+ mailpit:
118
+ image: axllent/mailpit:latest
119
+ ports:
120
+ - "8025:8025"
121
+ - "1025:1025"
122
+
123
+ volumes:
124
+ postgres-data:
125
+ ```
126
+
127
+ ## Dockerfile - multi-stage build
128
+
129
+ ```dockerfile
130
+ # Stage 1: Frontend build
131
+ FROM node:20-alpine AS frontend
132
+ WORKDIR /app
133
+ COPY package*.json ./
134
+ RUN npm ci
135
+ COPY resources/ resources/
136
+ COPY vite.config.ts tsconfig.json ./
137
+ RUN npm run build
138
+
139
+ # Stage 2: PHP application
140
+ FROM php:8.3-apache
141
+ WORKDIR /var/www/html
142
+
143
+ # System dependencies
144
+ RUN apt-get update && apt-get install -y \
145
+ libpq-dev libzip-dev zip unzip git \
146
+ && docker-php-ext-install pdo_pgsql zip opcache
147
+
148
+ # Composer
149
+ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
150
+
151
+ # Application
152
+ COPY . .
153
+ COPY --from=frontend /app/public/build public/build
154
+
155
+ RUN composer install --optimize-autoloader --no-dev \
156
+ && php artisan storage:link \
157
+ && chown -R www-data:www-data storage bootstrap/cache
158
+
159
+ EXPOSE 80
160
+ CMD ["apache2-foreground"]
161
+ ```
162
+
163
+ ## Debugowanie
164
+
165
+ ```bash
166
+ # Logi aplikacji (dzienne pliki!)
167
+ docker exec {{DOCKER_CONTAINER}} tail -f storage/logs/laravel-$(date +%Y-%m-%d).log
168
+
169
+ # Logi kontenera
170
+ docker compose logs -f app
171
+
172
+ # Status Horizon (kolejki)
173
+ docker exec {{DOCKER_CONTAINER}} php artisan horizon:status
174
+
175
+ # Sprawdz konfiguracje
176
+ docker exec {{DOCKER_CONTAINER}} php artisan config:show database
177
+ docker exec {{DOCKER_CONTAINER}} php artisan route:list --path=api/feature
178
+
179
+ # Czyszczenie po zmianach
180
+ docker exec {{DOCKER_CONTAINER}} php artisan optimize:clear
181
+ docker exec {{DOCKER_CONTAINER}} composer dump-autoload
182
+ ```
183
+
184
+ ## Zasady
185
+
186
+ 1. **Zawsze Docker** - komendy PHP/Artisan/Composer w kontenerze, nigdy na hoscie
187
+ 2. **Pelna nazwa kontenera** - `{{DOCKER_CONTAINER}}`, nie `docker compose exec app`
188
+ 3. **Logi dzienne** - `laravel-YYYY-MM-DD.log`, nie `laravel.log`
189
+ 4. **`optimize:clear`** - po zmianach konfiguracji, routow, zmiennych
190
+ 5. **Multi-stage build** - frontend oddzielnie, PHP oddzielnie
191
+ 6. **Volumes** - kod montowany przez volume w dev, kopiowany w prod
192
+ 7. **Horizon** - oddzielny kontener dla kolejek, `restart: unless-stopped`
193
+ 8. **Env** - `${VAR:-default}` w docker-compose, `.env` dla Laravel
@@ -0,0 +1,10 @@
1
+
2
+
3
+ ---
4
+
5
+ ## Docker
6
+
7
+ - Container: `{{DOCKER_CONTAINER}}`
8
+ - All commands run inside container: `docker exec {{DOCKER_CONTAINER}} ...`
9
+ - List containers: `docker compose ps`
10
+ - Logs: `docker compose logs -f [service]`
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "Docker",
3
+ "description": "Docker Compose — komendy w kontenerze",
4
+ "default": false,
5
+ "placeholders": {
6
+ "DOCKER_CONTAINER": { "question": "Nazwa kontenera Docker", "default": "app" }
7
+ }
8
+ }
@@ -0,0 +1,251 @@
1
+ ---
2
+ applyTo: "**/*Resource*.php,**/*Request*.php,**/*Policy*.php,**/*Filter*.php"
3
+ ---
4
+
5
+ # API Resources, Form Requests, Policies, Filters - Instrukcje
6
+
7
+ ## API Resources (transformery)
8
+
9
+ Formatuja odpowiedz API - nigdy nie zwracaj modelu bezposrednio:
10
+
11
+ ```php
12
+ declare(strict_types=1);
13
+
14
+ namespace Modules\Feature\Http\Resources;
15
+
16
+ use Illuminate\Http\Resources\Json\JsonResource;
17
+
18
+ class FeatureResource extends JsonResource
19
+ {
20
+ public function toArray($request): array
21
+ {
22
+ return [
23
+ 'id' => $this->getId(),
24
+ 'name' => $this->getName(),
25
+ 'description' => $this->getDescription(),
26
+ 'status' => $this->getStatus()->value,
27
+ 'createdAt' => $this->getCreatedAt()->toIso8601String(),
28
+ 'author' => UserResource::make($this->whenLoaded(self::RELATION_AUTHOR)),
29
+ 'category' => CategoryResource::make($this->whenLoaded(self::RELATION_CATEGORY)),
30
+ ];
31
+ }
32
+ }
33
+ ```
34
+
35
+ ### Collection Resource
36
+
37
+ ```php
38
+ class FeatureCollection extends ResourceCollection
39
+ {
40
+ public $collects = FeatureResource::class;
41
+
42
+ public function toArray($request): array
43
+ {
44
+ return [
45
+ 'data' => $this->collection,
46
+ 'meta' => [
47
+ 'total' => $this->total(),
48
+ 'perPage' => $this->perPage(),
49
+ 'currentPage' => $this->currentPage(),
50
+ ],
51
+ ];
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Form Requests (walidacja)
57
+
58
+ Kazdy endpoint z danymi wejsciowymi ma Form Request:
59
+
60
+ ```php
61
+ declare(strict_types=1);
62
+
63
+ namespace Modules\Feature\Http\Requests;
64
+
65
+ use Illuminate\Foundation\Http\FormRequest;
66
+ use Illuminate\Validation\Rule;
67
+
68
+ class StoreFeatureRequest extends FormRequest
69
+ {
70
+ public function authorize(): bool
71
+ {
72
+ return $this->user()->can('create', Feature::class);
73
+ }
74
+
75
+ public function rules(): array
76
+ {
77
+ return [
78
+ 'name' => ['required', 'string', 'min:2', 'max:255'],
79
+ 'description' => ['nullable', 'string', 'max:5000'],
80
+ 'category_id' => ['nullable', 'integer', 'exists:categories,id'],
81
+ 'status' => ['required', Rule::in(FeatureStatus::cases())],
82
+ 'tags' => ['nullable', 'array'],
83
+ 'tags.*' => ['string', 'max:50'],
84
+ ];
85
+ }
86
+
87
+ public function messages(): array
88
+ {
89
+ return [
90
+ 'name.required' => __('feature.validation.name_required'),
91
+ 'name.max' => __('feature.validation.name_max'),
92
+ ];
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### Index/List Request z filtrowaniem i sortowaniem
98
+
99
+ ```php
100
+ class IndexFeatureRequest extends FormRequest
101
+ {
102
+ public function rules(): array
103
+ {
104
+ return [
105
+ 'filter' => ['nullable', 'array'],
106
+ 'filter.name' => ['nullable', 'string'],
107
+ 'filter.status' => ['nullable', Rule::in(FeatureStatus::cases())],
108
+ 'filter.dateFrom' => ['nullable', 'date'],
109
+ 'filter.dateTo' => ['nullable', 'date', 'after_or_equal:filter.dateFrom'],
110
+ 'sort' => ['nullable', 'array'],
111
+ 'sort.*' => ['string', Rule::in([
112
+ 'name', '-name',
113
+ 'createdAt', '-createdAt',
114
+ 'status', '-status',
115
+ ])],
116
+ 'page' => ['required', 'integer', 'min:1'],
117
+ 'limit' => ['required', 'integer', 'min:1', 'max:100'],
118
+ ];
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## Policies (autoryzacja)
124
+
125
+ ```php
126
+ declare(strict_types=1);
127
+
128
+ namespace Modules\Feature\Http\Policies;
129
+
130
+ use App\Models\User;
131
+
132
+ class FeaturePolicy extends BasePolicy
133
+ {
134
+ public const MODULE_PERMISSION = Permission::FEATURES;
135
+
136
+ public function viewAny(User $user): bool
137
+ {
138
+ return $user->hasPermission(self::MODULE_PERMISSION);
139
+ }
140
+
141
+ public function view(User $user, Feature $feature): bool
142
+ {
143
+ return $this->viewAny($user)
144
+ && $feature->getUserId() === $user->getId();
145
+ }
146
+
147
+ public function create(User $user): bool
148
+ {
149
+ return $user->hasPermission(self::MODULE_PERMISSION);
150
+ }
151
+
152
+ public function update(User $user, Feature $feature): bool
153
+ {
154
+ return $this->admin($user)
155
+ || $feature->getUserId() === $user->getId();
156
+ }
157
+
158
+ public function delete(User $user, Feature $feature): bool
159
+ {
160
+ return $this->admin($user);
161
+ }
162
+
163
+ public function admin(User $user): bool
164
+ {
165
+ return $this->moduleAdmin($user);
166
+ }
167
+ }
168
+ ```
169
+
170
+ ### Uzycie w kontrolerze
171
+
172
+ ```php
173
+ // W kontrolerze - authorize przez can()
174
+ public function update(UpdateFeatureRequest $request, int $featureId): JsonResponse
175
+ {
176
+ $feature = $this->service->findOrFail($featureId);
177
+ $this->authorize('update', $feature);
178
+
179
+ // ...
180
+ }
181
+
182
+ // W routach
183
+ Route::get('/features', [FeatureController::class, 'index'])
184
+ ->can('viewAny', Feature::class);
185
+ ```
186
+
187
+ ## Custom Query Filters (Spatie QueryBuilder)
188
+
189
+ ```php
190
+ declare(strict_types=1);
191
+
192
+ namespace Modules\Feature\Database\Filters;
193
+
194
+ use Spatie\QueryBuilder\Filters\Filter;
195
+ use Illuminate\Database\Eloquent\Builder;
196
+
197
+ class DateRangeFilter implements Filter
198
+ {
199
+ public function __construct(
200
+ private readonly string $column,
201
+ ) {}
202
+
203
+ public function __invoke(Builder $query, $value, string $property): void
204
+ {
205
+ if (is_array($value)) {
206
+ if (isset($value['from'])) {
207
+ $query->where($this->column, '>=', Carbon::parse($value['from']));
208
+ }
209
+ if (isset($value['to'])) {
210
+ $query->where($this->column, '<=', Carbon::parse($value['to']));
211
+ }
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Rejestracja w modelu
218
+
219
+ ```php
220
+ class FeatureModel extends Model
221
+ {
222
+ public static function allowedFilters(): array
223
+ {
224
+ return [
225
+ AllowedFilter::exact('status', self::COLUMN_STATUS),
226
+ AllowedFilter::partial('name', self::COLUMN_NAME),
227
+ AllowedFilter::custom('date', new DateRangeFilter(self::COLUMN_CREATED_AT)),
228
+ AllowedFilter::exact('category', self::RELATION_CATEGORY . '.id'),
229
+ ];
230
+ }
231
+
232
+ public static function allowedSorts(): array
233
+ {
234
+ return [
235
+ AllowedSort::field('name', self::COLUMN_NAME),
236
+ AllowedSort::field('createdAt', self::COLUMN_CREATED_AT),
237
+ ];
238
+ }
239
+ }
240
+ ```
241
+
242
+ ## Zasady
243
+
244
+ 1. **Resource** - nigdy nie zwracaj modelu, zawsze Resource
245
+ 2. **`whenLoaded()`** - dla relacji w Resource, zapobiega N+1
246
+ 3. **Form Request** - `authorize()` + `rules()`, waliduj filtry i sortowania
247
+ 4. **Policy** - CRUD nazwy (`view`, `viewAny`, `create`, `update`, `delete`)
248
+ 5. **`->can()`** - uzyj w routach lub `$this->authorize()` w kontrolerze
249
+ 6. **Filters** - custom filtr implementuje `Spatie\QueryBuilder\Filters\Filter`
250
+ 7. **Kolumny jako stale** - `self::COLUMN_NAME`, nie magic strings
251
+ 8. **Tlumaczenia** - `messages()` w Form Request uzywaja `__('klucz')`