@famgia/omnify-laravel 0.0.114 → 0.0.115

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@famgia/omnify-laravel",
3
- "version": "0.0.114",
3
+ "version": "0.0.115",
4
4
  "description": "Laravel migration and TypeScript type generator for omnify-schema",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,9 +25,9 @@
25
25
  "README.md"
26
26
  ],
27
27
  "dependencies": {
28
- "@famgia/omnify-types": "0.0.103",
29
- "@famgia/omnify-core": "0.0.105",
30
- "@famgia/omnify-atlas": "0.0.99"
28
+ "@famgia/omnify-types": "0.0.104",
29
+ "@famgia/omnify-atlas": "0.0.100",
30
+ "@famgia/omnify-core": "0.0.106"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsup",
@@ -32,7 +32,7 @@ Route::get('/users', [UserController::class, 'index']);
32
32
  | `api.php` | `/api` | `/users` | `/users` |
33
33
  | `web.php` | (none) | `/dashboard` | `/dashboard` |
34
34
 
35
- **Full guide:** `.claude/guides/laravel/openapi.md`
35
+ **Full guide:** `.claude/omnify/guides/laravel/openapi.md`
36
36
 
37
37
  ---
38
38
 
@@ -7,7 +7,7 @@ alwaysApply: false
7
7
  # Testing Rules (PEST)
8
8
 
9
9
  > **Agent:** Act as **@tester** agent
10
- > - Read `.claude/guides/laravel/testing.md` for full guide
10
+ > - Read `.claude/omnify/guides/laravel/testing.md` for full guide
11
11
 
12
12
  ## How to Run Tests
13
13
 
@@ -6,8 +6,8 @@ alwaysApply: false
6
6
 
7
7
  # Laravel Rules
8
8
 
9
- > **Documentation:** `.claude/guides/laravel/`
10
- > **Team Migration Guide:** `.claude/guides/laravel/migrations-team.md`
9
+ > **Documentation:** `.claude/omnify/guides/laravel/`
10
+ > **Team Migration Guide:** `.claude/omnify/guides/laravel/migrations-team.md`
11
11
  > **Specific Rules:** See also `laravel-controller.mdc`, `laravel-resource.mdc`, `laravel-request.mdc`, `laravel-testing.mdc`, `migrations-workflow.mdc`, `omnify-migrations.mdc`
12
12
 
13
13
  ## ⛔ CRITICAL: DO NOT EDIT
@@ -77,6 +77,32 @@ See `migrations-workflow.mdc` for complete guide.
77
77
  | Reusable action | Action class |
78
78
  | Async task | Job |
79
79
 
80
+ ## Authentication Models
81
+
82
+ When `authenticatable: true` is set in schema, model extends `Authenticatable`:
83
+
84
+ ```yaml
85
+ # schemas/auth/User.yaml
86
+ options:
87
+ authenticatable: true
88
+ authenticatableLoginIdField: email
89
+ authenticatablePasswordField: password
90
+ authenticatableGuardName: web # Optional: for multi-guard
91
+ ```
92
+
93
+ **Required `config/auth.php` for custom guards:**
94
+
95
+ ```php
96
+ 'guards' => [
97
+ 'admin' => ['driver' => 'session', 'provider' => 'admins'],
98
+ ],
99
+ 'providers' => [
100
+ 'admins' => ['driver' => 'eloquent', 'model' => App\Models\Admin::class],
101
+ ],
102
+ ```
103
+
104
+ **See:** `.claude/omnify/guides/omnify/schema-guide.md` → "Authenticatable Option - Detailed Guide"
105
+
80
106
  ## Pre-Edit Checklist
81
107
 
82
108
  **BEFORE editing any file, MUST:**
@@ -109,4 +135,4 @@ public function store(): JsonResponse
109
135
  | "Improve" unrelated code | Change only what's needed |
110
136
  | Build for future "just in case" | YAGNI - build when needed |
111
137
 
112
- **Full examples:** `.claude/guides/laravel/architecture.md`
138
+ **Full examples:** `.claude/omnify/guides/laravel/architecture.md`
@@ -0,0 +1,588 @@
1
+ # Laravel Authentication with Omnify
2
+
3
+ > Complete guide for implementing authentication using Omnify-generated models.
4
+
5
+ ## Overview
6
+
7
+ When you set `authenticatable: true` in your schema, Omnify generates a Laravel model that:
8
+
9
+ 1. Extends `Illuminate\Foundation\Auth\User` (alias `Authenticatable`)
10
+ 2. Includes the `Notifiable` trait
11
+ 3. Auto-hides password and remember_token fields
12
+ 4. Works with Laravel's built-in auth system
13
+
14
+ ## Schema Configuration
15
+
16
+ ### Basic User Schema
17
+
18
+ ```yaml
19
+ name: User
20
+ displayName:
21
+ ja: ユーザー
22
+ en: User
23
+ options:
24
+ timestamps: true
25
+ softDelete: true
26
+ authenticatable: true
27
+ authenticatableLoginIdField: email # Field for login
28
+ authenticatablePasswordField: password # Field for password
29
+ # authenticatableGuardName: web # Optional: guard name
30
+
31
+ properties:
32
+ email:
33
+ type: Email
34
+ unique: true
35
+ password:
36
+ type: Password
37
+ name:
38
+ type: String
39
+ ```
40
+
41
+ ### Multi-Guard Setup (Admin + User)
42
+
43
+ For separate admin and user authentication:
44
+
45
+ **User Schema:**
46
+ ```yaml
47
+ name: User
48
+ options:
49
+ authenticatable: true
50
+ authenticatableGuardName: web
51
+
52
+ properties:
53
+ email:
54
+ type: Email
55
+ unique: true
56
+ password:
57
+ type: Password
58
+ name:
59
+ type: String
60
+ ```
61
+
62
+ **Admin Schema:**
63
+ ```yaml
64
+ name: Admin
65
+ options:
66
+ authenticatable: true
67
+ authenticatableGuardName: admin # Different guard
68
+
69
+ properties:
70
+ email:
71
+ type: Email
72
+ unique: true
73
+ password:
74
+ type: Password
75
+ name:
76
+ type: String
77
+ role:
78
+ type: EnumRef
79
+ enum: AdminRole
80
+ ```
81
+
82
+ ## Laravel Configuration
83
+
84
+ ### config/auth.php
85
+
86
+ ```php
87
+ <?php
88
+
89
+ return [
90
+ 'defaults' => [
91
+ 'guard' => 'web',
92
+ 'passwords' => 'users',
93
+ ],
94
+
95
+ 'guards' => [
96
+ 'web' => [
97
+ 'driver' => 'session',
98
+ 'provider' => 'users',
99
+ ],
100
+ 'admin' => [ // For Admin model
101
+ 'driver' => 'session',
102
+ 'provider' => 'admins',
103
+ ],
104
+ 'api' => [ // For API authentication
105
+ 'driver' => 'sanctum',
106
+ 'provider' => 'users',
107
+ ],
108
+ ],
109
+
110
+ 'providers' => [
111
+ 'users' => [
112
+ 'driver' => 'eloquent',
113
+ 'model' => App\Models\User::class,
114
+ ],
115
+ 'admins' => [ // For Admin model
116
+ 'driver' => 'eloquent',
117
+ 'model' => App\Models\Admin::class,
118
+ ],
119
+ ],
120
+
121
+ 'passwords' => [
122
+ 'users' => [
123
+ 'provider' => 'users',
124
+ 'table' => 'password_reset_tokens',
125
+ 'expire' => 60,
126
+ 'throttle' => 60,
127
+ ],
128
+ 'admins' => [ // For Admin password reset
129
+ 'provider' => 'admins',
130
+ 'table' => 'password_reset_tokens',
131
+ 'expire' => 60,
132
+ 'throttle' => 60,
133
+ ],
134
+ ],
135
+
136
+ 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
137
+ ];
138
+ ```
139
+
140
+ ## Generated Model Structure
141
+
142
+ ### Base Model (Auto-generated, DO NOT EDIT)
143
+
144
+ ```php
145
+ // app/Models/Base/UserBaseModel.php
146
+ <?php
147
+
148
+ namespace App\Models\Base;
149
+
150
+ use Illuminate\Foundation\Auth\User as Authenticatable;
151
+ use Illuminate\Notifications\Notifiable;
152
+
153
+ /**
154
+ * DO NOT EDIT - This file is auto-generated by Omnify.
155
+ * Any changes will be overwritten on next generation.
156
+ */
157
+ class UserBaseModel extends Authenticatable
158
+ {
159
+ use Notifiable;
160
+ use HasLocalizedDisplayName;
161
+
162
+ protected $table = 'users';
163
+ protected $primaryKey = 'id';
164
+ public $timestamps = true;
165
+
166
+ protected $fillable = [
167
+ 'email',
168
+ 'password',
169
+ 'name',
170
+ ];
171
+
172
+ protected $hidden = [
173
+ 'password',
174
+ 'remember_token',
175
+ ];
176
+
177
+ protected function casts(): array
178
+ {
179
+ return [
180
+ 'email_verified_at' => 'datetime',
181
+ 'password' => 'hashed',
182
+ ];
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### Your Model (Customizable)
188
+
189
+ ```php
190
+ // app/Models/User.php
191
+ <?php
192
+
193
+ namespace App\Models;
194
+
195
+ use App\Models\Base\UserBaseModel;
196
+
197
+ class User extends UserBaseModel
198
+ {
199
+ // Add your custom methods here
200
+
201
+ public function posts(): HasMany
202
+ {
203
+ return $this->hasMany(Post::class);
204
+ }
205
+
206
+ public function profile(): HasOne
207
+ {
208
+ return $this->hasOne(UserProfile::class);
209
+ }
210
+
211
+ // Custom scopes
212
+ public function scopeActive($query)
213
+ {
214
+ return $query->where('status', 'active');
215
+ }
216
+
217
+ // Custom accessors
218
+ public function getFullNameAttribute(): string
219
+ {
220
+ return $this->name;
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Authentication Controllers
226
+
227
+ ### Login Controller
228
+
229
+ ```php
230
+ <?php
231
+
232
+ namespace App\Http\Controllers\Auth;
233
+
234
+ use App\Http\Controllers\Controller;
235
+ use App\Http\Requests\Auth\LoginRequest;
236
+ use Illuminate\Http\JsonResponse;
237
+ use Illuminate\Support\Facades\Auth;
238
+
239
+ class LoginController extends Controller
240
+ {
241
+ public function store(LoginRequest $request): JsonResponse
242
+ {
243
+ $request->authenticate();
244
+ $request->session()->regenerate();
245
+
246
+ return response()->json([
247
+ 'user' => Auth::user(),
248
+ 'message' => 'ログインしました',
249
+ ]);
250
+ }
251
+
252
+ public function destroy(): JsonResponse
253
+ {
254
+ Auth::guard('web')->logout();
255
+ request()->session()->invalidate();
256
+ request()->session()->regenerateToken();
257
+
258
+ return response()->json([
259
+ 'message' => 'ログアウトしました',
260
+ ]);
261
+ }
262
+ }
263
+ ```
264
+
265
+ ### Login Request
266
+
267
+ ```php
268
+ <?php
269
+
270
+ namespace App\Http\Requests\Auth;
271
+
272
+ use Illuminate\Auth\Events\Lockout;
273
+ use Illuminate\Foundation\Http\FormRequest;
274
+ use Illuminate\Support\Facades\Auth;
275
+ use Illuminate\Support\Facades\RateLimiter;
276
+ use Illuminate\Support\Str;
277
+ use Illuminate\Validation\ValidationException;
278
+
279
+ class LoginRequest extends FormRequest
280
+ {
281
+ public function authorize(): bool
282
+ {
283
+ return true;
284
+ }
285
+
286
+ public function rules(): array
287
+ {
288
+ return [
289
+ 'email' => ['required', 'string', 'email'],
290
+ 'password' => ['required', 'string'],
291
+ ];
292
+ }
293
+
294
+ public function authenticate(): void
295
+ {
296
+ $this->ensureIsNotRateLimited();
297
+
298
+ if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
299
+ RateLimiter::hit($this->throttleKey());
300
+
301
+ throw ValidationException::withMessages([
302
+ 'email' => __('auth.failed'),
303
+ ]);
304
+ }
305
+
306
+ RateLimiter::clear($this->throttleKey());
307
+ }
308
+
309
+ protected function ensureIsNotRateLimited(): void
310
+ {
311
+ if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
312
+ return;
313
+ }
314
+
315
+ event(new Lockout($this));
316
+
317
+ $seconds = RateLimiter::availableIn($this->throttleKey());
318
+
319
+ throw ValidationException::withMessages([
320
+ 'email' => trans('auth.throttle', [
321
+ 'seconds' => $seconds,
322
+ 'minutes' => ceil($seconds / 60),
323
+ ]),
324
+ ]);
325
+ }
326
+
327
+ protected function throttleKey(): string
328
+ {
329
+ return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
330
+ }
331
+ }
332
+ ```
333
+
334
+ ## API Authentication (Laravel Sanctum)
335
+
336
+ ### Install Sanctum
337
+
338
+ ```bash
339
+ composer require laravel/sanctum
340
+ php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
341
+ php artisan migrate
342
+ ```
343
+
344
+ ### Add to User Model
345
+
346
+ ```php
347
+ // app/Models/User.php
348
+ use Laravel\Sanctum\HasApiTokens;
349
+
350
+ class User extends UserBaseModel
351
+ {
352
+ use HasApiTokens; // Add this trait
353
+
354
+ // ...
355
+ }
356
+ ```
357
+
358
+ ### API Login Controller
359
+
360
+ ```php
361
+ <?php
362
+
363
+ namespace App\Http\Controllers\Api\Auth;
364
+
365
+ use App\Http\Controllers\Controller;
366
+ use App\Models\User;
367
+ use Illuminate\Http\JsonResponse;
368
+ use Illuminate\Http\Request;
369
+ use Illuminate\Support\Facades\Hash;
370
+ use Illuminate\Validation\ValidationException;
371
+
372
+ class TokenController extends Controller
373
+ {
374
+ public function store(Request $request): JsonResponse
375
+ {
376
+ $request->validate([
377
+ 'email' => 'required|email',
378
+ 'password' => 'required',
379
+ 'device_name' => 'required',
380
+ ]);
381
+
382
+ $user = User::where('email', $request->email)->first();
383
+
384
+ if (! $user || ! Hash::check($request->password, $user->password)) {
385
+ throw ValidationException::withMessages([
386
+ 'email' => [__('auth.failed')],
387
+ ]);
388
+ }
389
+
390
+ return response()->json([
391
+ 'token' => $user->createToken($request->device_name)->plainTextToken,
392
+ 'user' => $user,
393
+ ]);
394
+ }
395
+
396
+ public function destroy(Request $request): JsonResponse
397
+ {
398
+ $request->user()->currentAccessToken()->delete();
399
+
400
+ return response()->json(['message' => 'Token revoked']);
401
+ }
402
+ }
403
+ ```
404
+
405
+ ## Routes Configuration
406
+
407
+ ### Web Routes (routes/web.php)
408
+
409
+ ```php
410
+ use App\Http\Controllers\Auth\LoginController;
411
+
412
+ Route::middleware('guest')->group(function () {
413
+ Route::post('/login', [LoginController::class, 'store']);
414
+ });
415
+
416
+ Route::middleware('auth')->group(function () {
417
+ Route::post('/logout', [LoginController::class, 'destroy']);
418
+ });
419
+ ```
420
+
421
+ ### API Routes (routes/api.php)
422
+
423
+ ```php
424
+ use App\Http\Controllers\Api\Auth\TokenController;
425
+
426
+ Route::post('/tokens', [TokenController::class, 'store']);
427
+
428
+ Route::middleware('auth:sanctum')->group(function () {
429
+ Route::delete('/tokens', [TokenController::class, 'destroy']);
430
+
431
+ Route::get('/user', function (Request $request) {
432
+ return $request->user();
433
+ });
434
+ });
435
+ ```
436
+
437
+ ## Multi-Guard Middleware
438
+
439
+ ### Admin-Only Routes
440
+
441
+ ```php
442
+ Route::middleware('auth:admin')->prefix('admin')->group(function () {
443
+ Route::get('/dashboard', [AdminDashboardController::class, 'index']);
444
+ });
445
+ ```
446
+
447
+ ### Admin Login Controller
448
+
449
+ ```php
450
+ public function store(AdminLoginRequest $request): JsonResponse
451
+ {
452
+ if (! Auth::guard('admin')->attempt($request->only('email', 'password'))) {
453
+ throw ValidationException::withMessages([
454
+ 'email' => __('auth.failed'),
455
+ ]);
456
+ }
457
+
458
+ $request->session()->regenerate();
459
+
460
+ return response()->json([
461
+ 'admin' => Auth::guard('admin')->user(),
462
+ ]);
463
+ }
464
+ ```
465
+
466
+ ## Common Patterns
467
+
468
+ ### Check Authentication
469
+
470
+ ```php
471
+ // In controller
472
+ if (Auth::check()) {
473
+ $user = Auth::user();
474
+ }
475
+
476
+ // In middleware
477
+ Route::middleware('auth')->group(function () {
478
+ // Protected routes
479
+ });
480
+
481
+ // In Blade
482
+ @auth
483
+ Welcome, {{ Auth::user()->name }}
484
+ @endauth
485
+ ```
486
+
487
+ ### Get Current User
488
+
489
+ ```php
490
+ // Via Auth facade
491
+ $user = Auth::user();
492
+ $userId = Auth::id();
493
+
494
+ // Via request
495
+ $user = $request->user();
496
+
497
+ // Via helper
498
+ $user = auth()->user();
499
+ ```
500
+
501
+ ### Password Hashing
502
+
503
+ ```php
504
+ // Hash password (automatic in Omnify models via cast)
505
+ $user->password = 'plain-text'; // Auto-hashed by 'hashed' cast
506
+ $user->save();
507
+
508
+ // Manual hashing if needed
509
+ use Illuminate\Support\Facades\Hash;
510
+ $hashed = Hash::make('plain-text');
511
+
512
+ // Verify password
513
+ if (Hash::check('plain-text', $user->password)) {
514
+ // Correct
515
+ }
516
+ ```
517
+
518
+ ## Testing Authentication
519
+
520
+ ```php
521
+ use App\Models\User;
522
+ use Illuminate\Foundation\Testing\RefreshDatabase;
523
+
524
+ class AuthTest extends TestCase
525
+ {
526
+ use RefreshDatabase;
527
+
528
+ public function test_user_can_login(): void
529
+ {
530
+ $user = User::factory()->create([
531
+ 'email' => 'test@example.com',
532
+ 'password' => 'password',
533
+ ]);
534
+
535
+ $response = $this->postJson('/login', [
536
+ 'email' => 'test@example.com',
537
+ 'password' => 'password',
538
+ ]);
539
+
540
+ $response->assertOk();
541
+ $this->assertAuthenticated();
542
+ }
543
+
544
+ public function test_authenticated_user_can_access_protected_route(): void
545
+ {
546
+ $user = User::factory()->create();
547
+
548
+ $response = $this->actingAs($user)
549
+ ->getJson('/api/user');
550
+
551
+ $response->assertOk();
552
+ }
553
+
554
+ public function test_guest_cannot_access_protected_route(): void
555
+ {
556
+ $response = $this->getJson('/api/user');
557
+
558
+ $response->assertUnauthorized();
559
+ }
560
+ }
561
+ ```
562
+
563
+ ## Future: Trait-based Authentication
564
+
565
+ > **Coming Soon:** In future versions, Omnify may support a trait-based approach:
566
+ >
567
+ > ```php
568
+ > use Illuminate\Database\Eloquent\Model;
569
+ > use Illuminate\Auth\Authenticatable;
570
+ > use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
571
+ >
572
+ > class UserBaseModel extends Model implements AuthenticatableContract
573
+ > {
574
+ > use Authenticatable; // Trait instead of extending class
575
+ > }
576
+ > ```
577
+ >
578
+ > This will provide more flexibility in model inheritance while maintaining
579
+ > full authentication support.
580
+
581
+ ## Summary
582
+
583
+ | Option | Type | Default | When to Use |
584
+ |--------|------|---------|-------------|
585
+ | `authenticatable` | boolean | `false` | User can login |
586
+ | `authenticatableLoginIdField` | string | `email` | Custom login field |
587
+ | `authenticatablePasswordField` | string | `password` | Custom password field |
588
+ | `authenticatableGuardName` | string | (none) | Multi-guard setup |