add : direct login
This commit is contained in:
@@ -1,12 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class DirectLoginDto {
|
|
||||||
@ApiProperty({ example: '09123456789', description: 'شماره تلفن کاربر' })
|
|
||||||
@IsString()
|
|
||||||
phone!: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'zhivan', description: 'شناسه (slug) رستوران' })
|
|
||||||
@IsString()
|
|
||||||
slug!: string;
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ import { FindRestaurantsDto } from "./DTO/find-restaurants.dto";
|
|||||||
import { SetupRestaurantDto } from "./DTO/create-restaurant.dto";
|
import { SetupRestaurantDto } from "./DTO/create-restaurant.dto";
|
||||||
import { UpdateRestaurantDto } from "./DTO/update-restaurant.dto";
|
import { UpdateRestaurantDto } from "./DTO/update-restaurant.dto";
|
||||||
import { CreateMyRestaurantAdminDto } from "./DTO/create-restaurant-admin.dto";
|
import { CreateMyRestaurantAdminDto } from "./DTO/create-restaurant-admin.dto";
|
||||||
import { DirectLoginDto } from "./DTO/direct-login.dto";
|
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
||||||
import { PermissionEnum } from "../users/enums/permission.enum";
|
import { PermissionEnum } from "../users/enums/permission.enum";
|
||||||
@@ -127,6 +126,13 @@ export class DmenuController {
|
|||||||
return this.restaurantService.findAll(queryDto);
|
return this.restaurantService.findAll(queryDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PermissionsDec(PermissionEnum.DMENU)
|
||||||
|
@Get('admin/dmenu/restaurants/:id')
|
||||||
|
@ApiOperation({ summary: "Get restaurant by ID" })
|
||||||
|
@ApiParam({ name: "id", required: true, type: String })
|
||||||
|
getRestaurantById(@Param("id") id: string) {
|
||||||
|
return this.restaurantService.getRestaurantById(id);
|
||||||
|
}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Get('admin/dmenu/restaurants/sms-count')
|
@Get('admin/dmenu/restaurants/sms-count')
|
||||||
@@ -196,9 +202,10 @@ export class DmenuController {
|
|||||||
return this.restaurantService.getRestaurantSubscription(subscriptionId);
|
return this.restaurantService.getRestaurantSubscription(subscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('dmenu/auth/direct-login')
|
@Post('dmenu/auth/direct-login/:userSubscriptionId')
|
||||||
@ApiOperation({ summary: "Direct login with phone number and restaurant slug" })
|
@ApiOperation({ summary: "Direct login with user subscription ID" })
|
||||||
directLogin(@Body() dto: DirectLoginDto) {
|
@ApiParam({ name: "userSubscriptionId", required: true, type: String })
|
||||||
return this.restaurantService.directLogin(dto);
|
directLogin(@Param("userSubscriptionId") userSubscriptionId: string, @UserDec("id") userId: string) {
|
||||||
|
return this.restaurantService.directLogin(userSubscriptionId, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,15 @@ import { IconsService } from "./providers/icons.service";
|
|||||||
import { RestaurantService } from "./providers/restaurant.service";
|
import { RestaurantService } from "./providers/restaurant.service";
|
||||||
import { dmenuConfig } from "../../configs/icons.config";
|
import { dmenuConfig } from "../../configs/icons.config";
|
||||||
import { SubscriptionsModule } from "../subscriptions/subscriptions.module";
|
import { SubscriptionsModule } from "../subscriptions/subscriptions.module";
|
||||||
|
import { UsersModule } from "../users/users.module";
|
||||||
|
import { DanakServicesModule } from "../danak-services/danak-services.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [forwardRef(() => SubscriptionsModule)],
|
imports: [
|
||||||
|
forwardRef(() => SubscriptionsModule),
|
||||||
|
forwardRef(() => UsersModule),
|
||||||
|
forwardRef(() => DanakServicesModule),
|
||||||
|
],
|
||||||
controllers: [DmenuController],
|
controllers: [DmenuController],
|
||||||
providers: [
|
providers: [
|
||||||
IconsService,
|
IconsService,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { BadRequestException, HttpException, Injectable, Logger } from "@nestjs/
|
|||||||
import { Inject } from "@nestjs/common";
|
import { Inject } from "@nestjs/common";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { catchError, firstValueFrom, throwError } from "rxjs";
|
import { catchError, firstValueFrom, throwError } from "rxjs";
|
||||||
|
import { DataSource } from "typeorm";
|
||||||
|
import slugify from "slugify";
|
||||||
|
|
||||||
import { IDmenuConfig } from "../../../configs/icons.config";
|
import { IDmenuConfig } from "../../../configs/icons.config";
|
||||||
import { DMENU_CONFIG } from "../constants";
|
import { DMENU_CONFIG } from "../constants";
|
||||||
@@ -13,7 +15,12 @@ import { CreateMyRestaurantAdminDto } from "../DTO/create-restaurant-admin.dto";
|
|||||||
import { PlanEnum, ISetupRestaurant, IExternalApiResponse } from "../interface/plan.interface";
|
import { PlanEnum, ISetupRestaurant, IExternalApiResponse } from "../interface/plan.interface";
|
||||||
import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
|
import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
|
||||||
import { SubscriptionMessage } from "../../../common/enums/message.enum";
|
import { SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||||
import { DirectLoginDto } from "../DTO/direct-login.dto";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
|
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
import { Role } from "../../users/entities/role.entity";
|
||||||
|
import { RoleEnum } from "../../users/enums/role.enum";
|
||||||
|
import { SubscribeServiceDto } from "../../subscriptions/DTO/subscribe-service.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestaurantService {
|
export class RestaurantService {
|
||||||
@@ -23,7 +30,10 @@ export class RestaurantService {
|
|||||||
@Inject(DMENU_CONFIG) private readonly config: IDmenuConfig,
|
@Inject(DMENU_CONFIG) private readonly config: IDmenuConfig,
|
||||||
private readonly httpService: HttpService,
|
private readonly httpService: HttpService,
|
||||||
private readonly subscriptionService: SubscriptionsService,
|
private readonly subscriptionService: SubscriptionsService,
|
||||||
) {}
|
private readonly usersService: UsersService,
|
||||||
|
private readonly danakServicesService: DanakServicesService,
|
||||||
|
private readonly dataSource: DataSource,
|
||||||
|
) { }
|
||||||
|
|
||||||
private getHeaders(): Record<string, string> {
|
private getHeaders(): Record<string, string> {
|
||||||
const credentials = Buffer.from(`${this.config.username}:${this.config.password}`).toString("base64");
|
const credentials = Buffer.from(`${this.config.username}:${this.config.password}`).toString("base64");
|
||||||
@@ -86,6 +96,35 @@ export class RestaurantService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRestaurantById(id: string) {
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.get(`${this.config.baseUrl}/super-admin/restaurants/${id}`, {
|
||||||
|
headers: this.getHeaders(),
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error(`Failed to get restaurant by ID: ${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 by ID: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createRestaurant(dto: SetupRestaurantDto, userId: string) {
|
async createRestaurant(dto: SetupRestaurantDto, userId: string) {
|
||||||
const { userSubscription } = await this.subscriptionService.getUserSubscriptionById(dto.userSubscriptionId, userId);
|
const { userSubscription } = await this.subscriptionService.getUserSubscriptionById(dto.userSubscriptionId, userId);
|
||||||
if (!userSubscription) {
|
if (!userSubscription) {
|
||||||
@@ -308,8 +347,116 @@ export class RestaurantService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateRestaurantSubscription(restaurantId: string, subscriptionId: string, subscriptionExpiryDate: Date) {
|
||||||
|
try {
|
||||||
|
const { data } = await firstValueFrom(
|
||||||
|
this.httpService
|
||||||
|
.patch(`${this.config.baseUrl}/super-admin/restaurants/${restaurantId}/subscription`, {
|
||||||
|
subscriptionId,
|
||||||
|
subscriptionExpiryDate,
|
||||||
|
}, {
|
||||||
|
headers: this.getHeaders(),
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err: AxiosError) => {
|
||||||
|
this.logger.error(`Failed to update restaurant subscription: ${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 subscription: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findOrCreateUserByPhone(phone: string, dto: CreateMyRestaurantAdminDto) {
|
||||||
|
let user = await this.usersService.findOneWithPhone(phone);
|
||||||
|
if (!user) {
|
||||||
|
// Create a basic user for restaurant admin assignment
|
||||||
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
try {
|
||||||
|
await queryRunner.connect();
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
|
const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER });
|
||||||
|
if (!role) throw new BadRequestException("User role not found");
|
||||||
|
|
||||||
|
const userName = slugify(`u-${phone}`, { lower: true, trim: true });
|
||||||
|
const defaultFirstName = "کاربر";
|
||||||
|
const defaultLastName = userName;
|
||||||
|
|
||||||
|
user = queryRunner.manager.create(User, {
|
||||||
|
phone,
|
||||||
|
userName,
|
||||||
|
password: "temp_password", // This should be changed later
|
||||||
|
firstName: dto.firstName || defaultFirstName,
|
||||||
|
lastName: dto.lastName || defaultLastName,
|
||||||
|
roles: [role],
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryRunner.manager.save(user);
|
||||||
|
|
||||||
|
// Note: Skipping user settings and wallet creation for now as they might be optional for this use case
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
async assignRestaurantAdmin(restaurantId: string, dto: CreateMyRestaurantAdminDto) {
|
async assignRestaurantAdmin(restaurantId: string, dto: CreateMyRestaurantAdminDto) {
|
||||||
try {
|
try {
|
||||||
|
// 1. Create user with phone if not exists
|
||||||
|
const user = await this.findOrCreateUserByPhone(dto.phone, dto);
|
||||||
|
|
||||||
|
// 2. Get restaurant by id
|
||||||
|
const restaurant = await this.getRestaurantById(restaurantId);
|
||||||
|
|
||||||
|
// 3. If restaurant doesn't have subscription id, create user subscription with free plan of dmenu service
|
||||||
|
let subscriptionId = restaurant.subscriptionId;
|
||||||
|
let subscriptionExpiryDate = restaurant.subscriptionExpiryDate;
|
||||||
|
|
||||||
|
if (!subscriptionId) {
|
||||||
|
const dmenuService = await this.danakServicesService.getServiceByName("dmenu");
|
||||||
|
const freePlan = await this.subscriptionService['subscriptionsPlanRepository'].findOne({
|
||||||
|
where: { service: { id: dmenuService.id }, isFree: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!freePlan) {
|
||||||
|
throw new BadRequestException("Free plan not found for dmenu service");
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptionDto: SubscribeServiceDto = {
|
||||||
|
planId: freePlan.id,
|
||||||
|
businessName: restaurant.name || `Restaurant ${restaurantId}`,
|
||||||
|
businessPhone: dto.phone,
|
||||||
|
description: restaurant.description || `Restaurant admin for ${restaurant.name || restaurantId}`,
|
||||||
|
slug: slugify(restaurant.name || `restaurant-${restaurantId}`, { lower: true, strict: true }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscriptionResult = await this.subscriptionService.subscribeToPlan(dmenuService.id, subscriptionDto, user.id);
|
||||||
|
subscriptionId = subscriptionResult.userSubscription.id;
|
||||||
|
subscriptionExpiryDate = subscriptionResult.userSubscription.endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Update restaurant with subscription id and expiry date
|
||||||
|
await this.updateRestaurantSubscription(restaurantId, subscriptionId, subscriptionExpiryDate);
|
||||||
|
|
||||||
|
// 5. Assign restaurant admin
|
||||||
const { data } = await firstValueFrom(
|
const { data } = await firstValueFrom(
|
||||||
this.httpService
|
this.httpService
|
||||||
.post(`${this.config.baseUrl}/super-admin/restaurants/${restaurantId}/admins`, dto, {
|
.post(`${this.config.baseUrl}/super-admin/restaurants/${restaurantId}/admins`, dto, {
|
||||||
@@ -322,7 +469,13 @@ export class RestaurantService {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return data;
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
user,
|
||||||
|
subscriptionId,
|
||||||
|
subscriptionExpiryDate,
|
||||||
|
};
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError && error.response) {
|
if (error instanceof AxiosError && error.response) {
|
||||||
this.logger.error(`External API error response:`, error.response.data);
|
this.logger.error(`External API error response:`, error.response.data);
|
||||||
@@ -369,13 +522,33 @@ export class RestaurantService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async directLogin(dto: DirectLoginDto) {
|
async directLogin(userSubscriptionId: string, userId: string) {
|
||||||
try {
|
try {
|
||||||
|
// 1. Get userSubscription by id
|
||||||
|
const { userSubscription } = await this.subscriptionService.getUserSubscriptionById(userSubscriptionId, userId);
|
||||||
|
|
||||||
|
// 2. Get phone from userSubscription
|
||||||
|
const phone = userSubscription.businessPhone;
|
||||||
|
if (!phone) {
|
||||||
|
throw new BadRequestException("Phone number not found in user subscription");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get restaurant by subscription id
|
||||||
|
const restaurant = await this.getRestaurantSubscription(userSubscriptionId);
|
||||||
|
console.log('restaurant', restaurant);
|
||||||
|
|
||||||
|
// 4. Get slug from restaurant
|
||||||
|
const slug = restaurant.data.slug;
|
||||||
|
if (!slug) {
|
||||||
|
throw new BadRequestException("Restaurant slug not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Login with phone and slug
|
||||||
const { data } = await firstValueFrom(
|
const { data } = await firstValueFrom(
|
||||||
this.httpService
|
this.httpService
|
||||||
.post(`${this.config.baseUrl}/super-admin/auth/direct-login`, {
|
.post(`${this.config.baseUrl}/super-admin/auth/direct-login`, {
|
||||||
phone: dto.phone,
|
phone,
|
||||||
slug: dto.slug,
|
slug,
|
||||||
}, {
|
}, {
|
||||||
headers: this.getHeaders(),
|
headers: this.getHeaders(),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user