update
This commit is contained in:
@@ -7,6 +7,7 @@ import type { ManyToMany } from '@adonisjs/lucid/types/relations'
|
||||
import { DateTime } from 'luxon'
|
||||
import Role from '#base_modules/Role/model/role'
|
||||
import Permission from '#base_modules/Permission/model/permission'
|
||||
import Tenant from '#base_modules/Tenant/model/tenant'
|
||||
|
||||
const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
|
||||
uids: ['email'],
|
||||
@@ -52,6 +53,13 @@ export default class Admins extends ComposedModel {
|
||||
})
|
||||
declare roles: ManyToMany<typeof Role>
|
||||
|
||||
@manyToMany(() => Tenant, {
|
||||
pivotTable: 'admin_tenants',
|
||||
pivotForeignKey: 'admin_id',
|
||||
pivotRelatedForeignKey: 'tenant_id',
|
||||
})
|
||||
declare tenants: ManyToMany<typeof Tenant>
|
||||
|
||||
declare permissions: Permission[]
|
||||
|
||||
static accessTokens = DbAccessTokensProvider.forModel(Admins)
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import CustomField from '#base_modules/CustomField/model/custom_field'
|
||||
|
||||
export default class CustomFieldsController {
|
||||
/**
|
||||
* @summary List custom fields in the current tenant
|
||||
* @responseBody 200 - {"data":[]}
|
||||
* @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"}
|
||||
*/
|
||||
public async index({ response, tenant }: HttpContext) {
|
||||
const fields = await CustomField.query().where('tenant_id', tenant.id).orderBy('id', 'asc')
|
||||
return response.ok({ data: fields })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { BaseModel, column } from '@adonisjs/lucid/orm'
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
export default class CustomField extends BaseModel {
|
||||
@column({ isPrimary: true }) declare id: number
|
||||
@column({ columnName: 'tenant_id', serializeAs: null }) declare tenantId: number
|
||||
@column({ columnName: 'entity_type', serializeAs: 'entity_type' }) declare entityType: string
|
||||
@column() declare name: string
|
||||
@column() declare key: string
|
||||
@column({ columnName: 'field_type', serializeAs: 'field_type' }) declare fieldType: string
|
||||
@column() declare required: boolean
|
||||
@column() declare options: Record<string, unknown> | null
|
||||
@column() declare status: string
|
||||
@column.dateTime({ autoCreate: true, serializeAs: null }) declare createdAt: DateTime
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
|
||||
declare updatedAt: DateTime
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import router from '@adonisjs/core/services/router'
|
||||
import { middleware } from '#start/kernel'
|
||||
|
||||
const CustomFieldsController = () =>
|
||||
import('#base_modules/CustomField/controller/custom_fields_controller')
|
||||
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/', [CustomFieldsController, 'index'])
|
||||
})
|
||||
.prefix('/api/custom-fields')
|
||||
.use([middleware.auth(), middleware.tenant()])
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
|
||||
export default class TenantController {
|
||||
/**
|
||||
* @summary Resolve current tenant context
|
||||
* @responseBody 200 - {"tenant":{"id":42,"name":"Acme GmbH","slug":"acme-gmbh"}}
|
||||
* @responseBody 400 - {"code":"E_TENANT_REQUIRED","message":"The X-Tenant-Id header is required."}
|
||||
* @responseBody 403 - {"code":"E_TENANT_ACCESS_DENIED","message":"You do not have access to this tenant."}
|
||||
*/
|
||||
async current({ response, tenant }: HttpContext) {
|
||||
return response.ok({ tenant })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { BaseModel, column, manyToMany } from '@adonisjs/lucid/orm'
|
||||
import type { ManyToMany } from '@adonisjs/lucid/types/relations'
|
||||
import { DateTime } from 'luxon'
|
||||
import Admin from '#base_modules/Admin/model/admin'
|
||||
|
||||
export default class Tenant extends BaseModel {
|
||||
@column({ isPrimary: true, columnName: 'id' })
|
||||
declare id: number
|
||||
|
||||
@column({ columnName: 'name' })
|
||||
declare name: string
|
||||
|
||||
@column({ columnName: 'slug' })
|
||||
declare slug: string
|
||||
|
||||
@column({ columnName: 'status' })
|
||||
declare status: 'active' | 'inactive'
|
||||
|
||||
@column.dateTime({ autoCreate: true, columnName: 'created_at' })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true, columnName: 'updated_at' })
|
||||
declare updatedAt: DateTime | null
|
||||
|
||||
@manyToMany(() => Admin, {
|
||||
pivotTable: 'admin_tenants',
|
||||
pivotForeignKey: 'tenant_id',
|
||||
pivotRelatedForeignKey: 'admin_id',
|
||||
})
|
||||
declare admins: ManyToMany<typeof Admin>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import router from '@adonisjs/core/services/router'
|
||||
import { middleware } from '#start/kernel'
|
||||
|
||||
const TenantController = () => import('#base_modules/Tenant/controller/tenant_controller')
|
||||
|
||||
router
|
||||
.get('/tenants/current', [TenantController, 'current'])
|
||||
.use([middleware.auth(), middleware.tenant()])
|
||||
@@ -0,0 +1,15 @@
|
||||
import Tenant from '#base_modules/Tenant/model/tenant'
|
||||
|
||||
export type ResolvedTenant = { id: number; name: string; slug: string }
|
||||
|
||||
export default class TenantAccessService {
|
||||
async resolveForAdmin(adminId: number, tenantId: number): Promise<ResolvedTenant | null> {
|
||||
const tenant = await Tenant.query()
|
||||
.where('id', tenantId)
|
||||
.where('status', 'active')
|
||||
.whereHas('admins', (admins) => admins.where('admins.id', adminId))
|
||||
.first()
|
||||
|
||||
return tenant ? { id: tenant.id, name: tenant.name, slug: tenant.slug } : null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user