@famgia/omnify-laravel 0.0.113 → 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 +4 -4
- package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +1 -1
- package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +1 -1
- package/stubs/ai-guides/cursor/laravel.mdc.stub +29 -2
- package/stubs/ai-guides/laravel/authentication.md.stub +588 -0
- package/stubs/ai-guides/laravel/migrations-team.md.stub +376 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@famgia/omnify-laravel",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
29
|
-
"@famgia/omnify-
|
|
30
|
-
"@famgia/omnify-
|
|
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
|
|
|
@@ -6,7 +6,8 @@ alwaysApply: false
|
|
|
6
6
|
|
|
7
7
|
# Laravel Rules
|
|
8
8
|
|
|
9
|
-
> **Documentation:** `.claude/guides/laravel/`
|
|
9
|
+
> **Documentation:** `.claude/omnify/guides/laravel/`
|
|
10
|
+
> **Team Migration Guide:** `.claude/omnify/guides/laravel/migrations-team.md`
|
|
10
11
|
> **Specific Rules:** See also `laravel-controller.mdc`, `laravel-resource.mdc`, `laravel-request.mdc`, `laravel-testing.mdc`, `migrations-workflow.mdc`, `omnify-migrations.mdc`
|
|
11
12
|
|
|
12
13
|
## ⛔ CRITICAL: DO NOT EDIT
|
|
@@ -76,6 +77,32 @@ See `migrations-workflow.mdc` for complete guide.
|
|
|
76
77
|
| Reusable action | Action class |
|
|
77
78
|
| Async task | Job |
|
|
78
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
|
+
|
|
79
106
|
## Pre-Edit Checklist
|
|
80
107
|
|
|
81
108
|
**BEFORE editing any file, MUST:**
|
|
@@ -108,4 +135,4 @@ public function store(): JsonResponse
|
|
|
108
135
|
| "Improve" unrelated code | Change only what's needed |
|
|
109
136
|
| Build for future "just in case" | YAGNI - build when needed |
|
|
110
137
|
|
|
111
|
-
**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 |
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Migration Team Workflow Guide
|
|
2
|
+
|
|
3
|
+
> Best practices for managing database migrations in team development environments.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Omnify follows **international standards** for database migration management:
|
|
8
|
+
|
|
9
|
+
1. **Schema as Source of Truth** - YAML schemas define the database structure
|
|
10
|
+
2. **Simple File Tracking** - Lock file tracks migrations for regeneration
|
|
11
|
+
3. **CI Validation** - Automated checks ensure sync between schema and migrations
|
|
12
|
+
4. **Git for Conflict Resolution** - Standard Git workflow handles merge conflicts
|
|
13
|
+
|
|
14
|
+
## Migration Tracking System
|
|
15
|
+
|
|
16
|
+
### Lock File Structure
|
|
17
|
+
|
|
18
|
+
The `.omnify.lock` file tracks all generated migrations:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"version": 2,
|
|
23
|
+
"migrations": [
|
|
24
|
+
{
|
|
25
|
+
"fileName": "2026_01_13_100000_create_users_table.php",
|
|
26
|
+
"timestamp": "2026_01_13_100000",
|
|
27
|
+
"tableName": "users",
|
|
28
|
+
"type": "create",
|
|
29
|
+
"generatedAt": "2026-01-13T10:00:00Z",
|
|
30
|
+
"schemas": ["User"],
|
|
31
|
+
"checksum": "sha256..."
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Key Fields
|
|
38
|
+
|
|
39
|
+
| Field | Purpose |
|
|
40
|
+
|-------|---------|
|
|
41
|
+
| `fileName` | Full migration filename |
|
|
42
|
+
| `timestamp` | Timestamp prefix for regeneration |
|
|
43
|
+
| `tableName` | Table name for lookup |
|
|
44
|
+
| `type` | Migration type (create/alter/drop/pivot) |
|
|
45
|
+
| `checksum` | SHA-256 hash for integrity verification |
|
|
46
|
+
|
|
47
|
+
## CI/CD Integration
|
|
48
|
+
|
|
49
|
+
### GitHub Actions Example
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
# .github/workflows/migration-check.yml
|
|
53
|
+
name: Migration Check
|
|
54
|
+
|
|
55
|
+
on: [push, pull_request]
|
|
56
|
+
|
|
57
|
+
jobs:
|
|
58
|
+
validate:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v4
|
|
62
|
+
|
|
63
|
+
- name: Setup Node.js
|
|
64
|
+
uses: actions/setup-node@v4
|
|
65
|
+
with:
|
|
66
|
+
node-version: '20'
|
|
67
|
+
|
|
68
|
+
- name: Install dependencies
|
|
69
|
+
run: npm ci
|
|
70
|
+
|
|
71
|
+
- name: Check migrations sync
|
|
72
|
+
run: npx omnify generate --check
|
|
73
|
+
|
|
74
|
+
- name: Test migrations (optional)
|
|
75
|
+
run: |
|
|
76
|
+
php artisan migrate:fresh --force
|
|
77
|
+
php artisan migrate:status
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### CI Check Mode
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Check if migrations are in sync (for CI)
|
|
84
|
+
npx omnify generate --check
|
|
85
|
+
|
|
86
|
+
# Exit codes:
|
|
87
|
+
# 0 = All migrations in sync ✅
|
|
88
|
+
# 1 = Schema changes detected or migration issues ❌
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Output example:
|
|
92
|
+
```
|
|
93
|
+
Omnify v1.0.113 - Generating Outputs
|
|
94
|
+
|
|
95
|
+
✔ Loading schemas from schemas
|
|
96
|
+
✔ Validating schemas...
|
|
97
|
+
✔ Checking for changes...
|
|
98
|
+
|
|
99
|
+
CI Check Mode Results:
|
|
100
|
+
Schemas: 12
|
|
101
|
+
Tracked migrations: 15
|
|
102
|
+
Migrations on disk: 15
|
|
103
|
+
Schema changes: 0
|
|
104
|
+
|
|
105
|
+
✅ All migrations in sync
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Team Development Workflow
|
|
109
|
+
|
|
110
|
+
### Standard Workflow
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
114
|
+
│ 1. Edit YAML Schema │
|
|
115
|
+
│ schemas/module/Model.yaml │
|
|
116
|
+
└─────────────────────────────────────────────────────────────┘
|
|
117
|
+
│
|
|
118
|
+
▼
|
|
119
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
120
|
+
│ 2. Generate Migrations │
|
|
121
|
+
│ npx omnify generate │
|
|
122
|
+
└─────────────────────────────────────────────────────────────┘
|
|
123
|
+
│
|
|
124
|
+
▼
|
|
125
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
126
|
+
│ 3. Commit Both │
|
|
127
|
+
│ git add schemas/ database/migrations/ .omnify.lock │
|
|
128
|
+
│ git commit -m "feat: add phone field to User" │
|
|
129
|
+
└─────────────────────────────────────────────────────────────┘
|
|
130
|
+
│
|
|
131
|
+
▼
|
|
132
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
133
|
+
│ 4. CI Validates │
|
|
134
|
+
│ GitHub Actions runs: npx omnify generate --check │
|
|
135
|
+
└─────────────────────────────────────────────────────────────┘
|
|
136
|
+
│
|
|
137
|
+
▼
|
|
138
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
139
|
+
│ 5. Merge & Deploy │
|
|
140
|
+
│ php artisan migrate │
|
|
141
|
+
└─────────────────────────────────────────────────────────────┘
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Parallel Development
|
|
145
|
+
|
|
146
|
+
When multiple developers work on different schemas:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
Developer A Developer B
|
|
150
|
+
│ │
|
|
151
|
+
Edit User.yaml Edit Product.yaml
|
|
152
|
+
(add phone) (add sku)
|
|
153
|
+
│ │
|
|
154
|
+
omnify generate omnify generate
|
|
155
|
+
│ │
|
|
156
|
+
Creates: Creates:
|
|
157
|
+
alter_users_add_phone.php alter_products_add_sku.php
|
|
158
|
+
│ │
|
|
159
|
+
└──────────┬───────────────────┘
|
|
160
|
+
│
|
|
161
|
+
Git Merge
|
|
162
|
+
│
|
|
163
|
+
Both migrations exist ✅
|
|
164
|
+
(different tables, no conflict)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Same Schema Conflict
|
|
168
|
+
|
|
169
|
+
When multiple developers edit the SAME schema:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Developer A Developer B
|
|
173
|
+
│ │
|
|
174
|
+
Edit User.yaml Edit User.yaml
|
|
175
|
+
(add phone) (add address)
|
|
176
|
+
│ │
|
|
177
|
+
omnify generate omnify generate
|
|
178
|
+
│ │
|
|
179
|
+
└──────────┬───────────────────┘
|
|
180
|
+
│
|
|
181
|
+
Git Merge
|
|
182
|
+
│
|
|
183
|
+
⚠️ YAML Conflict!
|
|
184
|
+
│
|
|
185
|
+
┌──────────┴──────────┐
|
|
186
|
+
│ │
|
|
187
|
+
Resolve YAML Keep both migrations
|
|
188
|
+
(merge both fields) (Laravel runs both)
|
|
189
|
+
│ │
|
|
190
|
+
└──────────┬──────────┘
|
|
191
|
+
│
|
|
192
|
+
Result:
|
|
193
|
+
User.yaml has phone + address
|
|
194
|
+
Both migrations exist
|
|
195
|
+
Laravel runs in timestamp order ✅
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Handling Common Scenarios
|
|
199
|
+
|
|
200
|
+
### Scenario 1: Accidentally Deleted Migration
|
|
201
|
+
|
|
202
|
+
**Problem:** Migration file deleted but lock file still tracks it.
|
|
203
|
+
|
|
204
|
+
**Detection:**
|
|
205
|
+
```bash
|
|
206
|
+
npx omnify generate
|
|
207
|
+
|
|
208
|
+
⚠️ Migration file issues detected:
|
|
209
|
+
Missing files (1):
|
|
210
|
+
- 2026_01_13_100000_create_users_table.php
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Solutions:**
|
|
214
|
+
```bash
|
|
215
|
+
# Option 1: Restore from git
|
|
216
|
+
git checkout -- database/migrations/omnify/
|
|
217
|
+
|
|
218
|
+
# Option 2: Reset and regenerate (destructive!)
|
|
219
|
+
npx omnify reset --migrations
|
|
220
|
+
npx omnify generate --force
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Scenario 2: Stale Migration from Old Branch
|
|
224
|
+
|
|
225
|
+
**Problem:** Branch created 1 month ago, merged today with old timestamp.
|
|
226
|
+
|
|
227
|
+
**Detection:**
|
|
228
|
+
```bash
|
|
229
|
+
npx omnify generate
|
|
230
|
+
|
|
231
|
+
⚠️ Stale migrations detected (old timestamp, not in lock file):
|
|
232
|
+
- 2026_01_04_100000_add_phone_to_users_table.php
|
|
233
|
+
These may be from merged branches. Review before running migrate.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Action:**
|
|
237
|
+
1. Review the migration - is it independent or dependent?
|
|
238
|
+
2. If independent: Keep as-is, Laravel runs in timestamp order
|
|
239
|
+
3. If dependent: Consider renaming with new timestamp
|
|
240
|
+
|
|
241
|
+
### Scenario 3: CI Fails with "Schema changes detected"
|
|
242
|
+
|
|
243
|
+
**Problem:** Someone edited schema but forgot to run generate.
|
|
244
|
+
|
|
245
|
+
**Detection:**
|
|
246
|
+
```bash
|
|
247
|
+
npx omnify generate --check
|
|
248
|
+
|
|
249
|
+
❌ Schema changes detected - run "npx omnify generate" to update migrations
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Solution:**
|
|
253
|
+
```bash
|
|
254
|
+
# Locally
|
|
255
|
+
npx omnify generate
|
|
256
|
+
git add .
|
|
257
|
+
git commit --amend # or new commit
|
|
258
|
+
git push --force # or regular push
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Best Practices
|
|
262
|
+
|
|
263
|
+
### DO ✅
|
|
264
|
+
|
|
265
|
+
1. **Always commit schema + migrations together**
|
|
266
|
+
```bash
|
|
267
|
+
git add schemas/ database/migrations/omnify/ .omnify.lock
|
|
268
|
+
git commit -m "feat: add field to schema"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
2. **Use CI to validate migrations**
|
|
272
|
+
```bash
|
|
273
|
+
npx omnify generate --check
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
3. **Pull and migrate before starting new work**
|
|
277
|
+
```bash
|
|
278
|
+
git pull
|
|
279
|
+
php artisan migrate
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
4. **Review stale migration warnings**
|
|
283
|
+
- Check dependencies before merging old branches
|
|
284
|
+
|
|
285
|
+
### DON'T ❌
|
|
286
|
+
|
|
287
|
+
1. **Don't manually edit migrations in `omnify/` folder**
|
|
288
|
+
- They will be overwritten!
|
|
289
|
+
|
|
290
|
+
2. **Don't ignore CI failures**
|
|
291
|
+
- Always run `npx omnify generate` if CI fails
|
|
292
|
+
|
|
293
|
+
3. **Don't commit migrations without schemas**
|
|
294
|
+
- Schema is source of truth
|
|
295
|
+
|
|
296
|
+
4. **Don't skip stale warnings without review**
|
|
297
|
+
- Old timestamps might run before dependent migrations
|
|
298
|
+
|
|
299
|
+
## Migration Validation
|
|
300
|
+
|
|
301
|
+
### Check Commands
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# Full check (for CI)
|
|
305
|
+
npx omnify generate --check
|
|
306
|
+
|
|
307
|
+
# Generate with stale warnings (default)
|
|
308
|
+
npx omnify generate
|
|
309
|
+
|
|
310
|
+
# Generate without stale warnings
|
|
311
|
+
npx omnify generate --no-warn-stale
|
|
312
|
+
|
|
313
|
+
# Force regenerate everything
|
|
314
|
+
npx omnify generate --force
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Validation Checks
|
|
318
|
+
|
|
319
|
+
| Check | Description | Exit Code |
|
|
320
|
+
|-------|-------------|-----------|
|
|
321
|
+
| Schema sync | Schema matches generated migrations | 1 if mismatch |
|
|
322
|
+
| Missing files | Migration files exist on disk | 1 if missing |
|
|
323
|
+
| Modified files | Files match stored checksum | Warning only |
|
|
324
|
+
| Stale files | Old timestamp, not tracked | Warning only |
|
|
325
|
+
|
|
326
|
+
## Troubleshooting
|
|
327
|
+
|
|
328
|
+
### "Missing migration files"
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Check what's missing
|
|
332
|
+
npx omnify generate --check
|
|
333
|
+
|
|
334
|
+
# Restore from git
|
|
335
|
+
git checkout -- database/migrations/omnify/
|
|
336
|
+
|
|
337
|
+
# Or reset completely
|
|
338
|
+
npx omnify reset --migrations
|
|
339
|
+
npx omnify generate --force
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### "Checksum mismatch"
|
|
343
|
+
|
|
344
|
+
Someone manually edited an auto-generated file.
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
# Option 1: Regenerate
|
|
348
|
+
npx omnify generate --force
|
|
349
|
+
|
|
350
|
+
# Option 2: Reset lock file entry
|
|
351
|
+
# (manually edit .omnify.lock to remove the entry)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### "Stale migrations"
|
|
355
|
+
|
|
356
|
+
Old branch merged with old timestamps.
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
# Usually safe to ignore if migrations are independent
|
|
360
|
+
# But review the migration order:
|
|
361
|
+
|
|
362
|
+
php artisan migrate:status
|
|
363
|
+
# Check: Will the old migration run before something it depends on?
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Summary
|
|
367
|
+
|
|
368
|
+
| Workflow Step | Command |
|
|
369
|
+
|---------------|---------|
|
|
370
|
+
| Edit schema | Edit `schemas/*.yaml` |
|
|
371
|
+
| Generate | `npx omnify generate` |
|
|
372
|
+
| Commit | `git add schemas/ database/migrations/ .omnify.lock` |
|
|
373
|
+
| CI Check | `npx omnify generate --check` |
|
|
374
|
+
| Deploy | `php artisan migrate` |
|
|
375
|
+
| Restore | `git checkout -- database/migrations/omnify/` |
|
|
376
|
+
| Reset | `npx omnify reset --migrations` |
|