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:
@@ -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>;
|
||||
@@ -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.');
|
||||
|
||||
13
src/modules/user/dto/user.dto.ts
Normal file
13
src/modules/user/dto/user.dto.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
122
src/modules/user/handlers/authentication/registration.ts
Normal file
122
src/modules/user/handlers/authentication/registration.ts
Normal 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: {},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
};
|
||||
});
|
||||
27
src/modules/user/services/user.service.ts
Normal file
27
src/modules/user/services/user.service.ts
Normal 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');
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user