@jaimevalasek/aioson 1.3.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.
- package/CHANGELOG.md +456 -0
- package/CODE_OF_CONDUCT.md +12 -0
- package/CONTRIBUTING.md +13 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/bin/aioson.js +4 -0
- package/docs/en/cli-reference.md +398 -0
- package/docs/en/i18n.md +52 -0
- package/docs/en/json-schemas.md +41 -0
- package/docs/en/mcp.md +56 -0
- package/docs/en/parallel.md +82 -0
- package/docs/en/qa-browser.md +339 -0
- package/docs/en/release-flow.md +22 -0
- package/docs/en/release-notes-template.md +41 -0
- package/docs/en/release.md +28 -0
- package/docs/en/schemas/agent-prompt.schema.json +17 -0
- package/docs/en/schemas/agents.schema.json +32 -0
- package/docs/en/schemas/context-validate.schema.json +36 -0
- package/docs/en/schemas/doctor.schema.json +89 -0
- package/docs/en/schemas/error.schema.json +24 -0
- package/docs/en/schemas/i18n-add.schema.json +15 -0
- package/docs/en/schemas/index.json +116 -0
- package/docs/en/schemas/info.schema.json +39 -0
- package/docs/en/schemas/init.schema.json +48 -0
- package/docs/en/schemas/install.schema.json +60 -0
- package/docs/en/schemas/locale-apply.schema.json +30 -0
- package/docs/en/schemas/mcp-doctor.schema.json +95 -0
- package/docs/en/schemas/mcp-init.schema.json +122 -0
- package/docs/en/schemas/package-test.schema.json +24 -0
- package/docs/en/schemas/parallel-assign.schema.json +57 -0
- package/docs/en/schemas/parallel-doctor.schema.json +86 -0
- package/docs/en/schemas/parallel-init.schema.json +53 -0
- package/docs/en/schemas/parallel-status.schema.json +94 -0
- package/docs/en/schemas/setup-context.schema.json +39 -0
- package/docs/en/schemas/smoke.schema.json +23 -0
- package/docs/en/schemas/update.schema.json +48 -0
- package/docs/en/schemas/workflow-plan.schema.json +30 -0
- package/docs/en/web3.md +54 -0
- package/docs/pt/README.md +46 -0
- package/docs/pt/advisor-spec.md +335 -0
- package/docs/pt/agentes.md +453 -0
- package/docs/pt/cenarios.md +1230 -0
- package/docs/pt/clientes-ai.md +224 -0
- package/docs/pt/comandos-cli.md +511 -0
- package/docs/pt/genome-3.0-spec.md +296 -0
- package/docs/pt/guia-engineer.md +226 -0
- package/docs/pt/inicio-rapido.md +138 -0
- package/docs/pt/profiler-system.md +214 -0
- package/docs/pt/runtime-observability.md +72 -0
- package/docs/pt/squad-genoma.md +777 -0
- package/docs/pt/web3.md +797 -0
- package/docs/testing/genome-2.0-manual-regression.md +23 -0
- package/docs/testing/genome-2.0-matrix.md +36 -0
- package/docs/testing/genome-2.0-rollout.md +184 -0
- package/package.json +50 -0
- package/src/agents.js +56 -0
- package/src/cli.js +497 -0
- package/src/commands/agents.js +142 -0
- package/src/commands/cloud.js +1767 -0
- package/src/commands/config.js +90 -0
- package/src/commands/context-validate.js +91 -0
- package/src/commands/doctor.js +123 -0
- package/src/commands/genome-doctor.js +41 -0
- package/src/commands/genome-migrate.js +49 -0
- package/src/commands/i18n-add.js +56 -0
- package/src/commands/info.js +41 -0
- package/src/commands/init.js +75 -0
- package/src/commands/install.js +68 -0
- package/src/commands/locale-apply.js +51 -0
- package/src/commands/locale-diff.js +126 -0
- package/src/commands/mcp-doctor.js +406 -0
- package/src/commands/mcp-init.js +379 -0
- package/src/commands/package-e2e.js +273 -0
- package/src/commands/parallel-assign.js +403 -0
- package/src/commands/parallel-doctor.js +437 -0
- package/src/commands/parallel-init.js +249 -0
- package/src/commands/parallel-status.js +290 -0
- package/src/commands/qa-doctor.js +185 -0
- package/src/commands/qa-init.js +161 -0
- package/src/commands/qa-report.js +58 -0
- package/src/commands/qa-run.js +873 -0
- package/src/commands/qa-scan.js +337 -0
- package/src/commands/runtime.js +948 -0
- package/src/commands/scan-project.js +1107 -0
- package/src/commands/setup-context.js +650 -0
- package/src/commands/smoke.js +426 -0
- package/src/commands/squad-doctor.js +358 -0
- package/src/commands/squad-export.js +46 -0
- package/src/commands/squad-pipeline.js +97 -0
- package/src/commands/squad-repair-genomes.js +39 -0
- package/src/commands/squad-status.js +424 -0
- package/src/commands/squad-validate.js +230 -0
- package/src/commands/test-agents.js +194 -0
- package/src/commands/update.js +55 -0
- package/src/commands/workflow-next.js +594 -0
- package/src/commands/workflow-plan.js +108 -0
- package/src/constants.js +314 -0
- package/src/context-parse-reason.js +22 -0
- package/src/context-writer.js +150 -0
- package/src/context.js +217 -0
- package/src/detector.js +261 -0
- package/src/doctor.js +289 -0
- package/src/execution-gateway.js +461 -0
- package/src/genome-files.js +198 -0
- package/src/genome-format.js +442 -0
- package/src/genome-schema.js +215 -0
- package/src/genomes/bindings.js +281 -0
- package/src/genomes.js +467 -0
- package/src/i18n/index.js +103 -0
- package/src/i18n/messages/en.js +784 -0
- package/src/i18n/messages/es.js +718 -0
- package/src/i18n/messages/fr.js +725 -0
- package/src/i18n/messages/pt-BR.js +818 -0
- package/src/i18n/scaffold.js +64 -0
- package/src/installer.js +232 -0
- package/src/lib/genomes/compat.js +206 -0
- package/src/lib/genomes/migrate.js +90 -0
- package/src/lib/squads/genome-repair.js +49 -0
- package/src/locales.js +84 -0
- package/src/onboarding.js +305 -0
- package/src/parser.js +53 -0
- package/src/prompt-tool.js +20 -0
- package/src/qa-html-report.js +472 -0
- package/src/runtime-store.js +1527 -0
- package/src/squads/apply-genome.js +21 -0
- package/src/squads/genome-binding-service.js +154 -0
- package/src/updater.js +32 -0
- package/src/utils.js +46 -0
- package/src/version.js +50 -0
- package/template/.aioson/advisors/.gitkeep +1 -0
- package/template/.aioson/agents/analyst.md +225 -0
- package/template/.aioson/agents/architect.md +221 -0
- package/template/.aioson/agents/dev.md +201 -0
- package/template/.aioson/agents/discovery-design-doc.md +196 -0
- package/template/.aioson/agents/genoma.md +300 -0
- package/template/.aioson/agents/orchestrator.md +107 -0
- package/template/.aioson/agents/pm.md +89 -0
- package/template/.aioson/agents/product.md +361 -0
- package/template/.aioson/agents/profiler-enricher.md +266 -0
- package/template/.aioson/agents/profiler-forge.md +188 -0
- package/template/.aioson/agents/profiler-researcher.md +245 -0
- package/template/.aioson/agents/qa.md +344 -0
- package/template/.aioson/agents/setup.md +381 -0
- package/template/.aioson/agents/squad.md +837 -0
- package/template/.aioson/agents/ux-ui.md +416 -0
- package/template/.aioson/config.md +56 -0
- package/template/.aioson/context/.gitkeep +0 -0
- package/template/.aioson/context/parallel/.gitkeep +0 -0
- package/template/.aioson/context/spec.md.template +37 -0
- package/template/.aioson/genomas/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +214 -0
- package/template/.aioson/locales/en/agents/architect.md +210 -0
- package/template/.aioson/locales/en/agents/dev.md +187 -0
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
- package/template/.aioson/locales/en/agents/genoma.md +212 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
- package/template/.aioson/locales/en/agents/pm.md +77 -0
- package/template/.aioson/locales/en/agents/product.md +310 -0
- package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/en/agents/qa.md +214 -0
- package/template/.aioson/locales/en/agents/setup.md +342 -0
- package/template/.aioson/locales/en/agents/squad.md +247 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
- package/template/.aioson/locales/es/agents/analyst.md +203 -0
- package/template/.aioson/locales/es/agents/architect.md +208 -0
- package/template/.aioson/locales/es/agents/dev.md +183 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/es/agents/genoma.md +102 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/es/agents/pm.md +81 -0
- package/template/.aioson/locales/es/agents/product.md +310 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/es/agents/qa.md +163 -0
- package/template/.aioson/locales/es/agents/setup.md +347 -0
- package/template/.aioson/locales/es/agents/squad.md +247 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/fr/agents/analyst.md +203 -0
- package/template/.aioson/locales/fr/agents/architect.md +208 -0
- package/template/.aioson/locales/fr/agents/dev.md +183 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/fr/agents/genoma.md +102 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/fr/agents/pm.md +81 -0
- package/template/.aioson/locales/fr/agents/product.md +310 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/fr/agents/qa.md +163 -0
- package/template/.aioson/locales/fr/agents/setup.md +347 -0
- package/template/.aioson/locales/fr/agents/squad.md +247 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
- package/template/.aioson/mcp/servers.md +24 -0
- package/template/.aioson/profiler-reports/.gitkeep +1 -0
- package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
- package/template/.aioson/schemas/genome-meta.schema.json +150 -0
- package/template/.aioson/schemas/genome.schema.json +115 -0
- package/template/.aioson/schemas/readiness.schema.json +27 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
- package/template/.aioson/skills/dynamic/README.md +30 -0
- package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
- package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
- package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
- package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
- package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
- package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
- package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
- package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
- package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
- package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
- package/template/.aioson/skills/static/django-patterns.md +342 -0
- package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
- package/template/.aioson/skills/static/filament-patterns.md +267 -0
- package/template/.aioson/skills/static/flux-ui-components.md +262 -0
- package/template/.aioson/skills/static/git-conventions.md +227 -0
- package/template/.aioson/skills/static/interface-design.md +372 -0
- package/template/.aioson/skills/static/jetstream-setup.md +200 -0
- package/template/.aioson/skills/static/laravel-conventions.md +491 -0
- package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
- package/template/.aioson/skills/static/node-express-patterns.md +317 -0
- package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
- package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
- package/template/.aioson/skills/static/rails-conventions.md +307 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
- package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
- package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
- package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
- package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
- package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
- package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
- package/template/.aioson/squads/.artisan/.gitkeep +0 -0
- package/template/.aioson/squads/.gitkeep +0 -0
- package/template/.aioson/squads/memory.md +5 -0
- package/template/.aioson/tasks/squad-analyze.md +83 -0
- package/template/.aioson/tasks/squad-create.md +99 -0
- package/template/.aioson/tasks/squad-design.md +100 -0
- package/template/.aioson/tasks/squad-export.md +20 -0
- package/template/.aioson/tasks/squad-extend.md +68 -0
- package/template/.aioson/tasks/squad-pipeline.md +122 -0
- package/template/.aioson/tasks/squad-repair.md +85 -0
- package/template/.aioson/tasks/squad-validate.md +58 -0
- package/template/.aioson/templates/squads/content-basic/template.json +21 -0
- package/template/.aioson/templates/squads/media-channel/template.json +24 -0
- package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
- package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
- package/template/.claude/commands/aioson/analyst.md +5 -0
- package/template/.claude/commands/aioson/architect.md +5 -0
- package/template/.claude/commands/aioson/dev.md +5 -0
- package/template/.claude/commands/aioson/orchestrator.md +5 -0
- package/template/.claude/commands/aioson/pm.md +5 -0
- package/template/.claude/commands/aioson/qa.md +5 -0
- package/template/.claude/commands/aioson/setup.md +5 -0
- package/template/.claude/commands/aioson/ux-ui.md +5 -0
- package/template/.gemini/GEMINI.md +10 -0
- package/template/.gemini/commands/aios-analyst.toml +4 -0
- package/template/.gemini/commands/aios-architect.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -0
- package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -0
- package/template/.gemini/commands/aios-pm.toml +8 -0
- package/template/.gemini/commands/aios-product.toml +4 -0
- package/template/.gemini/commands/aios-qa.toml +6 -0
- package/template/.gemini/commands/aios-setup.toml +3 -0
- package/template/.gemini/commands/aios-ux-ui.toml +8 -0
- package/template/AGENTS.md +67 -0
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +24 -0
- package/template/aioson-models.json +40 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Jetstream Setup
|
|
2
|
+
|
|
3
|
+
> Laravel Jetstream scaffolds authentication, teams, API tokens, and profile management. Decide at project creation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Decision: Inertia vs Blade
|
|
8
|
+
|
|
9
|
+
Make this choice before `artisan jetstream:install` — it cannot be changed without reinstalling.
|
|
10
|
+
|
|
11
|
+
| Factor | Blade + Livewire | Inertia + Vue/React |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Team skillset | PHP-first | JS-first |
|
|
14
|
+
| SPA behavior needed? | No | Yes |
|
|
15
|
+
| Existing Livewire investment? | Yes → Blade | No |
|
|
16
|
+
| Complex client-side state? | No | Yes |
|
|
17
|
+
| SEO critical? | Both work | Both work (with SSR) |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Blade + Livewire (most common for TALL stack)
|
|
25
|
+
composer require laravel/jetstream
|
|
26
|
+
php artisan jetstream:install livewire --teams # omit --teams if not needed
|
|
27
|
+
npm install && npm run build
|
|
28
|
+
php artisan migrate
|
|
29
|
+
|
|
30
|
+
# Inertia + Vue
|
|
31
|
+
php artisan jetstream:install inertia --teams
|
|
32
|
+
npm install && npm run build
|
|
33
|
+
php artisan migrate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Teams — use only when multi-tenant is required
|
|
39
|
+
|
|
40
|
+
Teams give each user an isolated workspace. Enable only if your domain genuinely needs it.
|
|
41
|
+
|
|
42
|
+
```php
|
|
43
|
+
// Check if the current user belongs to a team
|
|
44
|
+
auth()->user()->currentTeam->name;
|
|
45
|
+
|
|
46
|
+
// Add a custom field to teams
|
|
47
|
+
// database/migrations/xxxx_add_plan_to_teams.php
|
|
48
|
+
Schema::table('teams', function (Blueprint $table) {
|
|
49
|
+
$table->string('plan')->default('free');
|
|
50
|
+
$table->integer('member_limit')->default(3);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Team model customization
|
|
54
|
+
// app/Models/Team.php
|
|
55
|
+
class Team extends JetstreamTeam
|
|
56
|
+
{
|
|
57
|
+
protected $fillable = ['name', 'personal_team', 'plan', 'member_limit'];
|
|
58
|
+
|
|
59
|
+
public function hasAvailableSlots(): bool
|
|
60
|
+
{
|
|
61
|
+
return $this->users()->count() < $this->member_limit;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Roles and permissions (Teams)
|
|
69
|
+
|
|
70
|
+
```php
|
|
71
|
+
// Define in JetstreamServiceProvider
|
|
72
|
+
Jetstream::defaultApiTokenPermissions(['read']);
|
|
73
|
+
|
|
74
|
+
Jetstream::role('admin', 'Administrator', [
|
|
75
|
+
'create', 'read', 'update', 'delete',
|
|
76
|
+
])->description('Administrators have full control.');
|
|
77
|
+
|
|
78
|
+
Jetstream::role('editor', 'Editor', [
|
|
79
|
+
'create', 'read', 'update',
|
|
80
|
+
])->description('Editors can create and edit content.');
|
|
81
|
+
|
|
82
|
+
// Check in controllers
|
|
83
|
+
if (! $request->user()->hasTeamRole($team, 'admin')) {
|
|
84
|
+
abort(403);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check in Policies
|
|
88
|
+
public function update(User $user, Post $post): bool
|
|
89
|
+
{
|
|
90
|
+
return $user->hasTeamPermission($post->team, 'update');
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## API tokens
|
|
97
|
+
|
|
98
|
+
```php
|
|
99
|
+
// Token creation is built-in via Jetstream UI
|
|
100
|
+
// Access in routes
|
|
101
|
+
Route::middleware(['auth:sanctum'])->group(function () {
|
|
102
|
+
Route::get('/api/appointments', [AppointmentController::class, 'index']);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Check specific token permissions
|
|
106
|
+
Route::middleware(['auth:sanctum'])->group(function () {
|
|
107
|
+
Route::post('/api/appointments', [AppointmentController::class, 'store'])
|
|
108
|
+
->middleware('can:create,App\Models\Appointment');
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Profile customization
|
|
115
|
+
|
|
116
|
+
Add fields to the profile update form:
|
|
117
|
+
|
|
118
|
+
```php
|
|
119
|
+
// app/Actions/Fortify/UpdateUserProfileInformation.php
|
|
120
|
+
public function update(User $user, array $input): void
|
|
121
|
+
{
|
|
122
|
+
Validator::make($input, [
|
|
123
|
+
'name' => ['required', 'string', 'max:255'],
|
|
124
|
+
'email' => ['required', 'email', 'unique:users,email,' . $user->id],
|
|
125
|
+
'phone' => ['nullable', 'string', 'max:20'], // custom field
|
|
126
|
+
])->validateWithBag('updateProfileInformation');
|
|
127
|
+
|
|
128
|
+
if (isset($input['photo'])) {
|
|
129
|
+
$user->updateProfilePhoto($input['photo']);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
$user->forceFill([
|
|
133
|
+
'name' => $input['name'],
|
|
134
|
+
'email' => $input['email'],
|
|
135
|
+
'phone' => $input['phone'] ?? null,
|
|
136
|
+
])->save();
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Two-factor authentication
|
|
143
|
+
|
|
144
|
+
Jetstream includes 2FA via TOTP out of the box.
|
|
145
|
+
|
|
146
|
+
```php
|
|
147
|
+
// Require 2FA for admin routes
|
|
148
|
+
Route::middleware(['auth', 'verified', '2fa'])->group(function () {
|
|
149
|
+
Route::get('/admin', AdminController::class);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Force users to confirm password before sensitive actions
|
|
153
|
+
Route::middleware(['auth', 'password.confirm'])->group(function () {
|
|
154
|
+
Route::delete('/account', [AccountController::class, 'destroy']);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Middleware stack understanding
|
|
161
|
+
|
|
162
|
+
```php
|
|
163
|
+
// web.php — routes available to all
|
|
164
|
+
Route::middleware(['web'])->group(function () { ... });
|
|
165
|
+
|
|
166
|
+
// Authenticated routes
|
|
167
|
+
Route::middleware(['auth', 'verified'])->group(function () { ... });
|
|
168
|
+
|
|
169
|
+
// Team-scoped routes
|
|
170
|
+
Route::middleware(['auth', 'verified', EnsureUserHasTeam::class])->group(function () { ... });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Custom views — override Jetstream defaults
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
php artisan vendor:publish --tag=jetstream-views
|
|
179
|
+
# Publishes to resources/views/vendor/jetstream/
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Override only the views you need. Keep Jetstream's Action classes unless you need custom logic.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Post-install checklist
|
|
187
|
+
|
|
188
|
+
- [ ] Choose Livewire or Inertia (cannot change later)
|
|
189
|
+
- [ ] Decide on Teams before first migration
|
|
190
|
+
- [ ] Configure `FILESYSTEM_DISK` for profile photos (`public` or `s3`)
|
|
191
|
+
- [ ] Set `MAIL_*` env vars — email verification requires working mail
|
|
192
|
+
- [ ] Update `JetstreamServiceProvider` with roles/permissions
|
|
193
|
+
- [ ] Customize `UpdateUserProfileInformation` if adding custom fields
|
|
194
|
+
- [ ] Override views for custom branding
|
|
195
|
+
|
|
196
|
+
## NEVER
|
|
197
|
+
- Install Jetstream into an existing project without checking for migration conflicts
|
|
198
|
+
- Enable Teams if the domain does not genuinely need multi-tenancy
|
|
199
|
+
- Modify Jetstream Action classes unless customization is explicitly required
|
|
200
|
+
- Mix Jetstream auth with another auth package (Breeze, Fortify standalone) in the same app
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# Laravel Conventions
|
|
2
|
+
|
|
3
|
+
> Production-grade Laravel. Thin controllers. Explicit intent. No shortcuts.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Project structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
myproject/
|
|
11
|
+
├── app/
|
|
12
|
+
│ ├── Actions/ # Business logic — one class per operation
|
|
13
|
+
│ ├── Console/
|
|
14
|
+
│ │ └── Commands/
|
|
15
|
+
│ ├── Events/
|
|
16
|
+
│ ├── Exceptions/
|
|
17
|
+
│ ├── Http/
|
|
18
|
+
│ │ ├── Controllers/ # HTTP orchestration only
|
|
19
|
+
│ │ ├── Middleware/
|
|
20
|
+
│ │ └── Requests/ # Form Request classes (validation)
|
|
21
|
+
│ ├── Jobs/
|
|
22
|
+
│ ├── Listeners/
|
|
23
|
+
│ ├── Mail/
|
|
24
|
+
│ ├── Models/ # Eloquent models (singular class name)
|
|
25
|
+
│ ├── Notifications/
|
|
26
|
+
│ ├── Policies/
|
|
27
|
+
│ ├── Providers/
|
|
28
|
+
│ └── View/
|
|
29
|
+
│ └── Components/ # Blade components
|
|
30
|
+
├── database/
|
|
31
|
+
│ ├── factories/
|
|
32
|
+
│ ├── migrations/
|
|
33
|
+
│ └── seeders/
|
|
34
|
+
├── resources/
|
|
35
|
+
│ ├── views/
|
|
36
|
+
│ │ ├── users/ # Plural folder per resource
|
|
37
|
+
│ │ │ ├── index.blade.php
|
|
38
|
+
│ │ │ ├── show.blade.php
|
|
39
|
+
│ │ │ ├── create.blade.php
|
|
40
|
+
│ │ │ └── edit.blade.php
|
|
41
|
+
│ │ ├── components/
|
|
42
|
+
│ │ └── layouts/
|
|
43
|
+
│ └── js/
|
|
44
|
+
├── routes/
|
|
45
|
+
│ ├── web.php
|
|
46
|
+
│ └── api.php
|
|
47
|
+
└── tests/
|
|
48
|
+
├── Feature/
|
|
49
|
+
└── Unit/
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**With Jetstream + Livewire** — additional folders:
|
|
53
|
+
```
|
|
54
|
+
app/
|
|
55
|
+
└── Livewire/
|
|
56
|
+
├── Auth/
|
|
57
|
+
└── Users/ # Group components by domain
|
|
58
|
+
├── UserList.php
|
|
59
|
+
└── EditUser.php
|
|
60
|
+
resources/views/
|
|
61
|
+
└── livewire/
|
|
62
|
+
├── auth/
|
|
63
|
+
└── users/
|
|
64
|
+
├── user-list.blade.php # kebab-case filename matches component
|
|
65
|
+
└── edit-user.blade.php
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Naming conventions
|
|
71
|
+
|
|
72
|
+
| Artifact | Convention | Example |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| Model | Singular, PascalCase | `User`, `BlogPost` |
|
|
75
|
+
| Table | Plural, snake_case | `users`, `blog_posts` |
|
|
76
|
+
| Controller | Singular model + `Controller` | `UserController`, `BlogPostController` |
|
|
77
|
+
| Form Request | Action + model | `CreateUserRequest`, `UpdateUserRequest` |
|
|
78
|
+
| Action | Verb + noun + `Action` | `CreateUserAction`, `SendWelcomeEmailAction` |
|
|
79
|
+
| Policy | Singular model + `Policy` | `UserPolicy` |
|
|
80
|
+
| Event | Past-tense noun phrase | `UserCreated`, `OrderShipped` |
|
|
81
|
+
| Listener | Present verb phrase | `SendWelcomeEmail`, `NotifyAdminOfOrder` |
|
|
82
|
+
| Job | Imperative verb phrase | `GenerateInvoice`, `ProcessPayment` |
|
|
83
|
+
| API Resource | Singular model + `Resource` | `UserResource` |
|
|
84
|
+
| Livewire component class | PascalCase, singular or descriptive | `UserList`, `EditUser` |
|
|
85
|
+
| Livewire component file | kebab-case matching class | `user-list.blade.php` |
|
|
86
|
+
| View folder | Plural, kebab-case | `users/`, `blog-posts/` |
|
|
87
|
+
| Route URI | Plural, kebab-case | `/users`, `/blog-posts` |
|
|
88
|
+
| Migration | `create_table_table`, `add_col_to_table` | `create_users_table` |
|
|
89
|
+
|
|
90
|
+
**Singular vs plural rule of thumb:**
|
|
91
|
+
- Class names → **singular** (represents one record: `User`, `Order`)
|
|
92
|
+
- Folders grouping multiple files → **plural** (`Controllers/`, `Models/`, `views/users/`)
|
|
93
|
+
- Database tables and route URIs → **plural** (`users`, `/orders`)
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Controllers — HTTP orchestration only
|
|
98
|
+
|
|
99
|
+
Controllers validate the request, call an Action, and return a response. Nothing else.
|
|
100
|
+
|
|
101
|
+
```php
|
|
102
|
+
// WRONG — business logic in controller
|
|
103
|
+
public function store(Request $request)
|
|
104
|
+
{
|
|
105
|
+
if (Appointment::where('doctor_id', $request->doctor_id)
|
|
106
|
+
->whereDate('date', $request->date)->exists()) {
|
|
107
|
+
return back()->withErrors(['date' => 'Already booked.']);
|
|
108
|
+
}
|
|
109
|
+
$appointment = Appointment::create([...]);
|
|
110
|
+
Mail::to(auth()->user())->send(new AppointmentConfirmed($appointment));
|
|
111
|
+
return redirect()->route('appointments.index');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// RIGHT — controller as orchestrator
|
|
115
|
+
public function store(CreateAppointmentRequest $request): RedirectResponse
|
|
116
|
+
{
|
|
117
|
+
$appointment = (new CreateAppointmentAction)->execute(
|
|
118
|
+
auth()->user(),
|
|
119
|
+
AppointmentData::fromRequest($request)
|
|
120
|
+
);
|
|
121
|
+
return redirect()->route('appointments.show', $appointment);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Form Requests — all validation lives here
|
|
128
|
+
|
|
129
|
+
```php
|
|
130
|
+
class CreateAppointmentRequest extends FormRequest
|
|
131
|
+
{
|
|
132
|
+
public function authorize(): bool
|
|
133
|
+
{
|
|
134
|
+
return $this->user()->can('create', Appointment::class);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public function rules(): array
|
|
138
|
+
{
|
|
139
|
+
return [
|
|
140
|
+
'doctor_id' => ['required', 'exists:doctors,id'],
|
|
141
|
+
'date' => ['required', 'date', 'after:today'],
|
|
142
|
+
'notes' => ['nullable', 'string', 'max:500'],
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public function messages(): array
|
|
147
|
+
{
|
|
148
|
+
return [
|
|
149
|
+
'date.after' => 'The appointment must be scheduled for a future date.',
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Actions — one class, one business operation
|
|
158
|
+
|
|
159
|
+
```php
|
|
160
|
+
// app/Actions/CreateAppointmentAction.php
|
|
161
|
+
class CreateAppointmentAction
|
|
162
|
+
{
|
|
163
|
+
public function execute(User $user, AppointmentData $data): Appointment
|
|
164
|
+
{
|
|
165
|
+
$this->ensureNoConflict($data->doctorId, $data->date);
|
|
166
|
+
|
|
167
|
+
$appointment = Appointment::create([
|
|
168
|
+
'user_id' => $user->id,
|
|
169
|
+
'doctor_id' => $data->doctorId,
|
|
170
|
+
'date' => $data->date,
|
|
171
|
+
'notes' => $data->notes,
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
AppointmentCreated::dispatch($appointment);
|
|
175
|
+
|
|
176
|
+
return $appointment;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private function ensureNoConflict(int $doctorId, Carbon $date): void
|
|
180
|
+
{
|
|
181
|
+
if (Appointment::where('doctor_id', $doctorId)
|
|
182
|
+
->whereDate('date', $date)
|
|
183
|
+
->where('status', '!=', 'cancelled')
|
|
184
|
+
->exists()) {
|
|
185
|
+
throw new AppointmentConflictException();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Policies — authorization layer, not inside Actions
|
|
194
|
+
|
|
195
|
+
```php
|
|
196
|
+
class AppointmentPolicy
|
|
197
|
+
{
|
|
198
|
+
public function view(User $user, Appointment $appointment): bool
|
|
199
|
+
{
|
|
200
|
+
return $user->id === $appointment->user_id
|
|
201
|
+
|| $user->hasRole('admin');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public function cancel(User $user, Appointment $appointment): bool
|
|
205
|
+
{
|
|
206
|
+
return $this->view($user, $appointment)
|
|
207
|
+
&& $appointment->date->isAfter(now()->addHours(24));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Call via `$this->authorize('cancel', $appointment)` in controllers or Form Requests.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Events + Listeners — side effects always async/queued
|
|
217
|
+
|
|
218
|
+
Never call external services synchronously inside an Action.
|
|
219
|
+
|
|
220
|
+
```php
|
|
221
|
+
// Event — carries minimum required data
|
|
222
|
+
class AppointmentCreated
|
|
223
|
+
{
|
|
224
|
+
use Dispatchable, SerializesModels;
|
|
225
|
+
public function __construct(public readonly Appointment $appointment) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Listener — always queued
|
|
229
|
+
class SendConfirmationEmail implements ShouldQueue
|
|
230
|
+
{
|
|
231
|
+
public int $tries = 3;
|
|
232
|
+
|
|
233
|
+
public function handle(AppointmentCreated $event): void
|
|
234
|
+
{
|
|
235
|
+
Mail::to($event->appointment->user)
|
|
236
|
+
->send(new AppointmentConfirmedMail($event->appointment));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Jobs — long-running or retriable work
|
|
244
|
+
|
|
245
|
+
```php
|
|
246
|
+
class GenerateInvoiceJob implements ShouldQueue
|
|
247
|
+
{
|
|
248
|
+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
249
|
+
|
|
250
|
+
public int $tries = 3;
|
|
251
|
+
public int $backoff = 60;
|
|
252
|
+
|
|
253
|
+
public function __construct(private readonly Order $order) {}
|
|
254
|
+
|
|
255
|
+
public function handle(InvoiceService $service): void
|
|
256
|
+
{
|
|
257
|
+
$service->generate($this->order);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public function failed(Throwable $e): void
|
|
261
|
+
{
|
|
262
|
+
Log::error('Invoice generation failed', [
|
|
263
|
+
'order_id' => $this->order->id,
|
|
264
|
+
'error' => $e->getMessage(),
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## API Resources — always transform, never expose raw models
|
|
273
|
+
|
|
274
|
+
```php
|
|
275
|
+
class AppointmentResource extends JsonResource
|
|
276
|
+
{
|
|
277
|
+
public function toArray(Request $request): array
|
|
278
|
+
{
|
|
279
|
+
return [
|
|
280
|
+
'id' => $this->id,
|
|
281
|
+
'date' => $this->date->toIso8601String(),
|
|
282
|
+
'status' => $this->status,
|
|
283
|
+
'doctor' => new DoctorResource($this->whenLoaded('doctor')),
|
|
284
|
+
'can' => [
|
|
285
|
+
'cancel' => $request->user()->can('cancel', $this->resource),
|
|
286
|
+
],
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## N+1 prevention
|
|
295
|
+
|
|
296
|
+
```php
|
|
297
|
+
// WRONG — N+1 (one query per row)
|
|
298
|
+
$appointments = Appointment::all();
|
|
299
|
+
foreach ($appointments as $a) { echo $a->doctor->name; }
|
|
300
|
+
|
|
301
|
+
// RIGHT — eager loading
|
|
302
|
+
$appointments = Appointment::with(['doctor.user', 'patient'])->paginate(20);
|
|
303
|
+
|
|
304
|
+
// Define reusable scope on the model
|
|
305
|
+
public function scopeWithRelations(Builder $query): Builder
|
|
306
|
+
{
|
|
307
|
+
return $query->with(['doctor.user', 'patient']);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Use Laravel Debugbar or Telescope in dev to catch N+1 before production.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Model conventions
|
|
316
|
+
|
|
317
|
+
```php
|
|
318
|
+
class Appointment extends Model
|
|
319
|
+
{
|
|
320
|
+
protected $fillable = ['user_id', 'doctor_id', 'date', 'status', 'notes'];
|
|
321
|
+
|
|
322
|
+
protected $casts = [
|
|
323
|
+
'date' => 'datetime',
|
|
324
|
+
'cancelled_at' => 'datetime',
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
public function scopePending(Builder $query): Builder
|
|
328
|
+
{
|
|
329
|
+
return $query->where('status', 'pending');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public function scopeUpcoming(Builder $query): Builder
|
|
333
|
+
{
|
|
334
|
+
return $query->where('date', '>', now())->orderBy('date');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public function doctor(): BelongsTo
|
|
338
|
+
{
|
|
339
|
+
return $this->belongsTo(Doctor::class);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Migrations
|
|
347
|
+
|
|
348
|
+
- One migration per schema change — never edit after deployment.
|
|
349
|
+
- Always add foreign key constraints.
|
|
350
|
+
- Name columns after the domain: `cancelled_at`, not `is_cancelled_timestamp`.
|
|
351
|
+
- Add indexes for columns used in WHERE clauses and ORDER BY.
|
|
352
|
+
|
|
353
|
+
```php
|
|
354
|
+
Schema::create('appointments', function (Blueprint $table) {
|
|
355
|
+
$table->id();
|
|
356
|
+
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
|
357
|
+
$table->foreignId('doctor_id')->constrained()->restrictOnDelete();
|
|
358
|
+
$table->dateTime('date');
|
|
359
|
+
$table->enum('status', ['pending', 'confirmed', 'cancelled'])->default('pending');
|
|
360
|
+
$table->text('notes')->nullable();
|
|
361
|
+
$table->timestamp('cancelled_at')->nullable();
|
|
362
|
+
$table->timestamps();
|
|
363
|
+
|
|
364
|
+
$table->index(['doctor_id', 'date']); // for conflict checks
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Testing
|
|
371
|
+
|
|
372
|
+
```php
|
|
373
|
+
// Feature — test full HTTP layer
|
|
374
|
+
test('patient can book appointment', function () {
|
|
375
|
+
$patient = User::factory()->create();
|
|
376
|
+
$doctor = Doctor::factory()->create();
|
|
377
|
+
|
|
378
|
+
actingAs($patient)
|
|
379
|
+
->post(route('appointments.store'), [
|
|
380
|
+
'doctor_id' => $doctor->id,
|
|
381
|
+
'date' => now()->addDay()->toDateTimeString(),
|
|
382
|
+
])
|
|
383
|
+
->assertRedirect();
|
|
384
|
+
|
|
385
|
+
expect(Appointment::where('user_id', $patient->id)->exists())->toBeTrue();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Unit — test Actions in isolation
|
|
389
|
+
test('CreateAppointmentAction throws on conflict', function () {
|
|
390
|
+
$existing = Appointment::factory()->confirmed()->create();
|
|
391
|
+
|
|
392
|
+
expect(fn () => (new CreateAppointmentAction)->execute(
|
|
393
|
+
$existing->user,
|
|
394
|
+
AppointmentData::from(['doctor_id' => $existing->doctor_id, 'date' => $existing->date])
|
|
395
|
+
))->toThrow(AppointmentConflictException::class);
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Livewire components (Jetstream stack)
|
|
402
|
+
|
|
403
|
+
Livewire replaces full-page controllers for interactive UI. Use it instead of writing separate Vue/React components when the project is on the Jetstream+Livewire stack.
|
|
404
|
+
|
|
405
|
+
```php
|
|
406
|
+
// app/Livewire/Users/UserList.php
|
|
407
|
+
namespace App\Livewire\Users;
|
|
408
|
+
|
|
409
|
+
use Livewire\Component;
|
|
410
|
+
use Livewire\WithPagination;
|
|
411
|
+
use Livewire\Attributes\Computed;
|
|
412
|
+
|
|
413
|
+
class UserList extends Component
|
|
414
|
+
{
|
|
415
|
+
use WithPagination;
|
|
416
|
+
|
|
417
|
+
public string $search = '';
|
|
418
|
+
|
|
419
|
+
// Computed property — recalculates automatically when $search changes
|
|
420
|
+
#[Computed]
|
|
421
|
+
public function users(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
|
422
|
+
{
|
|
423
|
+
return User::query()
|
|
424
|
+
->when($this->search, fn ($q) => $q->where('name', 'like', "%{$this->search}%"))
|
|
425
|
+
->paginate(10);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
public function render(): \Illuminate\View\View
|
|
429
|
+
{
|
|
430
|
+
return view('livewire.users.user-list');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```blade
|
|
436
|
+
{{-- resources/views/livewire/users/user-list.blade.php --}}
|
|
437
|
+
<div>
|
|
438
|
+
<input wire:model.live="search" type="text" placeholder="Search…" />
|
|
439
|
+
|
|
440
|
+
@foreach ($this->users as $user)
|
|
441
|
+
<div>{{ $user->name }}</div>
|
|
442
|
+
@endforeach
|
|
443
|
+
|
|
444
|
+
{{ $this->users->links() }}
|
|
445
|
+
</div>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Livewire conventions:**
|
|
449
|
+
- Class in `app/Livewire/<Domain>/ClassName.php` (PascalCase)
|
|
450
|
+
- View in `resources/views/livewire/<domain>/class-name.blade.php` (kebab-case)
|
|
451
|
+
- Use `#[Computed]` for derived data — never store computed values in public properties
|
|
452
|
+
- Use `wire:model.live` for real-time search; `wire:model.lazy` for form inputs (debounce on blur)
|
|
453
|
+
- Keep business logic in Actions — Livewire component only wires input → Action → response
|
|
454
|
+
- Never query DB inside Blade template — use `#[Computed]` property
|
|
455
|
+
|
|
456
|
+
**Classic controller variant (same project, non-Livewire views):**
|
|
457
|
+
```php
|
|
458
|
+
// app/Http/Controllers/UserController.php
|
|
459
|
+
class UserController extends Controller
|
|
460
|
+
{
|
|
461
|
+
public function index(Request $request): View
|
|
462
|
+
{
|
|
463
|
+
$users = User::query()
|
|
464
|
+
->when($request->search, fn ($q) => $q->where('name', 'like', "%{$request->search}%"))
|
|
465
|
+
->paginate(10);
|
|
466
|
+
|
|
467
|
+
return view('users.index', compact('users'));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Both patterns coexist fine in a Jetstream project — Livewire for interactive pages, classic controllers for simple read-only or API routes.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## ALWAYS
|
|
477
|
+
- Form Requests for validation
|
|
478
|
+
- Actions for business logic
|
|
479
|
+
- Policies for authorization
|
|
480
|
+
- Events + queued Listeners for side effects
|
|
481
|
+
- API Resources for JSON responses
|
|
482
|
+
- Eager loading with `with()`
|
|
483
|
+
- Follow naming conventions: singular classes, plural tables and view folders
|
|
484
|
+
|
|
485
|
+
## NEVER
|
|
486
|
+
- Business logic in controllers
|
|
487
|
+
- `Mail::send()` synchronously in request cycle
|
|
488
|
+
- `Auth::user()` inside an Action (inject `User` instead)
|
|
489
|
+
- Raw `DB::table()` queries bypassing Eloquent in feature code
|
|
490
|
+
- Exposing Eloquent models directly in API responses
|
|
491
|
+
- Queries inside Blade or Livewire templates directly (use `#[Computed]` or pass via controller)
|