omgkit 2.2.0 → 2.3.1

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 (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,81 +1,45 @@
1
1
  ---
2
- name: laravel
3
- description: Enterprise Laravel development with Eloquent, API resources, testing, and production patterns
4
- category: frameworks
5
- triggers:
6
- - laravel
7
- - php framework
8
- - eloquent
9
- - blade
10
- - artisan
11
- - php api
12
- - laravel api
13
- - lumen
2
+ name: building-laravel-apis
3
+ description: Builds enterprise Laravel applications with Eloquent, API Resources, Sanctum auth, and queue processing. Use when creating PHP backends, REST APIs, or full-stack Laravel applications.
14
4
  ---
15
5
 
16
6
  # Laravel
17
7
 
18
- Enterprise-grade **Laravel development** following industry best practices. This skill covers Eloquent ORM, API resources, service patterns, authentication, testing, queues, and production deployment configurations used by top engineering teams.
8
+ ## Quick Start
19
9
 
20
- ## Purpose
21
-
22
- Build robust PHP applications with confidence:
10
+ ```php
11
+ // routes/api.php
12
+ Route::get('/health', fn () => ['status' => 'ok']);
23
13
 
24
- - Design clean model architectures with Eloquent
25
- - Implement REST APIs with API Resources
26
- - Use service and repository patterns
27
- - Handle authentication with Laravel Sanctum
28
- - Write comprehensive tests with PHPUnit
29
- - Deploy production-ready applications
30
- - Leverage queues for background processing
14
+ Route::middleware('auth:sanctum')->group(function () {
15
+ Route::apiResource('users', UserController::class);
16
+ });
17
+ ```
31
18
 
32
19
  ## Features
33
20
 
34
- ### 1. Model Design and Relationships
21
+ | Feature | Description | Guide |
22
+ |---------|-------------|-------|
23
+ | Models | Eloquent ORM, relationships, scopes | [MODELS.md](MODELS.md) |
24
+ | Controllers | Resource controllers, form requests | [CONTROLLERS.md](CONTROLLERS.md) |
25
+ | API Resources | Response transformation | [RESOURCES.md](RESOURCES.md) |
26
+ | Auth | Sanctum, policies, gates | [AUTH.md](AUTH.md) |
27
+ | Queues | Jobs, events, listeners | [QUEUES.md](QUEUES.md) |
28
+ | Testing | Feature, unit tests | [TESTING.md](TESTING.md) |
35
29
 
36
- ```php
37
- <?php
38
- // app/Models/User.php
39
- namespace App\Models;
30
+ ## Common Patterns
40
31
 
41
- use Illuminate\Database\Eloquent\Concerns\HasUuids;
42
- use Illuminate\Database\Eloquent\Factories\HasFactory;
43
- use Illuminate\Database\Eloquent\Relations\BelongsToMany;
44
- use Illuminate\Database\Eloquent\Relations\HasMany;
45
- use Illuminate\Database\Eloquent\SoftDeletes;
46
- use Illuminate\Foundation\Auth\User as Authenticatable;
47
- use Illuminate\Notifications\Notifiable;
48
- use Laravel\Sanctum\HasApiTokens;
32
+ ### Model with Relationships
49
33
 
34
+ ```php
50
35
  class User extends Authenticatable
51
36
  {
52
- use HasApiTokens, HasFactory, Notifiable, HasUuids, SoftDeletes;
53
-
54
- protected $fillable = [
55
- 'name',
56
- 'email',
57
- 'password',
58
- 'role',
59
- 'is_active',
60
- ];
61
-
62
- protected $hidden = [
63
- 'password',
64
- 'remember_token',
65
- ];
66
-
67
- protected $casts = [
68
- 'email_verified_at' => 'datetime',
69
- 'password' => 'hashed',
70
- 'is_active' => 'boolean',
71
- ];
37
+ use HasApiTokens, HasFactory, SoftDeletes;
72
38
 
73
- protected $attributes = [
74
- 'role' => 'user',
75
- 'is_active' => true,
76
- ];
39
+ protected $fillable = ['name', 'email', 'password', 'role'];
40
+ protected $hidden = ['password', 'remember_token'];
41
+ protected $casts = ['email_verified_at' => 'datetime', 'password' => 'hashed'];
77
42
 
78
- // Relationships
79
43
  public function organizations(): BelongsToMany
80
44
  {
81
45
  return $this->belongsToMany(Organization::class, 'memberships')
@@ -83,1196 +47,132 @@ class User extends Authenticatable
83
47
  ->withTimestamps();
84
48
  }
85
49
 
86
- public function ownedOrganizations(): HasMany
87
- {
88
- return $this->hasMany(Organization::class, 'owner_id');
89
- }
90
-
91
- public function projects(): HasMany
92
- {
93
- return $this->hasMany(Project::class, 'created_by');
94
- }
95
-
96
- // Scopes
97
50
  public function scopeActive($query)
98
51
  {
99
52
  return $query->where('is_active', true);
100
53
  }
101
54
 
102
- public function scopeRole($query, string $role)
103
- {
104
- return $query->where('role', $role);
105
- }
106
-
107
55
  public function scopeSearch($query, ?string $search)
108
56
  {
109
- if (!$search) {
110
- return $query;
111
- }
112
-
113
- return $query->where(function ($q) use ($search) {
114
- $q->where('name', 'like', "%{$search}%")
115
- ->orWhere('email', 'like', "%{$search}%");
116
- });
117
- }
118
-
119
- // Accessors & Mutators
120
- protected function name(): Attribute
121
- {
122
- return Attribute::make(
123
- get: fn (string $value) => ucwords($value),
124
- set: fn (string $value) => strtolower($value),
125
- );
126
- }
127
-
128
- // Methods
129
- public function isAdmin(): bool
130
- {
131
- return $this->role === 'admin';
132
- }
133
-
134
- public function belongsToOrganization(Organization $organization): bool
135
- {
136
- return $this->organizations()->where('organizations.id', $organization->id)->exists();
137
- }
138
- }
139
-
140
-
141
- // app/Models/Organization.php
142
- namespace App\Models;
143
-
144
- use Illuminate\Database\Eloquent\Concerns\HasUuids;
145
- use Illuminate\Database\Eloquent\Factories\HasFactory;
146
- use Illuminate\Database\Eloquent\Model;
147
- use Illuminate\Database\Eloquent\Relations\BelongsTo;
148
- use Illuminate\Database\Eloquent\Relations\BelongsToMany;
149
- use Illuminate\Database\Eloquent\Relations\HasMany;
150
-
151
- class Organization extends Model
152
- {
153
- use HasFactory, HasUuids;
154
-
155
- protected $fillable = [
156
- 'name',
157
- 'slug',
158
- 'owner_id',
159
- ];
160
-
161
- // Relationships
162
- public function owner(): BelongsTo
163
- {
164
- return $this->belongsTo(User::class, 'owner_id');
165
- }
166
-
167
- public function members(): BelongsToMany
168
- {
169
- return $this->belongsToMany(User::class, 'memberships')
170
- ->withPivot('role')
171
- ->withTimestamps();
172
- }
173
-
174
- public function projects(): HasMany
175
- {
176
- return $this->hasMany(Project::class);
177
- }
178
-
179
- // Scopes
180
- public function scopeForUser($query, User $user)
181
- {
182
- return $query->whereHas('members', fn ($q) => $q->where('users.id', $user->id));
183
- }
184
- }
185
-
186
-
187
- // app/Models/Project.php
188
- namespace App\Models;
189
-
190
- use Illuminate\Database\Eloquent\Concerns\HasUuids;
191
- use Illuminate\Database\Eloquent\Factories\HasFactory;
192
- use Illuminate\Database\Eloquent\Model;
193
- use Illuminate\Database\Eloquent\SoftDeletes;
194
-
195
- class Project extends Model
196
- {
197
- use HasFactory, HasUuids, SoftDeletes;
198
-
199
- protected $fillable = [
200
- 'organization_id',
201
- 'name',
202
- 'description',
203
- 'status',
204
- 'created_by',
205
- ];
206
-
207
- protected $casts = [
208
- 'status' => ProjectStatus::class,
209
- ];
210
-
211
- protected $attributes = [
212
- 'status' => ProjectStatus::DRAFT,
213
- ];
214
-
215
- // Relationships
216
- public function organization()
217
- {
218
- return $this->belongsTo(Organization::class);
219
- }
220
-
221
- public function creator()
222
- {
223
- return $this->belongsTo(User::class, 'created_by');
224
- }
225
-
226
- public function tasks()
227
- {
228
- return $this->hasMany(Task::class);
229
- }
230
-
231
- // Scopes
232
- public function scopeActive($query)
233
- {
234
- return $query->where('status', ProjectStatus::ACTIVE);
235
- }
236
-
237
- public function scopeForOrganization($query, $organizationId)
238
- {
239
- return $query->where('organization_id', $organizationId);
240
- }
241
- }
242
-
243
-
244
- // app/Enums/ProjectStatus.php
245
- namespace App\Enums;
246
-
247
- enum ProjectStatus: string
248
- {
249
- case DRAFT = 'draft';
250
- case ACTIVE = 'active';
251
- case COMPLETED = 'completed';
252
- case ARCHIVED = 'archived';
253
-
254
- public function label(): string
255
- {
256
- return match ($this) {
257
- self::DRAFT => 'Draft',
258
- self::ACTIVE => 'Active',
259
- self::COMPLETED => 'Completed',
260
- self::ARCHIVED => 'Archived',
261
- };
262
- }
263
- }
264
- ```
265
-
266
- ### 2. API Resources and Collections
267
-
268
- ```php
269
- <?php
270
- // app/Http/Resources/UserResource.php
271
- namespace App\Http\Resources;
272
-
273
- use Illuminate\Http\Request;
274
- use Illuminate\Http\Resources\Json\JsonResource;
275
-
276
- class UserResource extends JsonResource
277
- {
278
- public function toArray(Request $request): array
279
- {
280
- return [
281
- 'id' => $this->id,
282
- 'name' => $this->name,
283
- 'email' => $this->email,
284
- 'role' => $this->role,
285
- 'is_active' => $this->is_active,
286
- 'email_verified_at' => $this->email_verified_at?->toIso8601String(),
287
- 'created_at' => $this->created_at->toIso8601String(),
288
- 'updated_at' => $this->updated_at->toIso8601String(),
289
-
290
- // Conditional relationships
291
- 'organizations' => OrganizationResource::collection(
292
- $this->whenLoaded('organizations')
293
- ),
294
- 'organization_count' => $this->when(
295
- $this->organizations_count !== null,
296
- $this->organizations_count
297
- ),
298
- ];
299
- }
300
- }
301
-
302
-
303
- // app/Http/Resources/OrganizationResource.php
304
- namespace App\Http\Resources;
305
-
306
- use Illuminate\Http\Request;
307
- use Illuminate\Http\Resources\Json\JsonResource;
308
-
309
- class OrganizationResource extends JsonResource
310
- {
311
- public function toArray(Request $request): array
312
- {
313
- return [
314
- 'id' => $this->id,
315
- 'name' => $this->name,
316
- 'slug' => $this->slug,
317
- 'owner' => new UserResource($this->whenLoaded('owner')),
318
- 'member_count' => $this->when(
319
- $this->members_count !== null,
320
- $this->members_count
321
- ),
322
- 'created_at' => $this->created_at->toIso8601String(),
323
-
324
- // Pivot data when loaded through relationship
325
- 'membership' => $this->when($this->pivot, [
326
- 'role' => $this->pivot?->role,
327
- 'joined_at' => $this->pivot?->created_at?->toIso8601String(),
328
- ]),
329
- ];
330
- }
331
- }
332
-
333
-
334
- // app/Http/Resources/ProjectResource.php
335
- namespace App\Http\Resources;
336
-
337
- use Illuminate\Http\Request;
338
- use Illuminate\Http\Resources\Json\JsonResource;
339
-
340
- class ProjectResource extends JsonResource
341
- {
342
- public function toArray(Request $request): array
343
- {
344
- return [
345
- 'id' => $this->id,
346
- 'name' => $this->name,
347
- 'description' => $this->description,
348
- 'status' => [
349
- 'value' => $this->status->value,
350
- 'label' => $this->status->label(),
351
- ],
352
- 'organization' => new OrganizationResource($this->whenLoaded('organization')),
353
- 'creator' => new UserResource($this->whenLoaded('creator')),
354
- 'task_count' => $this->when(
355
- $this->tasks_count !== null,
356
- $this->tasks_count
357
- ),
358
- 'created_at' => $this->created_at->toIso8601String(),
359
- 'updated_at' => $this->updated_at->toIso8601String(),
360
- ];
361
- }
362
- }
363
-
364
-
365
- // app/Http/Resources/PaginatedCollection.php
366
- namespace App\Http\Resources;
367
-
368
- use Illuminate\Http\Request;
369
- use Illuminate\Http\Resources\Json\ResourceCollection;
370
-
371
- class PaginatedCollection extends ResourceCollection
372
- {
373
- protected string $resourceClass;
374
-
375
- public function __construct($resource, string $resourceClass)
376
- {
377
- parent::__construct($resource);
378
- $this->resourceClass = $resourceClass;
379
- }
380
-
381
- public function toArray(Request $request): array
382
- {
383
- return [
384
- 'data' => $this->resourceClass::collection($this->collection),
385
- 'pagination' => [
386
- 'current_page' => $this->currentPage(),
387
- 'per_page' => $this->perPage(),
388
- 'total' => $this->total(),
389
- 'total_pages' => $this->lastPage(),
390
- 'has_more' => $this->hasMorePages(),
391
- ],
392
- ];
393
- }
394
- }
395
- ```
396
-
397
- ### 3. Form Requests and Validation
398
-
399
- ```php
400
- <?php
401
- // app/Http/Requests/User/CreateUserRequest.php
402
- namespace App\Http\Requests\User;
403
-
404
- use Illuminate\Foundation\Http\FormRequest;
405
- use Illuminate\Validation\Rules\Password;
406
-
407
- class CreateUserRequest extends FormRequest
408
- {
409
- public function authorize(): bool
410
- {
411
- return $this->user()->isAdmin();
412
- }
413
-
414
- public function rules(): array
415
- {
416
- return [
417
- 'name' => ['required', 'string', 'min:2', 'max:100'],
418
- 'email' => ['required', 'email', 'unique:users,email'],
419
- 'password' => [
420
- 'required',
421
- 'confirmed',
422
- Password::min(8)
423
- ->mixedCase()
424
- ->numbers()
425
- ->symbols(),
426
- ],
427
- 'role' => ['sometimes', 'string', 'in:admin,user,guest'],
428
- ];
429
- }
430
-
431
- public function messages(): array
432
- {
433
- return [
434
- 'email.unique' => 'This email is already registered.',
435
- 'password.confirmed' => 'Password confirmation does not match.',
436
- ];
437
- }
438
- }
439
-
440
-
441
- // app/Http/Requests/User/UpdateUserRequest.php
442
- namespace App\Http\Requests\User;
443
-
444
- use Illuminate\Foundation\Http\FormRequest;
445
- use Illuminate\Validation\Rule;
446
-
447
- class UpdateUserRequest extends FormRequest
448
- {
449
- public function authorize(): bool
450
- {
451
- return $this->user()->isAdmin() || $this->user()->id === $this->route('user')->id;
452
- }
453
-
454
- public function rules(): array
455
- {
456
- return [
457
- 'name' => ['sometimes', 'string', 'min:2', 'max:100'],
458
- 'email' => [
459
- 'sometimes',
460
- 'email',
461
- Rule::unique('users')->ignore($this->route('user')),
462
- ],
463
- 'is_active' => ['sometimes', 'boolean'],
464
- ];
465
- }
466
- }
467
-
468
-
469
- // app/Http/Requests/Organization/CreateOrganizationRequest.php
470
- namespace App\Http\Requests\Organization;
471
-
472
- use Illuminate\Foundation\Http\FormRequest;
473
-
474
- class CreateOrganizationRequest extends FormRequest
475
- {
476
- public function authorize(): bool
477
- {
478
- return true;
479
- }
480
-
481
- public function rules(): array
482
- {
483
- return [
484
- 'name' => ['required', 'string', 'min:2', 'max:255'],
485
- 'slug' => [
486
- 'required',
487
- 'string',
488
- 'min:2',
489
- 'max:100',
490
- 'regex:/^[a-z0-9-]+$/',
491
- 'unique:organizations,slug',
492
- ],
493
- ];
494
- }
495
-
496
- public function messages(): array
497
- {
498
- return [
499
- 'slug.regex' => 'Slug must contain only lowercase letters, numbers, and hyphens.',
500
- 'slug.unique' => 'This slug is already taken.',
501
- ];
502
- }
503
- }
504
-
505
-
506
- // app/Http/Requests/Project/CreateProjectRequest.php
507
- namespace App\Http\Requests\Project;
508
-
509
- use App\Enums\ProjectStatus;
510
- use Illuminate\Foundation\Http\FormRequest;
511
- use Illuminate\Validation\Rule;
512
-
513
- class CreateProjectRequest extends FormRequest
514
- {
515
- public function authorize(): bool
516
- {
517
- $organization = $this->route('organization');
518
- return $this->user()->belongsToOrganization($organization);
519
- }
520
-
521
- public function rules(): array
522
- {
523
- return [
524
- 'name' => [
525
- 'required',
526
- 'string',
527
- 'min:1',
528
- 'max:255',
529
- Rule::unique('projects')
530
- ->where('organization_id', $this->route('organization')->id),
531
- ],
532
- 'description' => ['nullable', 'string', 'max:5000'],
533
- 'status' => ['sometimes', Rule::enum(ProjectStatus::class)],
534
- ];
57
+ return $search
58
+ ? $query->where('name', 'like', "%{$search}%")
59
+ ->orWhere('email', 'like', "%{$search}%")
60
+ : $query;
535
61
  }
536
62
  }
537
63
  ```
538
64
 
539
- ### 4. Controllers
65
+ ### Controller with Service
540
66
 
541
67
  ```php
542
- <?php
543
- // app/Http/Controllers/Api/UserController.php
544
- namespace App\Http\Controllers\Api;
545
-
546
- use App\Http\Controllers\Controller;
547
- use App\Http\Requests\User\CreateUserRequest;
548
- use App\Http\Requests\User\UpdateUserRequest;
549
- use App\Http\Resources\PaginatedCollection;
550
- use App\Http\Resources\UserResource;
551
- use App\Models\User;
552
- use App\Services\UserService;
553
- use Illuminate\Http\JsonResponse;
554
- use Illuminate\Http\Request;
555
- use Illuminate\Http\Response;
556
-
557
68
  class UserController extends Controller
558
69
  {
559
- public function __construct(
560
- private readonly UserService $userService
561
- ) {}
70
+ public function __construct(private UserService $userService) {}
562
71
 
563
72
  public function index(Request $request): PaginatedCollection
564
73
  {
565
74
  $users = $this->userService->list(
566
75
  search: $request->input('search'),
567
- role: $request->input('role'),
568
76
  perPage: $request->input('per_page', 20)
569
77
  );
570
-
571
78
  return new PaginatedCollection($users, UserResource::class);
572
79
  }
573
80
 
574
- public function show(User $user): UserResource
575
- {
576
- return new UserResource(
577
- $user->load('organizations')
578
- );
579
- }
580
-
581
81
  public function store(CreateUserRequest $request): JsonResponse
582
82
  {
583
83
  $user = $this->userService->create($request->validated());
584
-
585
84
  return (new UserResource($user))
586
85
  ->response()
587
- ->setStatusCode(Response::HTTP_CREATED);
86
+ ->setStatusCode(201);
588
87
  }
589
88
 
590
- public function update(UpdateUserRequest $request, User $user): UserResource
591
- {
592
- $user = $this->userService->update($user, $request->validated());
593
-
594
- return new UserResource($user);
595
- }
596
-
597
- public function destroy(User $user): Response
598
- {
599
- $this->userService->delete($user);
600
-
601
- return response()->noContent();
602
- }
603
-
604
- public function me(Request $request): UserResource
605
- {
606
- return new UserResource(
607
- $request->user()->load('organizations')
608
- );
609
- }
610
- }
611
-
612
-
613
- // app/Http/Controllers/Api/AuthController.php
614
- namespace App\Http\Controllers\Api;
615
-
616
- use App\Http\Controllers\Controller;
617
- use App\Http\Requests\Auth\LoginRequest;
618
- use App\Http\Requests\Auth\RegisterRequest;
619
- use App\Http\Resources\UserResource;
620
- use App\Services\AuthService;
621
- use Illuminate\Http\JsonResponse;
622
- use Illuminate\Http\Request;
623
- use Illuminate\Http\Response;
624
-
625
- class AuthController extends Controller
626
- {
627
- public function __construct(
628
- private readonly AuthService $authService
629
- ) {}
630
-
631
- public function register(RegisterRequest $request): JsonResponse
632
- {
633
- $result = $this->authService->register($request->validated());
634
-
635
- return response()->json([
636
- 'user' => new UserResource($result['user']),
637
- 'token' => $result['token'],
638
- ], Response::HTTP_CREATED);
639
- }
640
-
641
- public function login(LoginRequest $request): JsonResponse
642
- {
643
- $result = $this->authService->login(
644
- $request->input('email'),
645
- $request->input('password')
646
- );
647
-
648
- if (!$result) {
649
- return response()->json([
650
- 'message' => 'Invalid credentials',
651
- ], Response::HTTP_UNAUTHORIZED);
652
- }
653
-
654
- return response()->json([
655
- 'user' => new UserResource($result['user']),
656
- 'token' => $result['token'],
657
- ]);
658
- }
659
-
660
- public function logout(Request $request): Response
661
- {
662
- $request->user()->currentAccessToken()->delete();
663
-
664
- return response()->noContent();
665
- }
666
-
667
- public function refresh(Request $request): JsonResponse
668
- {
669
- $token = $this->authService->refreshToken($request->user());
670
-
671
- return response()->json([
672
- 'token' => $token,
673
- ]);
674
- }
675
- }
676
-
677
-
678
- // app/Http/Controllers/Api/ProjectController.php
679
- namespace App\Http\Controllers\Api;
680
-
681
- use App\Http\Controllers\Controller;
682
- use App\Http\Requests\Project\CreateProjectRequest;
683
- use App\Http\Requests\Project\UpdateProjectRequest;
684
- use App\Http\Resources\PaginatedCollection;
685
- use App\Http\Resources\ProjectResource;
686
- use App\Models\Organization;
687
- use App\Models\Project;
688
- use App\Services\ProjectService;
689
- use Illuminate\Http\JsonResponse;
690
- use Illuminate\Http\Request;
691
- use Illuminate\Http\Response;
692
-
693
- class ProjectController extends Controller
694
- {
695
- public function __construct(
696
- private readonly ProjectService $projectService
697
- ) {}
698
-
699
- public function index(Request $request, Organization $organization): PaginatedCollection
700
- {
701
- $projects = $this->projectService->listForOrganization(
702
- $organization,
703
- status: $request->input('status'),
704
- perPage: $request->input('per_page', 20)
705
- );
706
-
707
- return new PaginatedCollection($projects, ProjectResource::class);
708
- }
709
-
710
- public function store(CreateProjectRequest $request, Organization $organization): JsonResponse
711
- {
712
- $project = $this->projectService->create(
713
- $organization,
714
- $request->user(),
715
- $request->validated()
716
- );
717
-
718
- return (new ProjectResource($project))
719
- ->response()
720
- ->setStatusCode(Response::HTTP_CREATED);
721
- }
722
-
723
- public function show(Organization $organization, Project $project): ProjectResource
724
- {
725
- return new ProjectResource(
726
- $project->load(['organization', 'creator'])
727
- );
728
- }
729
-
730
- public function update(
731
- UpdateProjectRequest $request,
732
- Organization $organization,
733
- Project $project
734
- ): ProjectResource {
735
- $project = $this->projectService->update($project, $request->validated());
736
-
737
- return new ProjectResource($project);
738
- }
739
-
740
- public function destroy(Organization $organization, Project $project): Response
89
+ public function show(User $user): UserResource
741
90
  {
742
- $this->projectService->delete($project);
743
-
744
- return response()->noContent();
91
+ return new UserResource($user->load('organizations'));
745
92
  }
746
93
  }
747
94
  ```
748
95
 
749
- ### 5. Service Layer
96
+ ### Form Request Validation
750
97
 
751
98
  ```php
752
- <?php
753
- // app/Services/UserService.php
754
- namespace App\Services;
755
-
756
- use App\Models\User;
757
- use Illuminate\Contracts\Pagination\LengthAwarePaginator;
758
- use Illuminate\Support\Facades\Hash;
759
-
760
- class UserService
761
- {
762
- public function list(
763
- ?string $search = null,
764
- ?string $role = null,
765
- int $perPage = 20
766
- ): LengthAwarePaginator {
767
- return User::query()
768
- ->active()
769
- ->search($search)
770
- ->when($role, fn ($q) => $q->role($role))
771
- ->withCount('organizations')
772
- ->orderByDesc('created_at')
773
- ->paginate($perPage);
774
- }
775
-
776
- public function create(array $data): User
777
- {
778
- return User::create([
779
- 'name' => $data['name'],
780
- 'email' => $data['email'],
781
- 'password' => Hash::make($data['password']),
782
- 'role' => $data['role'] ?? 'user',
783
- ]);
784
- }
785
-
786
- public function update(User $user, array $data): User
787
- {
788
- $user->update($data);
789
-
790
- return $user->fresh();
791
- }
792
-
793
- public function delete(User $user): void
794
- {
795
- $user->delete(); // Soft delete
796
- }
797
-
798
- public function findByEmail(string $email): ?User
799
- {
800
- return User::where('email', $email)->first();
801
- }
802
- }
803
-
804
-
805
- // app/Services/AuthService.php
806
- namespace App\Services;
807
-
808
- use App\Models\User;
809
- use Illuminate\Support\Facades\Hash;
810
-
811
- class AuthService
99
+ class CreateUserRequest extends FormRequest
812
100
  {
813
- public function __construct(
814
- private readonly UserService $userService
815
- ) {}
816
-
817
- public function register(array $data): array
101
+ public function authorize(): bool
818
102
  {
819
- $user = $this->userService->create($data);
820
- $token = $user->createToken('auth-token')->plainTextToken;
821
-
822
- return [
823
- 'user' => $user,
824
- 'token' => $token,
825
- ];
103
+ return $this->user()->isAdmin();
826
104
  }
827
105
 
828
- public function login(string $email, string $password): ?array
106
+ public function rules(): array
829
107
  {
830
- $user = $this->userService->findByEmail($email);
831
-
832
- if (!$user || !Hash::check($password, $user->password)) {
833
- return null;
834
- }
835
-
836
- if (!$user->is_active) {
837
- return null;
838
- }
839
-
840
- // Revoke existing tokens
841
- $user->tokens()->delete();
842
-
843
- $token = $user->createToken('auth-token')->plainTextToken;
844
-
845
108
  return [
846
- 'user' => $user,
847
- 'token' => $token,
109
+ 'name' => ['required', 'string', 'min:2', 'max:100'],
110
+ 'email' => ['required', 'email', 'unique:users,email'],
111
+ 'password' => ['required', 'confirmed', Password::min(8)->mixedCase()->numbers()],
112
+ 'role' => ['sometimes', 'in:admin,user,guest'],
848
113
  ];
849
114
  }
850
-
851
- public function refreshToken(User $user): string
852
- {
853
- $user->currentAccessToken()->delete();
854
-
855
- return $user->createToken('auth-token')->plainTextToken;
856
- }
857
- }
858
-
859
-
860
- // app/Services/ProjectService.php
861
- namespace App\Services;
862
-
863
- use App\Enums\ProjectStatus;
864
- use App\Models\Organization;
865
- use App\Models\Project;
866
- use App\Models\User;
867
- use Illuminate\Contracts\Pagination\LengthAwarePaginator;
868
-
869
- class ProjectService
870
- {
871
- public function listForOrganization(
872
- Organization $organization,
873
- ?string $status = null,
874
- int $perPage = 20
875
- ): LengthAwarePaginator {
876
- return Project::query()
877
- ->forOrganization($organization->id)
878
- ->when($status, fn ($q) => $q->where('status', $status))
879
- ->with(['creator'])
880
- ->withCount('tasks')
881
- ->orderByDesc('created_at')
882
- ->paginate($perPage);
883
- }
884
-
885
- public function create(
886
- Organization $organization,
887
- User $creator,
888
- array $data
889
- ): Project {
890
- return Project::create([
891
- 'organization_id' => $organization->id,
892
- 'created_by' => $creator->id,
893
- 'name' => $data['name'],
894
- 'description' => $data['description'] ?? null,
895
- 'status' => $data['status'] ?? ProjectStatus::DRAFT,
896
- ]);
897
- }
898
-
899
- public function update(Project $project, array $data): Project
900
- {
901
- $project->update($data);
902
-
903
- return $project->fresh();
904
- }
905
-
906
- public function delete(Project $project): void
907
- {
908
- $project->delete(); // Soft delete
909
- }
910
115
  }
911
116
  ```
912
117
 
913
- ### 6. Middleware and Policies
914
-
915
- ```php
916
- <?php
917
- // app/Http/Middleware/EnsureOrganizationMember.php
918
- namespace App\Http\Middleware;
919
-
920
- use App\Models\Organization;
921
- use Closure;
922
- use Illuminate\Http\Request;
923
- use Symfony\Component\HttpFoundation\Response;
118
+ ## Workflows
924
119
 
925
- class EnsureOrganizationMember
926
- {
927
- public function handle(Request $request, Closure $next): Response
928
- {
929
- $organization = $request->route('organization');
930
-
931
- if (!$organization instanceof Organization) {
932
- abort(404, 'Organization not found');
933
- }
934
-
935
- if (!$request->user()->belongsToOrganization($organization)) {
936
- abort(403, 'You are not a member of this organization');
937
- }
938
-
939
- return $next($request);
940
- }
941
- }
120
+ ### API Development
942
121
 
122
+ 1. Create model and migration
123
+ 2. Create controller with `php artisan make:controller --api`
124
+ 3. Add Form Request for validation
125
+ 4. Create API Resource for responses
126
+ 5. Write feature tests
943
127
 
944
- // app/Policies/ProjectPolicy.php
945
- namespace App\Policies;
946
-
947
- use App\Models\Project;
948
- use App\Models\User;
949
-
950
- class ProjectPolicy
951
- {
952
- public function viewAny(User $user): bool
953
- {
954
- return true;
955
- }
956
-
957
- public function view(User $user, Project $project): bool
958
- {
959
- return $user->belongsToOrganization($project->organization);
960
- }
961
-
962
- public function create(User $user): bool
963
- {
964
- return true;
965
- }
966
-
967
- public function update(User $user, Project $project): bool
968
- {
969
- if ($user->isAdmin()) {
970
- return true;
971
- }
972
-
973
- return $user->id === $project->created_by;
974
- }
975
-
976
- public function delete(User $user, Project $project): bool
977
- {
978
- if ($user->isAdmin()) {
979
- return true;
980
- }
981
-
982
- return $user->id === $project->created_by;
983
- }
984
- }
985
- ```
986
-
987
- ### 7. Testing Patterns
128
+ ### Resource Response
988
129
 
989
130
  ```php
990
- <?php
991
- // tests/Feature/UserTest.php
992
- namespace Tests\Feature;
993
-
994
- use App\Models\User;
995
- use Illuminate\Foundation\Testing\RefreshDatabase;
996
- use Laravel\Sanctum\Sanctum;
997
- use Tests\TestCase;
998
-
999
- class UserTest extends TestCase
1000
- {
1001
- use RefreshDatabase;
1002
-
1003
- public function test_admin_can_list_users(): void
1004
- {
1005
- $admin = User::factory()->create(['role' => 'admin']);
1006
- User::factory()->count(5)->create();
1007
-
1008
- Sanctum::actingAs($admin);
1009
-
1010
- $response = $this->getJson('/api/users');
1011
-
1012
- $response->assertOk()
1013
- ->assertJsonStructure([
1014
- 'data' => [
1015
- '*' => ['id', 'name', 'email', 'role', 'created_at'],
1016
- ],
1017
- 'pagination' => ['current_page', 'total', 'per_page'],
1018
- ]);
1019
- }
1020
-
1021
- public function test_non_admin_cannot_list_users(): void
1022
- {
1023
- $user = User::factory()->create(['role' => 'user']);
1024
-
1025
- Sanctum::actingAs($user);
1026
-
1027
- $response = $this->getJson('/api/users');
1028
-
1029
- $response->assertForbidden();
1030
- }
1031
-
1032
- public function test_user_can_get_own_profile(): void
1033
- {
1034
- $user = User::factory()->create();
1035
-
1036
- Sanctum::actingAs($user);
1037
-
1038
- $response = $this->getJson('/api/users/me');
1039
-
1040
- $response->assertOk()
1041
- ->assertJson([
1042
- 'data' => [
1043
- 'id' => $user->id,
1044
- 'email' => $user->email,
1045
- ],
1046
- ]);
1047
- }
1048
-
1049
- public function test_admin_can_create_user(): void
1050
- {
1051
- $admin = User::factory()->create(['role' => 'admin']);
1052
-
1053
- Sanctum::actingAs($admin);
1054
-
1055
- $response = $this->postJson('/api/users', [
1056
- 'name' => 'New User',
1057
- 'email' => 'new@example.com',
1058
- 'password' => 'SecurePass123!',
1059
- 'password_confirmation' => 'SecurePass123!',
1060
- ]);
1061
-
1062
- $response->assertCreated()
1063
- ->assertJson([
1064
- 'data' => [
1065
- 'email' => 'new@example.com',
1066
- ],
1067
- ]);
1068
-
1069
- $this->assertDatabaseHas('users', [
1070
- 'email' => 'new@example.com',
1071
- ]);
1072
- }
1073
-
1074
- public function test_cannot_create_user_with_duplicate_email(): void
1075
- {
1076
- $admin = User::factory()->create(['role' => 'admin']);
1077
- $existing = User::factory()->create();
1078
-
1079
- Sanctum::actingAs($admin);
1080
-
1081
- $response = $this->postJson('/api/users', [
1082
- 'name' => 'New User',
1083
- 'email' => $existing->email,
1084
- 'password' => 'SecurePass123!',
1085
- 'password_confirmation' => 'SecurePass123!',
1086
- ]);
1087
-
1088
- $response->assertUnprocessable()
1089
- ->assertJsonValidationErrors(['email']);
1090
- }
1091
- }
1092
-
1093
-
1094
- // tests/Feature/AuthTest.php
1095
- namespace Tests\Feature;
1096
-
1097
- use App\Models\User;
1098
- use Illuminate\Foundation\Testing\RefreshDatabase;
1099
- use Tests\TestCase;
1100
-
1101
- class AuthTest extends TestCase
131
+ class UserResource extends JsonResource
1102
132
  {
1103
- use RefreshDatabase;
1104
-
1105
- public function test_user_can_register(): void
1106
- {
1107
- $response = $this->postJson('/api/auth/register', [
1108
- 'name' => 'Test User',
1109
- 'email' => 'test@example.com',
1110
- 'password' => 'SecurePass123!',
1111
- 'password_confirmation' => 'SecurePass123!',
1112
- ]);
1113
-
1114
- $response->assertCreated()
1115
- ->assertJsonStructure([
1116
- 'user' => ['id', 'email', 'name'],
1117
- 'token',
1118
- ]);
1119
-
1120
- $this->assertDatabaseHas('users', [
1121
- 'email' => 'test@example.com',
1122
- ]);
1123
- }
1124
-
1125
- public function test_user_can_login(): void
1126
- {
1127
- $user = User::factory()->create([
1128
- 'password' => bcrypt('password123'),
1129
- ]);
1130
-
1131
- $response = $this->postJson('/api/auth/login', [
1132
- 'email' => $user->email,
1133
- 'password' => 'password123',
1134
- ]);
1135
-
1136
- $response->assertOk()
1137
- ->assertJsonStructure([
1138
- 'user' => ['id', 'email'],
1139
- 'token',
1140
- ]);
1141
- }
1142
-
1143
- public function test_login_fails_with_wrong_password(): void
133
+ public function toArray(Request $request): array
1144
134
  {
1145
- $user = User::factory()->create();
1146
-
1147
- $response = $this->postJson('/api/auth/login', [
1148
- 'email' => $user->email,
1149
- 'password' => 'wrong-password',
1150
- ]);
1151
-
1152
- $response->assertUnauthorized();
135
+ return [
136
+ 'id' => $this->id,
137
+ 'name' => $this->name,
138
+ 'email' => $this->email,
139
+ 'organizations' => OrganizationResource::collection($this->whenLoaded('organizations')),
140
+ 'created_at' => $this->created_at->toIso8601String(),
141
+ ];
1153
142
  }
1154
143
  }
1155
144
  ```
1156
145
 
1157
- ## Use Cases
1158
-
1159
- ### Queue Jobs for Background Processing
1160
-
1161
- ```php
1162
- <?php
1163
- // app/Jobs/SendWelcomeEmail.php
1164
- namespace App\Jobs;
1165
-
1166
- use App\Mail\WelcomeEmail;
1167
- use App\Models\User;
1168
- use Illuminate\Bus\Queueable;
1169
- use Illuminate\Contracts\Queue\ShouldQueue;
1170
- use Illuminate\Foundation\Bus\Dispatchable;
1171
- use Illuminate\Queue\InteractsWithQueue;
1172
- use Illuminate\Queue\SerializesModels;
1173
- use Illuminate\Support\Facades\Mail;
1174
-
1175
- class SendWelcomeEmail implements ShouldQueue
1176
- {
1177
- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
1178
-
1179
- public int $tries = 3;
1180
- public int $backoff = 60;
1181
-
1182
- public function __construct(
1183
- public readonly User $user
1184
- ) {}
146
+ ## Best Practices
1185
147
 
1186
- public function handle(): void
1187
- {
1188
- Mail::to($this->user->email)
1189
- ->send(new WelcomeEmail($this->user));
1190
- }
148
+ | Do | Avoid |
149
+ |----|-------|
150
+ | Use Form Requests for validation | Validating in controllers |
151
+ | Use API Resources for responses | Returning models directly |
152
+ | Use service classes for logic | Fat controllers |
153
+ | Use eager loading | N+1 queries |
154
+ | Use soft deletes | Hard deletes for important data |
1191
155
 
1192
- public function failed(\Throwable $exception): void
1193
- {
1194
- // Log or notify about the failure
1195
- }
1196
- }
156
+ ## Project Structure
1197
157
 
1198
- // Usage
1199
- SendWelcomeEmail::dispatch($user);
1200
158
  ```
1201
-
1202
- ### Event-Driven Architecture
1203
-
1204
- ```php
1205
- <?php
1206
- // app/Events/UserCreated.php
1207
- namespace App\Events;
1208
-
1209
- use App\Models\User;
1210
- use Illuminate\Foundation\Events\Dispatchable;
1211
- use Illuminate\Queue\SerializesModels;
1212
-
1213
- class UserCreated
1214
- {
1215
- use Dispatchable, SerializesModels;
1216
-
1217
- public function __construct(
1218
- public readonly User $user
1219
- ) {}
1220
- }
1221
-
1222
- // app/Listeners/SendWelcomeNotification.php
1223
- namespace App\Listeners;
1224
-
1225
- use App\Events\UserCreated;
1226
- use App\Jobs\SendWelcomeEmail;
1227
-
1228
- class SendWelcomeNotification
1229
- {
1230
- public function handle(UserCreated $event): void
1231
- {
1232
- SendWelcomeEmail::dispatch($event->user);
1233
- }
1234
- }
1235
-
1236
- // app/Providers/EventServiceProvider.php
1237
- protected $listen = [
1238
- UserCreated::class => [
1239
- SendWelcomeNotification::class,
1240
- ],
1241
- ];
159
+ app/
160
+ ├── Http/
161
+ │ ├── Controllers/Api/
162
+ │ ├── Requests/
163
+ │ ├── Resources/
164
+ │ └── Middleware/
165
+ ├── Models/
166
+ ├── Services/
167
+ ├── Policies/
168
+ ├── Jobs/
169
+ └── Events/
170
+ routes/
171
+ ├── api.php
172
+ └── web.php
173
+ tests/
174
+ ├── Feature/
175
+ └── Unit/
1242
176
  ```
1243
177
 
1244
- ## Best Practices
1245
-
1246
- ### Do's
1247
-
1248
- - Use UUID primary keys for public APIs
1249
- - Use Form Requests for validation
1250
- - Use API Resources for response formatting
1251
- - Use service classes for business logic
1252
- - Use policies for authorization
1253
- - Use eager loading to prevent N+1
1254
- - Use database transactions for writes
1255
- - Write feature and unit tests
1256
- - Use queues for heavy operations
1257
- - Use soft deletes for important data
1258
-
1259
- ### Don'ts
1260
-
1261
- - Don't put business logic in controllers
1262
- - Don't use raw queries without bindings
1263
- - Don't ignore validation
1264
- - Don't skip authorization checks
1265
- - Don't expose internal IDs
1266
- - Don't use mutable defaults
1267
- - Don't ignore exceptions
1268
- - Don't skip testing
1269
- - Don't use sync for heavy tasks
1270
- - Don't forget rate limiting
1271
-
1272
- ## References
1273
-
1274
- - [Laravel Documentation](https://laravel.com/docs)
1275
- - [Laravel Best Practices](https://github.com/alexeymezenin/laravel-best-practices)
1276
- - [Laravel API Tutorial](https://laravel.com/docs/eloquent-resources)
1277
- - [Laravel Testing](https://laravel.com/docs/testing)
1278
- - [Spatie Guidelines](https://spatie.be/guidelines/laravel-php)
178
+ For detailed examples and patterns, see reference files above.