From 5e2021da7d824c3fa3050a9eeb3e1e46bcc680d0 Mon Sep 17 00:00:00 2001 From: akbarsaputrait Date: Wed, 24 Jan 2024 23:27:09 +0700 Subject: [PATCH 1/2] Owner - Restaurant Table --- src/app.routes.ts | 5 + src/app/owner/auth/auth.controller.ts | 2 +- .../location/location.controller.ts | 7 +- src/app/owner/restaurant/restaurant.module.ts | 3 +- .../restaurant/table/table.controller.ts | 98 +++++++++++++++++++ .../owner/restaurant/table/table.module.ts | 9 ++ src/core/decorators/location.decorator.ts | 7 ++ src/core/guards/owner.guard.ts | 19 ++-- src/core/services/auth.service.ts | 2 - src/core/services/role.service.ts | 4 +- src/database/entities/owner/owner.entity.ts | 8 ++ .../1706111840738-owner-location.ts | 17 ++++ .../transformers/location.transformer.ts | 4 +- .../transformers/table.transformer.ts | 8 ++ 14 files changed, 177 insertions(+), 16 deletions(-) create mode 100644 src/app/owner/restaurant/table/table.controller.ts create mode 100644 src/app/owner/restaurant/table/table.module.ts create mode 100644 src/core/decorators/location.decorator.ts create mode 100644 src/database/migrations/1706111840738-owner-location.ts create mode 100644 src/database/transformers/table.transformer.ts diff --git a/src/app.routes.ts b/src/app.routes.ts index 2be31a7..0f334e6 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -4,6 +4,7 @@ import { OwnerModule } from './app/owner/owner.module'; import { OwnerProfileModule } from './app/owner/profile/profile.module'; import { OwnerLocationModule } from './app/owner/restaurant/location/location.module'; import { OwnerRestaurantModule } from './app/owner/restaurant/restaurant.module'; +import { OwnerTableModule } from './app/owner/restaurant/table/table.module'; export const routes: Routes = [ // { path: '/auth', module: AuthModule }, @@ -22,6 +23,10 @@ export const routes: Routes = [ path: '/:restaurant_id/locations', module: OwnerLocationModule, }, + { + path: '/:restaurant_id/tables', + module: OwnerTableModule, + }, ], }, ], diff --git a/src/app/owner/auth/auth.controller.ts b/src/app/owner/auth/auth.controller.ts index 93e174e..d6edbfb 100644 --- a/src/app/owner/auth/auth.controller.ts +++ b/src/app/owner/auth/auth.controller.ts @@ -64,7 +64,7 @@ export class AuthController { throw new UnauthorizedException('Password is incorrect'); } - if (user.isActive) { + if (!user.isActive) { throw new UnauthorizedException('Your account was inactive by system'); } diff --git a/src/app/owner/restaurant/location/location.controller.ts b/src/app/owner/restaurant/location/location.controller.ts index 4d0b3a8..7e25bb9 100644 --- a/src/app/owner/restaurant/location/location.controller.ts +++ b/src/app/owner/restaurant/location/location.controller.ts @@ -6,6 +6,7 @@ import { Location } from '@db/entities/owner/location.entity'; import { LocationTransformer } from '@db/transformers/location.transformer'; import { PlainTransformer } from '@db/transformers/plain.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; +import { isTrue } from '@lib/helpers/utils.helper'; import { Validator } from '@lib/helpers/validator.helper'; import { Permissions } from '@lib/rbac'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; @@ -30,7 +31,7 @@ export class LocationController { @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Location}@${PermAct.R}`) async show(@Rest() rest, @Res() response, @Param() param) { - const location = await Location.findOneBy({ restaurant_id: rest.id, id: param.location_id }); + const location = await Location.findOneByOrFail({ restaurant_id: rest.id, id: param.location_id }); await response.item(location, LocationTransformer); } @@ -49,7 +50,7 @@ export class LocationController { const loc = new Location(); loc.name = body.name; - loc.is_default = false; + loc.is_default = isTrue(body.is_default); loc.restaurant_id = rest.id; await loc.save(); @@ -75,7 +76,7 @@ export class LocationController { const loc = await Location.findOneByOrFail({ id: param.location_id }); loc.name = body.name; - loc.is_default = body.is_default; + loc.is_default = isTrue(body.is_default); await loc.save(); return response.item(loc, LocationTransformer); diff --git a/src/app/owner/restaurant/restaurant.module.ts b/src/app/owner/restaurant/restaurant.module.ts index 936723c..9a4ee6f 100644 --- a/src/app/owner/restaurant/restaurant.module.ts +++ b/src/app/owner/restaurant/restaurant.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { OwnerLocationModule } from './location/location.module'; import { RestaurantController } from './restaurant.controller'; +import { OwnerTableModule } from './table/table.module'; @Module({ - imports: [OwnerLocationModule], + imports: [OwnerLocationModule, OwnerTableModule], controllers: [RestaurantController], providers: [], }) diff --git a/src/app/owner/restaurant/table/table.controller.ts b/src/app/owner/restaurant/table/table.controller.ts new file mode 100644 index 0000000..c3e59ad --- /dev/null +++ b/src/app/owner/restaurant/table/table.controller.ts @@ -0,0 +1,98 @@ +import { Quero } from '@core/decorators/quero.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { Location } from '@db/entities/owner/location.entity'; +import { Restaurant } from '@db/entities/owner/restaurant.entity'; +import { Table, TableStatus } from '@db/entities/owner/table.entity'; +import { PlainTransformer } from '@db/transformers/plain.transformer'; +import { TableTransformer } from '@db/transformers/table.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; + +@Controller() +@UseGuards(OwnerAuthGuard()) +export class TableController { + @Get() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Table}@${PermAct.R}`) + async index(@Rest() rest, @Res() response, @Quero() quero) { + const tables = AppDataSource.createQueryBuilder(Table, 't1'); + tables.where({ restaurant_id: rest.id }); + + if (quero.location_id) { + tables.andWhere({ location_id: quero.location_id }); + } + + const data = await tables.search().sort().getPaged(); + + await response.paginate(data, PlainTransformer); + } + + @Get('/:table_id') + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Table}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const table = await Table.findOneByOrFail({ restaurant_id: rest.id, id: param.table_id }); + await response.item(table, TableTransformer); + } + + @Post() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Table}@${PermAct.C}`) + async create(@Rest() rest: Restaurant, @Body() body, @Res() response) { + console.log(Object.values(TableStatus).join(',')); + const rules = { + number: 'required|unique|safe_text', + location_id: 'required', + status: `required|in:${Object.values(TableStatus).join(',')}`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const loc = await Location.findOneByOrFail({ id: body.location_id }); + + const table = new Table(); + table.number = body.number; + table.status = body.status; + table.location_id = loc.id; + table.restaurant_id = rest.id; + await table.save(); + + return response.item(table, TableTransformer); + } + + @Put('/:table_id') + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Location}@${PermAct.C}`) + async update(@Rest() rest: Restaurant, @Body() body, @Res() response, @Param() param) { + const rules = { + number: 'required|unique|safe_text', + location_id: 'required', + status: `required|in:${Object.values(TableStatus).join(',')}`, + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!param.table_id) { + throw new BadRequestException(); + } + + const table = await Table.findOneByOrFail({ id: param.table_id }); + table.number = body.number; + table.status = body.status; + table.location_id = body.location_id; + table.restaurant_id = rest.id; + await table.save(); + + return response.item(table, TableTransformer); + } +} diff --git a/src/app/owner/restaurant/table/table.module.ts b/src/app/owner/restaurant/table/table.module.ts new file mode 100644 index 0000000..677f871 --- /dev/null +++ b/src/app/owner/restaurant/table/table.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TableController } from './table.controller'; + +@Module({ + imports: [], + controllers: [TableController], + providers: [], +}) +export class OwnerTableModule {} diff --git a/src/core/decorators/location.decorator.ts b/src/core/decorators/location.decorator.ts new file mode 100644 index 0000000..1fcc474 --- /dev/null +++ b/src/core/decorators/location.decorator.ts @@ -0,0 +1,7 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const Loc = createParamDecorator((data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + + return request.current.location || null; +}); diff --git a/src/core/guards/owner.guard.ts b/src/core/guards/owner.guard.ts index 9e1c348..9298dd8 100644 --- a/src/core/guards/owner.guard.ts +++ b/src/core/guards/owner.guard.ts @@ -1,4 +1,5 @@ import { Role } from '@db/entities/core/role.entity'; +import { Location } from '@db/entities/owner/location.entity'; import { Owner } from '@db/entities/owner/owner.entity'; import { Restaurant } from '@db/entities/owner/restaurant.entity'; import { GuardException } from '@lib/exceptions/guard.exception'; @@ -31,22 +32,28 @@ const validateRestaurant = async (request: any) => { throw new GuardException('Getting role was failed'); } - return { user, restaurant, roles, role }; + let location: Location = null; + if (user.location_id) { + location = await Location.findOneBy({ id: user.location_id }); + } + + return { user, restaurant, roles, role, location }; }; export const setupPermission = async (request) => { - const restaurant = await validateRestaurant(request); + const { restaurant, role, roles, location } = await validateRestaurant(request); // assign additional data to request object Object.assign(request, { current: { - restaurant: restaurant.restaurant, - role: restaurant.role, + restaurant: restaurant, + role, + location, }, }); // filter based on available modules on plan - const grants = restaurant.roles.reduce((acc, cur) => { + const grants = roles.reduce((acc, cur) => { acc[cur.slug] = cur.permissions || []; return acc; }, {}); @@ -57,7 +64,7 @@ export const setupPermission = async (request) => { const filter = new ParamsFilter(); filter.setParam(RBAC_REQUEST_FILTER, { ...request }); - return { ...restaurant, filter }; + return { restaurant, role, roles, location, filter }; }; @Injectable() diff --git a/src/core/services/auth.service.ts b/src/core/services/auth.service.ts index 469e00b..9cb270b 100644 --- a/src/core/services/auth.service.ts +++ b/src/core/services/auth.service.ts @@ -2,7 +2,6 @@ import { jwt } from '@config/jwt.config'; import { Role } from '@db/entities/core/role.entity'; import { Owner, OwnerStatus } from '@db/entities/owner/owner.entity'; import { Restaurant, RestaurantStatus } from '@db/entities/owner/restaurant.entity'; -import { config } from '@lib/helpers/config.helper'; import { hash, hashAreEqual } from '@lib/helpers/encrypt.helper'; import Logger from '@lib/logger/logger.library'; import AppDataSource from '@lib/typeorm/datasource.typeorm'; @@ -32,7 +31,6 @@ export class AuthService { const payload = { email: user.email, phone: user.phone, sub: user.id }; return { access_token: JWT.sign(payload, jwt.secret, jwt.signOptions), - pubsub_token: JWT.sign(payload, config.get('CENTRIFUGO_SECRET')), }; } diff --git a/src/core/services/role.service.ts b/src/core/services/role.service.ts index 4483c39..f123d98 100644 --- a/src/core/services/role.service.ts +++ b/src/core/services/role.service.ts @@ -14,9 +14,10 @@ export enum PermOwner { Profile = 'profile', Restaurant = 'restaurant', Location = 'location', + Table = 'table', } -export const DefaultPerms = [PermOwner.Profile, PermOwner.Restaurant, PermOwner.Location]; +export const DefaultPerms = [PermOwner.Profile, PermOwner.Restaurant]; @Injectable() export class RoleService implements IDynamicStorageRbac { @@ -31,6 +32,7 @@ export class RoleService implements IDynamicStorageRbac { [PermOwner.Profile]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Restaurant]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Location]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermOwner.Table]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], }; return { diff --git a/src/database/entities/owner/owner.entity.ts b/src/database/entities/owner/owner.entity.ts index 4fd3f4f..599b376 100644 --- a/src/database/entities/owner/owner.entity.ts +++ b/src/database/entities/owner/owner.entity.ts @@ -13,6 +13,7 @@ import { import { Exclude } from 'class-transformer'; import { JoinColumn, ManyToOne, OneToOne } from 'typeorm'; import { Role } from '../core/role.entity'; +import { Location } from './location.entity'; import { Restaurant } from './restaurant.entity'; export enum OwnerStatus { @@ -73,6 +74,13 @@ export class Owner extends BaseEntity { @OneToOne(() => Restaurant, { onDelete: 'RESTRICT' }) restaurant: Promise; + @Exclude() + @ForeignColumn() + location_id: string; + + @ManyToOne(() => Location, { onDelete: 'SET NULL' }) + location: Promise; + @Exclude() @OneToOne(() => Media, (media) => media.owner) image: Media; diff --git a/src/database/migrations/1706111840738-owner-location.ts b/src/database/migrations/1706111840738-owner-location.ts new file mode 100644 index 0000000..f5a0f09 --- /dev/null +++ b/src/database/migrations/1706111840738-owner-location.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ownerLocation1706111840738 implements MigrationInterface { + name = 'ownerLocation1706111840738'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`owner\` ADD \`location_id\` varchar(26) NULL`); + await queryRunner.query( + `ALTER TABLE \`owner\` ADD CONSTRAINT \`FK_71cfe28bf443954332aca78d9e1\` FOREIGN KEY (\`location_id\`) REFERENCES \`location\`(\`id\`) ON DELETE SET NULL ON UPDATE NO ACTION` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`owner\` DROP FOREIGN KEY \`FK_71cfe28bf443954332aca78d9e1\``); + await queryRunner.query(`ALTER TABLE \`owner\` DROP COLUMN \`location_id\``); + } +} diff --git a/src/database/transformers/location.transformer.ts b/src/database/transformers/location.transformer.ts index 7231a1b..9397bee 100644 --- a/src/database/transformers/location.transformer.ts +++ b/src/database/transformers/location.transformer.ts @@ -1,8 +1,8 @@ -import { Owner } from '@db/entities/owner/owner.entity'; +import { Location } from '@db/entities/owner/location.entity'; import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; export class LocationTransformer extends TransformerAbstract { - transform(entity: Owner) { + transform(entity: Location) { return entity.toJSON(); } } diff --git a/src/database/transformers/table.transformer.ts b/src/database/transformers/table.transformer.ts new file mode 100644 index 0000000..c41ec68 --- /dev/null +++ b/src/database/transformers/table.transformer.ts @@ -0,0 +1,8 @@ +import { Table } from '@db/entities/owner/table.entity'; +import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; + +export class TableTransformer extends TransformerAbstract { + transform(entity: Table) { + return entity.toJSON(); + } +} From b81472fe8f4857e37178cd14977f142edcf41874 Mon Sep 17 00:00:00 2001 From: akbarsaputrait Date: Mon, 29 Jan 2024 23:18:52 +0700 Subject: [PATCH 2/2] Owner - Restaurant manage staff --- .husky/pre-commit | 31 ++-- src/app.routes.ts | 5 + src/app/owner/restaurant/restaurant.module.ts | 3 +- .../owner/restaurant/staff/role.controller.ts | 20 +++ .../restaurant/staff/staff.controller.ts | 140 ++++++++++++++++++ .../owner/restaurant/staff/staff.module.ts | 10 ++ src/core/services/role.service.ts | 4 + src/database/entities/base/basic.ts | 2 +- .../transformers/staff.transformer.ts | 68 +++++++++ 9 files changed, 266 insertions(+), 17 deletions(-) create mode 100644 src/app/owner/restaurant/staff/role.controller.ts create mode 100644 src/app/owner/restaurant/staff/staff.controller.ts create mode 100644 src/app/owner/restaurant/staff/staff.module.ts create mode 100644 src/database/transformers/staff.transformer.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index c509e61..6fc7022 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,17 +1,19 @@ #!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -export NVM_DIR="$HOME/.nvm/nvm.sh" -. "$(dirname $NVM_DIR)/nvm.sh" - -export NVM_DIR="$HOME/.nvm" -a=$(nvm ls | grep 'node') -b=${a#*(-> } -v=${b%%[)| ]*} - -export PATH="$NVM_DIR/versions/node/$v/bin:$PATH" - -echo '🏗️👷 Styling, testing and building your project before committing' +if [[ "$OSTYPE" =~ ^msys ]]; then + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + . "$(dirname "$0")/_/husky.sh" +else + export NVM_DIR="$HOME/.nvm/nvm.sh" + . "$(dirname $NVM_DIR)/nvm.sh" + + export NVM_DIR="$HOME/.nvm" + a=$(nvm ls | grep 'node') + b=${a#*(-> } + v=${b%%[)| ]*} + + export PATH="$NVM_DIR/versions/node/$v/bin:$PATH" +fi #fix & format npx lint-staged @@ -24,5 +26,4 @@ npm run lint || false; ) -echo '🎉🎉🎉🎉 Yeayy, there is no error in your code... I am committing this now. ✨🚀🏄‍♂️🍻' - +echo '🎉🎉🎉🎉 Yeayy, there is no error in your code... I am committing this now. ✨🚀🏄‍♂️🍻' \ No newline at end of file diff --git a/src/app.routes.ts b/src/app.routes.ts index 0f334e6..9927fb6 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -4,6 +4,7 @@ import { OwnerModule } from './app/owner/owner.module'; import { OwnerProfileModule } from './app/owner/profile/profile.module'; import { OwnerLocationModule } from './app/owner/restaurant/location/location.module'; import { OwnerRestaurantModule } from './app/owner/restaurant/restaurant.module'; +import { OwnerStaffModule } from './app/owner/restaurant/staff/staff.module'; import { OwnerTableModule } from './app/owner/restaurant/table/table.module'; export const routes: Routes = [ @@ -27,6 +28,10 @@ export const routes: Routes = [ path: '/:restaurant_id/tables', module: OwnerTableModule, }, + { + path: '/:restaurant_id/staff', + module: OwnerStaffModule, + }, ], }, ], diff --git a/src/app/owner/restaurant/restaurant.module.ts b/src/app/owner/restaurant/restaurant.module.ts index 9a4ee6f..8d51d4c 100644 --- a/src/app/owner/restaurant/restaurant.module.ts +++ b/src/app/owner/restaurant/restaurant.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { OwnerLocationModule } from './location/location.module'; import { RestaurantController } from './restaurant.controller'; +import { OwnerStaffModule } from './staff/staff.module'; import { OwnerTableModule } from './table/table.module'; @Module({ - imports: [OwnerLocationModule, OwnerTableModule], + imports: [OwnerLocationModule, OwnerTableModule, OwnerStaffModule], controllers: [RestaurantController], providers: [], }) diff --git a/src/app/owner/restaurant/staff/role.controller.ts b/src/app/owner/restaurant/staff/role.controller.ts new file mode 100644 index 0000000..5375fb6 --- /dev/null +++ b/src/app/owner/restaurant/staff/role.controller.ts @@ -0,0 +1,20 @@ +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { StaffRole } from '@db/entities/staff/role.entity'; +import { RawTransformer } from '@db/transformers/raw.transformer'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { Controller, Get, Res, UseGuards } from '@nestjs/common'; + +@Controller('roles') +@UseGuards(OwnerAuthGuard()) +export class RoleController { + @Get() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Role}@${PermAct.R}`) + async index(@Res() response) { + const roles = await AppDataSource.createQueryBuilder(StaffRole, 't1').search().sort().getPaged(); + await response.paginate(roles, RawTransformer); + } +} diff --git a/src/app/owner/restaurant/staff/staff.controller.ts b/src/app/owner/restaurant/staff/staff.controller.ts new file mode 100644 index 0000000..30849bf --- /dev/null +++ b/src/app/owner/restaurant/staff/staff.controller.ts @@ -0,0 +1,140 @@ +import { Quero } from '@core/decorators/quero.decorator'; +import { Rest } from '@core/decorators/restaurant.decorator'; +import { OwnerAuthGuard } from '@core/guards/auth.guard'; +import { OwnerGuard } from '@core/guards/owner.guard'; +import { PermAct, PermOwner } from '@core/services/role.service'; +import { Location } from '@db/entities/owner/location.entity'; +import { StaffRole } from '@db/entities/staff/role.entity'; +import { StaffUser, StaffUserStatus } from '@db/entities/staff/user.entity'; +import { StaffTransformer } from '@db/transformers/staff.transformer'; +import { ValidationException } from '@lib/exceptions/validation.exception'; +import { hash } from '@lib/helpers/encrypt.helper'; +import { randomChar } from '@lib/helpers/utils.helper'; +import { Validator } from '@lib/helpers/validator.helper'; +import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; +import { BadRequestException, Body, Controller, Get, Param, Post, Put, Res, UseGuards } from '@nestjs/common'; + +@Controller() +@UseGuards(OwnerAuthGuard()) +export class StaffController { + @Get() + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Staff}@${PermAct.R}`) + async index(@Rest() rest, @Res() response, @Quero() quero) { + const query = AppDataSource.createQueryBuilder(StaffUser, 't1').search().sort(); + query.where({ restaurant_id: rest.id }); + + if (quero.location_id) { + query.andWhere({ location_id: quero.location_id }); + } + + const staffs = await query.getPaged(); + await response.paginate(staffs, StaffTransformer); + } + + @Get('/:id') + @UseGuards(OwnerGuard) + @Permissions(`${PermOwner.Staff}@${PermAct.R}`) + async show(@Rest() rest, @Res() response, @Param() param) { + const staff = await StaffUser.findOrFailByRestaurant(param.id, rest); + await response.item(staff, StaffTransformer); + } + + @Post() + @Permissions(`${PermOwner.Staff}@${PermAct.C}`) + async store(@Body() body, @Res() response, @Res() rest) { + const rules = { + name: 'required|safe_text', + email: 'required|email', + phone: 'required|phone', + role_id: 'required|uid', + location_id: 'uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + if (!rest) { + throw new BadRequestException('You must be assigned to a restaurant'); + } + + const emailExists = await StaffUser.exists({ where: { email: body.email } }); + if (emailExists) { + throw new BadRequestException('Email has already been used'); + } + + const phoneExists = await StaffUser.exists({ where: { phone: body.phone } }); + if (phoneExists) { + throw new BadRequestException('Phone has already been used'); + } + + const role = await StaffRole.findOneByOrFail({ id: body.role_id }); + + let location: Location = null; + if (body.location_id) { + location = await Location.findOneByOrFail({ id: body.location_id }); + } + + const plainPass = randomChar(8); + const staff = new StaffUser(); + staff.name = body.name; + staff.email = String(body.email).toLowerCase(); + staff.phone = body.phone; + staff.password = await hash(plainPass); + staff.status = StaffUserStatus.Active; + staff.role_slug = role.slug; + staff.location_id = location ? location.id : null; + staff.restaurant_id = rest.id; + await staff.save(); + + // this.mail + // .sendMail({ + // to: staff.email, + // subject: 'Your staff account!', + // template: 'staff-register', + // context: { + // name: staff.name, + // password: plainPass, + // }, + // }) + // .then(() => null) + // .catch((error) => Logger.getInstance().notify(error)); + + await response.item(staff, StaffTransformer); + } + + @Put('/:id') + @Permissions(`${PermOwner.Staff}@${PermAct.U}`) + async update(@Param() param, @Body() body, @Res() response) { + const rules = { + name: 'required|safe_text', + phone: 'required|phone', + status: `required|in:${Object.values(StaffUserStatus).join(',')}`, + role_id: 'required|uid', + location_id: 'uid', + }; + const validation = Validator.init(body, rules); + if (validation.fails()) { + throw new ValidationException(validation); + } + + const role = await StaffRole.findOneByOrFail({ id: body.role_id }); + let location: Location = null; + if (body.location_id) { + location = await Location.findOneByOrFail({ id: body.location_id }); + } + + const staff = await StaffUser.findOneByOrFail({ id: param.id }); + + staff.name = body.name; + staff.phone = body.phone; + staff.status = body.status; + staff.role_slug = role.slug; + staff.location_id = location ? location.id : null; + await staff.save(); + + await response.item(staff, StaffTransformer); + } +} diff --git a/src/app/owner/restaurant/staff/staff.module.ts b/src/app/owner/restaurant/staff/staff.module.ts new file mode 100644 index 0000000..93883b4 --- /dev/null +++ b/src/app/owner/restaurant/staff/staff.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { RoleController } from './role.controller'; +import { StaffController } from './staff.controller'; + +@Module({ + imports: [], + controllers: [StaffController, RoleController], + providers: [], +}) +export class OwnerStaffModule {} diff --git a/src/core/services/role.service.ts b/src/core/services/role.service.ts index f123d98..0d9942b 100644 --- a/src/core/services/role.service.ts +++ b/src/core/services/role.service.ts @@ -15,6 +15,8 @@ export enum PermOwner { Restaurant = 'restaurant', Location = 'location', Table = 'table', + Staff = 'staff', + Role = 'role', } export const DefaultPerms = [PermOwner.Profile, PermOwner.Restaurant]; @@ -33,6 +35,8 @@ export class RoleService implements IDynamicStorageRbac { [PermOwner.Restaurant]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Location]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], [PermOwner.Table]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermOwner.Staff]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], + [PermOwner.Role]: [PermAct.R, PermAct.C, PermAct.U, PermAct.D], }; return { diff --git a/src/database/entities/base/basic.ts b/src/database/entities/base/basic.ts index b3f6077..91171ad 100644 --- a/src/database/entities/base/basic.ts +++ b/src/database/entities/base/basic.ts @@ -56,7 +56,7 @@ export class BasicEntity extends Base { ): Promise { const obj = await this.findOne({ where: { id, restaurant_id: restaurant.id } } as any); if (isEmpty(obj)) { - throw new NotFoundException(`Could not find entity ${startCase(this.name).toLowerCase()} on the company`); + throw new NotFoundException(`Could not find entity ${startCase(this.name).toLowerCase()} on the restaurant`); } return obj; diff --git a/src/database/transformers/staff.transformer.ts b/src/database/transformers/staff.transformer.ts new file mode 100644 index 0000000..1406fbf --- /dev/null +++ b/src/database/transformers/staff.transformer.ts @@ -0,0 +1,68 @@ +import { Media } from '@db/entities/core/media.entity'; +import { StaffUser } from '@db/entities/staff/user.entity'; +import { encrypt } from '@lib/helpers/encrypt.helper'; +import { RequestHelper } from '@lib/helpers/request.helper'; +import { TransformerAbstract } from '@lib/transformer/abstract.transformer'; +import { LocationTransformer } from './location.transformer'; + +export class StaffTransformer extends TransformerAbstract { + get availableInclude() { + return ['location', 'role']; + } + + get defaultInclude() { + return []; + } + + transform(entity: StaffUser) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { media, ...rest } = entity.toJSON(); + + return { + ...rest, + avatar: Media.getImage(entity.media), + }; + } + + async transformWithPermission(entity: StaffUser) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { media, ...rest } = entity.toJSON(); + + const grants = RequestHelper.getPermissionGrants(); + const role = await entity.role; + const location = await entity.location; + return { + ...rest, + role: { + id: role.id, + name: role.slug, + permissions: encrypt(grants[role.slug]), + }, + location: location + ? { + id: location.id, + name: location.name, + } + : null, + avatar: Media.getImage(entity.media), + }; + } + + async includeLocation(entity: StaffUser) { + const location = await entity.location; + if (!location) { + return this.null(); + } + + return this.item(location, LocationTransformer); + } + + async includeRole(entity: StaffUser) { + const role = await entity.role; + if (!role) { + return this.null(); + } + + return { id: role.id, name: role.slug }; + } +}