@malamute/ai-rules 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -9
- package/configs/_shared/rules/conventions/interaction.md +38 -0
- package/configs/_shared/rules/domain/backend/api-design.md +58 -2
- package/configs/_shared/skills/analysis/sudden-death/SKILL.md +148 -0
- package/configs/adonisjs/rules/auth.md +192 -0
- package/configs/adonisjs/rules/controllers.md +111 -0
- package/configs/adonisjs/rules/core.md +95 -0
- package/configs/adonisjs/rules/database/lucid.md +167 -0
- package/configs/adonisjs/rules/middleware.md +140 -0
- package/configs/adonisjs/rules/services.md +154 -0
- package/configs/adonisjs/rules/testing.md +170 -0
- package/configs/adonisjs/rules/validation.md +130 -0
- package/configs/adonisjs/settings.json +34 -0
- package/package.json +1 -1
- package/src/tech-config.json +4 -0
package/README.md
CHANGED
|
@@ -41,14 +41,15 @@ npx @malamute/ai-rules <command>
|
|
|
41
41
|
|
|
42
42
|
## Supported Technologies
|
|
43
43
|
|
|
44
|
-
| Technology
|
|
45
|
-
|
|
|
46
|
-
| **Angular**
|
|
47
|
-
| **Next.js**
|
|
48
|
-
| **NestJS**
|
|
49
|
-
|
|
|
50
|
-
| **
|
|
51
|
-
| **
|
|
44
|
+
| Technology | Stack | Version |
|
|
45
|
+
| ------------ | ----------------------------------------- | ------- |
|
|
46
|
+
| **Angular** | Nx + NgRx + Signals + Vitest | 21+ |
|
|
47
|
+
| **Next.js** | App Router + React 19 + Server Components | 15+ |
|
|
48
|
+
| **NestJS** | Prisma/TypeORM + Passport + Vitest | 11+ |
|
|
49
|
+
| **AdonisJS** | Lucid ORM + VineJS + Japa | 6+ |
|
|
50
|
+
| **.NET** | Clean Architecture + MediatR + EF Core | 9+ |
|
|
51
|
+
| **FastAPI** | Pydantic v2 + SQLAlchemy 2.0 + pytest | 0.115+ |
|
|
52
|
+
| **Flask** | Marshmallow + SQLAlchemy 2.0 + pytest | 3.0+ |
|
|
52
53
|
|
|
53
54
|
## Commands
|
|
54
55
|
|
|
@@ -121,17 +122,19 @@ Interactive workflows invoked with `/skill-name`:
|
|
|
121
122
|
| `/review` | Code review with security/perf checklist |
|
|
122
123
|
| `/debug` | Structured debugging workflow |
|
|
123
124
|
| `/spec` | Write technical spec before implementing |
|
|
125
|
+
| `/sudden-death` | Kill indecision with rapid-fire questions |
|
|
124
126
|
| `/fix-issue` | Analyze GitHub issue and implement fix |
|
|
125
127
|
| `/generate-tests` | Generate comprehensive tests |
|
|
126
128
|
|
|
127
129
|
<details>
|
|
128
|
-
<summary><strong>See all
|
|
130
|
+
<summary><strong>See all 14 skills</strong></summary>
|
|
129
131
|
|
|
130
132
|
| Skill | Usage | Description |
|
|
131
133
|
| ----------------- | ----------------------------- | ------------------------------------- |
|
|
132
134
|
| `/learning` | `/learning nextjs` | Explains concepts before implementing |
|
|
133
135
|
| `/review` | `/review src/users/` | Code review with checklist |
|
|
134
136
|
| `/spec` | `/spec add auth` | Technical specification |
|
|
137
|
+
| `/sudden-death` | `/sudden-death backend` | Kill indecision, get a verdict |
|
|
135
138
|
| `/debug` | `/debug TypeError...` | Systematic debugging |
|
|
136
139
|
| `/fix-issue` | `/fix-issue 123` | Fix GitHub issue |
|
|
137
140
|
| `/review-pr` | `/review-pr 456` | Review pull request |
|
|
@@ -253,6 +256,19 @@ ai-rules update
|
|
|
253
256
|
|
|
254
257
|
</details>
|
|
255
258
|
|
|
259
|
+
<details>
|
|
260
|
+
<summary><strong>AdonisJS</strong></summary>
|
|
261
|
+
|
|
262
|
+
| Aspect | Convention |
|
|
263
|
+
| ------------ | ----------------------------------- |
|
|
264
|
+
| Architecture | MVC with Services layer |
|
|
265
|
+
| Validation | VineJS |
|
|
266
|
+
| ORM | Lucid (Active Record) |
|
|
267
|
+
| Auth | Access Tokens / Session-based |
|
|
268
|
+
| Tests | Japa |
|
|
269
|
+
|
|
270
|
+
</details>
|
|
271
|
+
|
|
256
272
|
<details>
|
|
257
273
|
<summary><strong>.NET</strong></summary>
|
|
258
274
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Interaction Rules
|
|
7
|
+
|
|
8
|
+
## Rules Are Absolute
|
|
9
|
+
|
|
10
|
+
1. **Rules can NEVER be violated. Tasks can fail.**
|
|
11
|
+
2. If a task requires violating a rule, the task fails - not the rule.
|
|
12
|
+
3. If a task is blocked, explain the problem and ask how to proceed.
|
|
13
|
+
|
|
14
|
+
## Protected Changes
|
|
15
|
+
|
|
16
|
+
Never modify without explaining WHY and asking permission:
|
|
17
|
+
- Package manager config (yarn, npm, pnpm)
|
|
18
|
+
- Infrastructure (docker, CI/CD, deployment)
|
|
19
|
+
- Project structure
|
|
20
|
+
- Build config
|
|
21
|
+
|
|
22
|
+
## Questions vs Actions
|
|
23
|
+
|
|
24
|
+
- **Question** ("what is...", "why...", "how does...") → Answer only, no code
|
|
25
|
+
- **Explicit request** ("create", "implement", "fix", "add") → Action with code
|
|
26
|
+
|
|
27
|
+
When the user asks a question, answer it. Do not start coding or running commands.
|
|
28
|
+
|
|
29
|
+
## Confirmation Before Action
|
|
30
|
+
|
|
31
|
+
For non-trivial changes, confirm approach before implementing:
|
|
32
|
+
1. Explain what will be done
|
|
33
|
+
2. Wait for user approval
|
|
34
|
+
3. Then execute
|
|
35
|
+
|
|
36
|
+
## Language
|
|
37
|
+
|
|
38
|
+
Match the user's language. If they write in French, respond in French.
|
|
@@ -151,21 +151,77 @@ GET /api/v1/users?sort=lastName:asc,firstName:asc
|
|
|
151
151
|
GET /api/v1/users?fields=id,name,email
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
## Versioning
|
|
154
|
+
## API Versioning
|
|
155
155
|
|
|
156
|
-
###
|
|
156
|
+
### When to Version (Breaking Changes)
|
|
157
|
+
|
|
158
|
+
- Removing or renaming a field
|
|
159
|
+
- Changing field type (string → number)
|
|
160
|
+
- Removing an endpoint
|
|
161
|
+
- Changing authentication method
|
|
162
|
+
- Modifying error response structure
|
|
163
|
+
|
|
164
|
+
### NOT Breaking (no version bump)
|
|
165
|
+
|
|
166
|
+
- Adding new optional fields
|
|
167
|
+
- Adding new endpoints
|
|
168
|
+
- Adding new query parameters
|
|
169
|
+
- Performance improvements
|
|
170
|
+
|
|
171
|
+
### Strategy: URL Path (recommended)
|
|
157
172
|
|
|
158
173
|
```
|
|
159
174
|
/api/v1/users
|
|
160
175
|
/api/v2/users
|
|
161
176
|
```
|
|
162
177
|
|
|
178
|
+
- Simple, explicit, cacheable
|
|
179
|
+
- Easy to route at load balancer level
|
|
180
|
+
- Version visible in logs
|
|
181
|
+
|
|
163
182
|
### Header-based (alternative)
|
|
164
183
|
|
|
165
184
|
```
|
|
166
185
|
Accept: application/vnd.api+json; version=1
|
|
167
186
|
```
|
|
168
187
|
|
|
188
|
+
### Deprecation Policy
|
|
189
|
+
|
|
190
|
+
1. Announce deprecation (minimum 6 months before sunset)
|
|
191
|
+
2. Add `Deprecation` header to responses
|
|
192
|
+
3. Document migration path
|
|
193
|
+
4. Monitor usage, notify active consumers
|
|
194
|
+
5. Sunset old version
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
Deprecation: true
|
|
198
|
+
Sunset: Sat, 01 Jun 2025 00:00:00 GMT
|
|
199
|
+
Link: <https://api.example.com/docs/migration-v2>; rel="deprecation"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Version Lifecycle
|
|
203
|
+
|
|
204
|
+
| Status | Description |
|
|
205
|
+
|--------|-------------|
|
|
206
|
+
| **Current** | Latest stable, recommended |
|
|
207
|
+
| **Supported** | Still maintained, receives security fixes |
|
|
208
|
+
| **Deprecated** | Works but scheduled for removal |
|
|
209
|
+
| **Sunset** | No longer available |
|
|
210
|
+
|
|
211
|
+
### Code Organization
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
project/
|
|
215
|
+
├── src/
|
|
216
|
+
│ ├── v1/
|
|
217
|
+
│ │ ├── controllers/
|
|
218
|
+
│ │ └── dto/
|
|
219
|
+
│ ├── v2/
|
|
220
|
+
│ │ ├── controllers/
|
|
221
|
+
│ │ └── dto/
|
|
222
|
+
│ └── shared/ # Version-agnostic (services, repositories)
|
|
223
|
+
```
|
|
224
|
+
|
|
169
225
|
## Rate Limiting
|
|
170
226
|
|
|
171
227
|
Include headers in response:
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sudden-death
|
|
3
|
+
description: Kill indecision with rapid-fire questionnaires
|
|
4
|
+
argument-hint: [decision-topic]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Sudden Death Mode
|
|
8
|
+
|
|
9
|
+
You are now in **sudden death mode**. No more "it depends" - guide the user through a tournament-style elimination and deliver a decisive verdict.
|
|
10
|
+
|
|
11
|
+
**IMPORTANT: Always respond in the user's language.** If they write in French, respond in French. Polish? Polish. The examples below are in French for flavor, but adapt to the user.
|
|
12
|
+
|
|
13
|
+
## Input
|
|
14
|
+
|
|
15
|
+
Decision topic: `$ARGUMENTS`
|
|
16
|
+
|
|
17
|
+
If no argument provided, ask: "What's on the chopping block? (e.g., backend stack, database, UI library, hosting)"
|
|
18
|
+
|
|
19
|
+
## The Game
|
|
20
|
+
|
|
21
|
+
### Phase 1: Candidates
|
|
22
|
+
|
|
23
|
+
List all reasonable options for the domain. Example for backend:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Candidats: NestJS, Hono, Fastify, Elysia, AdonisJS, .NET, FastAPI, Go
|
|
27
|
+
|
|
28
|
+
En garde. Première question...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Phase 2: Elimination Tournament
|
|
32
|
+
|
|
33
|
+
Ask **5-8 killer questions**. Each question should potentially eliminate candidates.
|
|
34
|
+
|
|
35
|
+
Format:
|
|
36
|
+
```
|
|
37
|
+
### Q1: [Short punchy question]
|
|
38
|
+
[Context if needed]
|
|
39
|
+
|
|
40
|
+
→ User answers
|
|
41
|
+
→ **Eliminated: [X, Y]** or **Advantage: [Z]** or **Point: [Z]**
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Example questions (backend stack):
|
|
45
|
+
- "Full TypeScript (front + back) or ok to switch languages?"
|
|
46
|
+
- "Structured framework (modules, DI, conventions) or minimal?"
|
|
47
|
+
- "Decorators (@Controller, @Get) or simple functions?"
|
|
48
|
+
- "Batteries included or pick your own libs?"
|
|
49
|
+
- "Big community or cutting-edge?"
|
|
50
|
+
|
|
51
|
+
**Elimination rules:**
|
|
52
|
+
- Strong preference → Eliminate mismatches immediately
|
|
53
|
+
- Slight preference → Note advantage, keep in race
|
|
54
|
+
- "Tie" / "Both good" → No elimination, move on
|
|
55
|
+
|
|
56
|
+
### Phase 3: Final Showdown
|
|
57
|
+
|
|
58
|
+
When down to 2-3 candidates:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
### Finale: [A] vs [B]
|
|
62
|
+
|
|
63
|
+
[A]: [2-3 key traits]
|
|
64
|
+
[B]: [2-3 key traits]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If clear winner → Declare it
|
|
68
|
+
If tie → Go to tiebreaker
|
|
69
|
+
|
|
70
|
+
### Phase 4: Tiebreaker (when needed)
|
|
71
|
+
|
|
72
|
+
Frame it as a **character choice**, not just technical:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Score: [A] 2 - [B] 2
|
|
76
|
+
|
|
77
|
+
Tu as choisi [previous bold choice] pour sortir de ta zone.
|
|
78
|
+
[A] = full send, nouvelle expérience
|
|
79
|
+
[B] = un pied dans le connu
|
|
80
|
+
|
|
81
|
+
On est des fous ou pas ?
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Phase 5: Verdict
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
### Winner: **[Option]**
|
|
88
|
+
|
|
89
|
+
[One-liner why it fits THEIR specific answers]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Between Decisions
|
|
93
|
+
|
|
94
|
+
After each major decision, recap and offer options:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Stack actuelle:
|
|
98
|
+
- Frontend: Next.js 15
|
|
99
|
+
- Backend: AdonisJS
|
|
100
|
+
- ORM: Lucid
|
|
101
|
+
|
|
102
|
+
On continue ? Il reste:
|
|
103
|
+
- UI lib (shadcn, autre ?)
|
|
104
|
+
- State management
|
|
105
|
+
- Hosting
|
|
106
|
+
|
|
107
|
+
**Sudden death** ou **tu tranches direct** ?
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- **Sudden death** = Full questionnaire
|
|
111
|
+
- **Tu tranches direct** = User is confident, give quick recommendation
|
|
112
|
+
|
|
113
|
+
## Tone
|
|
114
|
+
|
|
115
|
+
- **Playful combat** - "En garde", "Eliminated", "Survivor"
|
|
116
|
+
- **Call out bold choices** - "On est des fous !", "Allez on y va !"
|
|
117
|
+
- **No corporate speak** - Skip the "it depends on your requirements"
|
|
118
|
+
- **Quick and punchy** - Short questions, fast eliminations
|
|
119
|
+
- **Celebrate decisions** - Each choice is a win, not a compromise
|
|
120
|
+
|
|
121
|
+
## Quick Verdict Mode
|
|
122
|
+
|
|
123
|
+
If user says "tu tranches" or wants fast advice:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Pour [context], je dirais **[Option]**.
|
|
127
|
+
|
|
128
|
+
[One sentence why]
|
|
129
|
+
|
|
130
|
+
Sold ? Ou on fait un sudden death pour être sûr ?
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Adapt to Domain
|
|
134
|
+
|
|
135
|
+
Common sudden death topics:
|
|
136
|
+
|
|
137
|
+
| Domain | Typical Candidates |
|
|
138
|
+
|--------|-------------------|
|
|
139
|
+
| Backend | NestJS, Fastify, Hono, AdonisJS, .NET, FastAPI, Go |
|
|
140
|
+
| Frontend | Next.js, Nuxt, SvelteKit, Remix, Angular |
|
|
141
|
+
| Database | PostgreSQL, MySQL, MongoDB, SQLite, Supabase, PlanetScale |
|
|
142
|
+
| ORM | Prisma, Drizzle, TypeORM, Lucid, SQLAlchemy |
|
|
143
|
+
| UI | shadcn/ui, Radix, Chakra, MUI, Mantine |
|
|
144
|
+
| Hosting | Vercel, Railway, Render, Fly.io, AWS, Coolify |
|
|
145
|
+
| State | Zustand, Jotai, Redux Toolkit, Signals, TanStack Query |
|
|
146
|
+
| Auth | Auth.js, Lucia, Clerk, Supabase Auth, custom JWT |
|
|
147
|
+
|
|
148
|
+
For unknown domains, identify the key trade-offs and build questions on the fly.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/controllers/auth/**/*.ts"
|
|
4
|
+
- "app/middleware/**/*.ts"
|
|
5
|
+
- "config/auth.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AdonisJS Authentication
|
|
9
|
+
|
|
10
|
+
## Access Tokens (API)
|
|
11
|
+
|
|
12
|
+
### Configuration
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// config/auth.ts
|
|
16
|
+
import { defineConfig } from '@adonisjs/auth'
|
|
17
|
+
import { tokensGuard, tokensUserProvider } from '@adonisjs/auth/access_tokens'
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
default: 'api',
|
|
21
|
+
guards: {
|
|
22
|
+
api: tokensGuard({
|
|
23
|
+
provider: tokensUserProvider({
|
|
24
|
+
tokens: 'accessTokens',
|
|
25
|
+
model: () => import('#models/user'),
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### User Model Setup
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { DbAccessTokensProvider } from '@adonisjs/auth/access_tokens'
|
|
36
|
+
|
|
37
|
+
export default class User extends BaseModel {
|
|
38
|
+
// ... other columns
|
|
39
|
+
|
|
40
|
+
static accessTokens = DbAccessTokensProvider.forModel(User)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Auth Controller
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import type { HttpContext } from '@adonisjs/core/http'
|
|
48
|
+
import User from '#models/user'
|
|
49
|
+
import hash from '@adonisjs/core/services/hash'
|
|
50
|
+
import { loginValidator, registerValidator } from '#validators/auth'
|
|
51
|
+
|
|
52
|
+
export default class AuthController {
|
|
53
|
+
async register({ request, response }: HttpContext) {
|
|
54
|
+
const payload = await request.validateUsing(registerValidator)
|
|
55
|
+
const user = await User.create(payload)
|
|
56
|
+
const token = await User.accessTokens.create(user)
|
|
57
|
+
|
|
58
|
+
return response.created({
|
|
59
|
+
user,
|
|
60
|
+
token: token.value!.release(),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async login({ request, response }: HttpContext) {
|
|
65
|
+
const { email, password } = await request.validateUsing(loginValidator)
|
|
66
|
+
|
|
67
|
+
const user = await User.findBy('email', email)
|
|
68
|
+
if (!user) {
|
|
69
|
+
return response.unauthorized({ message: 'Invalid credentials' })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const isValid = await hash.verify(user.password, password)
|
|
73
|
+
if (!isValid) {
|
|
74
|
+
return response.unauthorized({ message: 'Invalid credentials' })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const token = await User.accessTokens.create(user)
|
|
78
|
+
|
|
79
|
+
return response.ok({
|
|
80
|
+
user,
|
|
81
|
+
token: token.value!.release(),
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async logout({ auth, response }: HttpContext) {
|
|
86
|
+
const user = auth.user!
|
|
87
|
+
await User.accessTokens.delete(user, user.currentAccessToken.identifier)
|
|
88
|
+
return response.noContent()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async me({ auth, response }: HttpContext) {
|
|
92
|
+
return response.ok(auth.user)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Routes
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// start/routes.ts
|
|
101
|
+
import router from '@adonisjs/core/services/router'
|
|
102
|
+
import { middleware } from '#start/kernel'
|
|
103
|
+
|
|
104
|
+
const AuthController = () => import('#controllers/auth_controller')
|
|
105
|
+
|
|
106
|
+
router.group(() => {
|
|
107
|
+
router.post('register', [AuthController, 'register'])
|
|
108
|
+
router.post('login', [AuthController, 'login'])
|
|
109
|
+
|
|
110
|
+
router.group(() => {
|
|
111
|
+
router.delete('logout', [AuthController, 'logout'])
|
|
112
|
+
router.get('me', [AuthController, 'me'])
|
|
113
|
+
}).use(middleware.auth())
|
|
114
|
+
}).prefix('auth')
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Middleware
|
|
118
|
+
|
|
119
|
+
### Auth Middleware
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Protect routes
|
|
123
|
+
router.get('profile', [ProfileController, 'show']).use(middleware.auth())
|
|
124
|
+
|
|
125
|
+
// In controller, access user
|
|
126
|
+
async show({ auth }: HttpContext) {
|
|
127
|
+
const user = auth.user! // Typed as User
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Custom Middleware
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// app/middleware/admin_middleware.ts
|
|
135
|
+
import type { HttpContext } from '@adonisjs/core/http'
|
|
136
|
+
import type { NextFn } from '@adonisjs/core/types/http'
|
|
137
|
+
|
|
138
|
+
export default class AdminMiddleware {
|
|
139
|
+
async handle({ auth, response }: HttpContext, next: NextFn) {
|
|
140
|
+
if (auth.user?.role !== 'admin') {
|
|
141
|
+
return response.forbidden({ message: 'Admin access required' })
|
|
142
|
+
}
|
|
143
|
+
await next()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Register Middleware
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// start/kernel.ts
|
|
152
|
+
import router from '@adonisjs/core/services/router'
|
|
153
|
+
|
|
154
|
+
router.named({
|
|
155
|
+
auth: () => import('#middleware/auth_middleware'),
|
|
156
|
+
admin: () => import('#middleware/admin_middleware'),
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Password Hashing
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import hash from '@adonisjs/core/services/hash'
|
|
164
|
+
|
|
165
|
+
// Hash password
|
|
166
|
+
const hashed = await hash.make('password')
|
|
167
|
+
|
|
168
|
+
// Verify password
|
|
169
|
+
const isValid = await hash.verify(hashed, 'password')
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Auth Validators
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// app/validators/auth.ts
|
|
176
|
+
import vine from '@vinejs/vine'
|
|
177
|
+
|
|
178
|
+
export const registerValidator = vine.compile(
|
|
179
|
+
vine.object({
|
|
180
|
+
email: vine.string().email().normalizeEmail(),
|
|
181
|
+
password: vine.string().minLength(8).confirmed(),
|
|
182
|
+
name: vine.string().minLength(2),
|
|
183
|
+
})
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
export const loginValidator = vine.compile(
|
|
187
|
+
vine.object({
|
|
188
|
+
email: vine.string().email(),
|
|
189
|
+
password: vine.string(),
|
|
190
|
+
})
|
|
191
|
+
)
|
|
192
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/controllers/**/*.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AdonisJS Controllers
|
|
7
|
+
|
|
8
|
+
## Structure
|
|
9
|
+
|
|
10
|
+
Controllers handle HTTP concerns only. Delegate business logic to services.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import type { HttpContext } from '@adonisjs/core/http'
|
|
14
|
+
import { inject } from '@adonisjs/core'
|
|
15
|
+
import UserService from '#services/user_service'
|
|
16
|
+
import { createUserValidator, updateUserValidator } from '#validators/user'
|
|
17
|
+
|
|
18
|
+
@inject()
|
|
19
|
+
export default class UsersController {
|
|
20
|
+
constructor(private userService: UserService) {}
|
|
21
|
+
|
|
22
|
+
async index({ response }: HttpContext) {
|
|
23
|
+
const users = await this.userService.getAll()
|
|
24
|
+
return response.ok(users)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async store({ request, response }: HttpContext) {
|
|
28
|
+
const payload = await request.validateUsing(createUserValidator)
|
|
29
|
+
const user = await this.userService.create(payload)
|
|
30
|
+
return response.created(user)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async show({ params, response }: HttpContext) {
|
|
34
|
+
const user = await this.userService.findOrFail(params.id)
|
|
35
|
+
return response.ok(user)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async update({ params, request, response }: HttpContext) {
|
|
39
|
+
const payload = await request.validateUsing(updateUserValidator)
|
|
40
|
+
const user = await this.userService.update(params.id, payload)
|
|
41
|
+
return response.ok(user)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async destroy({ params, response }: HttpContext) {
|
|
45
|
+
await this.userService.delete(params.id)
|
|
46
|
+
return response.noContent()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Best Practices
|
|
52
|
+
|
|
53
|
+
### Use Dependency Injection
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Good
|
|
57
|
+
@inject()
|
|
58
|
+
export default class OrdersController {
|
|
59
|
+
constructor(
|
|
60
|
+
private orderService: OrderService,
|
|
61
|
+
private notificationService: NotificationService
|
|
62
|
+
) {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Avoid: instantiating services manually
|
|
66
|
+
export default class OrdersController {
|
|
67
|
+
private orderService = new OrderService() // Hard to test
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Validate Input
|
|
72
|
+
|
|
73
|
+
Always validate using VineJS validators:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
async store({ request }: HttpContext) {
|
|
77
|
+
// Validates and returns typed payload
|
|
78
|
+
const payload = await request.validateUsing(createOrderValidator)
|
|
79
|
+
// payload is now typed and validated
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Use Response Helpers
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
response.ok(data) // 200
|
|
87
|
+
response.created(data) // 201
|
|
88
|
+
response.noContent() // 204
|
|
89
|
+
response.badRequest(error) // 400
|
|
90
|
+
response.unauthorized() // 401
|
|
91
|
+
response.forbidden() // 403
|
|
92
|
+
response.notFound() // 404
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Resource Routes
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// start/routes.ts
|
|
99
|
+
import router from '@adonisjs/core/services/router'
|
|
100
|
+
|
|
101
|
+
const UsersController = () => import('#controllers/users_controller')
|
|
102
|
+
|
|
103
|
+
router.resource('users', UsersController).apiOnly()
|
|
104
|
+
|
|
105
|
+
// Generates:
|
|
106
|
+
// GET /users → index
|
|
107
|
+
// POST /users → store
|
|
108
|
+
// GET /users/:id → show
|
|
109
|
+
// PUT /users/:id → update
|
|
110
|
+
// DELETE /users/:id → destroy
|
|
111
|
+
```
|