Compare commits
2 Commits
mayankSpri
...
841539b8cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
841539b8cc | ||
|
|
5fcff67916 |
12
serverless.operator.yml
Normal file
12
serverless.operator.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
service: minglar-operator
|
||||
|
||||
useDotenv: ${file(./serverless/common.yml):useDotenv}
|
||||
params: ${file(./serverless/common.yml):params}
|
||||
provider: ${file(./serverless/common.yml):provider}
|
||||
build: ${file(./serverless/common.yml):build}
|
||||
package: ${file(./serverless/common.yml):package}
|
||||
plugins: ${file(./serverless/common.yml):plugins}
|
||||
custom: ${file(./serverless/common.yml):custom}
|
||||
|
||||
functions:
|
||||
- ${file(./serverless/functions/operator.yml)}
|
||||
@@ -155,6 +155,7 @@ package:
|
||||
# Import function definitions from separate files organized by module
|
||||
functions:
|
||||
- ${file(./serverless/functions/host.yml)}
|
||||
- ${file(./serverless/functions/operator.yml)}
|
||||
- ${file(./serverless/functions/minglaradmin.yml)}
|
||||
- ${file(./serverless/functions/prepopulate.yml)}
|
||||
- ${file(./serverless/functions/user.yml)}
|
||||
|
||||
@@ -511,7 +511,6 @@ resendOTPmail:
|
||||
path: /resend-otp
|
||||
method: post
|
||||
|
||||
|
||||
mediaUploadTos3:
|
||||
handler: src/modules/host/handlers/mediaUploadToS3.handler
|
||||
memorySize: 512
|
||||
@@ -527,7 +526,6 @@ mediaUploadTos3:
|
||||
path: /media/upload/activity/{activityXid}
|
||||
method: post
|
||||
|
||||
|
||||
venueMediaUploadTos3:
|
||||
handler: src/modules/host/handlers/mediaUploadForVenueToS3.handler
|
||||
memorySize: 512
|
||||
@@ -543,7 +541,6 @@ venueMediaUploadTos3:
|
||||
path: /media/upload/venue/activity/{activityXid}
|
||||
method: post
|
||||
|
||||
|
||||
mediaDeleteFroms3:
|
||||
handler: src/modules/host/handlers/mediaDeleteFromS3.handler
|
||||
memorySize: 512
|
||||
|
||||
73
serverless/functions/operator.yml
Normal file
73
serverless/functions/operator.yml
Normal file
@@ -0,0 +1,73 @@
|
||||
# Operator Module Functions
|
||||
|
||||
operatorSignUp:
|
||||
handler: src/modules/host/handlers/operator/signUp.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/operator/**'
|
||||
- 'src/modules/host/services/operatorAuth.service.ts'
|
||||
- 'src/modules/host/services/token.service.ts'
|
||||
- 'src/common/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /operator/signup
|
||||
method: post
|
||||
|
||||
operatorVerifyOtp:
|
||||
handler: src/modules/host/handlers/operator/verifyOtp.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/operator/**'
|
||||
- 'src/modules/host/services/operatorAuth.service.ts'
|
||||
- 'src/modules/host/services/token.service.ts'
|
||||
- 'src/common/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /operator/verify-otp
|
||||
method: post
|
||||
|
||||
operatorCreatePassword:
|
||||
handler: src/modules/host/handlers/operator/createPassword.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/operator/**'
|
||||
- 'src/modules/host/services/operatorAuth.service.ts'
|
||||
- 'src/modules/host/services/token.service.ts'
|
||||
- 'src/common/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /operator/create-password
|
||||
method: post
|
||||
|
||||
operatorLogin:
|
||||
handler: src/modules/host/handlers/operator/login.handler
|
||||
memorySize: 384
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/operator/**'
|
||||
- 'src/modules/host/services/operatorAuth.service.ts'
|
||||
- 'src/modules/host/services/token.service.ts'
|
||||
- 'src/common/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /operator/login
|
||||
method: post
|
||||
112
src/common/middlewares/jwt/authForOperator.ts
Normal file
112
src/common/middlewares/jwt/authForOperator.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import httpStatus from 'http-status';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { prisma } from '../../database/prisma.client';
|
||||
import ApiError from '../../utils/helper/ApiError';
|
||||
import config from '../../../config/config';
|
||||
import { ROLE } from '../../utils/constants/common.constant';
|
||||
|
||||
interface DecodedToken {
|
||||
id?: number;
|
||||
sub?: string | number;
|
||||
role?: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
interface UserPayload {
|
||||
id: string;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
declare module 'express-serve-static-core' {
|
||||
interface Request {
|
||||
user?: UserPayload;
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyOperatorToken(
|
||||
token: string,
|
||||
): Promise<{ id: number; role?: string }> {
|
||||
if (!token) {
|
||||
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
||||
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
||||
|
||||
if (!userId) {
|
||||
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: { role: true },
|
||||
});
|
||||
|
||||
const latestToken = await prisma.token.findFirst({
|
||||
where: { userXid: userId },
|
||||
orderBy: { id: 'desc' },
|
||||
});
|
||||
|
||||
if (latestToken?.isBlackListed === true) {
|
||||
throw new ApiError(401, 'This session is expired. Please login.');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
||||
}
|
||||
|
||||
if (user.isActive === false) {
|
||||
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
|
||||
}
|
||||
|
||||
if (user.roleXid !== ROLE.OPERATOR) {
|
||||
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
||||
}
|
||||
|
||||
return { id: user.id, role: user.role?.roleName };
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
throw new ApiError(
|
||||
httpStatus.UNAUTHORIZED,
|
||||
'Your session has expired. Please log in again.',
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof ApiError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
|
||||
}
|
||||
}
|
||||
|
||||
const verifyCallback = async (
|
||||
req: Request,
|
||||
resolve: (value?: unknown) => void,
|
||||
reject: (reason?: Error) => void,
|
||||
) => {
|
||||
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
||||
|
||||
try {
|
||||
const userInfo = await verifyOperatorToken(token);
|
||||
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
||||
resolve();
|
||||
} catch (error) {
|
||||
return reject(error as Error);
|
||||
}
|
||||
};
|
||||
|
||||
const authForOperator =
|
||||
() =>
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
verifyCallback(req, resolve, reject);
|
||||
})
|
||||
.then(() => next())
|
||||
.catch((err) => next(err));
|
||||
};
|
||||
|
||||
export default authForOperator;
|
||||
58
src/modules/host/handlers/operator/createPassword.ts
Normal file
58
src/modules/host/handlers/operator/createPassword.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||
import { verifyOperatorToken } from '../../../../common/middlewares/jwt/authForOperator';
|
||||
import { OperatorAuthService } from '../../services/operatorAuth.service';
|
||||
|
||||
const operatorAuthService = new OperatorAuthService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context,
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
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.');
|
||||
}
|
||||
|
||||
const userInfo = await verifyOperatorToken(token);
|
||||
|
||||
let body: { password?: string; confirmPassword?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { password, confirmPassword } = body;
|
||||
|
||||
if (!password || !confirmPassword) {
|
||||
throw new ApiError(400, 'Password and confirm password are required');
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
throw new ApiError(400, 'Password and confirm password do not match');
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||
}
|
||||
|
||||
await operatorAuthService.createOperatorPassword(userInfo.id, password);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Password created successfully',
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
55
src/modules/host/handlers/operator/login.ts
Normal file
55
src/modules/host/handlers/operator/login.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||
import { GetHostLoginResponseDTO } from '../../dto/host.dto';
|
||||
import { OperatorAuthService } from '../../services/operatorAuth.service';
|
||||
import { TokenService } from '../../services/token.service';
|
||||
|
||||
const operatorAuthService = new OperatorAuthService(prismaClient);
|
||||
const tokenService = new TokenService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context,
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
let body: { emailAddress?: string; userPassword?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { emailAddress, userPassword } = body;
|
||||
|
||||
if (!emailAddress || !userPassword) {
|
||||
throw new ApiError(400, 'Email and password are required');
|
||||
}
|
||||
|
||||
const operator = await operatorAuthService.loginForOperator(
|
||||
emailAddress.trim().toLowerCase(),
|
||||
userPassword,
|
||||
);
|
||||
|
||||
const generatedToken = await tokenService.generateAuthToken(operator.id);
|
||||
|
||||
const response = new GetHostLoginResponseDTO(
|
||||
operator,
|
||||
generatedToken.access.token,
|
||||
generatedToken.refresh.token,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
data: response,
|
||||
}),
|
||||
};
|
||||
});
|
||||
53
src/modules/host/handlers/operator/signUp.ts
Normal file
53
src/modules/host/handlers/operator/signUp.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||
import { OperatorAuthService } from '../../services/operatorAuth.service';
|
||||
|
||||
const operatorAuthService = new OperatorAuthService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context,
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
let body: {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
emailAddress?: string;
|
||||
isdCode?: string;
|
||||
mobileNumber?: string;
|
||||
};
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { firstName, lastName, emailAddress, isdCode, mobileNumber } = body;
|
||||
|
||||
if (!emailAddress && !mobileNumber) {
|
||||
throw new ApiError(400, 'Email address or mobile number is required');
|
||||
}
|
||||
|
||||
const signupResponse = await operatorAuthService.signUpOperator({
|
||||
firstName,
|
||||
lastName,
|
||||
emailAddress,
|
||||
isdCode,
|
||||
mobileNumber,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'OTP sent successfully',
|
||||
data: signupResponse,
|
||||
}),
|
||||
};
|
||||
});
|
||||
51
src/modules/host/handlers/operator/verifyOtp.ts
Normal file
51
src/modules/host/handlers/operator/verifyOtp.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||
import { OperatorAuthService } from '../../services/operatorAuth.service';
|
||||
import { TokenService } from '../../services/token.service';
|
||||
|
||||
const operatorAuthService = new OperatorAuthService(prismaClient);
|
||||
const tokenService = new TokenService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context,
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
let body: { emailAddress?: string; mobileNumber?: string; otp?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { emailAddress, mobileNumber, otp } = body;
|
||||
|
||||
if ((!emailAddress && !mobileNumber) || !otp) {
|
||||
throw new ApiError(400, 'Email address or mobile number and OTP are required');
|
||||
}
|
||||
|
||||
const operator = await operatorAuthService.verifyOperatorOtp({
|
||||
emailAddress,
|
||||
mobileNumber,
|
||||
otp,
|
||||
});
|
||||
|
||||
const generatedToken = await tokenService.generateAuthToken(operator.id);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'OTP verified successfully',
|
||||
accessToken: generatedToken.access.token,
|
||||
refreshToken: generatedToken.refresh.token,
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
257
src/modules/host/services/operatorAuth.service.ts
Normal file
257
src/modules/host/services/operatorAuth.service.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { OtpGeneratorSixDigit } from '../../../common/utils/helper/OtpGenerator';
|
||||
import { ROLE, USER_STATUS } from '../../../common/utils/constants/common.constant';
|
||||
|
||||
type OperatorSignupInput = {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
emailAddress?: string;
|
||||
isdCode?: string;
|
||||
mobileNumber?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class OperatorAuthService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
async findInvitedOperator(input: {
|
||||
emailAddress?: string;
|
||||
mobileNumber?: string;
|
||||
}) {
|
||||
const emailAddress = input.emailAddress?.trim().toLowerCase();
|
||||
const mobileNumber = input.mobileNumber?.trim();
|
||||
|
||||
if (!emailAddress && !mobileNumber) {
|
||||
throw new ApiError(400, 'Email address or mobile number is required');
|
||||
}
|
||||
|
||||
const invitedOperator = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
roleXid: ROLE.OPERATOR,
|
||||
isActive: true,
|
||||
OR: [
|
||||
...(emailAddress ? [{ emailAddress }] : []),
|
||||
...(mobileNumber ? [{ mobileNumber }] : []),
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
emailAddress: true,
|
||||
isdCode: true,
|
||||
mobileNumber: true,
|
||||
roleXid: true,
|
||||
userPassword: true,
|
||||
isEmailVerfied: true,
|
||||
userStatus: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!invitedOperator) {
|
||||
throw new ApiError(
|
||||
403,
|
||||
'Operator record not found. Please contact your host.',
|
||||
);
|
||||
}
|
||||
|
||||
return invitedOperator;
|
||||
}
|
||||
|
||||
async signUpOperator(input: OperatorSignupInput) {
|
||||
const emailAddress = input.emailAddress?.trim().toLowerCase();
|
||||
const mobileNumber = input.mobileNumber?.trim();
|
||||
const invitedOperator = await this.findInvitedOperator({
|
||||
emailAddress,
|
||||
mobileNumber,
|
||||
});
|
||||
|
||||
if (invitedOperator.userPassword) {
|
||||
throw new ApiError(409, 'Operator account is already created. Please login.');
|
||||
}
|
||||
|
||||
const otp = OtpGeneratorSixDigit.generateOtp();
|
||||
const hashedOtp = await bcrypt.hash(otp, 10);
|
||||
const expiry = new Date(Date.now() + 5 * 60 * 1000);
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await tx.user.update({
|
||||
where: { id: invitedOperator.id },
|
||||
data: {
|
||||
firstName: input.firstName?.trim() || invitedOperator.firstName || null,
|
||||
lastName: input.lastName?.trim() || invitedOperator.lastName || null,
|
||||
emailAddress: emailAddress || invitedOperator.emailAddress,
|
||||
isdCode: input.isdCode?.trim() || invitedOperator.isdCode || null,
|
||||
mobileNumber: mobileNumber || invitedOperator.mobileNumber,
|
||||
userStatus: USER_STATUS.INVITED,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.userOtp.updateMany({
|
||||
where: {
|
||||
userXid: invitedOperator.id,
|
||||
otpType: 'Register',
|
||||
isActive: true,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.userOtp.create({
|
||||
data: {
|
||||
userXid: invitedOperator.id,
|
||||
otpType: 'Register',
|
||||
otpCode: hashedOtp,
|
||||
expiresOn: expiry,
|
||||
isVerified: false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
userId: invitedOperator.id,
|
||||
emailAddress: emailAddress || invitedOperator.emailAddress,
|
||||
mobileNumber: mobileNumber || invitedOperator.mobileNumber,
|
||||
otp,
|
||||
expiresOn: expiry,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyOperatorOtp(input: { emailAddress?: string; mobileNumber?: string; otp: string }) {
|
||||
const emailAddress = input.emailAddress?.trim().toLowerCase();
|
||||
const mobileNumber = input.mobileNumber?.trim();
|
||||
const otp = input.otp.trim();
|
||||
|
||||
const invitedOperator = await this.findInvitedOperator({
|
||||
emailAddress,
|
||||
mobileNumber,
|
||||
});
|
||||
|
||||
const latestOtp = await this.prisma.userOtp.findFirst({
|
||||
where: {
|
||||
userXid: invitedOperator.id,
|
||||
otpType: 'Register',
|
||||
isActive: true,
|
||||
isVerified: false,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
if (!latestOtp) {
|
||||
throw new ApiError(400, 'No OTP found.');
|
||||
}
|
||||
|
||||
if (new Date() > latestOtp.expiresOn) {
|
||||
throw new ApiError(400, 'OTP has expired.');
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(otp, latestOtp.otpCode);
|
||||
|
||||
if (!isMatch) {
|
||||
throw new ApiError(400, 'Invalid OTP.');
|
||||
}
|
||||
|
||||
await this.prisma.userOtp.update({
|
||||
where: { id: latestOtp.id },
|
||||
data: {
|
||||
isVerified: true,
|
||||
verifiedOn: new Date(),
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
await this.prisma.user.update({
|
||||
where: { id: invitedOperator.id },
|
||||
data: {
|
||||
isEmailVerfied: emailAddress ? true : invitedOperator.isEmailVerfied,
|
||||
},
|
||||
});
|
||||
|
||||
return invitedOperator;
|
||||
}
|
||||
|
||||
async createOperatorPassword(userId: number, password: string) {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
roleXid: ROLE.OPERATOR,
|
||||
isActive: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
userPassword: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'Operator not found');
|
||||
}
|
||||
|
||||
if (user.userPassword) {
|
||||
throw new ApiError(400, 'Password already exists. Please login.');
|
||||
}
|
||||
|
||||
const saltRounds = parseInt(process.env.SALT_ROUNDS || '10', 10);
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
userPassword: hashedPassword,
|
||||
userStatus: USER_STATUS.ACTIVE,
|
||||
isEmailVerfied: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
emailAddress: user.emailAddress,
|
||||
};
|
||||
}
|
||||
|
||||
async loginForOperator(emailAddress: string, userPassword: string) {
|
||||
const existingOperator = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
emailAddress,
|
||||
roleXid: ROLE.OPERATOR,
|
||||
isActive: true,
|
||||
userStatus: USER_STATUS.ACTIVE,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
emailAddress: true,
|
||||
mobileNumber: true,
|
||||
roleXid: true,
|
||||
isActive: true,
|
||||
userStatus: true,
|
||||
userPassword: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingOperator) {
|
||||
throw new ApiError(404, 'Operator not found');
|
||||
}
|
||||
|
||||
const isPasswordMatched = await bcrypt.compare(
|
||||
userPassword,
|
||||
existingOperator.userPassword || '',
|
||||
);
|
||||
|
||||
if (!isPasswordMatched) {
|
||||
throw new ApiError(401, 'Invalid credentials');
|
||||
}
|
||||
|
||||
delete existingOperator.userPassword;
|
||||
|
||||
return existingOperator;
|
||||
}
|
||||
}
|
||||
91
src/modules/minglaradmin/handlers/addPQQSuggestion.ts
Normal file
91
src/modules/minglaradmin/handlers/addPQQSuggestion.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { MinglarService } from '../services/minglar.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||
import { HOST_SUGGESTION_TITLES } from '../../../common/utils/constants/minglar.constant';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
|
||||
interface AddSuggestionBody {
|
||||
hostXid: number;
|
||||
title: string;
|
||||
comments: string;
|
||||
activity_pqq_header_xid:number
|
||||
}
|
||||
|
||||
/**
|
||||
* Add suggestion handler for host applications
|
||||
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||
*/
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Verify authentication token
|
||||
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||
if (!token) {
|
||||
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||
}
|
||||
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyMinglarAdminToken(token);
|
||||
|
||||
// Get user details
|
||||
const user = await prismaService.user.findUnique({
|
||||
where: { id: userInfo.id },
|
||||
select: { id: true, roleXid: true }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
let body: AddSuggestionBody;
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { title, comments , activity_pqq_header_xid} = body;
|
||||
|
||||
if (!title) {
|
||||
throw new ApiError(400, 'Title is required');
|
||||
}
|
||||
|
||||
if (!comments) {
|
||||
throw new ApiError(400, 'Comments are required');
|
||||
}
|
||||
|
||||
if(!activity_pqq_header_xid){
|
||||
throw new ApiError(400 , "Activity Pqq HeaderXid Required");
|
||||
}
|
||||
|
||||
// Validate title is one of the allowed types
|
||||
const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
|
||||
if (!allowedTitles.includes(title)) {
|
||||
throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
|
||||
}
|
||||
|
||||
// Add suggestion using service
|
||||
await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Suggestion added successfully',
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user