stuff
This commit is contained in:
+312
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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(),
|
||||
})
|
||||
)
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user