Files
MinglarBackendNestJS/src/modules/user/handlers/authentication/registration.ts

123 lines
4.0 KiB
TypeScript

import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import * as bcrypt from 'bcryptjs';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { ROLE } from '../../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { encryptUserId } from '../../../../common/utils/helper/CodeGenerator';
import { OtpGeneratorSixDigit } from '../../../../common/utils/helper/OtpGenerator';
export async function generateUserRefNumber(tx: any) {
const lastrecord = await tx.user.findFirst({
orderBy: {
id: 'desc',
},
select: {
id: true,
},
});
const nextId = lastrecord ? lastrecord.id + 1 : 1;
return `USR-${String(nextId).padStart(6, '0')}`;;
}
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Parse request body
let body: { mobileNumber?: string };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { mobileNumber } = body;
if (!mobileNumber) {
throw new ApiError(400, 'Mobile number is required');
}
// Use a single transaction for user creation/lookup and OTP storage
const transactionResult = await prismaClient.$transaction(async (tx) => {
const user = await tx.user.findFirst({
where: { mobileNumber: mobileNumber, isActive: true },
select: { emailAddress: true, id: true, userPasscode: true, mobileNumber: true },
});
if (user && user.userPasscode) {
throw new ApiError(409, 'User is already registered. Please login.');
}
let newUserLocal;
const referenceNumber = await generateUserRefNumber(tx);
if (user && !user.userPasscode) {
// reuse existing invited user record
newUserLocal = user;
} else {
// create new user record within the transaction
newUserLocal = await tx.user.create({
data: {
mobileNumber: mobileNumber,
role: {
connect: {
id: ROLE.USER, // 👈 Role ID
},
},
userRefNumber: referenceNumber
},
});
}
// Generate OTP (6-digit) and store within the same transaction
const otp = OtpGeneratorSixDigit.generateOtp();
const hashedOtp = await bcrypt.hash(otp, 10);
const expiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
// delete old active OTPs for this user/purpose
await tx.userOtp.deleteMany({
where: { userXid: Number(newUserLocal.id), otpType: 'Register', isActive: true },
});
await tx.userOtp.create({
data: {
userXid: Number(newUserLocal.id),
otpType: 'Register',
otpCode: hashedOtp,
expiresOn: expiry,
isVerified: false,
isActive: true,
},
});
const encryptedId = encryptUserId(String(newUserLocal.id));
return { newUser: newUserLocal, otp, encryptedId };
});
if (!transactionResult || !transactionResult.otp) {
throw new ApiError(500, 'Failed to generate OTP');
}
// Send OTP email outside the DB transaction
// await sendOtpEmailForHost(transactionResult.newUser.emailAddress, transactionResult.otp);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'OTP sent successfully.',
data: {},
}),
};
});