Initial commit
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import {{pascalCaseName}} from '#modules/{{capName}}/model/{{snakeCaseName}}'
|
||||
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)
|
||||
*/
|
||||
async index({ response }: HttpContext) {
|
||||
const result = await {{pascalCaseName}}.all()
|
||||
return response.ok(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/{{name}}/:id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @show
|
||||
* @operationId show{{pascalCaseName}}
|
||||
* @description Returns record
|
||||
* @responseBody 200 - {{{swaggerModel}}}
|
||||
* @responseBody 404 - { "message": "Not found" }
|
||||
* @responseHeader 200 - X-pages - A description of the header - @example(test)
|
||||
*/
|
||||
async show({ params, response }: HttpContext) {
|
||||
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
|
||||
* @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)
|
||||
*/
|
||||
async store({ request, response }: HttpContext) {
|
||||
const payload = await create{{pascalCaseName}}ApiValidator.validate(request.all())
|
||||
const record = await {{pascalCaseName}}.create(payload)
|
||||
return response.created(record)
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/{{name}}/:id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @update
|
||||
* @operationId update{{pascalCaseName}}
|
||||
* @description Updates and Returns record
|
||||
* @requestBody <update{{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)
|
||||
*/
|
||||
async update({ params, request, response }: HttpContext) {
|
||||
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)
|
||||
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
|
||||
* @responseBody 200 - { "message": "Deleted" }
|
||||
* @responseHeader 200 - X-pages - A description of the header - @example(test)
|
||||
*/
|
||||
async destroy({ params, response }: HttpContext) {
|
||||
const record = await {{pascalCaseName}}.find(params.id)
|
||||
if (!record) return response.notFound({ message: 'Not found' })
|
||||
|
||||
await record.delete()
|
||||
return response.ok({ message: 'Deleted' })
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
import router from '@adonisjs/core/services/router'
|
||||
const {{pascalCaseName}}ApiController = () => import('#modules/{{moduleName}}/api/controllers/{{snakeCaseName}}_api_controller')
|
||||
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/', [{{pascalCaseName}}ApiController, 'index'])
|
||||
router.get('/:id', [{{pascalCaseName}}ApiController, 'show'])
|
||||
router.post('/', [{{pascalCaseName}}ApiController, 'store'])
|
||||
router.put('/:id', [{{pascalCaseName}}ApiController, 'update'])
|
||||
router.delete('/:id', [{{pascalCaseName}}ApiController, 'destroy'])
|
||||
})
|
||||
.prefix('/api/{{snakeCaseName}}')
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const {{functionName}} = vine.compile(
|
||||
vine.object({
|
||||
id: vine.number().optional(),
|
||||
{{#each fields}}
|
||||
{{name}}: vine.{{validatorType}}(){{#if isEmail}}.trim().email(){{/if}}{{#if isPassword}}.trim().minLength(8){{/if}}{{#if needsTrim}}.trim(){{/if}}{{#unless isRequired}}.optional(){{/unless}},
|
||||
{{/each}}
|
||||
})
|
||||
)
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
import { HttpContext } from '@adonisjs/core/http'
|
||||
import { create{{capName}}Validator } from '#modules/{{capName}}/validators/{{snakeName}}_create_validator'
|
||||
import { update{{capName}}Validator } from '#modules/{{capName}}/validators/{{snakeName}}_update_validator'
|
||||
import { addSearchFilter, searchableColumns } from '#services/database_inspector'
|
||||
import {{pascalCaseName}} from '#modules/{{capName}}/model/{{snakeName}}'
|
||||
import {{pascalCaseName}}Policy from '#modules/{{capName}}/policy/{{snakeName}}_policy'
|
||||
|
||||
export default class {{pascalCaseName}}Controller {
|
||||
public async index({ request, response, bouncer }: HttpContext) {
|
||||
await bouncer.with({{pascalCaseName}}Policy).authorize('view{{pascalCaseName}}List')
|
||||
|
||||
const {
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
search = null,
|
||||
sortBy = 'id',
|
||||
sortOrder = 'desc',
|
||||
} = request.all()
|
||||
|
||||
const query = {{pascalCaseName}}.query()
|
||||
|
||||
if (search) {
|
||||
const columns = {{pascalCaseName}}.searchable || (await searchableColumns('{{tableName}}'))
|
||||
addSearchFilter(query, columns, search)
|
||||
}
|
||||
|
||||
const result = await query.orderBy(sortBy, sortOrder).paginate(page, perPage)
|
||||
return response.ok(result.toJSON())
|
||||
}
|
||||
|
||||
public async store({ request, response, bouncer }: HttpContext) {
|
||||
await bouncer.with({{pascalCaseName}}Policy).authorize('create{{pascalCaseName}}')
|
||||
|
||||
const data = await create{{capName}}Validator.validate(request.all())
|
||||
const record = await {{pascalCaseName}}.create(data)
|
||||
|
||||
if (record) {
|
||||
return response.created(record.toJSON())
|
||||
}
|
||||
return response.badRequest({ message: 'Cannot create.' })
|
||||
}
|
||||
|
||||
public async show({ request, response, bouncer }: HttpContext) {
|
||||
const { id } = request.params()
|
||||
if (!id) return response.notFound({ message: 'Not found.' })
|
||||
|
||||
const record = await {{pascalCaseName}}.find(id)
|
||||
if (!record) return response.notFound({ message: 'Not found.' })
|
||||
|
||||
await bouncer.with({{pascalCaseName}}Policy).authorize('view{{pascalCaseName}}')
|
||||
return response.ok(record.toJSON())
|
||||
}
|
||||
|
||||
public async update({ request, response, bouncer }: HttpContext) {
|
||||
const { id } = request.params()
|
||||
if (!id) return response.notFound({ message: 'Not found.' })
|
||||
|
||||
const data = await update{{capName}}Validator.validate(request.all())
|
||||
const record = await {{pascalCaseName}}.find(id)
|
||||
if (!record) return response.notFound({ message: 'Not found.' })
|
||||
|
||||
await bouncer.with({{pascalCaseName}}Policy).authorize('update{{pascalCaseName}}')
|
||||
record.merge(data)
|
||||
await record.save()
|
||||
|
||||
return response.ok(record.toJSON())
|
||||
}
|
||||
|
||||
public async destroy({ request, response, bouncer }: HttpContext) {
|
||||
const { id } = request.params()
|
||||
if (!id) return response.notFound({ message: 'Not found.' })
|
||||
|
||||
const record = await {{pascalCaseName}}.find(id)
|
||||
if (!record) return response.notFound({ message: 'Not found.' })
|
||||
|
||||
await bouncer.with({{pascalCaseName}}Policy).authorize('delete{{pascalCaseName}}')
|
||||
await {{pascalCaseName}}.query().where('id', id).delete()
|
||||
|
||||
return response.ok({ message: 'Deleted.' })
|
||||
}
|
||||
}
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
import { BaseModel, column{{#if imports}}, {{imports}}{{/if}} } from '@adonisjs/lucid/orm'
|
||||
import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations'
|
||||
import { DateTime } from 'luxon'
|
||||
{{#if hasPassword}}
|
||||
import hash from '@adonisjs/core/services/hash'
|
||||
import { beforeSave } from '@adonisjs/lucid/orm'
|
||||
{{/if}}
|
||||
|
||||
export default class {{pascalCaseName}} extends BaseModel {
|
||||
static searchable = {
|
||||
string: [{{#each columns}}{{#if (and (eq validatorType "string") (not isPassword))}}'{{camelCase}}'{{#unless @last}}, {{/unless}}{{/if}}{{/each}}],
|
||||
number: [{{#each columns}}{{#if (eq validatorType "number")}}'{{camelCase}}'{{#unless @last}}, {{/unless}}{{/if}}{{/each}}],
|
||||
}
|
||||
|
||||
{{#each columns}}
|
||||
{{#if isPrimary}}
|
||||
@column({ isPrimary: true, columnName: '{{name}}', serializeAs: '{{name}}' })
|
||||
declare {{camelCase}}: {{type}}
|
||||
{{else if isDateTime}}
|
||||
@column.dateTime({ autoCreate: true{{#if (eq name "updated_at")}}, autoUpdate: true{{/if}}, columnName: '{{name}}', serializeAs: '{{name}}' })
|
||||
declare {{camelCase}}: DateTime | null
|
||||
{{else}}
|
||||
@column({ columnName: '{{name}}', serializeAs: '{{name}}' })
|
||||
declare {{camelCase}}: {{type}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{#each relationships}}
|
||||
{{{this}}}
|
||||
{{/each}}
|
||||
|
||||
{{#if hasPassword}}
|
||||
@beforeSave()
|
||||
static async hashPassword(model: {{className}}) {
|
||||
if (model.$dirty.password) {
|
||||
model.password = await hash.make(model.password)
|
||||
}
|
||||
}
|
||||
{{/if}}
|
||||
}
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
import { BasePolicy } from '@adonisjs/bouncer'
|
||||
import type { AuthorizerResponse } from '@adonisjs/bouncer/types'
|
||||
import { getAdminPermissionSlugs } from '#services/permission_service'
|
||||
import Admins from '#base_modules/Admin/model/admin'
|
||||
|
||||
export default class {{pascalCaseName}}Policy extends BasePolicy {
|
||||
async view{{pascalCaseName}}List(user: Admins): Promise<AuthorizerResponse> {
|
||||
if (!user) return false
|
||||
const perms = await getAdminPermissionSlugs(await user)
|
||||
return perms.has('{{name}}s.view')
|
||||
}
|
||||
|
||||
async view{{pascalCaseName}}(user: Admins): Promise<AuthorizerResponse> {
|
||||
if (!user) return false
|
||||
const perms = await getAdminPermissionSlugs(await user)
|
||||
return perms.has('{{name}}s.view')
|
||||
}
|
||||
|
||||
async create{{pascalCaseName}}(user: Admins): Promise<AuthorizerResponse> {
|
||||
if (!user) return false
|
||||
const perms = await getAdminPermissionSlugs(await user)
|
||||
return perms.has('{{name}}s.create')
|
||||
}
|
||||
|
||||
async update{{pascalCaseName}}(user: Admins): Promise<AuthorizerResponse> {
|
||||
if (!user) return false
|
||||
const perms = await getAdminPermissionSlugs(await user)
|
||||
return perms.has('{{name}}s.update')
|
||||
}
|
||||
|
||||
async delete{{pascalCaseName}}(user: Admins): Promise<AuthorizerResponse> {
|
||||
if (!user) return false
|
||||
const perms = await getAdminPermissionSlugs(await user)
|
||||
return perms.has('{{name}}s.delete')
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
import router from '@adonisjs/core/services/router'
|
||||
import { middleware } from '#start/kernel'
|
||||
|
||||
const {{name}}Controller = () => import('#modules/{{capName}}/controller/{{snakeCaseName}}_controller')
|
||||
|
||||
router.resource('/{{routeName}}', {{name}}Controller).apiOnly().use('*', [middleware.auth()])
|
||||
|
||||
// If you prefer only mutating actions protected, use this instead:
|
||||
// router.resource('/{{routeName}}', {{name}}Controller).apiOnly().use(['store', 'update', 'destroy'], [middleware.auth()])
|
||||
@@ -0,0 +1,24 @@
|
||||
import { test } from '@japa/runner'
|
||||
import db from '@adonisjs/lucid/services/db'
|
||||
|
||||
test.group('API /api/{{snakeCaseName}}', (group) => {
|
||||
group.each.setup(async () => {
|
||||
await db.beginGlobalTransaction()
|
||||
return () => db.rollbackGlobalTransaction()
|
||||
})
|
||||
|
||||
test('GET /api/{{snakeCaseName}} -> list', async ({ client }) => {
|
||||
const res = await client.get('/api/{{snakeCaseName}}')
|
||||
res.assertStatus(200)
|
||||
})
|
||||
|
||||
test('POST /api/{{snakeCaseName}} -> create', async ({ client }) => {
|
||||
const payload = {
|
||||
{{#each createFields}}
|
||||
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
|
||||
{{/each}}
|
||||
}
|
||||
const res = await client.post('/api/{{snakeCaseName}}').json(payload)
|
||||
res.assertStatus(201)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
import { test } from '@japa/runner'
|
||||
import db from '@adonisjs/lucid/services/db'
|
||||
import { loginAsAdmin } from '../../utils/auth.js'
|
||||
|
||||
test.group('{{pascalCaseName}}Controller', (group) => {
|
||||
group.each.setup(async () => {
|
||||
await db.beginGlobalTransaction()
|
||||
return () => db.rollbackGlobalTransaction()
|
||||
})
|
||||
|
||||
test('index: lists paginated {{snakeCaseName}}', async ({ client, assert }) => {
|
||||
const token = await loginAsAdmin(client)
|
||||
const res = await client.get('/{{snakeCaseName}}').header('Authorization', `Bearer ${token}`)
|
||||
res.assertStatus(200)
|
||||
assert.exists(res.body().meta, 'pagination meta exists')
|
||||
})
|
||||
|
||||
test('store: creates a new {{snakeCaseName}}', async ({ client, assert }) => {
|
||||
const token = await loginAsAdmin(client)
|
||||
|
||||
const payload = {
|
||||
{{#each createFields}}
|
||||
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
const res = await client
|
||||
.post('/{{snakeCaseName}}')
|
||||
.header('Authorization', `Bearer ${token}`)
|
||||
.json(payload)
|
||||
|
||||
res.assertStatus(201)
|
||||
const body = res.body()
|
||||
{{#each createFields}}
|
||||
assert.property(body, '{{column_name}}')
|
||||
{{/each}}
|
||||
})
|
||||
|
||||
test('show: returns one {{snakeCaseName}} by id', async ({ client }) => {
|
||||
const token = await loginAsAdmin(client)
|
||||
|
||||
const created = await db
|
||||
.table('{{tableName}}')
|
||||
.insert(
|
||||
{{{insertJson}}}
|
||||
)
|
||||
.returning('id')
|
||||
|
||||
const id = Array.isArray(created) ? (created[0]?.id ?? created[0]) : created
|
||||
|
||||
const res = await client
|
||||
.get(`/{{snakeCaseName}}/${id}`)
|
||||
.header('Authorization', `Bearer ${token}`)
|
||||
|
||||
res.assertStatus(200)
|
||||
})
|
||||
|
||||
test('update: updates {{snakeCaseName}}', async ({ client, assert }) => {
|
||||
const token = await loginAsAdmin(client)
|
||||
|
||||
const created = await db
|
||||
.table('{{tableName}}')
|
||||
.insert(
|
||||
{{{insertJson}}}
|
||||
)
|
||||
.returning('id')
|
||||
|
||||
const id = Array.isArray(created) ? (created[0]?.id ?? created[0]) : created
|
||||
|
||||
const patch = {
|
||||
{{#each updateFields}}
|
||||
{{column_name}}: {{{sampleValue}}}{{#unless @last}},{{/unless}}
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
const res = await client
|
||||
.put(`/{{snakeCaseName}}/${id}`)
|
||||
.header('Authorization', `Bearer ${token}`)
|
||||
.json(patch)
|
||||
|
||||
res.assertStatus(200)
|
||||
const body = res.body()
|
||||
{{#each updateFields}}
|
||||
assert.property(body, '{{column_name}}')
|
||||
{{/each}}
|
||||
})
|
||||
|
||||
test('destroy: deletes {{snakeCaseName}}', async ({ client }) => {
|
||||
const token = await loginAsAdmin(client)
|
||||
|
||||
const created = await db
|
||||
.table('{{tableName}}')
|
||||
.insert(
|
||||
{{{insertJson}}}
|
||||
)
|
||||
.returning('id')
|
||||
|
||||
const id = Array.isArray(created) ? (created[0]?.id ?? created[0]) : created
|
||||
|
||||
const res = await client
|
||||
.delete(`/{{snakeCaseName}}/${id}`)
|
||||
.header('Authorization', `Bearer ${token}`)
|
||||
|
||||
res.assertStatus(200)
|
||||
})
|
||||
})
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const {{functionName}} = vine.compile(
|
||||
vine.object({
|
||||
id: vine.number().optional(),
|
||||
{{#each fields}}
|
||||
{{name}}: vine.{{validatorType}}(){{#if isEmail}}.trim().email(){{/if}}{{#if isPassword}}.trim().minLength(8){{/if}}{{#if needsTrim}}.trim(){{/if}}{{#unless isRequired}}.optional(){{/unless}},
|
||||
{{/each}}
|
||||
})
|
||||
)
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
export const MODULE_NAME = '{{name}}'
|
||||
export const MODULE_NAME_LABEL = MODULE_NAME.replace(/^./, (char) => char.toUpperCase()); // prettier-ignore
|
||||
export const MODULE_API_ENDPOINT = '/' + MODULE_NAME
|
||||
export const MODULE_ROOT_PATH = '/' + MODULE_NAME
|
||||
export const MODULE_PERMISSION_ROOT = MODULE_NAME + 's'
|
||||
export const MODULE_PERMISSION_CREATE = MODULE_PERMISSION_ROOT + '.create'
|
||||
export const MODULE_PERMISSION_UPDATE = MODULE_PERMISSION_ROOT + '.update'
|
||||
export const MODULE_PERMISSION_DELETE = MODULE_PERMISSION_ROOT + '.delete'
|
||||
export const MODULE_PERMISSION_VIEW = MODULE_PERMISSION_ROOT + '.view'
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
{{detaTable}}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { IsoDateString } from '@crux-kit/types';
|
||||
|
||||
export interface Entity {
|
||||
{{#each fields}}
|
||||
{{name}}: {{type}}
|
||||
{{/each}}
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
{{form}}
|
||||
Reference in New Issue
Block a user