165 lines
5.9 KiB
TypeScript
165 lines
5.9 KiB
TypeScript
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
|
import * as bcrypt from 'bcryptjs';
|
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
|
import { ROLE, USER_STATUS } 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 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 || !/^\d{10,15}$/.test(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, userStatus: USER_STATUS.ACTIVE },
|
|
select: { id: true, userPasscode: true, mobileNumber: true },
|
|
});
|
|
|
|
let newUserLocal;
|
|
let isNewUser = false;
|
|
|
|
|
|
if (user && !user.userPasscode) {
|
|
// reuse existing invited user record
|
|
newUserLocal = user;
|
|
} else if (user) {
|
|
// Fully registered user already exists
|
|
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
|
|
},
|
|
},
|
|
userStatus: USER_STATUS.ACTIVE
|
|
},
|
|
});
|
|
|
|
const referenceNumber = `USR-${String(newUserLocal.id).padStart(6, '0')}`;
|
|
|
|
await tx.user.update({
|
|
where: { id: newUserLocal.id },
|
|
data: { userRefNumber: referenceNumber }
|
|
});
|
|
|
|
await tx.activitySorting.createMany({
|
|
data: [
|
|
{
|
|
userXid: newUserLocal.id,
|
|
activitySortXid: 1, // Default sorting (e.g., "Rating")
|
|
sortOrder: 'desc', // Default order
|
|
filterValue: 'rating', // Default filter
|
|
displayOrder: 1, // First in the list
|
|
isActive: true
|
|
},
|
|
{
|
|
userXid: newUserLocal.id,
|
|
activitySortXid: 2, // e.g., "Price"
|
|
sortOrder: 'asc',
|
|
filterValue: 'price',
|
|
displayOrder: 2,
|
|
isActive: true
|
|
},
|
|
{
|
|
userXid: newUserLocal.id,
|
|
activitySortXid: 3, // e.g., "Distance"
|
|
sortOrder: 'desc',
|
|
filterValue: 'sustainability',
|
|
displayOrder: 3,
|
|
isActive: true
|
|
},
|
|
{
|
|
userXid: newUserLocal.id,
|
|
activitySortXid: 4, // e.g., "Distance"
|
|
sortOrder: 'asc',
|
|
filterValue: 'nearbyradius',
|
|
displayOrder: 4,
|
|
isActive: true
|
|
},
|
|
{
|
|
userXid: newUserLocal.id,
|
|
activitySortXid: 5, // e.g., "Distance"
|
|
sortOrder: 'asc',
|
|
filterValue: 'quality',
|
|
displayOrder: 5,
|
|
isActive: true
|
|
},
|
|
|
|
],
|
|
skipDuplicates: true
|
|
});
|
|
|
|
isNewUser = true;
|
|
}
|
|
|
|
// 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, isNewUser };
|
|
});
|
|
|
|
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: {
|
|
isNewUser: transactionResult.isNewUser,
|
|
},
|
|
}),
|
|
};
|
|
}); |