Loading...
Loading...
Compare original and translation side by side
luciddatabase/schema.tslucidluciddatabase/schema.tslucidimport router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
import { controllers } from '#generated/controllers'
// CRITICAL: fixed routes BEFORE dynamic params
router.get('/posts/create', [controllers.Posts, 'create']).use(middleware.auth())
router.post('/posts', [controllers.Posts, 'store']).use(middleware.auth())
router.get('/posts/:id', [controllers.Posts, 'show'])
router.get('/posts/:id/edit', [controllers.Posts, 'edit']).use(middleware.auth())
router.put('/posts/:id', [controllers.Posts, 'update']).use(middleware.auth())
// Guest-only
router.group(() => {
router.get('/login', [controllers.Session, 'create'])
router.post('/login', [controllers.Session, 'store'])
}).use(middleware.guest())import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
import { controllers } from '#generated/controllers'
// CRITICAL: fixed routes BEFORE dynamic params
router.get('/posts/create', [controllers.Posts, 'create']).use(middleware.auth())
router.post('/posts', [controllers.Posts, 'store']).use(middleware.auth())
router.get('/posts/:id', [controllers.Posts, 'show'])
router.get('/posts/:id/edit', [controllers.Posts, 'edit']).use(middleware.auth())
router.put('/posts/:id', [controllers.Posts, 'update']).use(middleware.auth())
// Guest-only
router.group(() => {
router.get('/login', [controllers.Session, 'create'])
router.post('/login', [controllers.Session, 'store'])
}).use(middleware.guest())import type { HttpContext } from '@adonisjs/core/http'
import PostTransformer from '#transformers/post_transformer'
import PostPolicy from '#policies/post_policy'
import { createPostValidator } from '#validators/post'
import postsService from '#services/posts_service'
export default class PostsController {
async index({ inertia }: HttpContext) { // swap inertia for view/serialize per architecture
const posts = await postsService.listForIndex()
return inertia.render('posts/index', { posts: PostTransformer.transform(posts) })
}
async store({ request, auth, response }: HttpContext) {
const payload = await request.validateUsing(createPostValidator)
await postsService.create(auth.user!, payload)
return response.redirect().toRoute('posts.index')
}
async update({ bouncer, params, request, response, session }: HttpContext) {
const post = await postsService.findForUpdate(params.id)
await bouncer.with(PostPolicy).authorize('edit', post) // throws 403 if denied
const data = await request.validateUsing(updatePostValidator)
await postsService.update(post, data)
session.flash('success', 'Post updated successfully')
return response.redirect().toRoute('posts.show', { id: post.id })
}
}import type { HttpContext } from '@adonisjs/core/http'
import PostTransformer from '#transformers/post_transformer'
import PostPolicy from '#policies/post_policy'
import { createPostValidator } from '#validators/post'
import postsService from '#services/posts_service'
export default class PostsController {
async index({ inertia }: HttpContext) { // swap inertia for view/serialize per architecture
const posts = await postsService.listForIndex()
return inertia.render('posts/index', { posts: PostTransformer.transform(posts) })
}
async store({ request, auth, response }: HttpContext) {
const payload = await request.validateUsing(createPostValidator)
await postsService.create(auth.user!, payload)
return response.redirect().toRoute('posts.index')
}
async update({ bouncer, params, request, response, session }: HttpContext) {
const post = await postsService.findForUpdate(params.id)
await bouncer.with(PostPolicy).authorize('edit', post) // throws 403 if denied
const data = await request.validateUsing(updatePostValidator)
await postsService.update(post, data)
session.flash('success', 'Post updated successfully')
return response.redirect().toRoute('posts.show', { id: post.id })
}
}import { BaseTransformer } from '@adonisjs/core/transformers'
import { inject } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
import type Post from '#models/post'
import PostPolicy from '#policies/post_policy'
export default class PostTransformer extends BaseTransformer<Post> {
toObject() {
return {
...this.pick(this.resource, ['id', 'title', 'url', 'summary', 'createdAt']),
author: UserTransformer.transform(this.resource.author),
// whenLoaded() — omits field if relation was not preloaded
comments: CommentTransformer.transform(this.whenLoaded(this.resource.comments)),
}
}
// Variant — extends toObject() with permission flags
@inject()
async forDetailedView({ bouncer }: HttpContext) {
return {
...this.toObject(),
can: {
edit: await bouncer.with(PostPolicy).allows('edit', this.resource),
delete: await bouncer.with(PostPolicy).allows('delete', this.resource),
},
}
}
}
// Single: PostTransformer.transform(post)
// Array: PostTransformer.transform(posts)
// Variant: PostTransformer.transform(post).useVariant('forDetailedView')
// Paginated: PostTransformer.paginate(posts.all(), posts.getMeta())import { BaseTransformer } from '@adonisjs/core/transformers'
import { inject } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
import type Post from '#models/post'
import PostPolicy from '#policies/post_policy'
export default class PostTransformer extends BaseTransformer<Post> {
toObject() {
return {
...this.pick(this.resource, ['id', 'title', 'url', 'summary', 'createdAt']),
author: UserTransformer.transform(this.resource.author),
// whenLoaded() — omits field if relation was not preloaded
comments: CommentTransformer.transform(this.whenLoaded(this.resource.comments)),
}
}
// Variant — extends toObject() with permission flags
@inject()
async forDetailedView({ bouncer }: HttpContext) {
return {
...this.toObject(),
can: {
edit: await bouncer.with(PostPolicy).allows('edit', this.resource),
delete: await bouncer.with(PostPolicy).allows('delete', this.resource),
},
}
}
}
// Single: PostTransformer.transform(post)
// Array: PostTransformer.transform(posts)
// Variant: PostTransformer.transform(post).useVariant('forDetailedView')
// Paginated: PostTransformer.paginate(posts.all(), posts.getMeta())import vine from '@vinejs/vine'
// vine.create() — not vine.compile()
export const createPostValidator = vine.create({
title: vine.string().trim().minLength(3).maxLength(255),
url: vine.string().url(),
summary: vine.string().trim().minLength(80).maxLength(500),
})
// Clone schema to reuse rules for update
export const updatePostValidator = vine.create(
createPostValidator.schema.clone()
)import vine from '@vinejs/vine'
// vine.create() — not vine.compile()
export const createPostValidator = vine.create({
title: vine.string().trim().minLength(3).maxLength(255),
url: vine.string().url(),
summary: vine.string().trim().minLength(80).maxLength(500),
})
// Clone schema to reuse rules for update
export const updatePostValidator = vine.create(
createPostValidator.schema.clone()
)// WRONG — never import from root package
import { HttpContext } from '@adonisjs/core'
// CORRECT sub-path imports
import type { HttpContext } from '@adonisjs/core/http'
import { inject } from '@adonisjs/core'
import { BaseTransformer } from '@adonisjs/core/transformers'
import { BasePolicy } from '@adonisjs/bouncer'
import router from '@adonisjs/core/services/router'
import { urlFor, signedUrlFor } from '@adonisjs/core/services/url_builder'
import vine from '@vinejs/vine'
import Post from '#models/post'
import { controllers } from '#generated/controllers'// WRONG — never import from root package
import { HttpContext } from '@adonisjs/core'
// CORRECT sub-path imports
import type { HttpContext } from '@adonisjs/core/http'
import { inject } from '@adonisjs/core'
import { BaseTransformer } from '@adonisjs/core/transformers'
import { BasePolicy } from '@adonisjs/bouncer'
import router from '@adonisjs/core/services/router'
import { urlFor, signedUrlFor } from '@adonisjs/core/services/url_builder'
import vine from '@vinejs/vine'
import Post from '#models/post'
import { controllers } from '#generated/controllers'| Situation | File |
|---|---|
| Implementing any new feature | workflows/build-feature.md |
| Debugging errors, unexpected behavior | workflows/debug.md |
| Creating Service Providers, bindings, IoC | workflows/providers.md |
| Customizing errors, domain exceptions | workflows/exceptions.md |
| Decoupling side effects with events | workflows/events.md |
| Reviewing a PR or code snippet | workflows/code-review.md |
| 场景 | 文件 |
|---|---|
| 实现任何新功能 | workflows/build-feature.md |
| 调试错误、异常行为 | workflows/debug.md |
| 创建服务提供者、绑定、IoC | workflows/providers.md |
| 自定义错误、领域异常 | workflows/exceptions.md |
| 使用事件解耦副作用 | workflows/events.md |
| 评审PR或代码片段 | workflows/code-review.md |
| Topic | File |
|---|---|
| Architecture, folder structure, three rendering modes | references/architecture.md |
| Auth, guards, Bouncer (.authorize vs .allows) | references/auth.md |
| Cache, invalidation, TTL, tags | references/cache.md |
| Controllers, resource vs action, route ordering | references/controllers.md |
| Events, listeners, emitter | references/events.md |
| Exceptions handler, custom domain errors | references/exceptions.md |
| HTTP — request, response, session, URL builder | references/http.md |
| Mail — send, mail classes, templates, testing | references/mail.md |
| Middleware — named, params, global | references/middleware.md |
| Performance — cache, queues, deferred expensive work | references/performance.md |
| Queue, jobs, retry, workers | references/queue.md |
| Security — hashing, encryption, CORS, CSRF | references/security.md |
| Transformers — BaseTransformer, pick, whenLoaded, variants | references/transformers.md |
| VineJS validations — vine.create(), all field types | references/validations.md |
| Ace commands — create, args, flags, prompts | references/ace-commands.md |
| 主题 | 文件 |
|---|---|
| 架构、目录结构、三种渲染模式 | references/architecture.md |
| 认证、守卫、Bouncer(.authorize vs .allows) | references/auth.md |
| 缓存、失效、TTL、标签 | references/cache.md |
| 控制器、资源vs动作、路由顺序 | references/controllers.md |
| 事件、监听器、发射器 | references/events.md |
| 异常处理器、自定义领域错误 | references/exceptions.md |
| HTTP — 请求、响应、会话、URL构建器 | references/http.md |
| 邮件 — 发送、邮件类、模板、测试 | references/mail.md |
| 中间件 — 命名、参数、全局 | references/middleware.md |
| 性能 — 缓存、队列、延迟处理耗时任务 | references/performance.md |
| 队列、任务、重试、工作进程 | references/queue.md |
| 安全 — 哈希、加密、CORS、CSRF | references/security.md |
| 转换器 — BaseTransformer、pick、whenLoaded、变体 | references/transformers.md |
| VineJS验证 — vine.create()、所有字段类型 | references/validations.md |
| Ace命令 — 创建、参数、标志、提示 | references/ace-commands.md |
node ace make:controller Post --resource
node ace make:validator post
node ace make:transformer post
node ace make:policy post
node ace make:service PostService
node ace make:event OrderPlaced
node ace make:listener SendEmail --event=OrderPlaced
node ace make:job ProcessImage
node ace make:middleware AuthMiddleware
node ace make:exception DomainException
node ace make:command SendReminders
node ace make:mail OrderConfirmation
node ace list:routes
node ace repl
node ace generate:keynode ace make:controller Post --resource
node ace make:validator post
node ace make:transformer post
node ace make:policy post
node ace make:service PostService
node ace make:event OrderPlaced
node ace make:listener SendEmail --event=OrderPlaced
node ace make:job ProcessImage
node ace make:middleware AuthMiddleware
node ace make:exception DomainException
node ace make:command SendReminders
node ace make:mail OrderConfirmation
node ace list:routes
node ace repl
node ace generate:key
---
---| Wrong | Correct |
|---|---|
| |
| Inline validation in controller | Separate |
| Business logic in controller | Move to |
| Side effects (email) in controller | Events + Listeners |
| Sub-path: |
| 8+ methods on one controller | Split into focused Action controllers |
| Email verification behind auth middleware | Verification routes must be public |
Dynamic route ( | Fixed routes first |
| |
| |
| |
Missing | Always pass |
| 错误做法 | 正确做法 |
|---|---|
| |
| 在控制器中内联验证逻辑 | 单独创建 |
| 业务逻辑写在控制器中 | 迁移到 |
| 在控制器中处理副作用(如发送邮件) | 使用事件 + 监听器 |
直接从 | 使用子路径: |
| 单个控制器包含8个以上方法 | 拆分为专注单一功能的Action控制器 |
| 邮件验证放在认证中间件之后 | 验证路由必须公开 |
动态路由( | 固定路由优先 |
在转换器中使用 | 在转换器中使用 |
使用 | 使用 |
在Edge模板中使用 | 在Edge模板中使用 |
在邮件的 | 外部链接必须始终传入 |