// src/modules/host/services/host.service.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../../common/database/prisma.service'; import { AddPaymentDetailsDTO, CreateHostDto, UpdateHostDto } from '../dto/host.dto'; import * as bcrypt from 'bcryptjs'; import ApiError from '../../../common/utils/helper/ApiError'; import { User } from '@prisma/client'; import { z } from 'zod'; import { hostCompanyDetailsSchema } from '@/common/utils/validation/host/hostCompanyDetails.validation'; type HostCompanyDetailsInput = z.infer; // Document input after S3 upload (with S3 URL as filePath) interface HostDocumentInput { documentTypeXid: number; documentName: string; filePath: string; // S3 URL } @Injectable() export class HostService { constructor(private prisma: PrismaService) { } async createHost(data: CreateHostDto) { return this.prisma.user.create({ data }); } async getAllHosts() { return this.prisma.user.findMany({ where: { roleXid: 3 } }); } async getHostById(id: number) { const host = await this.prisma.user.findUnique({ where: { id } }); if (!host || host.roleXid !== 4) { throw new ApiError(404, 'Host not found'); } return host; } async updateHost(id: number, data: UpdateHostDto) { return this.prisma.user.update({ where: { id }, data, }); } async deleteHost(id: number) { return this.prisma.user.delete({ where: { id } }); } async getHostByEmail(email: string): Promise { return this.prisma.user.findUnique({ where: { emailAddress: email } }); } async verifyHostOtp(email: string, otp: string): Promise { const user = await this.prisma.user.findUnique({ where: { emailAddress: email }, select: { id: true, emailAddress: true, UserOtp: { where: { isActive: true, isVerified: false }, orderBy: { createdAt: 'desc' }, take: 1, }, }, }); if (!user) { throw new ApiError(404, 'User not found.'); } const userOtp = user.UserOtp[0]; if (!userOtp) { throw new ApiError(400, 'No OTP found.'); } if (new Date() > userOtp.expiresOn) { throw new ApiError(400, 'OTP has expired.'); } const isMatch = await bcrypt.compare(otp, userOtp.otpCode); if (!isMatch) { throw new ApiError(400, 'Invalid OTP.'); } await this.prisma.userOtp.update({ where: { id: userOtp.id }, data: { isVerified: true, verifiedOn: new Date(), isActive: false, }, }); return true; } async loginForHost(emailAddress: string, userPassword: string) { const existingUser = await this.prisma.user.findUnique({ where: { emailAddress: emailAddress }, }); if (!existingUser) { throw new ApiError(404, 'User not found'); } if (existingUser.roleXid !== 4) { throw new ApiError(403, 'Access denied. Not a host user.'); } const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword); if (!matchPassword) { throw new ApiError(401, 'Invalid credentials'); } return existingUser; } async createHostUser(email: string) { const newUser = await this.prisma.user.create({ data: { emailAddress: email, roleXid: 4 }, }); return newUser; } async createPassword(user_xid: number, password: string): Promise { // Find user by id const user = await this.prisma.user.findUnique({ where: { id: user_xid }, select: { id: true, emailAddress: true, userPassword: true }, }); if (!user) { throw new ApiError(404, 'User not found'); } // Check if password already exists if (user.userPassword) { throw new ApiError(400, 'Password already exists. Use update password instead.'); } // Hash the password const saltRounds = parseInt(process.env.SALT_ROUNDS || '10', 10); const hashedPassword = await bcrypt.hash(password, saltRounds); // Update user with hashed password await this.prisma.user.update({ where: { id: user.id }, data: { userPassword: hashedPassword }, }); return true; } async addPaymentDetails(id: number, data: AddPaymentDetailsDTO): Promise { const existingUser = await this.prisma.user.findUnique({ where: { id }, }); if (!existingUser) { throw new ApiError(404, 'User not found'); } const addedPaymentDetails = await this.prisma.hostBankDetails.create({ data, }); if (!addedPaymentDetails) { throw new ApiError(400, 'Failed to add payment details'); } return addedPaymentDetails; } async addCompanyDetails( companyData: HostCompanyDetailsInput, documents: HostDocumentInput[] // Documents with S3 URLs ) { return await this.prisma.$transaction(async (tx) => { // ✅ Check for existing company const existingHost = await tx.hostHeader.findFirst({ where: { registrationNumber: companyData.registrationNumber }, }); if (existingHost) { throw new ApiError(400, 'Company already exists with this registration number'); } // ✅ Create company record const createdHost = await tx.hostHeader.create({ data: { companyName: companyData.companyName, hostRefNumber: companyData.hostRefNumber, address1: companyData.address1, address2: companyData.address2, cityXid: companyData.cityXid, stateXid: companyData.stateXid, countryXid: companyData.countryXid, pinCode: companyData.pinCode, logoPath: companyData.logoPath, isSubsidairy: companyData.isSubsidairy, registrationNumber: companyData.registrationNumber, panNumber: companyData.panNumber, gstNumber: companyData.gstNumber, formationDate: new Date(companyData.formationDate), companyType: companyData.companyType, websiteUrl: companyData.websiteUrl, instagramUrl: companyData.instagramUrl, facebookUrl: companyData.facebookUrl, linkedinUrl: companyData.linkedinUrl, twitterUrl: companyData.twitterUrl, currencyXid: companyData.currencyXid, }, }); // ✅ Create documents (if provided) if (documents && documents.length > 0) { const docsData = documents.map((doc) => ({ hostXid: createdHost.id, documentTypeXid: doc.documentTypeXid, documentName: doc.documentName, filePath: doc.filePath, })); await tx.hostDocuments.createMany({ data: docsData }); } return createdHost; }); } }