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