diff --git a/src/modules/dmenu/DTO/create-restaurant-admin.dto.ts b/src/modules/dmenu/DTO/create-restaurant-admin.dto.ts new file mode 100644 index 0000000..9387a01 --- /dev/null +++ b/src/modules/dmenu/DTO/create-restaurant-admin.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class CreateMyRestaurantAdminDto { + @ApiProperty({ description: 'Mobile phone number' }) + @IsNotEmpty() + @IsString() + phone!: string; + + @ApiProperty({ description: 'First name', required: false }) + @IsOptional() + @IsString() + firstName?: string; + + @ApiProperty({ description: 'Last name', required: false }) + @IsOptional() + @IsString() + lastName?: string; + + @ApiProperty({ description: 'Role id for the admin' }) + @IsNotEmpty() + @IsString() + roleId!: string; +} diff --git a/src/modules/dmenu/DTO/update-restaurant.dto.ts b/src/modules/dmenu/DTO/update-restaurant.dto.ts new file mode 100644 index 0000000..b47069f --- /dev/null +++ b/src/modules/dmenu/DTO/update-restaurant.dto.ts @@ -0,0 +1,48 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsOptional, IsNumber, IsEnum, IsDate } from 'class-validator'; +import { Type } from 'class-transformer'; +import { PlanEnum } from '../interface/plan.interface'; + +export class UpdateRestaurantDto { + @ApiPropertyOptional({ example: 'کافه ژیوان', description: 'نام رستوران' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ example: 'zhivan', description: 'شناسه (slug) رستوران، لاتین و بدون فاصله' }) + @IsOptional() + @IsString() + slug?: string; + + @ApiPropertyOptional({ example: 2020, description: 'سال تأسیس' }) + @IsOptional() + @IsNumber() + establishedYear?: number; + + @ApiPropertyOptional({ example: '09123456789', description: 'شماره تلفن' }) + @IsOptional() + @IsString() + phone?: string; + + @ApiPropertyOptional({ example: 'base', enum: PlanEnum, description: 'پلن رستوران' }) + @IsOptional() + @IsEnum(PlanEnum) + plan?: PlanEnum; + + @ApiPropertyOptional({ example: 'sub_1234567890', description: 'شناسه اشتراک' }) + @IsOptional() + @IsString() + subscriptionId?: string; + + @ApiPropertyOptional({ example: '2025-12-26', description: 'تاریخ انقضای اشتراک' }) + @IsOptional() + @Type(() => Date) + @IsDate() + subscriptionEndDate?: Date; + + @ApiPropertyOptional({ example: '2025-12-26', description: 'تاریخ شروع اشتراک' }) + @IsOptional() + @Type(() => Date) + @IsDate() + subscriptionStartDate?: Date; +} diff --git a/src/modules/dmenu/dmenu.controller.ts b/src/modules/dmenu/dmenu.controller.ts index 78e498e..c88b913 100644 --- a/src/modules/dmenu/dmenu.controller.ts +++ b/src/modules/dmenu/dmenu.controller.ts @@ -10,6 +10,8 @@ import { CreateGroupDto } from "./DTO/create-group.dto"; import { UpdateGroupDto } from "./DTO/update-group.dto"; import { FindRestaurantsDto } from "./DTO/find-restaurants.dto"; import { SetupRestaurantDto } from "./DTO/create-restaurant.dto"; +import { UpdateRestaurantDto } from "./DTO/update-restaurant.dto"; +import { CreateMyRestaurantAdminDto } from "./DTO/create-restaurant-admin.dto"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { PermissionEnum } from "../users/enums/permission.enum"; @@ -126,7 +128,7 @@ export class DmenuController { @ApiBearerAuth() - @Get('super-admin/restaurants/sms-count') + @Get('admin/dmenu/restaurants/sms-count') @ApiOperation({ summary: 'Get SMS count for each restaurant with pagination' }) @ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)', minimum: 1 }) @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 10)', minimum: 1 }) @@ -139,9 +141,47 @@ export class DmenuController { return await this.restaurantService.getSmsCountByRestaurant(parsedPage, parsedLimit); } -//here + @PermissionsDec(PermissionEnum.DMENU) + @Patch('admin/dmenu/restaurants/:id') + @ApiOperation({ summary: "Update restaurant" }) + @ApiParam({ name: "id", required: true, type: String }) + updateRestaurant(@Param("id") id: string, @Body() dto: UpdateRestaurantDto) { + return this.restaurantService.updateRestaurant(id, dto); + } - //User Endpoints + @PermissionsDec(PermissionEnum.DMENU) + @Get('admin/dmenu/system-roles') + @ApiOperation({ summary: "Get system roles" }) + getSystemRoles() { + return this.restaurantService.getSystemRoles(); + } + + @PermissionsDec(PermissionEnum.DMENU) + @Get('admin/dmenu/restaurants/:restaurantId/admins') + @ApiOperation({ summary: "Get restaurant admins" }) + @ApiParam({ name: "restaurantId", required: true, type: String }) + getRestaurantAdmins(@Param("restaurantId") restaurantId: string) { + return this.restaurantService.getRestaurantAdmins(restaurantId); + } + + @PermissionsDec(PermissionEnum.DMENU) + @Post('admin/dmenu/restaurants/:restaurantId/admins') + @ApiOperation({ summary: "Assign admin to restaurant" }) + @ApiParam({ name: "restaurantId", required: true, type: String }) + assignRestaurantAdmin(@Param("restaurantId") restaurantId: string, @Body() dto: CreateMyRestaurantAdminDto) { + return this.restaurantService.assignRestaurantAdmin(restaurantId, dto); + } + + @PermissionsDec(PermissionEnum.DMENU) + @Delete('admin/dmenu/restaurants/:restaurantId/admins/:adminId') + @ApiOperation({ summary: "Revoke admin from restaurant" }) + @ApiParam({ name: "restaurantId", required: true, type: String }) + @ApiParam({ name: "adminId", required: true, type: String }) + revokeRestaurantAdmin(@Param("restaurantId") restaurantId: string, @Param("adminId") adminId: string) { + return this.restaurantService.revokeRestaurantAdmin(restaurantId, adminId); + } + + //***User Endpoints*** @Post('dmenu/restaurants') @ApiOperation({ summary: "Create restaurant" }) createRestaurant(@Body() dto: SetupRestaurantDto, @UserDec("id") userId: string) { diff --git a/src/modules/dmenu/providers/restaurant.service.ts b/src/modules/dmenu/providers/restaurant.service.ts index 3082505..95ab1fe 100644 --- a/src/modules/dmenu/providers/restaurant.service.ts +++ b/src/modules/dmenu/providers/restaurant.service.ts @@ -8,6 +8,8 @@ import { IDmenuConfig } from "../../../configs/icons.config"; import { DMENU_CONFIG } from "../constants"; import { FindRestaurantsDto } from "../DTO/find-restaurants.dto"; import { SetupRestaurantDto } from "../DTO/create-restaurant.dto"; +import { UpdateRestaurantDto } from "../DTO/update-restaurant.dto"; +import { CreateMyRestaurantAdminDto } from "../DTO/create-restaurant-admin.dto"; import { PlanEnum, ISetupRestaurant } from "../interface/plan.interface"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { SubscriptionMessage } from "../../../common/enums/message.enum"; @@ -125,6 +127,35 @@ export class RestaurantService { } } + async updateRestaurant(id: string, dto: UpdateRestaurantDto) { + try { + const { data } = await firstValueFrom( + this.httpService + .patch(`${this.config.baseUrl}/super-admin/restaurants/${id}`, dto, { + headers: this.getHeaders(), + }) + .pipe( + catchError((err: AxiosError) => { + this.logger.error(`Failed to update restaurant: ${err.message}`, err.stack); + return throwError(() => err); + }), + ), + ); + return data; + } catch (error: unknown) { + if (error instanceof AxiosError && error.response) { + this.logger.error(`External API error response:`, error.response.data); + const errorResponse = { + ...error.response.data, + message: error.response.data.error?.message || error.message, + }; + throw new HttpException(errorResponse, error.response.status); + } + this.logger.error(`Error updating restaurant: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + async getRestaurantSubscription(subscriptionId: string) { try { const { data } = await firstValueFrom( @@ -183,5 +214,121 @@ export class RestaurantService { throw error; } } + + async getSystemRoles() { + try { + const { data } = await firstValueFrom( + this.httpService + .get(`${this.config.baseUrl}/super-admin/system-roles`, { + headers: this.getHeaders(), + }) + .pipe( + catchError((err: AxiosError) => { + this.logger.error(`Failed to get system roles: ${err.message}`, err.stack); + return throwError(() => err); + }), + ), + ); + return data; + } catch (error: unknown) { + if (error instanceof AxiosError && error.response) { + this.logger.error(`External API error response:`, error.response.data); + const errorResponse = { + ...error.response.data, + message: error.response.data.error?.message || error.message, + }; + throw new HttpException(errorResponse, error.response.status); + } + this.logger.error(`Error getting system roles: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + + async getRestaurantAdmins(restaurantId: string) { + try { + const { data } = await firstValueFrom( + this.httpService + .get(`${this.config.baseUrl}/super-admin/restaurants/${restaurantId}/admins`, { + headers: this.getHeaders(), + }) + .pipe( + catchError((err: AxiosError) => { + this.logger.error(`Failed to get restaurant admins: ${err.message}`, err.stack); + return throwError(() => err); + }), + ), + ); + return data; + } catch (error: unknown) { + if (error instanceof AxiosError && error.response) { + this.logger.error(`External API error response:`, error.response.data); + const errorResponse = { + ...error.response.data, + message: error.response.data.error?.message || error.message, + }; + throw new HttpException(errorResponse, error.response.status); + } + this.logger.error(`Error getting restaurant admins: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + + async revokeRestaurantAdmin(restaurantId: string, adminId: string) { + try { + const { data } = await firstValueFrom( + this.httpService + .delete(`${this.config.baseUrl}/super-admin/restaurants/${restaurantId}/admins/${adminId}`, { + headers: this.getHeaders(), + }) + .pipe( + catchError((err: AxiosError) => { + this.logger.error(`Failed to revoke restaurant admin: ${err.message}`, err.stack); + return throwError(() => err); + }), + ), + ); + return data; + } catch (error: unknown) { + if (error instanceof AxiosError && error.response) { + this.logger.error(`External API error response:`, error.response.data); + const errorResponse = { + ...error.response.data, + message: error.response.data.error?.message || error.message, + }; + throw new HttpException(errorResponse, error.response.status); + } + this.logger.error(`Error revoking restaurant admin: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + + async assignRestaurantAdmin(restaurantId: string, dto: CreateMyRestaurantAdminDto) { + try { + const { data } = await firstValueFrom( + this.httpService + .post(`${this.config.baseUrl}/super-admin/restaurants/${restaurantId}/admins`, dto, { + headers: this.getHeaders(), + }) + .pipe( + catchError((err: AxiosError) => { + this.logger.error(`Failed to assign restaurant admin: ${err.message}`, err.stack); + return throwError(() => err); + }), + ), + ); + return data; + } catch (error: unknown) { + if (error instanceof AxiosError && error.response) { + this.logger.error(`External API error response:`, error.response.data); + const errorResponse = { + ...error.response.data, + message: error.response.data.error?.message || error.message, + }; + throw new HttpException(errorResponse, error.response.status); + } + this.logger.error(`Error assigning restaurant admin: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } }