From 465613f6a091dad4e1e38b0e37915008fd9a8894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiss=20D=C3=A1vid?= Date: Mon, 28 Jul 2025 13:00:46 +0200 Subject: [PATCH] Model controllers added --- .env.test | 12 ++ app/controllers/auth_controller.ts | 34 +++- .../restaurant_menu_items_controller.ts | 120 +++++++++++++ .../restaurant_menus_controller.ts | 90 ++++++++++ app/models/restaurant_menu.ts | 10 +- app/models/restaurant_menu_item.ts | 18 ++ app/validators/auth.ts | 1 + app/validators/menu.ts | 10 ++ bin/test.ts | 9 +- config/limiter.ts | 2 +- ...030795466_create_restaurant_menus_table.ts | 20 +++ ...3937_create_restaurant_menu_items_table.ts | 23 +++ package-lock.json | 170 ++++++++---------- package.json | 2 +- start/routes.ts | 20 +++ tests/bootstrap.ts | 8 +- .../unit/restaurant_menu_items/create.spec.ts | 21 +++ 17 files changed, 470 insertions(+), 100 deletions(-) create mode 100644 .env.test create mode 100644 app/controllers/restaurant_menu_items_controller.ts create mode 100644 app/controllers/restaurant_menus_controller.ts create mode 100644 app/validators/menu.ts create mode 100644 database/migrations/1753030795466_create_restaurant_menus_table.ts create mode 100644 database/migrations/1753031513937_create_restaurant_menu_items_table.ts create mode 100644 tests/unit/restaurant_menu_items/create.spec.ts diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..5db6e5c --- /dev/null +++ b/.env.test @@ -0,0 +1,12 @@ +TZ=UTC +PORT=3333 +HOST=localhost +LOG_LEVEL=info +APP_KEY= +NODE_ENV=test +DB_HOST=localhost +DB_PORT=5432 +DB_USER=paprika +DB_PASSWORD=Paprika_2025 +DB_DATABASE=paprika_test +LIMITER_STORE=database \ No newline at end of file diff --git a/app/controllers/auth_controller.ts b/app/controllers/auth_controller.ts index a9a6f70..3dc2454 100644 --- a/app/controllers/auth_controller.ts +++ b/app/controllers/auth_controller.ts @@ -5,11 +5,19 @@ import logger from '@adonisjs/core/services/logger' export default class AuthController { + /** + * Registers a user using the provided email, password, and full name. + * + * @bodyParam *email* - string | The email of the user. + * @bodyParam *password* - string | The password of the user. + * @bodyParam *fullName* - string | The full name of the user. + * @returns The user access token. + */ async register({ request }: HttpContext) { try { const data = await request.validateUsing(registerValidator) const user = await User.create(data) - logger.info('User registered: %s', user.fullName, user.email, request.ip()) + logger.info('User registered: %s, %s, %s', user.fullName, user.email, request.ip()) return User.accessTokens.create(user) } catch(error) { @@ -18,6 +26,13 @@ export default class AuthController { } } + /** + * Log in a user using the provided email and password. + * + * @bodyParam *email* - string | The email of the user. + * @bodyParam *password* - string | The password of the user. + * @returns The access token of the user. + */ async login({ request }: HttpContext) { try { const { email, password } = await request.validateUsing(loginValidator) @@ -31,6 +46,14 @@ export default class AuthController { } } + + + + + /** + * Deletes the user's access token and logs them out. If the user is not + * authenticated, returns an unauthorized message. + */ async logout({ auth }: HttpContext) { const user = auth.user! logger.info('User logout: %s', auth.getUserOrFail().email) @@ -44,6 +67,15 @@ export default class AuthController { } } + /** + * Retrieves the authenticated user's information if authenticated, + * otherwise deletes the access token and returns an unauthorized message. + * + * @param auth - The HTTP context's auth object. + * @returns An object containing the user data if authenticated, + * otherwise an unauthorized message. + */ + async me({ auth }: HttpContext) { if (await auth.check()) { return { user: auth.user } diff --git a/app/controllers/restaurant_menu_items_controller.ts b/app/controllers/restaurant_menu_items_controller.ts new file mode 100644 index 0000000..28308eb --- /dev/null +++ b/app/controllers/restaurant_menu_items_controller.ts @@ -0,0 +1,120 @@ +import type { HttpContext } from '@adonisjs/core/http' +import RestaurantMenuItem from '#models/restaurant_menu_item' +import logger from '@adonisjs/core/services/logger' +import { menuItemValidator } from '#validators/menu' + +export default class RestaurantMenuItemsController { + + /** + * Retrieve all restaurant menu items. + * + * @returns An array of all restaurant menu items. + */ + async getAll() { + const items = await RestaurantMenuItem.all() + return items + } + + /** + * Retrieve a single restaurant menu item. + * + * @param request - The HTTP context containing the request data. + * @returns The restaurant menu item with the specified id. + */ + + async getOne({request}: HttpContext) { + try { + const id = request.body().id + const item = await RestaurantMenuItem.find(id) + return item + } + catch (error) { + logger.error('Menu item retrieval failed: %s',error) + } + } + + // The following methods require authentication. Log user activity. + + /** + * Create a restaurant menu item. + * + * @bodyParam *name* - string | The name of the menu item. + * @bodyParam *priceSmall* - number | The price of the small serving of the menu item. + * @bodyParam *priceLarge* - number | The price of the large serving of the menu item. + * @bodyParam *description* - string | The description of the menu item. + * @bodyParam *menu_id* - number | The menu id of the menu item. + * @returns The created menu item. + */ + async create({request, auth}: HttpContext) { + const data = await request.validateUsing(menuItemValidator) + try { + const item = await RestaurantMenuItem.create(data) + logger.info('Menu item created: %s by %s', item.name, auth.getUserOrFail().email) + return item + } + catch(error) { + logger.error('Menu item creation failed: %s',error) + } + } + + /** + * Update a restaurant menu item. + * + * @bodyParam *id* - number | The id of the menu item to update. + * @bodyParam *name* - string | The new name of the menu item. + * @bodyParam *priceSmall* - number | The new price of the small size of the menu item. + * @bodyParam *priceLarge* - number | The new price of the large size of the menu item. + * @bodyParam *description* - string | The new description of the menu item. + * @bodyParam *menu_id* - number | The new menu id of the menu item. + */ + async update({request, auth}: HttpContext) { + const data = request.body() + try { + const item = await RestaurantMenuItem.find(data.id) + item?.merge(data) + await item?.save() + logger.info('Menu item updated: %s by %s', item?.name, auth.getUserOrFail().email) + } + catch(error) { + logger.error('Menu item update failed: %s',error) + } + } + + /** + * Delete a restaurant menu item. + * + * @bodyParam *id* - number | The id of the menu item to delete. + */ + async delete({request, auth}: HttpContext) { + const id = request.body().id + try { + const item = await RestaurantMenuItem.find(id) + await item?.delete() + logger.info('Menu item deleted: %s by %s', item?.name, auth.getUserOrFail().email) + } + catch(error) { + logger.error('Menu item deletion failed: %s',error) + } + } + + // This method connects the menu item to a menu. + + /** + * Connect a restaurant menu item to a restaurant menu. + * + * @bodyParam *item_id* - number | The id of the menu item to connect. + * @bodyParam *menu_id* - number | The id of the menu to connect the menu item to. + */ + async connectToMenu({request}: HttpContext) { + const data = request.body() + try { + const item = await RestaurantMenuItem.find(data.item_id) + const menu = await data.menu_id + item?.merge({menu_id: menu}) + await item?.save() + } + catch(error) { + logger.error('Menu item connection failed: %s',error) + } + } +} \ No newline at end of file diff --git a/app/controllers/restaurant_menus_controller.ts b/app/controllers/restaurant_menus_controller.ts new file mode 100644 index 0000000..891657c --- /dev/null +++ b/app/controllers/restaurant_menus_controller.ts @@ -0,0 +1,90 @@ +// import type { HttpContext } from '@adonisjs/core/http' + +import RestaurantMenu from "#models/restaurant_menu" +import { HttpContext } from "@adonisjs/core/http" +import logger from "@adonisjs/core/services/logger" + +export default class RestaurantMenusController { + + /** + * Retrieve all restaurant menus. + * + * @returns An array of all restaurant menus. + */ + async getAll() { + const menus = await RestaurantMenu.all() + return menus + } + /** + * Retrieve a restaurant menu. + * + * @bodyParam id number The id of the menu to retrieve. + * @returns The retrieved menu. + */ + async getOne({ request }: HttpContext) { + const id = request.body().id + try { + const menu = await RestaurantMenu.find(id) + return menu + } + catch (error) { + logger.error('Menu retrieval failed: %s', error) + } + } + + /** + * Create a restaurant menu. + * + * @bodyParam name string The name of the menu. + * @bodyParam description string The description of the menu. + * @returns The created menu. + */ + async create({ request, auth }: HttpContext) { + const data = request.body() + try { + const menu = await RestaurantMenu.create(data) + logger.info('Menu created: %s by %s', menu.name, auth.getUserOrFail().email) + return menu + } + catch (error) { + logger.error('Menu creation failed: %s', error) + } + } + + /** + * Update a restaurant menu. + * + * @bodyParam id number The id of the menu to update. + * @bodyParam name string The new name of the menu. + */ + async update({ request, auth }: HttpContext) { + const data = request.body() + try { + const menu = await RestaurantMenu.find(data.id) + menu?.merge(data) + await menu?.save() + logger.info('Menu updated: %s by %s', menu?.name, auth.getUserOrFail().email) + } + catch (error) { + logger.error('Menu update failed: %s', error) + } + } + + + /** + * Delete a restaurant menu. + * + * @bodyParam id number The id of the menu to delete. + */ + async delete({ request, auth }: HttpContext) { + const id = request.body().id + try { + const menu = await RestaurantMenu.find(id) + await menu?.delete() + logger.info('Menu deleted: %s by %s', menu?.name, auth.getUserOrFail().email) + } + catch (error) { + logger.error('Menu deletion failed: %s', error) + } + } +} \ No newline at end of file diff --git a/app/models/restaurant_menu.ts b/app/models/restaurant_menu.ts index 3edb7b4..bb5ea54 100644 --- a/app/models/restaurant_menu.ts +++ b/app/models/restaurant_menu.ts @@ -1,5 +1,7 @@ import { DateTime } from 'luxon' -import { BaseModel, column } from '@adonisjs/lucid/orm' +import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm' +import type { HasMany } from '@adonisjs/lucid/types/relations' +import RestaurantMenuItem from './restaurant_menu_item.js' export default class RestaurantMenu extends BaseModel { @column({ isPrimary: true }) @@ -10,4 +12,10 @@ export default class RestaurantMenu extends BaseModel { @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime + + @column() + declare name: string + + @hasMany(() => RestaurantMenuItem) + declare items: HasMany } \ No newline at end of file diff --git a/app/models/restaurant_menu_item.ts b/app/models/restaurant_menu_item.ts index 4fc2f4e..7b882d2 100644 --- a/app/models/restaurant_menu_item.ts +++ b/app/models/restaurant_menu_item.ts @@ -10,4 +10,22 @@ export default class RestaurantMenuItem extends BaseModel { @column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime + + @column() + declare name: string + + @column() + declare priceSmall: number + + @column() + declare priceLarge: number + + @column() + declare description: string | null + + //connet to restaurant_menu + @column() + declare menu_id: number | null + + } \ No newline at end of file diff --git a/app/validators/auth.ts b/app/validators/auth.ts index 3b7b83b..311fda1 100644 --- a/app/validators/auth.ts +++ b/app/validators/auth.ts @@ -13,6 +13,7 @@ export const registerValidator = vine.compile( return !match }), password, + fullName: vine.string() }) ) export const loginValidator = vine.compile( diff --git a/app/validators/menu.ts b/app/validators/menu.ts new file mode 100644 index 0000000..2032418 --- /dev/null +++ b/app/validators/menu.ts @@ -0,0 +1,10 @@ +import vine from "@vinejs/vine"; + +export const menuItemValidator = vine.compile( + vine.object({ + name: vine.string(), + priceSmall: vine.number(), + priceLarge: vine.number(), + description: vine.string(), + }) +); \ No newline at end of file diff --git a/bin/test.ts b/bin/test.ts index d759efe..2b3c637 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -9,12 +9,17 @@ | command to run this file and monitor file changes. | */ - process.env.NODE_ENV = 'test' import 'reflect-metadata' import { Ignitor, prettyPrintError } from '@adonisjs/core' import { configure, processCLIArgs, run } from '@japa/runner' +import { apiClient } from '@japa/api-client' + +configure({ + files: ['tests/**/*.spec.js'], + plugins: [apiClient('https://localhost:3333')] +}) /** * URL to the application root. AdonisJS need it to resolve @@ -44,7 +49,7 @@ new Ignitor(APP_ROOT, { importer: IMPORTER }) .testRunner() .configure(async (app) => { const { runnerHooks, ...config } = await import('../tests/bootstrap.js') - + processCLIArgs(process.argv.splice(2)) configure({ ...app.rcFile.tests, diff --git a/config/limiter.ts b/config/limiter.ts index e4f8f9d..e2595cc 100644 --- a/config/limiter.ts +++ b/config/limiter.ts @@ -10,7 +10,7 @@ const limiterConfig = defineConfig({ */ database: stores.database({ connectionName: 'postgres', - dbName:'paprika', + dbName: env.get('DB_DATABASE'), tableName: 'rate_limits', clearExpiredByTimeout: false }), diff --git a/database/migrations/1753030795466_create_restaurant_menus_table.ts b/database/migrations/1753030795466_create_restaurant_menus_table.ts new file mode 100644 index 0000000..465d52d --- /dev/null +++ b/database/migrations/1753030795466_create_restaurant_menus_table.ts @@ -0,0 +1,20 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'restaurant_menus' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id').primary() + + table.string('name').notNullable() + + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} \ No newline at end of file diff --git a/database/migrations/1753031513937_create_restaurant_menu_items_table.ts b/database/migrations/1753031513937_create_restaurant_menu_items_table.ts new file mode 100644 index 0000000..86bd56f --- /dev/null +++ b/database/migrations/1753031513937_create_restaurant_menu_items_table.ts @@ -0,0 +1,23 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +export default class extends BaseSchema { + protected tableName = 'restaurant_menu_items' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.increments('id').primary() + table.integer('restaurant_menu_id').unsigned().references('restaurant_menus.id') + table.string('name').notNullable() + table.integer('price_small').notNullable() + table.integer('price_large').notNullable() + table.string('description').nullable() + + table.timestamp('created_at') + table.timestamp('updated_at') + }) + } + + async down() { + this.schema.dropTable(this.tableName) + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 27f3e3c..2091962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@adonisjs/eslint-config": "^2.0.0", "@adonisjs/prettier-config": "^1.4.0", "@adonisjs/tsconfig": "^1.4.0", - "@japa/api-client": "^3.0.3", + "@japa/api-client": "^3.1.0", "@japa/assert": "^4.0.1", "@japa/plugin-adonisjs": "^4.0.0", "@japa/runner": "^4.2.0", @@ -744,9 +744,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -759,9 +759,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -783,9 +783,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -793,9 +793,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -830,9 +830,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -854,13 +854,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", - "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -874,32 +877,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@faker-js/faker": { "version": "9.7.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz", @@ -983,9 +973,9 @@ } }, "node_modules/@japa/api-client": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@japa/api-client/-/api-client-3.0.4.tgz", - "integrity": "sha512-MSjC8Ds4jjcuKM+2tm4uUjQQ8Jg13ggPRsQGJ3DxKJ3pC39JWsAYedgDU7G18OK7R6GsdC1glKJieFtuqilfVw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@japa/api-client/-/api-client-3.1.0.tgz", + "integrity": "sha512-wrJVRgu8kF7odTnpNCsV6zpQgNOBrer3BHVBXm3qnS0FiqLlv8a6UZ5EKOqoLSRwfHV0LwBE7kcZq9f4yTvUfw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -994,7 +984,7 @@ "@types/superagent": "^8.1.9", "cookie": "^1.0.2", "set-cookie-parser": "^2.7.1", - "superagent": "^10.2.0" + "superagent": "^10.2.1" }, "engines": { "node": ">=18.16.0" @@ -2146,9 +2136,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -3294,20 +3284,20 @@ } }, "node_modules/eslint": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", - "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.24.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -3318,9 +3308,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3446,9 +3436,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3463,9 +3453,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3476,9 +3466,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3509,15 +3499,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3851,15 +3841,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "devOptional": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -3867,16 +3858,19 @@ } }, "node_modules/formidable": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", - "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "devOptional": true, "license": "MIT", "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", - "hexoid": "^2.0.0", "once": "^1.4.0" }, + "engines": { + "node": ">=14.0.0" + }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" } @@ -4173,16 +4167,6 @@ "dev": true, "license": "MIT" }, - "node_modules/hexoid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", - "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -6932,21 +6916,21 @@ } }, "node_modules/superagent": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.0.tgz", - "integrity": "sha512-IKeoGox6oG9zyDeizaezkJ2/aK0wc5la9st7WsAKyrAkfJ56W3whVbVtF68k6wuc87/y9T85NyON5FLz7Mrzzw==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "devOptional": true, "license": "MIT", "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^3.5.2", + "form-data": "^4.0.4", + "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0" + "qs": "^6.11.2" }, "engines": { "node": ">=14.18.0" diff --git a/package.json b/package.json index d90c7e5..98c110e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@adonisjs/eslint-config": "^2.0.0", "@adonisjs/prettier-config": "^1.4.0", "@adonisjs/tsconfig": "^1.4.0", - "@japa/api-client": "^3.0.3", + "@japa/api-client": "^3.1.0", "@japa/assert": "^4.0.1", "@japa/plugin-adonisjs": "^4.0.0", "@japa/runner": "^4.2.0", diff --git a/start/routes.ts b/start/routes.ts index b7a6c8c..42e1ff1 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -12,8 +12,28 @@ import { middleware } from '#start/kernel' import { throttle } from '#start/limiter' const AuthController = () => import('#controllers/auth_controller') +const RestaurantMenuItemsController = () => import('#controllers/restaurant_menu_items_controller') +const RestaurantMenusController = () => import('#controllers/restaurant_menus_controller') +//Auth routes router.post('/register', [AuthController, 'register']).as('auth.register').use(throttle) router.post('/login', [AuthController, 'login']).as('auth.login').use(throttle) router.delete('/logout', [AuthController, 'logout']).as('auth.logout').use(middleware.auth()) router.get('/me', [AuthController, 'me']).as('auth.me').use(middleware.auth()) + + +// RestaurantMenuItem routes +router.get('/api/menu-items', [RestaurantMenuItemsController, 'getAll']).as('menu-items.getAll') +router.post('/api/menu-items', [RestaurantMenuItemsController, 'getOne']).as('menu-items.getOne') +router.post('/api/menu-items/create', [RestaurantMenuItemsController, 'create']).as('menu-items.create').use(middleware.auth()) +router.put('/api/menu-items', [RestaurantMenuItemsController, 'update']).as('menu-items.update').use(middleware.auth()) +router.delete('/api/menu-items', [RestaurantMenuItemsController, 'delete']).as('menu-items.delete').use(middleware.auth()).use(throttle) + + +// RestaurantMenu routes +router.get('/api/menus', [RestaurantMenusController, 'getAll']).as('menus.getAll') +router.post('/api/menus', [RestaurantMenusController, 'getOne']).as('menus.getOne') +router.post('/api/menus/create', [RestaurantMenusController, 'create']).as('menus.create').use(middleware.auth()) +router.put('/api/menus', [RestaurantMenusController, 'update']).as('menus.update').use(middleware.auth()) +router.delete('/api/menus', [RestaurantMenusController, 'delete']).as('menus.delete').use(middleware.auth()).use(throttle) + diff --git a/tests/bootstrap.ts b/tests/bootstrap.ts index ffa7dad..09b0cfc 100644 --- a/tests/bootstrap.ts +++ b/tests/bootstrap.ts @@ -5,6 +5,7 @@ import type { Config } from '@japa/runner/types' import { pluginAdonisJS } from '@japa/plugin-adonisjs' import testUtils from '@adonisjs/core/services/test_utils' + /** * This file is imported by the "bin/test.ts" entrypoint file */ @@ -13,7 +14,12 @@ import testUtils from '@adonisjs/core/services/test_utils' * Configure Japa plugins in the plugins array. * Learn more - https://japa.dev/docs/runner-config#plugins-optional */ -export const plugins: Config['plugins'] = [assert(), apiClient(), pluginAdonisJS(app)] +export const plugins: Config['plugins'] = [ + assert(), + apiClient({ + baseURL: 'http://localhost:3333', + }), + pluginAdonisJS(app)] /** * Configure lifecycle function to run before and after all the diff --git a/tests/unit/restaurant_menu_items/create.spec.ts b/tests/unit/restaurant_menu_items/create.spec.ts new file mode 100644 index 0000000..3872e81 --- /dev/null +++ b/tests/unit/restaurant_menu_items/create.spec.ts @@ -0,0 +1,21 @@ +import { test } from '@japa/runner' + +test.group('Restaurant menu items', () => { + test('create', async ({ assert, client }) => { + const item = { + name: 'test', + priceSmall: 10, + priceLarge: 20, + description: 'test' + } + const response = await client.post('/api/menu-items/create').json(item) + assert.equal(response.body().name, item.name) + assert.equal(response.body().priceSmall, item.priceSmall) + assert.equal(response.body().priceLarge, item.priceLarge) + assert.equal(response.body().description, item.description) + }), + test('get all', async ({ assert, client }) => { + const response = await client.get('/api/menu-items') + assert.isArray(response.body()) + }) +}) \ No newline at end of file