This commit is contained in:
2026-09-02 09:19:43 +03:00
parent 74cb5b6176
commit 962f489142
34 changed files with 1190 additions and 124 deletions
@@ -1,102 +1,91 @@
import type { HttpContext } from '@adonisjs/core/http'
import {{pascalCaseName}} from '#modules/{{capName}}/model/{{snakeCaseName}}'
import {{pascalCaseName}}Policy from '#modules/{{capName}}/policy/{{snakeCaseName}}_policy'
import { create{{pascalCaseName}}ApiValidator } from '#modules/{{capName}}/api/validators/{{snakeCaseName}}_create_api_validator'
import { update{{pascalCaseName}}ApiValidator } from '#modules/{{capName}}/api/validators/{{snakeCaseName}}_update_api_validator'
export default class {{pascalCaseName}}ApiController {
/**
* GET /api/{{name}}
*/
/**
* @index
* @operationId get{{pascalCaseName}}
* @description Returns array of records
* @responseBody 200 - [{{{swaggerModel}}}]
* @responseHeader 200 - X-pages - A description of the header - @example(test)
* @description Returns records visible to an authorized user.
* @responseBody 200 - [{{{swaggerExample}}}]
* @responseBody 401 - { "message": "Unauthorized" }
* @responseBody 403 - { "message": "Forbidden" }
*/
async index({ response }: HttpContext) {
const result = await {{pascalCaseName}}.all()
return response.ok(result)
async index({ response, bouncer }: HttpContext) {
await bouncer.with({{pascalCaseName}}Policy).authorize('view{{pascalCaseName}}List')
return response.ok(await {{pascalCaseName}}.all())
}
/**
* GET /api/{{name}}/:id
*/
/**
* @show
* @operationId show{{pascalCaseName}}
* @description Returns record
* @responseBody 200 - {{{swaggerModel}}}
* @description Returns one record visible to an authorized user.
* @paramPath id - Record ID - @required
* @responseBody 200 - {{{swaggerExample}}}
* @responseBody 401 - { "message": "Unauthorized" }
* @responseBody 403 - { "message": "Forbidden" }
* @responseBody 404 - { "message": "Not found" }
* @responseHeader 200 - X-pages - A description of the header - @example(test)
*/
async show({ params, response }: HttpContext) {
async show({ params, response, bouncer }: HttpContext) {
await bouncer.with({{pascalCaseName}}Policy).authorize('view{{pascalCaseName}}')
const record = await {{pascalCaseName}}.find(params.id)
if (!record) return response.notFound({ message: 'Not found' })
return response.ok(record)
}
/**
* POST /api/{{name}}
*/
/**
* @store
* @operationId store{{pascalCaseName}}
* @description Creates and Returns record
* @description Creates and returns a record.
* @requestBody <create{{pascalCaseName}}ApiValidator>
* @responseBody 200 - {{{swaggerModel}}}
* @responseBody 404 - { "message": "Not found" }
* @responseBody 422 - <Error422> - Returns an error if the request body fails validation.
* @responseHeader 200 - X-pages - A description of the header - @example(test)
* @responseBody 201 - {{{swaggerExample}}}
* @responseBody 401 - { "message": "Unauthorized" }
* @responseBody 403 - { "message": "Forbidden" }
* @responseBody 422 - { "errors": [{ "message": "Validation failed", "rule": "required", "field": "name" }] }
*/
async store({ request, response }: HttpContext) {
async store({ request, response, bouncer }: HttpContext) {
await bouncer.with({{pascalCaseName}}Policy).authorize('create{{pascalCaseName}}')
const payload = await create{{pascalCaseName}}ApiValidator.validate(request.all())
const record = await {{pascalCaseName}}.create(payload)
return response.created(record)
return response.created(await {{pascalCaseName}}.create(payload))
}
/**
* PUT /api/{{name}}/:id
*/
/**
* @update
* @operationId update{{pascalCaseName}}
* @description Updates and Returns record
* @description Updates and returns a record.
* @paramPath id - Record ID - @required
* @requestBody <update{{pascalCaseName}}ApiValidator>
* @responseBody 200 - {{{swaggerModel}}}
* @responseBody 200 - {{{swaggerExample}}}
* @responseBody 401 - { "message": "Unauthorized" }
* @responseBody 403 - { "message": "Forbidden" }
* @responseBody 404 - { "message": "Not found" }
* @responseBody 422 - <Error422> - Returns an error if the request body fails validation.
* @responseHeader 200 - X-pages - A description of the header - @example(test)
* @responseBody 422 - { "errors": [{ "message": "Validation failed", "rule": "required", "field": "name" }] }
*/
async update({ params, request, response }: HttpContext) {
async update({ params, request, response, bouncer }: HttpContext) {
await bouncer.with({{pascalCaseName}}Policy).authorize('update{{pascalCaseName}}')
const record = await {{pascalCaseName}}.find(params.id)
if (!record) return response.notFound({ message: 'Not found' })
const payload = await update{{pascalCaseName}}ApiValidator.validate(request.all())
record.merge(payload)
record.merge(await update{{pascalCaseName}}ApiValidator.validate(request.all()))
await record.save()
return response.ok(record)
}
/**
* DELETE /api/{{name}}/:id
*/
/**
* @destroy
* @operationId destroy{{pascalCaseName}}
* @description Destroys record
* @paramPath id - The ID of the source - @required
* @description Deletes a record.
* @paramPath id - Record ID - @required
* @responseBody 200 - { "message": "Deleted" }
* @responseHeader 200 - X-pages - A description of the header - @example(test)
* @responseBody 401 - { "message": "Unauthorized" }
* @responseBody 403 - { "message": "Forbidden" }
* @responseBody 404 - { "message": "Not found" }
*/
async destroy({ params, response }: HttpContext) {
async destroy({ params, response, bouncer }: HttpContext) {
await bouncer.with({{pascalCaseName}}Policy).authorize('delete{{pascalCaseName}}')
const record = await {{pascalCaseName}}.find(params.id)
if (!record) return response.notFound({ message: 'Not found' })
await record.delete()
return response.ok({ message: 'Deleted' })
}
@@ -1,4 +1,6 @@
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
const {{pascalCaseName}}ApiController = () => import('#modules/{{moduleName}}/api/controllers/{{snakeCaseName}}_api_controller')
router
@@ -10,3 +12,4 @@ router
router.delete('/:id', [{{pascalCaseName}}ApiController, 'destroy'])
})
.prefix('/api/{{snakeCaseName}}')
.use([middleware.auth()])
@@ -1,24 +1,104 @@
import { test } from '@japa/runner'
import db from '@adonisjs/lucid/services/db'
import testUtils from '@adonisjs/core/services/test_utils'
import { createAdminWithToken } from '#tests/utils/auth'
const ALL_PERMISSIONS = ['{{name}}s.view', '{{name}}s.create', '{{name}}s.update', '{{name}}s.delete']
const ROUTES = [
['get', '/api/{{snakeCaseName}}'],
['get', '/api/{{snakeCaseName}}/999999'],
['post', '/api/{{snakeCaseName}}'],
['put', '/api/{{snakeCaseName}}/999999'],
['delete', '/api/{{snakeCaseName}}/999999'],
] as const
test.group('API /api/{{snakeCaseName}}', (group) => {
group.each.setup(async () => {
await db.beginGlobalTransaction()
return () => db.rollbackGlobalTransaction()
group.each.setup(() => testUtils.db().wrapInGlobalTransaction())
test('rejects unauthenticated requests', async ({ client }) => {
for (const [method, url] of ROUTES) {
const response = await client[method](url)
response.assertStatus(401)
}
})
test('rejects a token without the required permission', async ({ client }) => {
const { token } = await createAdminWithToken([])
for (const [method, url] of ROUTES) {
const response = await client[method](url).bearerToken(token)
response.assertStatus(403)
}
})
test('GET /api/{{snakeCaseName}} -> list', async ({ client }) => {
const res = await client.get('/api/{{snakeCaseName}}')
res.assertStatus(200)
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const response = await client.get('/api/{{snakeCaseName}}').bearerToken(token)
response.assertStatus(200)
})
test('POST /api/{{snakeCaseName}} -> create', async ({ client }) => {
test('POST /api/{{snakeCaseName}} -> create', async ({ assert, client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const payload = {
{{#each createFields}}
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
{{/each}}
}
const res = await client.post('/api/{{snakeCaseName}}').json(payload)
res.assertStatus(201)
const response = await client.post('/api/{{snakeCaseName}}').bearerToken(token).json(payload)
response.assertStatus(201)
assert.exists(response.body().id)
})
test('GET /api/{{snakeCaseName}}/:id -> show and 404', async ({ assert, client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const created = await client.post('/api/{{snakeCaseName}}').bearerToken(token).json({
{{#each createFields}}
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
{{/each}}
})
created.assertStatus(201)
const id = created.body().id
const response = await client.get(`/api/{{snakeCaseName}}/${id}`).bearerToken(token)
response.assertStatus(200)
assert.equal(response.body().id, id)
const missing = await client.get('/api/{{snakeCaseName}}/999999').bearerToken(token)
missing.assertStatus(404)
})
test('PUT /api/{{snakeCaseName}}/:id -> update and 404', async ({ client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const created = await client.post('/api/{{snakeCaseName}}').bearerToken(token).json({
{{#each createFields}}
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
{{/each}}
})
created.assertStatus(201)
const updated = await client.put(`/api/{{snakeCaseName}}/${created.body().id}`).bearerToken(token).json({
{{#each updateFields}}
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
{{/each}}
})
updated.assertStatus(200)
const missing = await client.put('/api/{{snakeCaseName}}/999999').bearerToken(token).json({})
missing.assertStatus(404)
})
test('DELETE /api/{{snakeCaseName}}/:id -> destroy and 404', async ({ client }) => {
const { token } = await createAdminWithToken(ALL_PERMISSIONS)
const created = await client.post('/api/{{snakeCaseName}}').bearerToken(token).json({
{{#each createFields}}
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
{{/each}}
})
created.assertStatus(201)
const deleted = await client.delete(`/api/{{snakeCaseName}}/${created.body().id}`).bearerToken(token)
deleted.assertStatus(200)
const missing = await client.delete('/api/{{snakeCaseName}}/999999').bearerToken(token)
missing.assertStatus(404)
})
})