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
@@ -0,0 +1,88 @@
import type { HttpContext } from '@adonisjs/core/http'
import Contact from '#base_modules/Contact/model/contact'
import { createContactValidator } from '#base_modules/Contact/validators/contact_create_validator'
import { updateContactValidator } from '#base_modules/Contact/validators/contact_update_validator'
export default class ContactsController {
/**
* @summary List contacts in the current tenant
* @responseBody 200 - {"data":[{"id":1,"display_name":"Ada Lovelace","email":"ada@example.test","phone":null,"status":"active"}]}
* @responseBody 401 - {"message":"Unauthorized"}
* @responseBody 400 - {"code":"E_TENANT_REQUIRED","message":"X-Tenant-Id header is required"}
* @responseBody 403 - {"code":"E_TENANT_ACCESS_DENIED","message":"You do not have access to this tenant"}
*/
async index({ response, tenant }: HttpContext) {
const contacts = await Contact.query().where('tenant_id', tenant.id).orderBy('id', 'asc')
return response.ok({ data: contacts })
}
/**
* @summary Show a contact in the current tenant
* @responseBody 200 - {"data":{"id":1,"display_name":"Katherine Johnson","email":"katherine@example.test","phone":null,"status":"active"}}
* @responseBody 404 - {"message":"Not found."}
*/
async show({ request, response, tenant }: HttpContext) {
const contact = await Contact.query()
.where('id', request.param('id'))
.where('tenant_id', tenant.id)
.first()
if (!contact) return response.notFound({ message: 'Not found.' })
return response.ok({ data: contact })
}
/**
* @summary Update a contact in the current tenant
* @responseBody 200 - {"data":{"id":1,"display_name":"Updated Name","email":"updated@example.test","phone":null,"status":"active"}}
* @responseBody 404 - {"message":"Not found."}
*/
async update({ request, response, tenant }: HttpContext) {
const contact = await Contact.query()
.where('id', request.param('id'))
.where('tenant_id', tenant.id)
.first()
if (!contact) return response.notFound({ message: 'Not found.' })
const data = await updateContactValidator.validate({
displayName: request.input('display_name'),
email: request.input('email'),
phone: request.input('phone'),
status: request.input('status'),
})
contact.merge(data)
await contact.save()
return response.ok({ data: contact })
}
/**
* @summary Delete a contact in the current tenant
* @responseBody 200 - {"message":"Deleted."}
* @responseBody 404 - {"message":"Not found."}
*/
async destroy({ request, response, tenant }: HttpContext) {
const contact = await Contact.query()
.where('id', request.param('id'))
.where('tenant_id', tenant.id)
.first()
if (!contact) return response.notFound({ message: 'Not found.' })
await contact.delete()
return response.ok({ message: 'Deleted.' })
}
/**
* @summary Create a contact in the current tenant
* @responseBody 201 - {"data":{"id":1,"display_name":"Grace Hopper","email":"grace@example.test","phone":"+1-555-0100","status":"active"}}
* @responseBody 401 - {"message":"Unauthorized"}
* @responseBody 400 - {"code":"E_TENANT_REQUIRED","message":"X-Tenant-Id header is required"}
* @responseBody 403 - {"code":"E_TENANT_ACCESS_DENIED","message":"You do not have access to this tenant"}
*/
async store({ request, response, tenant }: HttpContext) {
const input = await createContactValidator.validate(request.all())
const contact = await Contact.create({
tenantId: tenant.id,
displayName: input.display_name,
email: input.email,
phone: input.phone,
status: 'active',
})
return response.created({ data: contact })
}
}
@@ -0,0 +1,33 @@
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { DateTime } from 'luxon'
export default class Contact extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column({ columnName: 'tenant_id', serializeAs: null })
declare tenantId: number
@column({ columnName: 'display_name', serializeAs: 'display_name' })
declare displayName: string
@column()
declare email: string | null
@column()
declare phone: string | null
@column()
declare status: 'active' | 'inactive'
@column.dateTime({ autoCreate: true, columnName: 'created_at', serializeAs: null })
declare createdAt: DateTime
@column.dateTime({
autoCreate: true,
autoUpdate: true,
columnName: 'updated_at',
serializeAs: null,
})
declare updatedAt: DateTime | null
}
@@ -0,0 +1,15 @@
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
const ContactsController = () => import('#base_modules/Contact/controller/contacts_controller')
router
.group(() => {
router.get('/', [ContactsController, 'index'])
router.get('/:id', [ContactsController, 'show'])
router.post('/', [ContactsController, 'store'])
router.put('/:id', [ContactsController, 'update'])
router.delete('/:id', [ContactsController, 'destroy'])
})
.prefix('/api/contacts')
.use([middleware.auth(), middleware.tenant()])
@@ -0,0 +1,9 @@
import vine from '@vinejs/vine'
export const createContactValidator = vine.compile(
vine.object({
display_name: vine.string().trim().minLength(1),
email: vine.string().trim().email().optional(),
phone: vine.string().trim().optional(),
})
)
@@ -0,0 +1,10 @@
import vine from '@vinejs/vine'
export const updateContactValidator = vine.compile(
vine.object({
displayName: vine.string().trim().minLength(1).optional(),
email: vine.string().trim().email().optional(),
phone: vine.string().trim().optional(),
status: vine.enum(['active', 'inactive']).optional(),
})
)