omgkit 2.1.1 → 2.2.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 (50) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  5. package/plugin/skills/databases/redis/SKILL.md +885 -25
  6. package/plugin/skills/devops/aws/SKILL.md +686 -28
  7. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  8. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  9. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  10. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  11. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  12. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  13. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  14. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  15. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  16. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  17. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  18. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  19. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  20. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  21. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  22. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  23. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  24. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  25. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  26. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  27. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  28. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  29. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  30. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  31. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  32. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  33. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  34. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  35. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  36. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  37. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  38. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  39. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  40. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  41. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  42. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  43. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  44. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  45. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  46. package/plugin/skills/security/oauth/SKILL.md +968 -31
  47. package/plugin/skills/security/owasp/SKILL.md +894 -33
  48. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  49. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  50. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,65 +1,1278 @@
1
1
  ---
2
2
  name: laravel
3
- description: Laravel PHP development. Use for Laravel projects, Eloquent, Blade.
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
4
14
  ---
5
15
 
6
- # Laravel Skill
16
+ # Laravel
7
17
 
8
- ## Patterns
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.
19
+
20
+ ## Purpose
21
+
22
+ Build robust PHP applications with confidence:
23
+
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
31
+
32
+ ## Features
33
+
34
+ ### 1. Model Design and Relationships
9
35
 
10
- ### Model
11
36
  ```php
12
- class User extends Model
37
+ <?php
38
+ // app/Models/User.php
39
+ namespace App\Models;
40
+
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;
49
+
50
+ class User extends Authenticatable
51
+ {
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
+ ];
72
+
73
+ protected $attributes = [
74
+ 'role' => 'user',
75
+ 'is_active' => true,
76
+ ];
77
+
78
+ // Relationships
79
+ public function organizations(): BelongsToMany
80
+ {
81
+ return $this->belongsToMany(Organization::class, 'memberships')
82
+ ->withPivot('role')
83
+ ->withTimestamps();
84
+ }
85
+
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
+ public function scopeActive($query)
98
+ {
99
+ return $query->where('is_active', true);
100
+ }
101
+
102
+ public function scopeRole($query, string $role)
103
+ {
104
+ return $query->where('role', $role);
105
+ }
106
+
107
+ public function scopeSearch($query, ?string $search)
108
+ {
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
13
248
  {
14
- protected $fillable = ['email', 'password'];
15
- protected $hidden = ['password'];
249
+ case DRAFT = 'draft';
250
+ case ACTIVE = 'active';
251
+ case COMPLETED = 'completed';
252
+ case ARCHIVED = 'archived';
16
253
 
17
- public function posts()
254
+ public function label(): string
18
255
  {
19
- return $this->hasMany(Post::class);
256
+ return match ($this) {
257
+ self::DRAFT => 'Draft',
258
+ self::ACTIVE => 'Active',
259
+ self::COMPLETED => 'Completed',
260
+ self::ARCHIVED => 'Archived',
261
+ };
20
262
  }
21
263
  }
22
264
  ```
23
265
 
24
- ### Controller
266
+ ### 2. API Resources and Collections
267
+
25
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
+ ];
535
+ }
536
+ }
537
+ ```
538
+
539
+ ### 4. Controllers
540
+
541
+ ```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
+
26
557
  class UserController extends Controller
27
558
  {
28
- public function index()
559
+ public function __construct(
560
+ private readonly UserService $userService
561
+ ) {}
562
+
563
+ public function index(Request $request): PaginatedCollection
564
+ {
565
+ $users = $this->userService->list(
566
+ search: $request->input('search'),
567
+ role: $request->input('role'),
568
+ perPage: $request->input('per_page', 20)
569
+ );
570
+
571
+ return new PaginatedCollection($users, UserResource::class);
572
+ }
573
+
574
+ public function show(User $user): UserResource
575
+ {
576
+ return new UserResource(
577
+ $user->load('organizations')
578
+ );
579
+ }
580
+
581
+ public function store(CreateUserRequest $request): JsonResponse
582
+ {
583
+ $user = $this->userService->create($request->validated());
584
+
585
+ return (new UserResource($user))
586
+ ->response()
587
+ ->setStatusCode(Response::HTTP_CREATED);
588
+ }
589
+
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
741
+ {
742
+ $this->projectService->delete($project);
743
+
744
+ return response()->noContent();
745
+ }
746
+ }
747
+ ```
748
+
749
+ ### 5. Service Layer
750
+
751
+ ```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
812
+ {
813
+ public function __construct(
814
+ private readonly UserService $userService
815
+ ) {}
816
+
817
+ public function register(array $data): array
818
+ {
819
+ $user = $this->userService->create($data);
820
+ $token = $user->createToken('auth-token')->plainTextToken;
821
+
822
+ return [
823
+ 'user' => $user,
824
+ 'token' => $token,
825
+ ];
826
+ }
827
+
828
+ public function login(string $email, string $password): ?array
29
829
  {
30
- return User::all();
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
+ return [
846
+ 'user' => $user,
847
+ 'token' => $token,
848
+ ];
31
849
  }
32
850
 
33
- public function store(Request $request)
851
+ public function refreshToken(User $user): string
34
852
  {
35
- $validated = $request->validate([
36
- 'email' => 'required|email|unique:users',
37
- 'password' => 'required|min:8',
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,
38
896
  ]);
897
+ }
898
+
899
+ public function update(Project $project, array $data): Project
900
+ {
901
+ $project->update($data);
902
+
903
+ return $project->fresh();
904
+ }
39
905
 
40
- $user = User::create($validated);
41
- return response()->json($user, 201);
906
+ public function delete(Project $project): void
907
+ {
908
+ $project->delete(); // Soft delete
42
909
  }
43
910
  }
44
911
  ```
45
912
 
46
- ### Routes
913
+ ### 6. Middleware and Policies
914
+
47
915
  ```php
48
- Route::apiResource('users', UserController::class);
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;
924
+
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
+ }
942
+
943
+
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
+ }
49
985
  ```
50
986
 
51
- ### Migration
987
+ ### 7. Testing Patterns
988
+
52
989
  ```php
53
- Schema::create('users', function (Blueprint $table) {
54
- $table->id();
55
- $table->string('email')->unique();
56
- $table->string('password');
57
- $table->timestamps();
58
- });
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
1102
+ {
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
1144
+ {
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();
1153
+ }
1154
+ }
1155
+ ```
1156
+
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
+ ) {}
1185
+
1186
+ public function handle(): void
1187
+ {
1188
+ Mail::to($this->user->email)
1189
+ ->send(new WelcomeEmail($this->user));
1190
+ }
1191
+
1192
+ public function failed(\Throwable $exception): void
1193
+ {
1194
+ // Log or notify about the failure
1195
+ }
1196
+ }
1197
+
1198
+ // Usage
1199
+ SendWelcomeEmail::dispatch($user);
1200
+ ```
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
+ ];
59
1242
  ```
60
1243
 
61
1244
  ## Best Practices
62
- - Use Eloquent relationships
63
- - Use Form Requests
64
- - Use Resources for API responses
65
- - Use Jobs for async
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)