added gender name column and interest color and image column added activity seeder data till gamecraft made register and add personal info api for user mobile endpoints lambda and service

This commit is contained in:
2026-01-16 17:50:30 +05:30
parent 6d377296fc
commit f20e4191ee
8 changed files with 351 additions and 33 deletions

View File

@@ -0,0 +1,23 @@
// validations/hostBankDetails.validation.ts
import { z } from "zod";
export const userPersonalInfoSchema = z.object({
firstName: z
.string()
.nonempty("First name is required"),
lastName: z
.string()
.optional(),
genderName: z
.string()
.nonempty("Gender is required"),
dateOfBirth: z
.string()
.nonempty("Date of birth is required"),
});
export type UserPersonalInfoSchema = z.infer<typeof userPersonalInfoSchema>;

View File

@@ -27,7 +27,6 @@ export const handler = safeHandler(async (
if (!email) {
throw new ApiError(400, 'Email is required');
}
console.log(email, " -: Email")
const emailToLowerCase = email.toLowerCase()
@@ -35,7 +34,6 @@ export const handler = safeHandler(async (
where: { emailAddress: emailToLowerCase, isActive: true, userStatus: USER_STATUS.INVITED },
select: { emailAddress: true, id: true, userPassword: true, roleXid: true },
});
console.log(user, "sljdfjdf")
if (!user) {
throw new ApiError(403, 'You are not allowed to register directly. Please contact minglar admin.');

View File

@@ -0,0 +1,13 @@
export class AddPersonalInfoDTO {
firstName: string;
lastName?: string;
genderName: string;
dateOfBirth: string;
constructor(firstName: string, genderName: string, dateOfBirth: string, lastName?: string) {
this.firstName = firstName;
this.lastName = lastName;
this.genderName = genderName;
this.dateOfBirth = dateOfBirth;
}
}

View File

@@ -0,0 +1,122 @@
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: {},
}),
};
});

View File

@@ -0,0 +1,70 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { userPersonalInfoSchema } from '../../../../common/utils/validation/user/addPersonalInfo.validation';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using the shared authForHost function
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { firstName?: string; lastName?: string; genderName: string; dateOfBirth?: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
// ✅ Validate payload using Zod
const validationResult = userPersonalInfoSchema.safeParse({
...(body as object),
});
if (!validationResult.success) {
const errorMessages = validationResult.error.issues.map(e => e.message).join(', ');
throw new ApiError(400, `Validation failed: ${errorMessages}`);
}
const validatedData = validationResult.data;
await userService.addPersonalInfo({
...validatedData
});
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Personal Info added successfully',
}),
};
});

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient, User } from '@prisma/client';
import { AddPersonalInfoDTO } from '../dto/user.dto';
import ApiError from '@/common/utils/helper/ApiError';
@Injectable()
export class UserService {
constructor(private prisma: PrismaClient) { }
async getUserById(userId: number) {
return this.prisma.user.findUnique({
where: { id: userId, isActive: true },
});
}
async addPersonalInfo(data: AddPersonalInfoDTO){
return await this.prisma.$transaction(async (tx) => {
const addPersonalInfo = await tx.user.create({
data,
});
if (!addPersonalInfo) {
throw new ApiError(400, 'Failed to add personal info');
}
})
}
}