stuff
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import Category from '#models/category'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import { createCategoryValidator, updateCategoryValidator } from '#validators/category'
|
||||
|
||||
export default class CategoriesController {
|
||||
/**
|
||||
* Retrieve all categories.
|
||||
*
|
||||
* @returns An array of all categories.
|
||||
*/
|
||||
async getAll() {
|
||||
const categories = await Category.all()
|
||||
return categories
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single category.
|
||||
*
|
||||
* @param request - The HTTP context containing the request data.
|
||||
* @returns The category with the specified id.
|
||||
*/
|
||||
async getOne({ request }: HttpContext) {
|
||||
try {
|
||||
const id = request.body().id
|
||||
const category = await Category.find(id)
|
||||
return category
|
||||
} catch (error) {
|
||||
logger.error('Category retrieval failed: %s', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a category.
|
||||
*
|
||||
* @bodyParam *name* - string | The name of the category.
|
||||
* @bodyParam *slug* - string | The slug of the category.
|
||||
* @returns The created category.
|
||||
*/
|
||||
async create({ request, auth }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(createCategoryValidator)
|
||||
const category = await Category.create(data)
|
||||
logger.info('Category created: %s by %s', category.name, auth.getUserOrFail().email)
|
||||
return category
|
||||
} catch (error) {
|
||||
logger.error('Category creation failed: %s', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a category.
|
||||
*
|
||||
* @bodyParam *id* - number | The id of the category to update.
|
||||
* @bodyParam *name* - string | The new name of the category.
|
||||
* @bodyParam *slug* - string | The new slug of the category.
|
||||
*/
|
||||
async update({ request, auth }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(updateCategoryValidator)
|
||||
const category = await Category.find(data.id)
|
||||
if (!category) {
|
||||
return { message: 'Category not found' }
|
||||
}
|
||||
category.merge(data)
|
||||
await category.save()
|
||||
logger.info('Category updated: %s by %s', category.name, auth.getUserOrFail().email)
|
||||
return category
|
||||
} catch (error) {
|
||||
logger.error('Category update failed: %s', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a category.
|
||||
*
|
||||
* @bodyParam *id* - number | The id of the category to delete.
|
||||
*/
|
||||
async delete({ request, auth }: HttpContext) {
|
||||
const id = request.body().id
|
||||
try {
|
||||
const category = await Category.find(id)
|
||||
if (!category) {
|
||||
return { message: 'Category not found' }
|
||||
}
|
||||
await category.delete()
|
||||
logger.info('Category deleted: %s by %s', category.name, auth.getUserOrFail().email)
|
||||
return { message: 'Category deleted' }
|
||||
} catch (error) {
|
||||
logger.error('Category deletion failed: %s', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import app from '@adonisjs/core/services/app'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import { cuid } from '@adonisjs/core/helpers'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { extname } from 'node:path'
|
||||
|
||||
const allowedExtnames = ['jpg', 'jpeg', 'png', 'webp', 'gif']
|
||||
|
||||
const imageMimeByExt: Record<string, string> = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.webp': 'image/webp',
|
||||
'.gif': 'image/gif',
|
||||
}
|
||||
|
||||
export default class ImagesController {
|
||||
async upload({ request, response }: HttpContext) {
|
||||
const image = request.file('image', {
|
||||
size: '10mb',
|
||||
extnames: allowedExtnames,
|
||||
})
|
||||
|
||||
if (!image) {
|
||||
return response.badRequest({ message: 'Image file is required' })
|
||||
}
|
||||
|
||||
if (!image.isValid) {
|
||||
const message = image.errors.map((error) => error.message).join(', ')
|
||||
return response.badRequest({ message: message || 'Invalid image file' })
|
||||
}
|
||||
|
||||
const uploadsDir = app.makePath('storage', 'uploads')
|
||||
await fs.mkdir(uploadsDir, { recursive: true })
|
||||
|
||||
const extension = image.extname || 'bin'
|
||||
const filename = `${cuid()}.${extension}`
|
||||
await image.move(uploadsDir, { name: filename, overwrite: false })
|
||||
|
||||
if (image.state !== 'moved') {
|
||||
logger.error('Image upload failed: %s', image.errors)
|
||||
return response.internalServerError({ message: 'Image upload failed' })
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
filename,
|
||||
url: `/api/images/${filename}`,
|
||||
})
|
||||
}
|
||||
|
||||
async show({ params, response }: HttpContext) {
|
||||
const filename = String(params.filename || '')
|
||||
|
||||
if (!filename || filename.includes('/') || filename.includes('\\')) {
|
||||
return response.badRequest({ message: 'Invalid image name' })
|
||||
}
|
||||
|
||||
const filePath = app.makePath('storage', 'uploads', filename)
|
||||
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
} catch {
|
||||
return response.notFound({ message: 'Image not found' })
|
||||
}
|
||||
|
||||
const mimeType = imageMimeByExt[extname(filename).toLowerCase()] || 'application/octet-stream'
|
||||
response.header('Content-Type', mimeType)
|
||||
response.header('Cache-Control', 'public, max-age=86400')
|
||||
|
||||
return response.stream(createReadStream(filePath))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import User from '#models/user'
|
||||
import logger from '@adonisjs/core/services/logger'
|
||||
import { createUserValidator, updateUserValidator } from '#validators/user'
|
||||
|
||||
export default class UsersController {
|
||||
/**
|
||||
* Retrieve all users.
|
||||
*
|
||||
* @returns An array of all users.
|
||||
*/
|
||||
async getAll() {
|
||||
const users = await User.all()
|
||||
return users
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single user.
|
||||
*
|
||||
* @param request - The HTTP context containing the request data.
|
||||
* @returns The user with the specified id.
|
||||
*/
|
||||
async getOne({ request, response }: HttpContext) {
|
||||
try {
|
||||
const id = request.body().id
|
||||
const user = await User.find(id)
|
||||
if (!user) {
|
||||
return response.notFound({ message: 'User not found' })
|
||||
}
|
||||
return user
|
||||
} catch (error) {
|
||||
logger.error('User retrieval failed: %s', error)
|
||||
return response.internalServerError({ message: 'User retrieval failed' })
|
||||
}
|
||||
}
|
||||
async create({ request, auth, response }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(createUserValidator)
|
||||
const user = await User.create(data)
|
||||
logger.info('User created: %s by %s', user.email, auth.getUserOrFail().email)
|
||||
return user
|
||||
} catch (error) {
|
||||
logger.error('User creation failed: %s', error)
|
||||
return response.internalServerError({ message: 'User creation failed' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user.
|
||||
*
|
||||
* @bodyParam *id* - number | The id of the user to update.
|
||||
* @bodyParam *email* - string | The new email of the user.
|
||||
* @bodyParam *fullName* - string | The new full name of the user.
|
||||
* @bodyParam *role* - string | The new role of the user.
|
||||
*/
|
||||
async update({ request, auth, response }: HttpContext) {
|
||||
try {
|
||||
const data = await request.validateUsing(updateUserValidator)
|
||||
const user = await User.find(data.id)
|
||||
if (!user) {
|
||||
return response.notFound({ message: 'User not found' })
|
||||
}
|
||||
user.merge(data)
|
||||
await user.save()
|
||||
logger.info('User updated: %s by %s', user.email, auth.getUserOrFail().email)
|
||||
return user
|
||||
} catch (error) {
|
||||
logger.error('User update failed: %s', error)
|
||||
return response.internalServerError({ message: 'User update failed' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user.
|
||||
*
|
||||
* @bodyParam *id* - number | The id of the user to delete.
|
||||
*/
|
||||
async delete({ request, auth, response }: HttpContext) {
|
||||
const id = request.body().id
|
||||
try {
|
||||
const user = await User.find(id)
|
||||
if (!user) {
|
||||
return response.notFound({ message: 'User not found' })
|
||||
}
|
||||
await user.delete()
|
||||
logger.info('User deleted: %s by %s', user.email, auth.getUserOrFail().email)
|
||||
return response.ok({ message: 'User deleted' })
|
||||
} catch (error) {
|
||||
logger.error('User deletion failed: %s', error)
|
||||
return response.internalServerError({ message: 'User deletion failed' })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
|
||||
export default class AdminMiddleware {
|
||||
async handle(ctx: HttpContext, next: NextFn) {
|
||||
const user = ctx.auth.user
|
||||
|
||||
if (!user || user.role !== 'admin') {
|
||||
return ctx.response.forbidden({ message: 'Access denied. Administrators only.' })
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
|
||||
import type { HasMany } from '@adonisjs/lucid/types/relations'
|
||||
import RestaurantMenuItem from '#models/restaurant_menu_item'
|
||||
|
||||
export default class Category extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare name: string
|
||||
|
||||
@column({ columnName: 'name_hu' })
|
||||
declare nameHu: string | null
|
||||
|
||||
@column({ columnName: 'name_en' })
|
||||
declare nameEn: string | null
|
||||
|
||||
@column()
|
||||
declare slug: string
|
||||
|
||||
@hasMany(() => RestaurantMenuItem)
|
||||
declare menuItems: HasMany<typeof RestaurantMenuItem>
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const createCategoryValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().unique(async (db, value) => {
|
||||
const match = await db.from('categories').select('id').where('name', value).first()
|
||||
return !match
|
||||
}),
|
||||
nameHu: vine.string().optional(),
|
||||
nameEn: vine.string().optional(),
|
||||
slug: vine.string().unique(async (db, value) => {
|
||||
const match = await db.from('categories').select('id').where('slug', value).first()
|
||||
return !match
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
export const updateCategoryValidator = vine.compile(
|
||||
vine.object({
|
||||
id: vine.number(),
|
||||
name: vine
|
||||
.string()
|
||||
.unique(async (db, value, field) => {
|
||||
const match = await db
|
||||
.from('categories')
|
||||
.select('id')
|
||||
.where('name', value)
|
||||
.whereNot('id', field.data.id)
|
||||
.first()
|
||||
return !match
|
||||
})
|
||||
.optional(),
|
||||
nameHu: vine.string().optional(),
|
||||
nameEn: vine.string().optional(),
|
||||
slug: vine
|
||||
.string()
|
||||
.unique(async (db, value, field) => {
|
||||
const match = await db
|
||||
.from('categories')
|
||||
.select('id')
|
||||
.where('slug', value)
|
||||
.whereNot('id', field.data.id)
|
||||
.first()
|
||||
return !match
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const createUserValidator = vine.compile(
|
||||
vine.object({
|
||||
email: vine.string().email().normalizeEmail(),
|
||||
password: vine.string().minLength(8),
|
||||
fullName: vine.string(),
|
||||
role: vine.string(),
|
||||
})
|
||||
)
|
||||
export const updateUserValidator = vine.compile(
|
||||
vine.object({
|
||||
id: vine.number(),
|
||||
email: vine
|
||||
.string()
|
||||
.email()
|
||||
.normalizeEmail()
|
||||
.unique(async (db, value, field) => {
|
||||
const match = await db
|
||||
.from('users')
|
||||
.select('id')
|
||||
.where('email', value)
|
||||
.whereNot('id', field.data.id)
|
||||
.first()
|
||||
return !match
|
||||
})
|
||||
.optional(),
|
||||
password: vine.string().minLength(8).optional(),
|
||||
fullName: vine.string().optional(),
|
||||
role: vine.string().optional(),
|
||||
})
|
||||
)
|
||||
Reference in New Issue
Block a user