|
|
|
@@ -3,6 +3,8 @@ import { BadRequestException, HttpException, Injectable, Logger } from "@nestjs/
|
|
|
|
|
import { Inject } from "@nestjs/common";
|
|
|
|
|
import { AxiosError } from "axios";
|
|
|
|
|
import { catchError, firstValueFrom, throwError } from "rxjs";
|
|
|
|
|
import { DataSource } from "typeorm";
|
|
|
|
|
import slugify from "slugify";
|
|
|
|
|
|
|
|
|
|
import { IDmenuConfig } from "../../../configs/icons.config";
|
|
|
|
|
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 { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
|
|
|
|
|
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()
|
|
|
|
|
export class RestaurantService {
|
|
|
|
@@ -23,7 +30,10 @@ export class RestaurantService {
|
|
|
|
|
@Inject(DMENU_CONFIG) private readonly config: IDmenuConfig,
|
|
|
|
|
private readonly httpService: HttpService,
|
|
|
|
|
private readonly subscriptionService: SubscriptionsService,
|
|
|
|
|
) {}
|
|
|
|
|
private readonly usersService: UsersService,
|
|
|
|
|
private readonly danakServicesService: DanakServicesService,
|
|
|
|
|
private readonly dataSource: DataSource,
|
|
|
|
|
) { }
|
|
|
|
|
|
|
|
|
|
private getHeaders(): Record<string, string> {
|
|
|
|
|
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) {
|
|
|
|
|
const { userSubscription } = await this.subscriptionService.getUserSubscriptionById(dto.userSubscriptionId, userId);
|
|
|
|
|
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) {
|
|
|
|
|
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(
|
|
|
|
|
this.httpService
|
|
|
|
|
.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) {
|
|
|
|
|
if (error instanceof AxiosError && error.response) {
|
|
|
|
|
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 {
|
|
|
|
|
// 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(
|
|
|
|
|
this.httpService
|
|
|
|
|
.post(`${this.config.baseUrl}/super-admin/auth/direct-login`, {
|
|
|
|
|
phone: dto.phone,
|
|
|
|
|
slug: dto.slug,
|
|
|
|
|
phone,
|
|
|
|
|
slug,
|
|
|
|
|
}, {
|
|
|
|
|
headers: this.getHeaders(),
|
|
|
|
|
})
|
|
|
|
|