This commit is contained in:
Kiss Dávid
2026-03-29 11:18:31 +02:00
parent 1ebb2e53a9
commit d55e085741
16 changed files with 4130 additions and 0 deletions
+312
View File
@@ -0,0 +1,312 @@
# Paprika API Documentation
The Paprika API provides endpoints for user authentication, managing restaurant menus, and menu items.
## Base URL
The base URL for the API is `http://localhost:3333` (default for AdonisJS development).
## Authentication
Authentication is handled via Bearer tokens. Include `Authorization: Bearer <token>` in the headers for protected routes.
### Register
Registers a new user.
- **URL:** `/api/register`
- **Method:** `POST`
- **Body:**
- `email` (string, required): The user's email address.
- `password` (string, required): The user's password.
- `fullName` (string, optional): The user's full name.
- **Response:** User access token object.
### Login
Authenticates a user and returns an access token.
- **URL:** `/api/login`
- **Method:** `POST`
- **Body:**
- `email` (string, required): The user's email address.
- `password` (string, required): The user's password.
- **Response:** User access token object.
### Logout
Logs out the current user by deleting the access token.
- **URL:** `/api/logout`
- **Method:** `DELETE`
- **Authentication:** Required
- **Response:** `{"message": "User logged out"}`
### Get Me
Retrieves information about the currently authenticated user.
- **URL:** `/api/me`
- **Method:** `GET`
- **Authentication:** Required
- **Response:** `{"user": { ...user data... }}` or `{"message": "Unauthorized"}`
---
## Restaurant Menus
### Get All Menus
Retrieves all restaurant menus, preloading items and their categories.
- **URL:** `/api/menus`
- **Method:** `GET`
- **Response:** Array of menu objects.
### Get One Menu
Retrieves a single restaurant menu by ID, preloading items and their categories.
- **URL:** `/api/menus`
- **Method:** `POST`
- **Body:**
- `id` (number, required): The ID of the menu to retrieve.
- **Response:** Menu object.
### Create Menu
Creates a new restaurant menu.
- **URL:** `/api/menus/create`
- **Method:** `POST`
- **Authentication:** Required (Admin)
- **Body:**
- `name` (string, required): The name of the menu (e.g., "2025.09.08. Hétfő").
- `nameHu` (string, optional): Hungarian translation of the menu name.
- `nameEn` (string, optional): English translation of the menu name.
- **Response:** Created menu object.
### Update Menu
Updates an existing restaurant menu.
- **URL:** `/api/menus`
- **Method:** `PUT`
- **Authentication:** Required (Admin)
- **Body:**
- `id` (number, required): The ID of the menu to update.
- `name` (string, optional): The new name of the menu.
- `nameHu` (string, optional): The new Hungarian name of the menu.
- `nameEn` (string, optional): The new English name of the menu.
- **Response:** Updated menu object.
### Delete Menu
Deletes a restaurant menu and detaches all connected menu items.
- **URL:** `/api/menus`
- **Method:** `DELETE`
- **Authentication:** Required (Admin)
- **Rate Limited:** Yes
- **Body:**
- `id` (number, required): The ID of the menu to delete.
- **Response:** `{"message": "Menu deleted"}` or error.
---
## Restaurant Menu Items
### Get All Menu Items
Retrieves all restaurant menu items, preloading category and menus.
- **URL:** `/api/menu-items`
- **Method:** `GET`
- **Response:** Array of menu item objects.
### Get One Menu Item
Retrieves a single restaurant menu item by ID, preloading category and menus.
- **URL:** `/api/menu-items`
- **Method:** `POST`
- **Body:**
- `id` (number, required): The ID of the menu item to retrieve.
- **Response:** Menu item object.
### Create Menu Item
Creates a new restaurant menu item. Note: This item must be manually attached to a menu using the `/api/menu-items/attach` endpoint.
- **URL:** `/api/menu-items/create`
- **Method:** `POST`
- **Authentication:** Required (Admin)
- **Body:**
- `name` (string, required): The name of the menu item.
- `nameHu` (string, required): Hungarian translation of the item name.
- `nameEn` (string, required): English translation of the item name.
- `priceSmall` (number, required): The price for a small serving.
- `priceLarge` (number, required): The price for a large serving.
- `description` (string, optional): General description of the menu item.
- `descriptionHu` (string, optional): Hungarian translation of the description.
- `descriptionEn` (string, optional): English translation of the description.
- `categoryId` (number, optional): The ID of the category this item belongs to.
- **Response:** Created menu item object.
### Update Menu Item
Updates an existing restaurant menu item.
- **URL:** `/api/menu-items`
- **Method:** `PUT`
- **Authentication:** Required (Admin)
- **Body:**
- `id` (number, required): The ID of the menu item to update.
- `name` (string, optional): The new name.
- `nameHu` (string, optional): The new Hungarian name.
- `nameEn` (string, optional): The new English name.
- `priceSmall` (number, optional): The new price for small.
- `priceLarge` (number, optional): The new price for large.
- `description` (string, optional): The new description.
- `descriptionHu` (string, optional): The new Hungarian description.
- `descriptionEn` (string, optional): The new English description.
- `categoryId` (number, optional): The new category ID.
- **Response:** Updated menu item object.
### Attach Menu Item to Menu
Connects an existing menu item to a menu (many-to-many relationship).
- **URL:** `/api/menu-items/attach`
- **Method:** `POST`
- **Authentication:** Required (Admin)
- **Rate Limited:** No
- **Body:**
- `item_id` (number, required): The ID of the menu item to attach.
- `menu_id` (number, required): The ID of the menu to attach the item to.
- **Response:** `{"message": "Menu item connected to menu"}`
### Delete Menu Item
Deletes a restaurant menu item and detaches it from all menus.
- **URL:** `/api/menu-items`
- **Method:** `DELETE`
- **Authentication:** Required (Admin)
- **Rate Limited:** Yes
- **Body:**
- `id` (number, required): The ID of the menu item to delete.
- **Response:** `{"message": "Menu item deleted"}` or error.
---
## Users (Administrators Only)
### Get All Users
Retrieves all users.
- **URL:** `/api/users`
- **Method:** `GET`
- **Authentication:** Required (Admin)
- **Response:** Array of user objects.
### Get One User
Retrieves a single user by ID.
- **URL:** `/api/users/get-one`
- **Method:** `POST`
- **Authentication:** Required (Admin)
- **Body:**
- `id` (number, required): The ID of the user to retrieve.
- **Response:** User object.
### Update User
Updates an existing user.
- **URL:** `/api/users`
- **Method:** `PUT`
- **Authentication:** Required (Admin)
- **Body:**
- `id` (number, required): The ID of the user to update.
- `email` (string, optional): The new email of the user.
- `fullName` (string, optional): The new full name of the user.
- `role` (string, optional): The new role of the user (e.g., 'user', 'admin').
- **Response:** Updated user object.
### Delete User
Deletes a user.
- **URL:** `/api/users`
- **Method:** `DELETE`
- **Authentication:** Required (Admin)
- **Body:**
- `id` (number, required): The ID of the user to delete.
- **Response:** `{"message": "User deleted"}` or success.
---
## Categories
### Get All Categories
Retrieves all categories.
- **URL:** `/api/categories`
- **Method:** `GET`
- **Response:** Array of category objects.
### Get One Category
Retrieves a single category by ID.
- **URL:** `/api/categories`
- **Method:** `POST`
- **Body:**
- `id` (number, required): The ID of the category to retrieve.
- **Response:** Category object.
### Create Category
Creates a new category.
- **URL:** `/api/categories/create`
- **Method:** `POST`
- **Authentication:** Required (Admin)
- **Body:**
- `name` (string, required): The name of the category.
- `nameHu` (string, optional): Hungarian translation of the name.
- `nameEn` (string, optional): English translation of the name.
- `slug` (string, required): The slug of the category.
- **Response:** Created category object.
### Update Category
Updates an existing category.
- **URL:** `/api/categories`
- **Method:** `PUT`
- **Authentication:** Required (Admin)
- **Body:**
- `id` (number, required): The ID of the category to update.
- `name` (string, optional): The new name.
- `nameHu` (string, optional): The new Hungarian name.
- `nameEn` (string, optional): The new English name.
- `slug` (string, optional): The new slug.
- **Response:** Updated category object.
### Delete Category
Deletes a category.
- **URL:** `/api/categories`
- **Method:** `DELETE`
- **Authentication:** Required (Admin)
- **Rate Limited:** Yes
- **Body:**
- `id` (number, required): The ID of the category to delete.
- **Response:** `{"message": "Category deleted"}` or error.
---
## Images
### Upload Image
Uploads an image for menu items or categories. Supported extensions: jpg, jpeg, png, webp, gif. Max size: 10mb.
- **URL:** `/api/images/upload`
- **Method:** `POST`
- **Authentication:** Required (Admin)
- **Body:**
- `image` (file, required): The image file to upload.
- **Response:** `{"filename": "...", "url": "..."}`
### Show Image
Retrieves and displays a previously uploaded image.
- **URL:** `/api/images/:filename`
- **Method:** `GET`
- **Response:** Image stream with correct MIME type.
---
## Rate Limiting
Some endpoints (like login, registration, and deletion) are protected by rate limiting to prevent abuse. If you receive a `429 Too Many Requests` response, please wait before trying again.
+54
View File
@@ -0,0 +1,54 @@
### Paprika API Model Descriptions
This document describes the main data models used in the Paprika API, including their fields and relationships.
#### User
The `User` model represents an authenticated user in the system.
- `id`: Unique identifier (primary key).
- `fullName`: The full name of the user (can be null).
- `email`: The user's email address (unique, used for login).
- `password`: Hashed password for authentication.
- `role`: User role for authorization (e.g., 'user', 'admin').
- `createdAt`: Timestamp when the user was created.
- `updatedAt`: Timestamp when the user was last updated.
#### Category
Categories are used to group `RestaurantMenuItem` objects.
- `id`: Unique identifier (primary key).
- `name`: The primary name of the category (typically Hungarian).
- `nameHu`: Hungarian translation of the category name.
- `nameEn`: English translation of the category name.
- `slug`: A URL-friendly version of the category name.
- `createdAt`: Timestamp when the category was created.
- `updatedAt`: Timestamp when the category was last updated.
- **Relationships**:
- `menuItems`: Has many `RestaurantMenuItem` records.
#### RestaurantMenu
Represents a specific daily or periodic menu.
- `id`: Unique identifier (primary key).
- `name`: The primary name of the menu (e.g., "2025.09.08. Hétfő").
- `nameHu`: Hungarian translation of the menu name.
- `nameEn`: English translation of the menu name.
- `createdAt`: Timestamp when the menu was created.
- `updatedAt`: Timestamp when the menu was last updated.
- **Relationships**:
- `items`: Many-to-many relationship with `RestaurantMenuItem` records via `menu_item_menus` table.
#### RestaurantMenuItem
Individual dishes or items within a `RestaurantMenu`.
- `id`: Unique identifier (primary key).
- `name`: The primary name of the item.
- `nameHu`: Hungarian translation of the item name.
- `nameEn`: English translation of the item name.
- `priceSmall`: Price for a small portion.
- `priceLarge`: Price for a large portion.
- `description`: General description of the item.
- `descriptionHu`: Hungarian translation of the description.
- `descriptionEn`: English translation of the description.
- `categoryId`: Foreign key connecting to `Category`.
- `createdAt`: Timestamp when the item was created.
- `updatedAt`: Timestamp when the item was last updated.
- **Relationships**:
- `category`: Belongs to a `Category`.
- `menus`: Many-to-many relationship with `RestaurantMenu` records via `menu_item_menus` table.
+96
View File
@@ -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
}
}
}
+74
View File
@@ -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))
}
}
+93
View File
@@ -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' })
}
}
}
+14
View File
@@ -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()
}
}
+30
View File
@@ -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
}
+48
View File
@@ -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(),
})
)
+32
View File
@@ -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(),
})
)
+11
View File
@@ -0,0 +1,11 @@
import { defineConfig, services } from '@adonisjs/ally'
import type { InferSocialProviders } from '@adonisjs/ally/types'
import env from '#start/env'
const allyConfig = defineConfig({})
export default allyConfig
declare module '@adonisjs/ally/types' {
interface SocialProviders extends InferSocialProviders<typeof allyConfig> {}
}
@@ -0,0 +1,113 @@
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
async up() {
this.schema.createTable('users', (table) => {
table.increments('id')
table.string('full_name').notNullable()
table.string('email').notNullable().unique()
table.string('role').notNullable().defaultTo('user')
table.string('password').notNullable()
table.timestamp('created_at', { useTz: true }).notNullable()
table.timestamp('updated_at', { useTz: true }).notNullable()
})
this.schema.createTable('auth_access_tokens', (table) => {
table.increments('id')
table
.integer('tokenable_id')
.unsigned()
.notNullable()
.references('id')
.inTable('users')
.onDelete('CASCADE')
table.string('type').notNullable()
table.string('name').nullable()
table.string('hash').notNullable()
table.text('abilities').notNullable()
table.timestamp('created_at')
table.timestamp('updated_at')
table.timestamp('last_used_at').nullable()
table.timestamp('expires_at').nullable()
})
this.schema.createTable('categories', (table) => {
table.increments('id')
table.string('name').notNullable().unique()
table.string('name_hu')
table.string('name_en')
table.string('slug').notNullable().unique()
table.timestamp('created_at', { useTz: true }).notNullable()
table.timestamp('updated_at', { useTz: true }).notNullable()
})
this.schema.createTable('restaurant_menus', (table) => {
table.increments('id')
table.string('name').notNullable()
table.string('name_hu')
table.string('name_en')
table.timestamp('created_at', { useTz: true }).notNullable()
table.timestamp('updated_at', { useTz: true }).notNullable()
})
this.schema.createTable('restaurant_menu_items', (table) => {
table.increments('id')
table.string('name').notNullable()
table.string('name_hu')
table.string('name_en')
table.decimal('price_small', 10, 2).notNullable()
table.decimal('price_large', 10, 2).notNullable()
table.text('description')
table.text('description_hu')
table.text('description_en')
table
.integer('category_id')
.unsigned()
.references('id')
.inTable('categories')
.onDelete('SET NULL')
table.timestamp('created_at', { useTz: true }).notNullable()
table.timestamp('updated_at', { useTz: true }).notNullable()
})
this.schema.createTable('menu_item_menus', (table) => {
table.increments('id')
table
.integer('restaurant_menu_item_id')
.unsigned()
.notNullable()
.references('id')
.inTable('restaurant_menu_items')
.onDelete('CASCADE')
table
.integer('restaurant_menu_id')
.unsigned()
.notNullable()
.references('id')
.inTable('restaurant_menus')
.onDelete('CASCADE')
table.unique(['restaurant_menu_item_id', 'restaurant_menu_id'])
})
this.schema.createTable('rate_limits', (table) => {
table.string('key', 255).notNullable().primary()
table.integer('points', 9).notNullable().defaultTo(0)
table.bigint('expire').unsigned()
})
}
async down() {
this.schema.dropTable('menu_item_menus')
this.schema.dropTable('restaurant_menu_items')
this.schema.dropTable('restaurant_menus')
this.schema.dropTable('categories')
this.schema.dropTable('auth_access_tokens')
this.schema.dropTable('users')
this.schema.dropTable('rate_limits')
}
}
+94
View File
@@ -0,0 +1,94 @@
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import RestaurantMenuItem from '#models/restaurant_menu_item'
import Category from '#models/category'
import fs from 'node:fs'
import path from 'node:path'
import app from '@adonisjs/core/services/app'
export default class extends BaseSeeder {
async run() {
const filePath = path.join(app.makePath(), 'menu.json')
const content = fs.readFileSync(filePath, 'utf-8')
const menuItems = JSON.parse(content)
// Create categories
const categoriesData = [
{ name: 'Levesek', nameHu: 'Levesek', nameEn: 'Soups', slug: 'levesek' },
{ name: 'Főzelékek', nameHu: 'Főzelékek', nameEn: 'Vegetable Stews', slug: 'fozelekek' },
{ name: 'Készételek', nameHu: 'Készételek', nameEn: 'Ready Meals', slug: 'keszetelek' },
{
name: 'Frissensültek',
nameHu: 'Frissensültek',
nameEn: 'Fresh Roasts',
slug: 'frissensultek',
},
{ name: 'Tészták', nameHu: 'Tészták', nameEn: 'Pasta', slug: 'tesztak' },
{ name: 'Desszertek', nameHu: 'Desszertek', nameEn: 'Desserts', slug: 'desszertek' },
{ name: 'Egyéb', nameHu: 'Egyéb', nameEn: 'Other', slug: 'egyeb' },
{ name: 'Köretek', nameHu: 'Köretek', nameEn: 'Sides', slug: 'koretek' },
{ name: 'Savanyúság', nameHu: 'Savanyúság', nameEn: 'Pickles', slug: 'savanyusag' },
]
const categories: Record<string, Category> = {}
for (const catData of categoriesData) {
categories[catData.slug] = await Category.updateOrCreate({ slug: catData.slug }, catData)
}
const getCategoryId = (name: string): number => {
const lowerName = name.toLowerCase()
if (lowerName.includes('leves')) return categories['levesek'].id
if (lowerName.includes('főzelék') || lowerName.includes('káposzta'))
return categories['fozelekek'].id
if (
lowerName.includes('fánk') ||
(lowerName.includes('szelet') &&
(lowerName.includes('üdítős') || lowerName.includes('gesztenyés'))) ||
lowerName.includes('gombóc') ||
lowerName.includes('linzer') ||
lowerName.includes('nudli') ||
lowerName.includes('lúdláb') ||
lowerName.includes('tekercs')
)
return categories['desszertek'].id
if (
lowerName.includes('tészta') ||
lowerName.includes('spagetti') ||
lowerName.includes('nudli') ||
lowerName.includes('csusza')
)
return categories['tesztak'].id
if (
lowerName.includes('rántott') ||
lowerName.includes('sült') ||
lowerName.includes('grill')
)
return categories['frissensultek'].id
if (
lowerName.includes('pörkölt') ||
lowerName.includes('tokány') ||
lowerName.includes('aprópecsenye') ||
lowerName.includes('rakott')
)
return categories['keszetelek'].id
return categories['egyeb'].id
}
for (const item of menuItems) {
const { name, nameHu, nameEn, priceLarge, priceSmall } = item
if (name) {
await RestaurantMenuItem.updateOrCreate(
{ name },
{
name,
nameHu,
nameEn,
priceLarge,
priceSmall,
categoryId: getCategoryId(nameHu),
}
)
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'
export default class extends BaseSeeder {
async run() {
await User.create({
email: 'kiss.david@test.hu',
password: 'Palacsinta1.',
fullName: 'Kiss Dávid',
role: 'admin',
})
}
}
+2732
View File
File diff suppressed because it is too large Load Diff
Executable
+405
View File
@@ -0,0 +1,405 @@
LEVESEK Egész/Fél
Babgulyás, 1200, 840
Bakonyi betyárleves, 1200, 840
Bányászgulyás, 1300, 910
Bográcsgulyás, 1200, 840
Brokkoli krémleves, 1000, 700
Burgonya krémleves, 1000, 700
Csípőssavanyú leves, 1000, 700
Csont leves, 1000, 700
Daragaluska leves, 1000, 700
Fehérbab leves kolbásszal, 1200, 840
Fejtett bableves kolbásszal, 1200, 840
Fokhagyma krémleves, 1000, 700
Frankfurti leves, 1200, 840
Gombakrém leves, 1000, 700
Gombaleves, 1000, 700
Gulyásleves, 1200, 840
Gyümölcsleves, 1200, 840
Hagyma krémleves, 1000, 700
Halászlé, 2000, 1400
Hamis gulyásleves, 1000, 700
Húsgaluska leves, 1200, 840
Jókai bableves, 1200, 840
Káposztás bab leves, 1200, 840
Karalábé leves, 1000, 700
Karfiolleves, 1000, 700
Lebbencsleves, 1000, 700
Lencseleves, 1200, 840
Libaleves, 1200, 840
Májgaluskaleves, 1200, 840
Marhahús leves, 1200, 840
Nyírségi gomba leves, 1200, 840
Palócgulyás, 1200, 840
Paradicsom leves, 1000, 700
Sajtkrém leves, 1000, 700
Sertésragu leves, 1200, 840
Sütőtök krémleves, 1000, 700
Szárnyas raguleves, 1200, 840
Tarhonyaleves, 1000, 700
Tárkonyos libazúza leves, 1300, 910
Tarkabableves, 1000, 700
Tárkonyos pulykaragu leves, 1200, 840
Tárkonyos vadragu leves, 1300, 910
Tejfölös erdei gombaleves, 1000, 700
Tejfölös burgonya leves, 1000, 700
Tojás leves, 1000, 700
Tyúkhúsleves, 1200, 840
Zellerkrémleves, 1000, 700
Zöldbableves, 1000, 700
Zöldborsóleves, 1000, 700
Zöldséges burgonya leves, 1000, 700
Zöldségleves, 1000, 700
FŐZELÉKEK Egész/Fél
Fehérbab főzelék, 1100, 770
Kelkáposzta főzelék, 1100, 770
Lencsefőzelék, 1100, 770
Meggyszósz, 1100, 770
Paradicsomos káposzta, 1100, 770
Sárgaborsó főzelék, 1100, 770
Sóskamártás, 1100, 770
Spenót, 1100, 770
Tarkabab főzelék, 1100, 770
Tejfölös burgonya főzelék, 1100, 770
Tökfőzelék, 1100, 770
Zöldbabfőzelék, 1100, 770
Zöldborsó főz., 1100, 770
KÉSZÉTELEK Egész/Fél
Alfredo fussili, 2000, 1400
Baconos csirkemell saláta, 2600
Bácskai rizses hús, 1800, 1260
Bakony sertésszelet, 1700, 1190
Bakonyi csirkemell, 1700, 1190
Barackos csirkemell, 1800, 1260
BBQs csirkeszárny, 10 dkg, 900
Berni csirkemell, 1800, 1260
Birkapörkölt, 2000, 1400
Bolognai rakott tészta, 2000, 1400
Bolognai spagetti, 2000, 1400
Borjúpaprikás, 2000, 1400
Borsos tokány, 2000, 1400
Brassói aprópecsenye, 1700, 1190
Brokkolis csirkés tészta, 2000, 1400
Budapest szelet, 1700, 1190
Budapest tokány, 1700, 1190
Burgonya saláta (20 dkg), 800, 0
Burgonyás csirkemell, 2000, 1400
Cézár saláta, 2600, 0
Chillis bab, 1900, 1330
Cigánypecsenye, 1700, 1190
Citromos csirkemell, 1700, 1190
Cukkinisfetás csirkemell, 1800, 1260
Cukkinisricottás tészta, 2000, 1400
Currys csirkemell, 1700, 1190
Császár csirkemell (10 dkg), 1100, 0
Cserkészfalatok, 1700, 1190
Csikós tokány, 1700, 1190
Csirkemell Marcsi módra, 1800, 1260
Csirkemell Gellért, 1800, 1260
Csirkemell pörkölt, 1700, 1190
Csirkemelles rakott tészta, 2000, 1400
Csirkemelles tortilla, 2000, 0
Csirkepörkölt, 1700, 1190
Csirkerizottó, 1800, 1260
Csirkésfetás farfalle, 2000, 1400
Csőben sült brokkoli, 1300, 0
Csőben sült karfiol, 1700, 1190
Csülök Pékné, 2200, 1540
Csülökpörkölt, 1700, 1190
DélOlasz rakott tészta, 1900, 1330
Dubary szelet, 1800, 1260
Édessavanyú csirkemell, 1700, 1190
Erdélyi marhatokány, 2000, 1400
Erdészné kedvence, 1700, 1190
Eszterházy marhatokány, 2000, 1400
Fokhagymás medallion, 1800, 1260
Főtt füstölt csülök (15 kgos), 7000, 0
Francia rakott burgonya, 1400, 980
Francia saláta (20 dkg), 800, 0
Francia sertésborda, 1800, 1260
Fussili primavera, 2000, 1400
Fűszeres csirkemell falatok, 1800, 1260
Gombás csirkemell, 1700, 1190
Gombás máj (10 dkg), 800, 0
Gombás szelet, 1700, 1190
Gombássajtos tészta, 1600, 1120
Grill zöldséges sajtos bulgur, 1600, 0
Harcsapörkölt, 2000, 1400
Hawai csirkemell, 1800, 1260
Hawai saláta (20 dkg), 800, 0
Hentes tokány, 1700, 1190
Hétvezér tokány, 1700, 1190
Hízott kacsamáj (10 dkg), 5000, 0
Holstein szelet, 1700, 1190
Hortobágyi húsos palacsinta, 1700, 1190
Húsos lasagne, 2000, 0
Húsos rakott burgonya, 1500, 1050
Jóasszony tokány, 1700, 1190
Kacsamáj rizottó, 1800, 1260
Kakashere pörkölt, 1700, 1190
Káposztás hús, 1600, 1120
Karfiolos sertésszelet, 1700, 1190
Kétsajtos rizottó, 1800, 1260
Kocsonya, 1400, 0
Körömpörkölt, 1600, 1120
Kukorica saláta (20 dkg), 800, 0
Lecsós virsli, 1700, 1190
Libabelsőség pörkölt, 1700, 1190
Libamell paprikás, 2600, 1400
Libaszív pörkölt, 1700, 1190
Libaszív rizottó, 1800, 1260
Libaszív vetrece, 1700, 1190
Libazúza rizottó, 1800, 1260
Ludaskása, 1800, 1260
Magyaros csirkemell, 2000, 1400
Magyaróvári sertésszelet, 1800, 1260
Majonézes káposzta saláta (20 dkg), 800, 0
Majonézes tészta saláta (20 dkg), 800, 0
Marhapörkölt, 2000, 1400
Mexikói tokány, 1700, 1190
Mézzel csurgatott sárgadinnye, 800, 0
Milánói rakott tészta, 2000, 1400
Milánói spagetti, 2000, 1400
Mozzarellás bazsalikomos paradicsomos saláta, 900, 0
Mozzarellás csirkemell, 1800, 1260
Mozzarellás harcsafilé, 2000, 1400
Orosz hússaláta (20 dkg), 900, 0
Pacal pörkölt (10 dkg), 900, 0
Paprikás burgonya, 1300, 910
Párolt Marhaszelet, 1200, 600
Pásztortarhonya, 2000, 1400
Pirított csirke máj (10 dkg), 800, 0
Pirított sertés máj (10 dkg), 800, 0
Pulykapörkölt, 1700, 1190
Rácz hús, 1800, 1260
Rakott brokkoli, 1400, 980
Rakott burgonya, 1500, 1050
Rakott káposzta, 1500, 1050
Rakott karfiol, 1500, 1050
Rakott kelkáposzta, 1500, 1050
Rakott zöldbab, 1500, 1050
Rozmaringos marhasült, 2000, 1400
Sajtos brokkoli saláta (20 dkg), 800, 0
Savanyú vetrece, 1700, 1190
Savoyard szelet, 1700, 1190
Sertés pörkölt, 1700, 1190
Sertés sült, 1700, 1190
Sóletbab, 1900, 1330
Sombrero szelet, 1800, 1260
Sonkarizottó, 1800, 1260
Sonkás kocka, 1900, 1330
Spagetti Carbonara, 1900, 1330
Spenótos paradicsomos lasagne, 2000, 0
Spenótos tészta, 1600, 1120
Szalontüdő, 1700, 1190
Szarvas pörkölt, 2000, 1400
Székelykáposzta, 1600, 1120
Tarhonyás hús, 1800, 1260
Tavaszi csirkemell, 1700, 1190
Tejfölös kacsacomb darabok, 1800, 1260
Tejfölösgombás pulykacomb, 1700, 1190
Tejfölös csirkecomb, 1700, 1190
Tejfölös csirketokány, 1700, 1190
Tejfölös gombatokány, 1700, 1190
Tejfölös gombap., 1700, 1190
Tejfölös pulykacomb, 1700, 1190
Tejfölös sertészelet, 1700, 1190
Tejszínesgombás csirkemell, 1700, 1190
Tejszínesgyümölcsös csirkemell, 1700, 1190
Tejszínes rakott hal, 1600, 1120
Tejszínes zöldborsós csirkemell, 1700, 1190
Tejszíneskapros csirkemell, 1700, 1190
Temesvári sertéstokány, 1700, 1190
Tepsis zöld, 1100, 770
Thai csirkemell, 1900, 1330
Tócsni (sajt tejföl) 1 db, 1000, 0
Tócsnis hús, 2000, 0
Tojáspörkölt, 1000, 700
Tonhalas par. cukkini, 1200, 0
Tonhalas penne, 1900, 1330
Töltött káposzta, 1800, 1260
Töltött paprika, 1800, 1260
Tutitotti tészta, 2000, 1400
Tzatziki saláta (20 dkg), 800, 0
Üres kocsonya, 1000, 0
Vadas csirkemell, 1700, 1190
Vadas libamell, 2600, 1820
Vadas marhaszelet, 2000, 1400
Vaddisznó pörkölt, 2000, 1400
Váradi rakott burgonya, 1800, 1260
Velős máj (10 dkg), 800, 0
Vesevelő tojással, 1600, 1120
Vörösbabos csirkemell, 1700, 1190
Zöldborsós csirkemell, 1700, 1190
Zöldborsós sertéstokány, 1700, 1190
Zöldborsós szelet, 1700, 1190
Zöldséges spagetti felfújt, 1500, 1050
Zöldséges Lasagne, 2000, 0
Zöldséges sült tészta, 1300, 910
Zöldségesfetás tészta Saláta, 1600
Zúza pörkölt (10 dkg), 900, 0
FRISSENSÜLTEK Egész/Fél
Ananásszalsonkával töltött csirkemell, 1900, 950
Bacsa szelet, 2000, 1000
Brokkolissajtos csirkemell, 1900, 950
Egészben sült kacsa (fél kacsa), 2650
Fasírozott 1 db, 700, 350
Fűszeres steak csirkecomb (10 dkg), 1000
Gombaropogós 1 db, 600
Gombával töltött csirkemell, 1900, 950
Grill camembert, 1700
Grill csirkemell 1 db, 1100
Ínyenc rántott szelet, 2000, 1000
Ínyenc rántott sajt, 2000, 1000
Juhtúróval töltött csirkemell, 1900, 950
Kacsamájjal töltött csirkemell, 1900, 950
Karamellizált körte 1 db, 500
Kendermagos csirkemell, 1800, 900
Kijevi csirkemell, 1900, 950
Laci pecsenye (10 dkg), 1200
Lecsós szelet, 1700, 1190
Májjal töltött sertésszelet, 1900, 950
Manduláskókuszos csirkemell, 1800, 900
Natúr csirkemell, 1200, 600
Natúr szelet, 1200, 600
Nyitrai szelet, 2000, 1000
Parasztos töltött szelet, 1900, 950
Párizsi csirkemell, 1700, 850
Pecsenye kacsamáj (10 dkg), 1000
Rántott csirkecomb, 2000, 1000
Rántott lazac (10 dkg 1 db), 1500
Rántott brokkoli (10 dkg), 800
Rántott camembert, 1900, 950
Rántott cukkini (10 dkg), 800
Rántott csirkeszárny (10 dkg), 900
Rántott csirkemell, 1700, 850
Rántott gomba (10 dkg), 800
Rántott hagymakarika, 700, 350
Rántott halrudak, 1400, 700
Rántott harcsafilé (10 dkg), 850
Rántott karfiol (10 dkg), 800
Rántott máj, 1200, 600
Rántott patiszon (10 dkg), 800
Rántott sajt, 1800, 900
Rántott szelet, 1700, 850
Roston harcsa (10 dkg), 1200
Rozmaringos kacsamáj (10 dkg), 1000
Sajttalsonkával töltött csirkemell, 1900, 950
Sajttalsonkával töltött palacsinta 1 db, 900
Sajtropogós 1 db, 600
Sajttal töltött hal 1 db, 1000
Sörben sült csülök, 5500
Stefánia vagdalt, 1400, 700
Sült csirkecomb (10 dkg), 800
Sült házi kolbász (10 dkg), 900
Sült hekk (10 dkg), 800
Sült hurka (10 dkg), 800
Sült kacsacomb (10 dkg), 1700
Sült libacomb (10 dkg), 2600
Sült libamell filé (10 dkg), 2700
Sült pisztráng (10 dkg), 1200
Sült tök 1 db, 500
Szezámmagos csirkemell, 1700, 850
Szilvási csirkemell, 1900, 950
Tojásropogós 1 db, 600
Töltött csirkecomb 1 db, 1600
Velővel töltött sertésszelet, 1900, 950
Zöldségessajtos csirkemell, 1900, 950
Zöldségropogós 1 db, 600
TÉSZTÁK Egész/Fél
Aranygaluska, 1200 ,0
Csokikrémmel töltött fánk, 600, 0
Csokis fánk, 600, 0
Diós metélt, 1300, 910
Erdei gyümölcsös gombóc, 1300, 910
Fánk, 600, 0
Grízes metélt, 1300, 910
Gundel palacsinta, 700, 0
Juhtúrós sztrapacska, 1800, 1260
Káposztás kocka, 1300, 910
Káposztás sztrapacska, 1500, 1050
Krumplis tészta, 1300, 910
Lekvárral töltött fánk, 600
Máglyarakás, 1200, 0
Mákos guba vaníliával, 1500, 1050
Mákos metélt, 1300, 910
Mákos nudli, 1300, 910
Palacsinta, 500, 0
Parasztos csusza, 1800, 1260
Prézlis núdli, 1300, 910
Rakott metélt, 1300, 910
Rizsfelfújt, 1200, 0
Sajtos tejfölös spagetti, 1300, 910
Sárgabarackos gombóc vanília öntettel, 1500, 1050
Savanyú káposztás sztrapacska, 1500, 1050
Somlói, 1300, 0
Szilvalekváros derelye, 1300, 910
Szilvás gombóc, 1300, 910
Szilvás töltött nudli, 1300, 910
Tiramisu, 1200, 0
Tojásos galuska, 1300, 910
Túrógombóc tejfölös töltelékkel, 1300, 910
Túrókrémes csusza, 1600, 1120
Túrós csusza, 1500, 1050
Túrós derelye, 1300, 910
Túrós sztrapacska, 1500, 1050
Túrósvaníliás palacsinta rántva, 850
KÖRETEK Egész/Fél adag
Aszalt szilvás párolt káposzta, 800
Burgonya röszti, 800, 560
Burgonyakrokett, 800, 560
Burgonyapüré, 800, 560
Fitness saláta, 2600
Galuska, 600, 420
Görög saláta, 1600, 0
Grill zöldség, 1000, 0
Hagymás tört burgonya, 800, 560
Hasábburgonya, 800, 560
Juhtúrós galuska, 900, 630
Knédli, 700, 490
Köret tészta, 600, 420
Párolt káposzta, 800, 0
Párolt rizs, 600, 420
Petrezselymes burgonya, 600, 420
Steak burgonya, 800, 560
Sült burgonya, 800, 560
Tarhonya, 600, 420
Tükörtojás 1 db, 300
Zöld köret, 800, 560
Zöldséges rizs, 800, 560
Zsemlegombóc, 600, 420
SAVANYÚSÁG Egész/Fél adag
Almapaprika, 600, 0
Cékla, 600, 0
Csalamádé, 600, 0
Csemegeuborka, 600, 0
Erős paprika, 600, 0
Fejes saláta, 700, 0
Friss saláta, 900, 0
Káposzta saláta, 600, 0
Kovászos uborka, 800, 0
Paradicsom saláta, 800, 0
Savanyú káposzta, 600, 0
Uborka saláta, 800, 0
EGYÉB:
Kenyér, 150, 0
Ketchup, 500, 0
Mustár, 500, 0
Plusz lekvár, 400, 0
Plusz sajt, 500, 0
Plusz tejföl, 400, 0
Pörkölt szaft, 200, 0
Tartár mártás, 600, 0
Áfonya lekvár, 500, 0
Vanília öntet, 400, 0
Plusz mártás (Pl: Töltött paprika töltött káposzta vadas marhaszelet...), 400, 0
Műanyag doboz, 100, 0
Műanyag villa kanál, 10, 0
Szatyor, 30, 0
+9
View File
@@ -0,0 +1,9 @@
import { test } from '@japa/runner'
test.group('Categories', () => {
test('get all', async ({ assert, client }) => {
const response = await client.get('api/categories')
assert.equal(response.status(), 200)
assert.isArray(response.body())
})
})