Add initial project files

This commit is contained in:
Igor Martyniuk
2026-09-01 13:03:41 +03:00
parent 22e111bb44
commit 74cb5b6176
1841 changed files with 242819 additions and 30 deletions
+18
View File
@@ -0,0 +1,18 @@
export default [
{
slug: 'admins.view',
name: 'Admin view',
},
{
slug: 'admins.create',
name: 'Admin create',
},
{
slug: 'admins.update',
name: 'Admin update',
},
{
slug: 'admins.delete',
name: 'Admin delete',
},
]
@@ -0,0 +1,96 @@
import { HttpContext } from '@adonisjs/core/http'
import { createAdminValidator } from '#base_modules/Admin/validators/admin_create_validator'
import { updateAdminValidator } from '#base_modules/Admin/validators/admin_update_validator'
import { addSearchFilter, searchableColumns } from '#services/database_inspector'
import Admins from '#base_modules/Admin/model/admin'
import AdminsPolicy from '#base_modules/Admin/policy/admin_policy'
import { updateProfileAdminValidator } from '#base_modules/Admin/validators/admin_update_profile_validator'
import db from '@adonisjs/lucid/services/db'
export default class AdminsController {
public async index({ request, response, bouncer }: HttpContext) {
await bouncer.with(AdminsPolicy).authorize('viewList')
const {
page = 1,
perPage = 10,
search = null,
sortBy = 'id',
sortOrder = 'desc',
status = null,
} = request.all()
const query = Admins.query()
if (status) {
const statuses = Array.isArray(status) ? status : [status]
query.whereIn('status', statuses)
}
if (search) {
const columns = Admins.searchable || (await searchableColumns('admins'))
addSearchFilter(query, columns, search)
}
const result = await query.orderBy(sortBy, sortOrder).paginate(page, perPage)
return response.ok(result)
}
public async store({ request, response, bouncer }: HttpContext) {
await bouncer.with(AdminsPolicy).authorize('create')
const data = await createAdminValidator.validate(request.all())
const record = await Admins.create(data)
if (record) {
await record.related('roles').sync(data.roles)
return response.created({ message: 'Successfully created.' })
}
return response.badRequest({ message: 'Cannot create.' })
}
public async show({ request, response, bouncer }: HttpContext) {
await bouncer.with(AdminsPolicy).authorize('view')
const { id } = request.params()
if (!id) return response.notFound({ message: 'Not found.' })
const record = await Admins.find(id)
if (!record) return response.notFound({ message: 'Not found.' })
await record.load('roles')
return response.ok(record)
}
public async update({ request, response, bouncer }: HttpContext) {
await bouncer.with(AdminsPolicy).authorize('update')
const { id } = request.params()
if (!id) return response.notFound({ message: 'Not found.' })
const data = await updateAdminValidator.validate(request.all())
const record = await Admins.find(id)
if (!record) return response.notFound({ message: 'Not found.' })
record.merge(data)
await record.save()
await record.related('roles').sync(data.roles)
return response.ok({ message: 'Updated.' })
}
public async destroy({ request, response, bouncer }: HttpContext) {
await bouncer.with(AdminsPolicy).authorize('delete')
const { id } = request.params()
if (!id) return response.notFound({ message: 'Not found.' })
const admins = await Admins.all()
if (admins.length === 1) {
return response.badRequest({ message: "Last Admin can't be deleted!" })
}
const record = await Admins.find(id)
if (!record) return response.notFound({ message: 'Not found.' })
await Admins.query().where('id', id).delete()
return response.ok({ message: 'Deleted.' })
}
public async updateProfile({ request, response, auth }: HttpContext) {
const user = auth.use('api').user
if (!user) return response.unauthorized()
const data = await updateProfileAdminValidator.validate(request.all())
user.merge(data)
user.save()
await user.load('roles')
const roles = user.roles.map((role) => role.id)
const permissions = await db
.from('role_permissions')
.whereIn('role_id', roles)
.select('permission_slug')
return response.ok({ user, permissions: permissions.map((perm) => perm.permission_slug) })
}
}
+67
View File
@@ -0,0 +1,67 @@
import hash from '@adonisjs/core/services/hash'
import { compose } from '@adonisjs/core/helpers'
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
import { DbAccessTokensProvider } from '@adonisjs/auth/access_tokens'
import { BaseModel, column, manyToMany } from '@adonisjs/lucid/orm'
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'
const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
uids: ['email'],
passwordColumnName: 'password',
})
const ComposedModel = compose(BaseModel, AuthFinder) as typeof BaseModel
export default class Admins extends ComposedModel {
static searchable = {
string: ['email', 'first_name', 'last_name'],
number: ['id'],
}
@column({ isPrimary: true, columnName: 'id' })
declare id: number
@column({ columnName: 'first_name' })
declare firstName: string | null
@column({ columnName: 'last_name' })
declare lastName: string | null
@column({ columnName: 'email' })
declare email: string
@column({ columnName: 'status' })
declare status: 'active' | 'inactive'
@column({ serializeAs: null, columnName: 'password' })
declare password: string
@column.dateTime({ autoCreate: true, columnName: 'created_at' })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true, columnName: 'updated_at' })
declare updatedAt: DateTime | null
@manyToMany(() => Role, {
pivotTable: 'admin_roles',
pivotForeignKey: 'admin_id',
pivotRelatedForeignKey: 'role_id',
})
declare roles: ManyToMany<typeof Role>
declare permissions: Permission[]
static accessTokens = DbAccessTokensProvider.forModel(Admins)
async loadPermissions() {
this.permissions = await Permission.query()
.join('role_permissions', 'permissions.id', 'role_permissions.permission_id')
.join('admin_roles', 'role_permissions.role_id', 'admin_roles.role_id')
.where('admin_roles.admin_id', this.id)
.exec()
return this
}
}
+37
View File
@@ -0,0 +1,37 @@
import { BasePolicy } from '@adonisjs/bouncer'
import type { AuthorizerResponse } from '@adonisjs/bouncer/types'
import Admins from '#base_modules/Admin/model/admin'
import { getAdminPermissionSlugs } from '#services/permission_service'
export default class AdminsPolicy extends BasePolicy {
// Example permission slugs: admins.view, admins.create, admins.update, admins.delete
async viewList(user: Admins): Promise<AuthorizerResponse> {
if (!user) return false
const perms = await getAdminPermissionSlugs(user)
return perms.has('admins.view')
}
async view(user: Admins): Promise<AuthorizerResponse> {
if (!user) return false
const perms = await getAdminPermissionSlugs(user)
return perms.has('admins.view')
}
async create(user: Admins): Promise<AuthorizerResponse> {
if (!user) return false
const perms = await getAdminPermissionSlugs(user)
return perms.has('admins.create')
}
async update(user: Admins): Promise<AuthorizerResponse> {
if (!user) return false
const perms = await getAdminPermissionSlugs(user)
return perms.has('admins.update')
}
async delete(user: Admins): Promise<AuthorizerResponse> {
if (!user) return false
const perms = await getAdminPermissionSlugs(user)
return perms.has('admins.delete')
}
}
+7
View File
@@ -0,0 +1,7 @@
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
const adminController = () => import('#base_modules/Admin/controller/admin_controller')
router.put('/admin/update-profile', [adminController, 'updateProfile']).use([middleware.auth()])
router.resource('/admin', adminController).apiOnly().use('*', [middleware.auth()]) //.use(['store', 'update', 'destroy'], [middleware.auth()])
@@ -0,0 +1,20 @@
import vine from '@vinejs/vine'
export const createAdminValidator = vine.compile(
vine.object({
id: vine.number().optional(),
email: vine
.string()
.trim()
.email()
.unique(async (db, value) => {
const exists = await db.from('admins').where('email', value).first()
return !exists
}),
password: vine.string().trim().minLength(8),
firstName: vine.string().trim().optional(),
lastName: vine.string().trim().optional(),
status: vine.enum(['active', 'inactive']),
roles: vine.array(vine.number()),
})
)
@@ -0,0 +1,20 @@
import vine from '@vinejs/vine'
export const updateProfileAdminValidator = vine.compile(
vine.object({
id: vine.number(),
email: vine
.string()
.trim()
.email()
.unique(async (db, value, field) => {
const id = field.data.id
const exists = await db.from('admins').where('email', value).whereNot('id', id).first()
return !exists
}),
password: vine.string().trim().minLength(8).optional(),
firstName: vine.string().trim().minLength(1),
lastName: vine.string().trim().minLength(1),
status: vine.enum(['active', 'inactive']).optional(),
})
)
@@ -0,0 +1,22 @@
import vine from '@vinejs/vine'
export const updateAdminValidator = vine.compile(
vine.object({
id: vine.number().optional(),
email: vine
.string()
.trim()
.email()
.unique(async (db, value, field) => {
const id = field.data.id
const exists = await db.from('admins').where('email', value).whereNot('id', id).first()
return !exists
})
.optional(),
password: vine.string().trim().minLength(8).optional(),
firstName: vine.string().trim().optional(),
lastName: vine.string().trim().optional(),
status: vine.enum(['active', 'inactive']).optional(),
roles: vine.array(vine.number()),
})
)