made register and login apis for host

This commit is contained in:
2025-11-12 16:03:57 +05:30
parent 5399c8b987
commit c0e58fe1ce
24 changed files with 2052 additions and 163 deletions

View File

@@ -0,0 +1,37 @@
import * as crypto from 'crypto';
const algorithm = 'aes-256-cbc';
const secretKey = crypto.scryptSync('your-secret-password', 'salt', 32);
const ivLength = 16;
// Encrypt function
export function encryptUserId(id: string): string {
const iv = crypto.randomBytes(ivLength);
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
let encrypted = cipher.update(id, 'utf8', 'hex');
encrypted += cipher.final('hex');
return `${iv.toString('hex')}:${encrypted}`;
}
// Decrypt function
export function decryptUserId(encryptedId: string): string | null {
try {
const parts = encryptedId.split(':');
if (parts.length !== 2) {
console.error('Invalid encryptedId format:', encryptedId);
return null;
}
const iv = Buffer.from(parts[0], 'hex');
const encryptedText = Buffer.from(parts[1], 'hex');
const decipher = crypto.createDecipheriv(algorithm, secretKey, iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString('utf8');
} catch (error) {
console.error('Decryption failed:', error);
return null;
}
}

View File

@@ -0,0 +1,18 @@
import config from '../../../config/config';
export class OtpGenerator {
static generateOtp(): string {
if (config.byPassOTP) {
return '1234';
}
return Math.floor(1000 + Math.random() * 9000).toString();
}
}
export class OtpGeneratorSixDigit {
static generateOtp(): string {
if (config.byPassOTP) {
return '123456';
}
return Math.floor(100000 + Math.random() * 900000).toString();
}
}

View File

@@ -0,0 +1,91 @@
import * as bcrypt from "bcryptjs";
import { OtpGenerator, OtpGeneratorSixDigit } from "./OtpGenerator";
import { encryptUserId } from "./CodeGenerator";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export interface OtpResult {
otp: string; // Plain OTP (for sending)
hashedOtp: string; // Hashed OTP (for DB storage)
expiry: Date; // Expiry timestamp
encryptedId: string; // Encrypted user ID
emailMessage: string; // Message body for email
emailSubject: string; // Subject line for email
}
/**
* Generate OTP, store it in DB, and send email.
* @param userId The users ID
* @param email Recipient email
* @param emailPurpose For which flow (e.g. "Register", "Login", "ForgotPassword")
* @param otpLength OTP length (4 or 6)
* @param expiryMinutes Expiry time in minutes
*/
export async function generateOtpHelper(
userId: number,
email: string,
emailPurpose: "Register" | "Login" | "ForgotPassword",
otpLength: 4 | 6 = 4,
expiryMinutes: number = 5
): Promise<OtpResult> {
// Generate OTP
const otp =
otpLength === 6
? OtpGeneratorSixDigit.generateOtp()
: OtpGenerator.generateOtp();
console.log("Generated OTP:", otp);
// Hash OTP
const hashedOtp = await bcrypt.hash(otp, 10);
// Expiry time
const expiry = new Date(Date.now() + expiryMinutes * 60 * 1000);
// Encrypt user ID
const encryptedId = encryptUserId(userId.toString());
// 🔹 First delete old OTPs for this user & purpose
await prisma.userOtp.deleteMany({
where: {
userXid: userId,
otpType: emailPurpose,
isActive: true,
},
});
// Save OTP into user_otps table
await prisma.userOtp.create({
data: {
userXid: userId,
otpType: emailPurpose,
otpCode: hashedOtp,
expiresOn: expiry,
isVerified: false,
isActive: true,
// sendOn will default to now()
// createdAt will default to now()
},
});
// Build email content
const emailSubject = `${emailPurpose} OTP Verification`;
const emailMessage = `Your OTP for ${emailPurpose} is ${otp}. It will expire in ${expiryMinutes} minutes.`;
// Send email
// await sendBulkEmailForOTP([
// {
// to: [{ email: email }],
// subject: emailSubject,
// htmlContent: emailMessage,
// },
// ]);
return {
otp,
hashedOtp,
expiry,
encryptedId,
emailMessage,
emailSubject,
};
}

View File

@@ -0,0 +1,36 @@
// validations/hostBankDetails.validation.ts
import { z } from "zod";
export const hostBankDetailsSchema = z.object({
accountNumber: z
.number()
.int({ message: "Account number must be an integer" })
.positive({ message: "Account number must be a positive number" }),
accountHolderName: z
.string()
.nonempty("Account holder name is required")
.min(2, { message: "Account holder name must be at least 2 characters" }),
ifscCode: z
.string()
.nonempty("IFSC code is required")
.regex(/^[A-Z]{4}0[A-Z0-9]{6}$/, { message: "Invalid IFSC code format" }),
bankXid: z
.number()
.int({ message: "Bank ID must be an integer" })
.positive({ message: "Bank ID must be a positive number" }),
hostXid: z
.number()
.int({ message: "Host ID must be an integer" })
.positive({ message: "Host ID must be a positive number" }),
bankBranchXid: z
.number()
.int({ message: "Bank branch ID must be an integer" })
.positive({ message: "Bank branch ID must be a positive number" }),
});
export type HostBankDetailsSchema = z.infer<typeof hostBankDetailsSchema>;

View File

@@ -0,0 +1,44 @@
import { z } from "zod";
// Allowed document types (must match your DocumentType master table IDs)
export const REQUIRED_DOC_TYPES = {
PAN: 1,
GST: 2,
REGISTRATION: 3,
AADHAAR: 4,
};
export const hostCompanyDetailsSchema = z.object({
companyName: z.string().min(1, "Company name is required"),
address1: z.string().min(1, "Address1 is required"),
address2: z.string().optional(),
hostRefNumber: z.string().min(1, "Host reference number is required"),
cityXid: z.number().min(1, "City is required"),
stateXid: z.number().min(1, "State is required"),
countryXid: z.number().min(1,"Country is required"),
pinCode: z.string().min(4, "Pincode/Zipcode is required"),
logoPath: z.string().optional(),
isSubsidairy: z.boolean(),
registrationNumber: z.string().min(1, "Registration number is required"),
panNumber: z.string().min(1, "PAN number is required"),
gstNumber: z.string().optional(),
formationDate: z.string().refine((val) => !isNaN(Date.parse(val)), {
message: "Formation date must be a valid date",
}),
companyType: z.string().min(1, "Company type is required"),
websiteUrl: z.url().optional(),
instagramUrl: z.url().optional(),
facebookUrl: z.url().optional(),
linkedinUrl: z.url().optional(),
twitterUrl: z.url().optional(),
currencyXid: z.number().min(1, "Currency is required"),
});
// Validation for documents
export const hostDocumentsSchema = z.array(
z.object({
documentTypeXid: z.number(),
documentName: z.string(),
filePath: z.string(),
})
);

View File

@@ -0,0 +1,20 @@
// validations/hostBankDetails.validation.ts
import { z } from "zod";
export const loginForHostSchema = z.object({
emailAddress : z
.string()
.nonempty("Email is required"),
userPassword : z
.string()
.nonempty("Password is required")
.min(8, { message: "Password must be at least 8 characters" }),
});
export type loginForHostSchema = z.infer<typeof loginForHostSchema>;