@famgia/omnify-laravel 0.0.87 → 0.0.89
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.
- package/dist/{chunk-YVVAJA3T.js → chunk-V7LWJ6OM.js} +178 -12
- package/dist/chunk-V7LWJ6OM.js.map +1 -0
- package/dist/index.cjs +180 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +5 -1
- package/dist/plugin.cjs +176 -11
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +5 -5
- package/scripts/postinstall.js +29 -36
- package/stubs/ai-guides/claude-agents/architect.md.stub +150 -0
- package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
- package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
- package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
- package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
- package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
- package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
- package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
- package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
- package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
- package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
- package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
- package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
- package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
- package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
- package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
- package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
- package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
- package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
- package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
- package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
- package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
- package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
- package/stubs/ai-guides/laravel/README.md.stub +59 -0
- package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
- package/stubs/ai-guides/laravel/controller.md.stub +484 -0
- package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
- package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
- package/stubs/ai-guides/laravel/request.md.stub +450 -0
- package/stubs/ai-guides/laravel/resource.md.stub +516 -0
- package/stubs/ai-guides/laravel/service.md.stub +503 -0
- package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
- package/ai-guides/laravel-guide.md +0 -461
- package/dist/chunk-YVVAJA3T.js.map +0 -1
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Controller development rules - thin controller, query builder, OpenAPI"
|
|
3
|
+
globs: ["{{LARAVEL_BASE}}/Http/Controllers/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Controller Rules
|
|
8
|
+
|
|
9
|
+
## Golden Rule: Thin Controller
|
|
10
|
+
|
|
11
|
+
```php
|
|
12
|
+
// ✅ PERFECT
|
|
13
|
+
public function store(UserStoreRequest $request): UserResource
|
|
14
|
+
{
|
|
15
|
+
return new UserResource(User::create($request->validated()));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ❌ BAD: Fat controller with logic
|
|
19
|
+
public function store(Request $request)
|
|
20
|
+
{
|
|
21
|
+
$validated = $request->validate([...]); // Use FormRequest!
|
|
22
|
+
// 50 lines of business logic... // Use Service!
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Index Method - Query Builder Template
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
use Spatie\QueryBuilder\AllowedFilter;
|
|
30
|
+
use Spatie\QueryBuilder\QueryBuilder;
|
|
31
|
+
|
|
32
|
+
public function index(): AnonymousResourceCollection
|
|
33
|
+
{
|
|
34
|
+
$users = QueryBuilder::for(User::class)
|
|
35
|
+
->allowedFilters([
|
|
36
|
+
AllowedFilter::callback('search', function ($query, $value) {
|
|
37
|
+
$query->where(function ($q) use ($value) {
|
|
38
|
+
$q->where('name', 'like', "%{$value}%")
|
|
39
|
+
->orWhere('email', 'like', "%{$value}%");
|
|
40
|
+
});
|
|
41
|
+
}),
|
|
42
|
+
])
|
|
43
|
+
->allowedSorts(['id', 'name', 'email', 'created_at'])
|
|
44
|
+
->defaultSort('-id')
|
|
45
|
+
->paginate(request()->input('per_page', 10));
|
|
46
|
+
|
|
47
|
+
return UserResource::collection($users);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## OpenAPI - Schema MUST Match Code
|
|
52
|
+
|
|
53
|
+
Frontend needs exact response/request structure. Always verify against actual code.
|
|
54
|
+
|
|
55
|
+
### Golden Rule: Verify Before Writing OpenAPI
|
|
56
|
+
|
|
57
|
+
1. **Response** → Check `*Resource.php` schema
|
|
58
|
+
2. **Request** → Check `*Request.php` schema
|
|
59
|
+
3. **Parameters** → Check `allowedFilters()` / `allowedSorts()`
|
|
60
|
+
|
|
61
|
+
### Schema Location
|
|
62
|
+
|
|
63
|
+
| Type | Define At | Ref In Controller |
|
|
64
|
+
| ------------------------ | ------------------------ | --------------------------------------- |
|
|
65
|
+
| Response | `*Resource.php` | `#/components/schemas/User` |
|
|
66
|
+
| Request body | `*Request.php` | `#/components/schemas/UserStoreRequest` |
|
|
67
|
+
| Pagination | `Schemas.php` | `#/components/schemas/PaginationMeta` |
|
|
68
|
+
| Common params | `Schemas.php` | `#/components/parameters/QueryPage` |
|
|
69
|
+
| Resource-specific params | **Inline in Controller** | N/A |
|
|
70
|
+
|
|
71
|
+
### Common refs from `Schemas.php`
|
|
72
|
+
|
|
73
|
+
```php
|
|
74
|
+
// Parameters
|
|
75
|
+
new OA\Parameter(ref: '#/components/parameters/QueryPage'),
|
|
76
|
+
new OA\Parameter(ref: '#/components/parameters/QueryPerPage'),
|
|
77
|
+
new OA\Parameter(ref: '#/components/parameters/PathId'),
|
|
78
|
+
|
|
79
|
+
// Responses
|
|
80
|
+
new OA\Response(ref: '#/components/responses/ValidationError', response: 422),
|
|
81
|
+
new OA\Response(ref: '#/components/responses/NotFound', response: 404),
|
|
82
|
+
new OA\Response(ref: '#/components/responses/NoContent', response: 204),
|
|
83
|
+
|
|
84
|
+
// Pagination
|
|
85
|
+
new OA\Property(property: 'links', ref: '#/components/schemas/PaginationLinks'),
|
|
86
|
+
new OA\Property(property: 'meta', ref: '#/components/schemas/PaginationMeta'),
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Filter & Sort - MUST Document Specifically
|
|
90
|
+
|
|
91
|
+
`filter` and `sort` differ per resource → Write inline with specific details.
|
|
92
|
+
|
|
93
|
+
#### Filter Types (Spatie Query Builder)
|
|
94
|
+
|
|
95
|
+
| Type | Code | OpenAPI Description | Example |
|
|
96
|
+
| ----------------- | ------------------------------------------ | -------------------------- | ----------------------- |
|
|
97
|
+
| Callback (search) | `AllowedFilter::callback('search', fn...)` | List all searchable fields | `filter[search]=田中` |
|
|
98
|
+
| Exact | `AllowedFilter::exact('status')` | Exact match | `filter[status]=active` |
|
|
99
|
+
| Partial | `AllowedFilter::partial('name')` | LIKE %value% | `filter[name]=田中` |
|
|
100
|
+
| Scope | `AllowedFilter::scope('active')` | Model scope | `filter[active]=1` |
|
|
101
|
+
|
|
102
|
+
#### Filter Documentation Template
|
|
103
|
+
|
|
104
|
+
```php
|
|
105
|
+
// CODE: allowedFilters() configuration
|
|
106
|
+
->allowedFilters([
|
|
107
|
+
AllowedFilter::callback('search', function ($query, $value) {
|
|
108
|
+
$query->where(function ($q) use ($value) {
|
|
109
|
+
$q->where('name_lastname', 'like', "%{$value}%")
|
|
110
|
+
->orWhere('name_firstname', 'like', "%{$value}%")
|
|
111
|
+
->orWhere('name_kana_lastname', 'like', "%{$value}%")
|
|
112
|
+
->orWhere('name_kana_firstname', 'like', "%{$value}%")
|
|
113
|
+
->orWhere('email', 'like', "%{$value}%");
|
|
114
|
+
});
|
|
115
|
+
}),
|
|
116
|
+
AllowedFilter::exact('status'),
|
|
117
|
+
])
|
|
118
|
+
|
|
119
|
+
// OPENAPI: Document EACH filter with specific description
|
|
120
|
+
new OA\Parameter(
|
|
121
|
+
name: 'filter[search]',
|
|
122
|
+
in: 'query',
|
|
123
|
+
description: 'Partial match on: name_lastname, name_firstname, name_kana_lastname, name_kana_firstname, email',
|
|
124
|
+
schema: new OA\Schema(type: 'string'),
|
|
125
|
+
example: '田中'
|
|
126
|
+
),
|
|
127
|
+
new OA\Parameter(
|
|
128
|
+
name: 'filter[status]',
|
|
129
|
+
in: 'query',
|
|
130
|
+
description: 'Exact match on status',
|
|
131
|
+
schema: new OA\Schema(type: 'string', enum: ['active', 'inactive']),
|
|
132
|
+
example: 'active'
|
|
133
|
+
),
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Sort Documentation Template
|
|
137
|
+
|
|
138
|
+
```php
|
|
139
|
+
// CODE: allowedSorts() + defaultSort()
|
|
140
|
+
->allowedSorts(['id', 'name_lastname', 'name_firstname', 'email', 'created_at', 'updated_at'])
|
|
141
|
+
->defaultSort('-id')
|
|
142
|
+
|
|
143
|
+
// OPENAPI: enum MUST include both asc and desc for each field
|
|
144
|
+
new OA\Parameter(
|
|
145
|
+
name: 'sort',
|
|
146
|
+
in: 'query',
|
|
147
|
+
description: 'Sort field. Prefix `-` for descending. Default: `-id`',
|
|
148
|
+
schema: new OA\Schema(
|
|
149
|
+
type: 'string',
|
|
150
|
+
enum: [
|
|
151
|
+
'id', '-id',
|
|
152
|
+
'name_lastname', '-name_lastname',
|
|
153
|
+
'name_firstname', '-name_firstname',
|
|
154
|
+
'email', '-email',
|
|
155
|
+
'created_at', '-created_at',
|
|
156
|
+
'updated_at', '-updated_at'
|
|
157
|
+
]
|
|
158
|
+
),
|
|
159
|
+
example: '-created_at'
|
|
160
|
+
),
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Checklist: Filter & Sort Documentation
|
|
164
|
+
|
|
165
|
+
| Item | Check |
|
|
166
|
+
| ----------------------------- | ----------------------------------------------- |
|
|
167
|
+
| Each `AllowedFilter` | Has corresponding `filter[name]` parameter? |
|
|
168
|
+
| Callback filter | Description lists ALL searchable fields? |
|
|
169
|
+
| Exact filter with enum values | `schema.enum` matches valid values? |
|
|
170
|
+
| `allowedSorts()` | `sort.enum` includes both `field` and `-field`? |
|
|
171
|
+
| `defaultSort()` | Mentioned in `sort.description`? |
|
|
172
|
+
|
|
173
|
+
### OpenAPI Checklist per Action
|
|
174
|
+
|
|
175
|
+
#### `index()` - List
|
|
176
|
+
|
|
177
|
+
**Verify before writing:**
|
|
178
|
+
1. `allowedFilters()` → Each filter has `filter[name]` param with specific description
|
|
179
|
+
2. `allowedSorts()` → `sort.enum` includes all fields + `-field` versions
|
|
180
|
+
3. `defaultSort()` → Mentioned in sort description
|
|
181
|
+
4. Response → ref to Resource + PaginationLinks + PaginationMeta
|
|
182
|
+
|
|
183
|
+
```php
|
|
184
|
+
#[OA\Get(
|
|
185
|
+
parameters: [
|
|
186
|
+
// Each allowedFilter → specific param
|
|
187
|
+
new OA\Parameter(
|
|
188
|
+
name: 'filter[search]',
|
|
189
|
+
in: 'query',
|
|
190
|
+
description: 'Partial match on: name_lastname, name_firstname, email', // ← List actual fields!
|
|
191
|
+
schema: new OA\Schema(type: 'string'),
|
|
192
|
+
example: '田中'
|
|
193
|
+
),
|
|
194
|
+
// Pagination
|
|
195
|
+
new OA\Parameter(ref: '#/components/parameters/QueryPage'),
|
|
196
|
+
new OA\Parameter(ref: '#/components/parameters/QueryPerPage'),
|
|
197
|
+
// Sort with full enum
|
|
198
|
+
new OA\Parameter(
|
|
199
|
+
name: 'sort',
|
|
200
|
+
in: 'query',
|
|
201
|
+
description: 'Sort field. Prefix `-` for descending. Default: `-id`', // ← Include default!
|
|
202
|
+
schema: new OA\Schema(
|
|
203
|
+
type: 'string',
|
|
204
|
+
enum: ['id', '-id', 'name', '-name', 'created_at', '-created_at'] // ← Match allowedSorts!
|
|
205
|
+
),
|
|
206
|
+
example: '-created_at'
|
|
207
|
+
),
|
|
208
|
+
],
|
|
209
|
+
responses: [
|
|
210
|
+
new OA\Response(response: 200, content: new OA\JsonContent(
|
|
211
|
+
properties: [
|
|
212
|
+
new OA\Property(property: 'data', type: 'array', items: new OA\Items(ref: '#/components/schemas/User')),
|
|
213
|
+
new OA\Property(property: 'links', ref: '#/components/schemas/PaginationLinks'),
|
|
214
|
+
new OA\Property(property: 'meta', ref: '#/components/schemas/PaginationMeta'),
|
|
215
|
+
]
|
|
216
|
+
)),
|
|
217
|
+
]
|
|
218
|
+
)]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### `store()` - Create
|
|
222
|
+
|
|
223
|
+
```php
|
|
224
|
+
// 1. Request: ref to *StoreRequest schema
|
|
225
|
+
// 2. Response: ref to Resource schema
|
|
226
|
+
// 3. Errors: 422 ValidationError
|
|
227
|
+
|
|
228
|
+
#[OA\Post(
|
|
229
|
+
requestBody: new OA\RequestBody(
|
|
230
|
+
required: true,
|
|
231
|
+
content: new OA\JsonContent(ref: '#/components/schemas/UserStoreRequest') // ← Check UserStoreRequest.php
|
|
232
|
+
),
|
|
233
|
+
responses: [
|
|
234
|
+
new OA\Response(response: 201, content: new OA\JsonContent(
|
|
235
|
+
properties: [new OA\Property(property: 'data', ref: '#/components/schemas/User')]
|
|
236
|
+
)),
|
|
237
|
+
new OA\Response(ref: '#/components/responses/ValidationError', response: 422),
|
|
238
|
+
]
|
|
239
|
+
)]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### `show()` - Get Single
|
|
243
|
+
|
|
244
|
+
```php
|
|
245
|
+
// 1. Parameter: PathId
|
|
246
|
+
// 2. Response: ref to Resource schema
|
|
247
|
+
// 3. Errors: 404 NotFound
|
|
248
|
+
|
|
249
|
+
#[OA\Get(
|
|
250
|
+
parameters: [new OA\Parameter(ref: '#/components/parameters/PathId')],
|
|
251
|
+
responses: [
|
|
252
|
+
new OA\Response(response: 200, content: new OA\JsonContent(
|
|
253
|
+
properties: [new OA\Property(property: 'data', ref: '#/components/schemas/User')]
|
|
254
|
+
)),
|
|
255
|
+
new OA\Response(ref: '#/components/responses/NotFound', response: 404),
|
|
256
|
+
]
|
|
257
|
+
)]
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `update()` - Update
|
|
261
|
+
|
|
262
|
+
```php
|
|
263
|
+
// 1. Parameter: PathId
|
|
264
|
+
// 2. Request: ref to *UpdateRequest schema (NOT StoreRequest!)
|
|
265
|
+
// 3. Response: ref to Resource schema
|
|
266
|
+
// 4. Errors: 404, 422
|
|
267
|
+
|
|
268
|
+
#[OA\Put(
|
|
269
|
+
parameters: [new OA\Parameter(ref: '#/components/parameters/PathId')],
|
|
270
|
+
requestBody: new OA\RequestBody(
|
|
271
|
+
content: new OA\JsonContent(ref: '#/components/schemas/UserUpdateRequest') // ← Check UserUpdateRequest.php
|
|
272
|
+
),
|
|
273
|
+
responses: [
|
|
274
|
+
new OA\Response(response: 200, content: ...),
|
|
275
|
+
new OA\Response(ref: '#/components/responses/NotFound', response: 404),
|
|
276
|
+
new OA\Response(ref: '#/components/responses/ValidationError', response: 422),
|
|
277
|
+
]
|
|
278
|
+
)]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### `destroy()` - Delete
|
|
282
|
+
|
|
283
|
+
```php
|
|
284
|
+
// 1. Parameter: PathId
|
|
285
|
+
// 2. Response: 204 NoContent
|
|
286
|
+
// 3. Errors: 404 NotFound
|
|
287
|
+
|
|
288
|
+
#[OA\Delete(
|
|
289
|
+
parameters: [new OA\Parameter(ref: '#/components/parameters/PathId')],
|
|
290
|
+
responses: [
|
|
291
|
+
new OA\Response(ref: '#/components/responses/NoContent', response: 204),
|
|
292
|
+
new OA\Response(ref: '#/components/responses/NotFound', response: 404),
|
|
293
|
+
]
|
|
294
|
+
)]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Response Status Codes
|
|
298
|
+
|
|
299
|
+
| Action | Code | Return Type |
|
|
300
|
+
| ------- | ---- | ----------------------------- |
|
|
301
|
+
| index | 200 | Collection |
|
|
302
|
+
| store | 201 | Resource + status |
|
|
303
|
+
| show | 200 | Resource |
|
|
304
|
+
| update | 200 | Resource |
|
|
305
|
+
| destroy | 204 | `response()->json(null, 204)` |
|
|
306
|
+
|
|
307
|
+
## Imports - MUST use short names
|
|
308
|
+
|
|
309
|
+
```php
|
|
310
|
+
// ✅ CORRECT
|
|
311
|
+
use Illuminate\Http\JsonResponse;
|
|
312
|
+
use Spatie\QueryBuilder\QueryBuilder;
|
|
313
|
+
|
|
314
|
+
public function store(): JsonResponse
|
|
315
|
+
|
|
316
|
+
// ❌ WRONG - Never FQCN inline
|
|
317
|
+
public function store(): \Illuminate\Http\JsonResponse
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Keep It Simple
|
|
321
|
+
|
|
322
|
+
```php
|
|
323
|
+
// ✅ DO
|
|
324
|
+
->allowedFilters([
|
|
325
|
+
AllowedFilter::callback('search', fn($q, $v) => ...),
|
|
326
|
+
])
|
|
327
|
+
->allowedSorts(['id', 'name', 'created_at'])
|
|
328
|
+
|
|
329
|
+
// ❌ DON'T over-engineer
|
|
330
|
+
->allowedFilters([
|
|
331
|
+
AllowedFilter::exact('author.company.country'), // Too nested!
|
|
332
|
+
])
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Don't Mix Concepts
|
|
336
|
+
|
|
337
|
+
| Concept | Use For | DON'T Use For |
|
|
338
|
+
| ---------------- | ---------------- | -------------------- |
|
|
339
|
+
| `*StoreRequest` | POST create | GET list, PUT update |
|
|
340
|
+
| `*UpdateRequest` | PUT/PATCH update | POST create |
|
|
341
|
+
| `*Resource` | Response output | Request input |
|
|
342
|
+
| `index()` | List (no body) | Create/Update |
|
|
343
|
+
| `store()` | Create new | Update existing |
|
|
344
|
+
| `update()` | Update existing | Create new |
|
|
345
|
+
|
|
346
|
+
```php
|
|
347
|
+
// ❌ WRONG: Using StoreRequest for update
|
|
348
|
+
public function update(UserStoreRequest $request) // WRONG!
|
|
349
|
+
|
|
350
|
+
// ✅ CORRECT: Each action has its own Request
|
|
351
|
+
public function store(UserStoreRequest $request) // Create
|
|
352
|
+
public function update(UserUpdateRequest $request) // Update
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## After Coding Checklist
|
|
356
|
+
|
|
357
|
+
### Code
|
|
358
|
+
1. ✅ Run tests: `./artisan test --filter=ControllerTest`
|
|
359
|
+
|
|
360
|
+
### OpenAPI (only if annotations changed)
|
|
361
|
+
|
|
362
|
+
| Check | Code | OpenAPI | Match |
|
|
363
|
+
| --------------- | ------------------ | ----------------------- | ------------------------------------- |
|
|
364
|
+
| Response | `*Resource.php` | `ref: User` | Schema exists? Properties match? |
|
|
365
|
+
| Request | `*Request.php` | `ref: UserStoreRequest` | Schema exists? Required fields match? |
|
|
366
|
+
| Filters | `allowedFilters()` | `filter[name]` params | Each filter documented? |
|
|
367
|
+
| Callback filter | Fields in callback | `description` | All fields listed? |
|
|
368
|
+
| Exact filter | Valid values | `schema.enum` | Enum matches valid values? |
|
|
369
|
+
| Sort | `allowedSorts()` | `sort.enum` | Includes `field` AND `-field`? |
|
|
370
|
+
| Default sort | `defaultSort()` | `sort.description` | Default mentioned? |
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# Generate and verify
|
|
374
|
+
./artisan l5-swagger:generate
|
|
375
|
+
|
|
376
|
+
# Check json output
|
|
377
|
+
cat backend/storage/api-docs/api-docs.json | jq '.components.schemas.User'
|
|
378
|
+
cat backend/storage/api-docs/api-docs.json | jq '.components.schemas.UserStoreRequest'
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Common Mistakes
|
|
382
|
+
|
|
383
|
+
| Mistake | Fix |
|
|
384
|
+
| ------------------------------- | ------------------------------------------------------------------------- |
|
|
385
|
+
| `ref: User` missing | Add `#[OA\Schema]` to `UserResource.php` |
|
|
386
|
+
| `ref: UserStoreRequest` missing | Add `#[OA\Schema]` to `UserStoreRequest.php` |
|
|
387
|
+
| sort enum incomplete | Include BOTH `field` AND `-field` for each `allowedSorts()` |
|
|
388
|
+
| sort missing default | Add "Default: `-id`" to sort description |
|
|
389
|
+
| filter description vague | List ALL fields: "Partial match on: name_lastname, name_firstname, email" |
|
|
390
|
+
| filter[status] no enum | Add `enum: ['active', 'inactive']` for exact filters |
|
|
391
|
+
| Using StoreRequest for update | Use UpdateRequest for PUT/PATCH |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "FormRequest rules - OpenAPI Schema must match validation rules"
|
|
3
|
+
globs: ["{{LARAVEL_BASE}}/Http/Requests/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Request Rules
|
|
8
|
+
|
|
9
|
+
## Golden Rule: Schema MUST Match Validation Rules
|
|
10
|
+
|
|
11
|
+
OpenAPI Schema properties MUST match the actual `schemaRules()` fields.
|
|
12
|
+
|
|
13
|
+
## Before Editing Request
|
|
14
|
+
|
|
15
|
+
1. **Read `OmnifyBase/*RequestBase.php`** to see actual validation rules
|
|
16
|
+
2. **Compare with `#[OA\Schema]`** properties
|
|
17
|
+
3. **Ensure `required` array matches** required validation rules
|
|
18
|
+
4. **Match constraints** (maxLength, minLength, format)
|
|
19
|
+
|
|
20
|
+
## Checklist
|
|
21
|
+
|
|
22
|
+
```php
|
|
23
|
+
// Check OmnifyBase/*StoreRequestBase.php → schemaRules()
|
|
24
|
+
return [
|
|
25
|
+
'name_lastname' => ['required', 'string', 'max:50'],
|
|
26
|
+
'name_firstname' => ['required', 'string', 'max:50'],
|
|
27
|
+
'email' => ['required', 'string', 'max:255', 'unique:users'],
|
|
28
|
+
'password' => ['required', 'string', 'max:255'],
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// #[OA\Schema] MUST match
|
|
32
|
+
#[OA\Schema(
|
|
33
|
+
schema: 'UserStoreRequest',
|
|
34
|
+
required: ['name_lastname', 'name_firstname', 'email', 'password'], // ← Match 'required' rules
|
|
35
|
+
properties: [
|
|
36
|
+
new OA\Property(property: 'name_lastname', type: 'string', maxLength: 50), // ← max:50
|
|
37
|
+
new OA\Property(property: 'name_firstname', type: 'string', maxLength: 50),
|
|
38
|
+
new OA\Property(property: 'email', type: 'string', format: 'email', maxLength: 255),
|
|
39
|
+
new OA\Property(property: 'password', type: 'string', format: 'password', maxLength: 255),
|
|
40
|
+
]
|
|
41
|
+
)]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Mapping Validation Rules to OpenAPI
|
|
45
|
+
|
|
46
|
+
| Laravel Rule | OpenAPI Property |
|
|
47
|
+
| ------------ | --------------------------- |
|
|
48
|
+
| `required` | Add to `required: []` array |
|
|
49
|
+
| `max:50` | `maxLength: 50` |
|
|
50
|
+
| `min:8` | `minLength: 8` |
|
|
51
|
+
| `email` | `format: 'email'` |
|
|
52
|
+
| `string` | `type: 'string'` |
|
|
53
|
+
| `integer` | `type: 'integer'` |
|
|
54
|
+
| `boolean` | `type: 'boolean'` |
|
|
55
|
+
| `nullable` | `nullable: true` |
|
|
56
|
+
|
|
57
|
+
## StoreRequest vs UpdateRequest
|
|
58
|
+
|
|
59
|
+
| Type | `required` | Notes |
|
|
60
|
+
| ---------------- | ------------------- | ---------------------- |
|
|
61
|
+
| `*StoreRequest` | All required fields | Creating new resource |
|
|
62
|
+
| `*UpdateRequest` | Empty or partial | Partial update allowed |
|
|
63
|
+
|
|
64
|
+
```php
|
|
65
|
+
// StoreRequest - all fields required
|
|
66
|
+
#[OA\Schema(
|
|
67
|
+
schema: 'UserStoreRequest',
|
|
68
|
+
required: ['name_lastname', 'name_firstname', 'email', 'password'],
|
|
69
|
+
...
|
|
70
|
+
)]
|
|
71
|
+
|
|
72
|
+
// UpdateRequest - no required (partial update)
|
|
73
|
+
#[OA\Schema(
|
|
74
|
+
schema: 'UserUpdateRequest',
|
|
75
|
+
// No 'required' array - all fields optional
|
|
76
|
+
properties: [...]
|
|
77
|
+
)]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Common Mistakes
|
|
81
|
+
|
|
82
|
+
| Mistake | Fix |
|
|
83
|
+
| ----------------------- | ------------------------------------ |
|
|
84
|
+
| Missing field in schema | Check *RequestBase.php schemaRules() |
|
|
85
|
+
| Wrong maxLength | Match `max:X` rule |
|
|
86
|
+
| Missing required | Check if rule has `required` |
|
|
87
|
+
| Extra field in schema | Remove if not in rules |
|
|
88
|
+
|
|
89
|
+
## Custom Rules in Child Class
|
|
90
|
+
|
|
91
|
+
If you override rules in the child Request class, update schema accordingly:
|
|
92
|
+
|
|
93
|
+
```php
|
|
94
|
+
// UserStoreRequest.php
|
|
95
|
+
public function rules(): array
|
|
96
|
+
{
|
|
97
|
+
return array_merge($this->schemaRules(), [
|
|
98
|
+
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], // Added 'email' format
|
|
99
|
+
'password' => ['required', 'string', 'min:8', 'max:255'], // Added min:8
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Update schema to match
|
|
104
|
+
new OA\Property(property: 'email', type: 'string', format: 'email'), // ← Added format
|
|
105
|
+
new OA\Property(property: 'password', type: 'string', minLength: 8), // ← Added minLength
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## After Editing
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
./artisan l5-swagger:generate
|
|
112
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Resource class rules - OpenAPI Schema must match actual output"
|
|
3
|
+
globs: ["{{LARAVEL_BASE}}/Http/Resources/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Resource Rules
|
|
8
|
+
|
|
9
|
+
## Golden Rule: Schema MUST Match Output
|
|
10
|
+
|
|
11
|
+
OpenAPI Schema properties MUST match the actual `toArray()` / `schemaArray()` output.
|
|
12
|
+
|
|
13
|
+
## Before Editing Resource
|
|
14
|
+
|
|
15
|
+
1. **Read `OmnifyBase/*ResourceBase.php`** to see actual fields
|
|
16
|
+
2. **Compare with `#[OA\Schema]`** properties
|
|
17
|
+
3. **Add missing fields** to schema
|
|
18
|
+
4. **Remove non-existent fields** from schema
|
|
19
|
+
|
|
20
|
+
## Checklist
|
|
21
|
+
|
|
22
|
+
```php
|
|
23
|
+
// Check OmnifyBase/*ResourceBase.php → schemaArray()
|
|
24
|
+
return [
|
|
25
|
+
'id' => $this->id,
|
|
26
|
+
'name_lastname' => $this->name_lastname,
|
|
27
|
+
'name_full_name' => $this->name_full_name, // ← Computed field!
|
|
28
|
+
'email' => $this->email,
|
|
29
|
+
'created_at' => $this->created_at?->toISOString(),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// #[OA\Schema] MUST have ALL these properties
|
|
33
|
+
#[OA\Schema(
|
|
34
|
+
properties: [
|
|
35
|
+
new OA\Property(property: 'id', ...),
|
|
36
|
+
new OA\Property(property: 'name_lastname', ...),
|
|
37
|
+
new OA\Property(property: 'name_full_name', ...), // ← Don't forget!
|
|
38
|
+
new OA\Property(property: 'email', ...),
|
|
39
|
+
new OA\Property(property: 'created_at', ...),
|
|
40
|
+
]
|
|
41
|
+
)]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Common Mistakes
|
|
45
|
+
|
|
46
|
+
| Mistake | Fix |
|
|
47
|
+
| ---------------------------------------- | ----------------------- |
|
|
48
|
+
| Missing computed fields (name_full_name) | Check *ResourceBase.php |
|
|
49
|
+
| Schema has field not in output | Remove from schema |
|
|
50
|
+
| Field type mismatch | Match actual PHP type |
|
|
51
|
+
| Missing nullable | Check if `?->` used |
|
|
52
|
+
|
|
53
|
+
## JapaneseName Type Fields
|
|
54
|
+
|
|
55
|
+
JapaneseName expands to multiple fields including computed ones:
|
|
56
|
+
|
|
57
|
+
```php
|
|
58
|
+
// All these come from JapaneseName type
|
|
59
|
+
'name_lastname' => $this->name_lastname,
|
|
60
|
+
'name_firstname' => $this->name_firstname,
|
|
61
|
+
'name_kana_lastname' => $this->name_kana_lastname,
|
|
62
|
+
'name_kana_firstname' => $this->name_kana_firstname,
|
|
63
|
+
'name_full_name' => $this->name_full_name, // Computed!
|
|
64
|
+
'name_full_name_kana' => $this->name_full_name_kana, // Computed!
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**MUST include ALL in OpenAPI Schema!**
|
|
68
|
+
|
|
69
|
+
## After Editing
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
./artisan l5-swagger:generate
|
|
73
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Code review checklist - security, performance, quality"
|
|
3
|
+
globs: []
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Code Review Rules
|
|
8
|
+
|
|
9
|
+
> Review process for Laravel code
|
|
10
|
+
|
|
11
|
+
## Review Process
|
|
12
|
+
|
|
13
|
+
1. Security Check → 2. Performance Check → 3. Quality Check → 4. Test Check
|
|
14
|
+
|
|
15
|
+
## Security Checklist
|
|
16
|
+
|
|
17
|
+
| Check | Look For |
|
|
18
|
+
| --------------- | ---------------------------- |
|
|
19
|
+
| Mass Assignment | `$request->validated()` |
|
|
20
|
+
| SQL Injection | No raw SQL |
|
|
21
|
+
| `$fillable` | Defined, no sensitive fields |
|
|
22
|
+
| `$hidden` | Password hidden |
|
|
23
|
+
|
|
24
|
+
## Performance Checklist
|
|
25
|
+
|
|
26
|
+
| Check | Look For |
|
|
27
|
+
| ---------- | ---------------------- |
|
|
28
|
+
| N+1 | `with()` for relations |
|
|
29
|
+
| Pagination | `paginate()` for lists |
|
|
30
|
+
| Resources | `whenLoaded()` |
|
|
31
|
+
|
|
32
|
+
## Quality Checklist
|
|
33
|
+
|
|
34
|
+
| Check | Look For |
|
|
35
|
+
| ---------- | --------------- |
|
|
36
|
+
| Validation | FormRequest |
|
|
37
|
+
| Response | Resource |
|
|
38
|
+
| Dates | `toISOString()` |
|
|
39
|
+
|
|
40
|
+
## Code Style Checklist
|
|
41
|
+
|
|
42
|
+
| Check | Look For |
|
|
43
|
+
| ----------- | -------------------------------------- |
|
|
44
|
+
| **Imports** | No FQCN inline (`\Full\Path\Class`) |
|
|
45
|
+
| Use | All classes imported with `use` at top |
|
|
46
|
+
| Naming | Follows conventions |
|
|
47
|
+
|
|
48
|
+
## Test Checklist
|
|
49
|
+
|
|
50
|
+
| Endpoint | 正常系 | 異常系 |
|
|
51
|
+
| -------- | ------ | -------- |
|
|
52
|
+
| store | 201 | 422 |
|
|
53
|
+
| show | 200 | 404 |
|
|
54
|
+
| update | 200 | 404, 422 |
|
|
55
|
+
| destroy | 204 | 404 |
|
|
56
|
+
|
|
57
|
+
## Output Format
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
## Security
|
|
61
|
+
✅ Using `$request->validated()`
|
|
62
|
+
❌ Missing `$hidden` for password
|
|
63
|
+
|
|
64
|
+
## Performance
|
|
65
|
+
✅ Eager loading
|
|
66
|
+
❌ Missing pagination
|
|
67
|
+
|
|
68
|
+
## Recommendation: Request Changes
|
|
69
|
+
```
|