Merge branch 'paritosh' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank
This commit is contained in:
@@ -159,6 +159,81 @@ functions:
|
||||
path: /host/add-payment-details
|
||||
method: post
|
||||
|
||||
getHostById:
|
||||
handler: src/modules/host/handlers/getbyidhandler.handler
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/**'
|
||||
- 'common/**'
|
||||
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||
- 'node_modules/@prisma/client/**'
|
||||
- 'prisma/schema.prisma'
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/{id}
|
||||
method: get
|
||||
|
||||
getStepperInfo:
|
||||
handler: src/common/utils/handlers/getStepperHandler.handler
|
||||
package:
|
||||
patterns:
|
||||
- 'src/**'
|
||||
- 'common/**'
|
||||
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||
- 'node_modules/@prisma/client/**'
|
||||
- 'prisma/schema.prisma'
|
||||
events:
|
||||
- httpApi:
|
||||
path: /stepper
|
||||
method: get
|
||||
|
||||
# 👇 Minglar Admin Module
|
||||
|
||||
minglarRegistration:
|
||||
handler: src/modules/minglaradmin/handlers/registration.handler
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/minglaradmin/**'
|
||||
- 'common/**'
|
||||
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||
- 'node_modules/@prisma/client/**'
|
||||
- 'node_modules/@aws-sdk/**'
|
||||
- 'prisma/schema.prisma'
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/registration
|
||||
method: post
|
||||
|
||||
minglarLoginForAdmin:
|
||||
handler: src/modules/minglaradmin/handlers/loginForMinglar.handler
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/minglaradmin/**'
|
||||
- 'common/**'
|
||||
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||
- 'node_modules/@prisma/client/**'
|
||||
- 'node_modules/@aws-sdk/**'
|
||||
- 'prisma/schema.prisma'
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/login
|
||||
method: post
|
||||
|
||||
minglarCreatePassword:
|
||||
handler: src/modules/minglaradmin/handlers/createPassword.handler
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/minglaradmin/**'
|
||||
- 'common/**'
|
||||
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||
- 'node_modules/@prisma/client/**'
|
||||
- 'node_modules/@aws-sdk/**'
|
||||
- 'prisma/schema.prisma'
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/create-password
|
||||
method: post
|
||||
|
||||
addCompanyDetails:
|
||||
handler: src/modules/host/handlers/addCompanyDetails.handler
|
||||
package:
|
||||
|
||||
@@ -1,75 +1,191 @@
|
||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import ApiError from './ApiError';
|
||||
import * as crypto from 'crypto';
|
||||
import config from '@/config/config';
|
||||
|
||||
const AWS_REGION = config.aws.region;
|
||||
const S3_BUCKET_NAME = config.aws.bucketName;
|
||||
|
||||
const s3Client = new S3Client({
|
||||
region: AWS_REGION,
|
||||
});
|
||||
|
||||
interface UploadFileParams {
|
||||
fileData: string; // base64 encoded file
|
||||
fileName: string;
|
||||
folder?: string; // Optional folder path in S3
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file to S3 and return the S3 URL
|
||||
*/
|
||||
export async function uploadFileToS3(params: UploadFileParams): Promise<string> {
|
||||
try {
|
||||
const { fileData, fileName, folder = 'documents', contentType = 'application/pdf' } = params;
|
||||
|
||||
// Generate unique file name
|
||||
const fileExtension = fileName.split('.').pop() || 'pdf';
|
||||
const uniqueFileName = `${crypto.randomUUID()}-${Date.now()}.${fileExtension}`;
|
||||
const s3Key = folder ? `${folder}/${uniqueFileName}` : uniqueFileName;
|
||||
|
||||
// Decode base64 file data
|
||||
const fileBuffer = Buffer.from(fileData, 'base64');
|
||||
|
||||
// Upload to S3
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: S3_BUCKET_NAME,
|
||||
Key: s3Key,
|
||||
Body: fileBuffer,
|
||||
ContentType: contentType,
|
||||
// Make file publicly readable (adjust based on your needs)
|
||||
// ACL: 'public-read', // Uncomment if you want public access
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
|
||||
// Return S3 URL
|
||||
const s3Url = `https://${S3_BUCKET_NAME}.s3.${AWS_REGION}.amazonaws.com/${s3Key}`;
|
||||
|
||||
return s3Url;
|
||||
} catch (error) {
|
||||
console.error('S3 upload error:', error);
|
||||
throw new ApiError(500, 'Failed to upload file to S3');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload multiple files to S3
|
||||
*/
|
||||
export async function uploadFilesToS3(
|
||||
files: Array<{ fileData: string; fileName: string; contentType?: string }>,
|
||||
folder?: string
|
||||
): Promise<string[]> {
|
||||
const uploadPromises = files.map((file) =>
|
||||
uploadFileToS3({
|
||||
fileData: file.fileData,
|
||||
fileName: file.fileName,
|
||||
folder,
|
||||
contentType: file.contentType,
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import * as yup from 'yup';
|
||||
|
||||
dotenv.config({ path: path.join(__dirname, '../../.env') });
|
||||
|
||||
const envVarsSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
NODE_ENV: yup
|
||||
.string()
|
||||
.oneOf(['production', 'development', 'test'])
|
||||
.required(),
|
||||
PORT: yup.number().default(3000),
|
||||
// FRONTEND_URL: yup.string().required('Frontend URL is required'),
|
||||
//JWT
|
||||
JWT_SECRET: yup.string().required('JWT secret key is required'),
|
||||
JWT_ACCESS_EXPIRATION_MINUTES: yup
|
||||
.number()
|
||||
.default(30)
|
||||
.required('minutes after which access tokens expire'),
|
||||
JWT_REFRESH_EXPIRATION_DAYS: yup
|
||||
.number()
|
||||
.default(30)
|
||||
.required('days after which refresh tokens expire'),
|
||||
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: yup
|
||||
.number()
|
||||
.default(10)
|
||||
.required('minutes after which reset password token expires'),
|
||||
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: yup
|
||||
.number()
|
||||
.default(10)
|
||||
.required('minutes after which verify email token expires'),
|
||||
AWS_REGION: yup.string().required('AWS region is required'),
|
||||
S3_BUCKET_NAME: yup.string().required('S3 bucket name is required'),
|
||||
//SMTP and BREVO
|
||||
// BREVO_SMTP_HOST: yup
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .required('server that will send the emails'),
|
||||
// BREVO_SMTP_PORT: yup
|
||||
// .number()
|
||||
// .nullable()
|
||||
// .required('port to connect to the email server'),
|
||||
// BREVO_SMTP_USER: yup
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .required('username for email server'),
|
||||
// BREVO_SMTP_PASS: yup
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .required('password for email server'),
|
||||
// BREVO_FROM_EMAIL: yup
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .required('the from field in the emails sent by the app'),
|
||||
// BREVO_EMAIL_API_KEY: yup
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .required('the from field in the emails sent by the app api key'),
|
||||
// BREVO_API_BASEURL: yup.string().required('Brevo base URL is required'),
|
||||
// //one signal
|
||||
// ONESIGNAL_APPID: yup.string().required('One signal app id is required'),
|
||||
// ONESIGNAL_REST_APIKEY: yup
|
||||
// .string()
|
||||
// .required('One signal api key is required'),
|
||||
//branch IO
|
||||
// BRANCH_IO_KEY: yup.string().required('Branch IO key is required'),
|
||||
|
||||
// DataBase
|
||||
DB_USERNAME: yup.string().required('DB Username is required'),
|
||||
DB_PASSWORD: yup.string().required('DB Password is required'),
|
||||
DB_DATABASE_NAME: yup.string().required('Database name is required'),
|
||||
DB_HOSTNAME: yup
|
||||
.string()
|
||||
.default('127.0.0.1')
|
||||
.required('DB Hostname is required'),
|
||||
DB_PORT: yup.number().default(3306).required('DB Port is required'),
|
||||
//OTP Bypass
|
||||
BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'),
|
||||
})
|
||||
);
|
||||
|
||||
return Promise.all(uploadPromises);
|
||||
.noUnknown(true);
|
||||
|
||||
// Validate and prepare the configuration
|
||||
function getConfig() {
|
||||
try {
|
||||
// Validate the environment variables
|
||||
const envVars = envVarsSchema.validateSync(process.env, {
|
||||
abortEarly: false, // Validate all fields before throwing errors
|
||||
stripUnknown: true, // Remove fields not in the schema
|
||||
});
|
||||
|
||||
// Return the validated configuration
|
||||
return {
|
||||
env: envVars.NODE_ENV,
|
||||
port: envVars.PORT,
|
||||
jwt: {
|
||||
secret: envVars.JWT_SECRET,
|
||||
accessExpirationMinutes: envVars.JWT_ACCESS_EXPIRATION_MINUTES,
|
||||
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
|
||||
resetPasswordExpirationMinutes:
|
||||
envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
|
||||
verifyEmailExpirationMinutes:
|
||||
envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
|
||||
},
|
||||
database: {
|
||||
development: {
|
||||
host: envVars.DB_HOSTNAME,
|
||||
port: envVars.DB_PORT,
|
||||
username: envVars.DB_USERNAME,
|
||||
password: envVars.DB_PASSWORD,
|
||||
database: envVars.DB_DATABASE_NAME,
|
||||
logging: false,
|
||||
},
|
||||
test: {
|
||||
host: envVars.DB_HOSTNAME,
|
||||
port: envVars.DB_PORT,
|
||||
username: envVars.DB_USERNAME,
|
||||
password: envVars.DB_PASSWORD,
|
||||
database: envVars.DB_DATABASE_NAME,
|
||||
logging: false,
|
||||
socketPath: '/var/run/mysqld/mysqld.sock',
|
||||
},
|
||||
production: {
|
||||
host: envVars.DB_HOSTNAME,
|
||||
port: envVars.DB_PORT,
|
||||
username: envVars.DB_USERNAME,
|
||||
password: envVars.DB_PASSWORD,
|
||||
database: envVars.DB_DATABASE_NAME,
|
||||
logging: false,
|
||||
socketPath: '/var/run/mysqld/mysqld.sock',
|
||||
},
|
||||
},
|
||||
aws: {
|
||||
region: envVars.AWS_REGION,
|
||||
bucketName: envVars.S3_BUCKET_NAME,
|
||||
},
|
||||
byPassOTP: envVars.BYPASS_OTP,
|
||||
// BaseURL: envVars.BASEURL,
|
||||
// FRONTEND_URL: envVars.FRONTEND_URL,
|
||||
// email: {
|
||||
// smtp: {
|
||||
// host: envVars?.BREVO_SMTP_HOST,
|
||||
// port: envVars?.BREVO_SMTP_PORT,
|
||||
// secure: envVars?.BREVO_SMTP_PORT == 465, // true for 465, false for other ports
|
||||
// auth: {
|
||||
// user: envVars?.BREVO_SMTP_USER,
|
||||
// pass: envVars?.BREVO_SMTP_PASS,
|
||||
// },
|
||||
// },
|
||||
// from: envVars?.BREVO_FROM_EMAIL,
|
||||
// api_key: envVars?.BREVO_EMAIL_API_KEY,
|
||||
// BrevobaseURL: envVars?.BREVO_API_BASEURL,
|
||||
// },
|
||||
// oneSignal: {
|
||||
// appID: envVars.ONESIGNAL_APPID,
|
||||
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
|
||||
// },
|
||||
// branchIO: {
|
||||
// branchIOKey: envVars.BRANCH_IO_KEY,
|
||||
// },
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof yup.ValidationError) {
|
||||
console.error('Validation Errors:', error.errors.join(', '));
|
||||
} else {
|
||||
console.error('Unexpected error during configuration validation:', error);
|
||||
}
|
||||
|
||||
console.error(
|
||||
'Server shut down due to incomplete environment variable configuration.'
|
||||
);
|
||||
process.exit(1); // Exit with error code 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Created By : Angad Chauhan
|
||||
* Created at : 31/1/25
|
||||
* Use : For google login .env file global variable
|
||||
*/
|
||||
// export const googleConfig = {
|
||||
// clientID: process.env.GOOGLE_CLIENT_ID!,
|
||||
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||
// callbackURL: process.env.GOOGLE_CALLBACK_URL!,
|
||||
// };
|
||||
|
||||
// Validate and export configuration only if validation succeeds
|
||||
const config = getConfig();
|
||||
export default config;
|
||||
|
||||
98
src/modules/common/handlers/getStepperHandler.ts
Normal file
98
src/modules/common/handlers/getStepperHandler.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyHostToken(token);
|
||||
const userId = Number(userInfo.id);
|
||||
|
||||
if (!userId || isNaN(userId)) {
|
||||
throw new ApiError(400, 'Invalid user ID');
|
||||
}
|
||||
|
||||
// Fetch user with their HostHeader stepper info
|
||||
const user = await prismaService.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
emailAddress: true,
|
||||
roleXid: true,
|
||||
HostHeader: {
|
||||
select: {
|
||||
id: true,
|
||||
stepper: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
if (!user.HostHeader || user.HostHeader.length === 0) {
|
||||
throw new ApiError(404, 'No HostHeader record found for this user');
|
||||
}
|
||||
|
||||
// Return stepper info along with user and host details
|
||||
const hostHeader = user.HostHeader[0];
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Stepper information retrieved successfully',
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
emailAddress: user.emailAddress,
|
||||
roleXid: user.roleXid,
|
||||
},
|
||||
stepper: {
|
||||
hostId: hostHeader.id,
|
||||
currentStep: hostHeader.stepper,
|
||||
stepperDescription: getStepperDescription(hostHeader.stepper),
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a human-readable description of the stepper value
|
||||
*/
|
||||
function getStepperDescription(stepper: number): string {
|
||||
const stepDescriptions: { [key: number]: string } = {
|
||||
1: 'Basic Company Information',
|
||||
2: 'Company Documents & Verification',
|
||||
3: 'Bank & Payment Details',
|
||||
4: 'Activities Setup',
|
||||
5: 'Pricing & Services',
|
||||
6: 'Review & Approval',
|
||||
7: 'Active & Live',
|
||||
};
|
||||
|
||||
return stepDescriptions[stepper] || 'Unknown Step';
|
||||
}
|
||||
50
src/modules/host/handlers/getbyidhandler.ts
Normal file
50
src/modules/host/handlers/getbyidhandler.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { HostService } from '../services/host.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const hostService = new HostService(prismaService);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Get host ID from path parameters
|
||||
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 verifyHostToken(token);
|
||||
const id = Number(userInfo.id)
|
||||
|
||||
if (!id) {
|
||||
throw new ApiError(400, 'Host ID is required');
|
||||
}
|
||||
|
||||
if (isNaN(id)) {
|
||||
throw new ApiError(400, 'Invalid host ID format');
|
||||
}
|
||||
|
||||
const hostDetails = await hostService.getHostById(id);
|
||||
|
||||
if (!hostDetails) {
|
||||
throw new ApiError(404, 'Host not found');
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Host details retrieved successfully',
|
||||
data: hostDetails,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -44,7 +44,7 @@ export const handler = safeHandler(async (
|
||||
newUser = user;
|
||||
} else {
|
||||
// ✅ No user found → create new one
|
||||
newUser = await hostService.createHostUser(email);
|
||||
newUser = await hostService.createMinglarUser(email);
|
||||
}
|
||||
|
||||
const otpResult = await generateOtpHelper(
|
||||
|
||||
@@ -33,10 +33,71 @@ export class HostService {
|
||||
}
|
||||
|
||||
async getHostById(id: number) {
|
||||
const host = await this.prisma.user.findUnique({ where: { id } });
|
||||
const host = await this.prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
HostHeader: {
|
||||
select: {
|
||||
id: true,
|
||||
companyName: true,
|
||||
hostRefNumber: true,
|
||||
address1: true,
|
||||
address2: true,
|
||||
cityXid: true,
|
||||
stateXid: true,
|
||||
countryXid: true,
|
||||
pinCode: true,
|
||||
logoPath: true,
|
||||
isSubsidairy: true,
|
||||
registrationNumber: true,
|
||||
panNumber: true,
|
||||
gstNumber: true,
|
||||
formationDate: true,
|
||||
companyType: true,
|
||||
websiteUrl: true,
|
||||
instagramUrl: true,
|
||||
facebookUrl: true,
|
||||
linkedinUrl: true,
|
||||
twitterUrl: true,
|
||||
currencyXid: true,
|
||||
stepper: true,
|
||||
hostStatusInternal: true,
|
||||
hostStatusDisplay: true,
|
||||
adminStatusInternal: true,
|
||||
adminStatusDisplay: true,
|
||||
amStatus: true,
|
||||
agreementAccepted: true,
|
||||
accountManagerXid: true,
|
||||
isApproved: true,
|
||||
agreementStartDate: true,
|
||||
durationNumber: true,
|
||||
durationFrequency: true,
|
||||
isCommisionBase: true,
|
||||
commisionPer: true,
|
||||
amountPerBooking: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
deletedAt: true,
|
||||
currencies: true,
|
||||
cities: true,
|
||||
states: true,
|
||||
countries: true,
|
||||
HostBankDetails: true,
|
||||
HostDocuments: true,
|
||||
HostSuggestion: true,
|
||||
hostParent: true,
|
||||
HostTrack: true,
|
||||
Activities: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!host || host.roleXid !== 4) {
|
||||
throw new ApiError(404, 'Host not found');
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
@@ -122,7 +183,7 @@ export class HostService {
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
async createHostUser(email: string) {
|
||||
async createMinglarUser(email: string) {
|
||||
const newUser = await this.prisma.user.create({
|
||||
data: { emailAddress: email, roleXid: ROLE.HOST },
|
||||
});
|
||||
|
||||
93
src/modules/minglaradmin/dto/minglar.dto.ts
Normal file
93
src/modules/minglaradmin/dto/minglar.dto.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
// src/modules/host/dto/host.dto.ts
|
||||
import { IsInt, IsOptional, IsString, IsBoolean, IsEmail } from 'class-validator';
|
||||
|
||||
export class CreateMinglarDto {
|
||||
@IsString()
|
||||
firstName: string;
|
||||
|
||||
@IsString()
|
||||
lastName: string;
|
||||
|
||||
@IsEmail()
|
||||
emailAddress: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
isdCode?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
mobileNumber?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
userPassword?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
roleXid?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateMinglarDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
firstName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
lastName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
emailAddress?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export class GetMinglarLoginResponseDTO {
|
||||
id: number;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
emailAddress: string;
|
||||
mobileNumber: string | null;
|
||||
isActive: boolean;
|
||||
roleXid: number;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
|
||||
constructor(user: any, accessToken: string, refreshToken: string) {
|
||||
this.id = user.id;
|
||||
this.firstName = user.firstName;
|
||||
this.lastName = user.lastName;
|
||||
this.emailAddress = user.emailAddress;
|
||||
this.mobileNumber = user.mobileNumber;
|
||||
this.isActive = user.isActive;
|
||||
this.roleXid = user.roleXid;
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
|
||||
export class AddPaymentDetailsDTO {
|
||||
bankXid: number;
|
||||
bankBranchXid: number;
|
||||
accountNumber: number;
|
||||
accountHolderName: string;
|
||||
ifscCode: string;
|
||||
hostXid: number;
|
||||
|
||||
constructor(bankXid: number, bankBranchXid: number, accountNumber: number, accountHolderName: string, ifscCode: string, hostXid: number) {
|
||||
this.bankXid = bankXid;
|
||||
this.bankBranchXid = bankBranchXid;
|
||||
this.accountNumber = accountNumber;
|
||||
this.accountHolderName = accountHolderName;
|
||||
this.ifscCode = ifscCode;
|
||||
this.hostXid = hostXid;
|
||||
}
|
||||
}
|
||||
177
src/modules/minglaradmin/handlers/addCompanyDetails.ts
Normal file
177
src/modules/minglaradmin/handlers/addCompanyDetails.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { HostService } from '../../host/services/host.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||
import {
|
||||
hostCompanyDetailsSchema,
|
||||
REQUIRED_DOC_TYPES,
|
||||
} from '../../../common/utils/validation/host/hostCompanyDetails.validation';
|
||||
import AWS from 'aws-sdk';
|
||||
import Busboy from 'busboy';
|
||||
import crypto from 'crypto';
|
||||
import config from '@/config/config';
|
||||
|
||||
const prisma = new PrismaService();
|
||||
const hostService = new HostService(prisma);
|
||||
|
||||
const s3 = new AWS.S3({
|
||||
region: config.aws.region,
|
||||
});
|
||||
|
||||
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||
try {
|
||||
// ✅ 1. Verify Token
|
||||
// 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 verifyHostToken(token);
|
||||
|
||||
// ✅ 2. Ensure content-type is multipart/form-data
|
||||
const contentType = event.headers['content-type'] || event.headers['Content-Type'];
|
||||
if (!contentType?.startsWith('multipart/form-data'))
|
||||
throw new ApiError(400, 'Content-Type must be multipart/form-data.');
|
||||
|
||||
if (!event.isBase64Encoded)
|
||||
throw new ApiError(400, 'Event body must be base64 encoded for multipart uploads.');
|
||||
|
||||
const bodyBuffer = Buffer.from(event.body as string, 'base64');
|
||||
const fields: Record<string, any> = {};
|
||||
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
|
||||
|
||||
// ✅ 3. Parse multipart data using Busboy
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const bb = Busboy({ headers: { 'content-type': contentType } });
|
||||
|
||||
bb.on('file', (fieldname, file, info) => {
|
||||
const { filename, mimeType } = info;
|
||||
const chunks: Buffer[] = [];
|
||||
let totalSize = 0;
|
||||
const MAX_SIZE = 5 * 1024 * 1024; // 5 MB
|
||||
|
||||
file.on('data', (chunk) => {
|
||||
totalSize += chunk.length;
|
||||
if (totalSize > MAX_SIZE) {
|
||||
file.resume();
|
||||
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit.`));
|
||||
}
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
file.on('end', () => {
|
||||
files.push({
|
||||
buffer: Buffer.concat(chunks),
|
||||
mimeType,
|
||||
fileName: filename,
|
||||
fieldName: fieldname,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
bb.on('field', (fieldname, val) => {
|
||||
try {
|
||||
fields[fieldname] = JSON.parse(val);
|
||||
} catch {
|
||||
fields[fieldname] = val;
|
||||
}
|
||||
});
|
||||
|
||||
bb.on('close', resolve);
|
||||
bb.on('error', reject);
|
||||
bb.end(bodyBuffer);
|
||||
});
|
||||
|
||||
// ✅ 4. Validate fields
|
||||
if (!fields.companyDetails) throw new ApiError(400, 'Missing companyDetails field.');
|
||||
if (!fields.documents) throw new ApiError(400, 'Missing documents field.');
|
||||
if (files.length === 0) throw new ApiError(400, 'At least one document file is required.');
|
||||
|
||||
// ✅ Parse & validate JSON inputs
|
||||
let companyDetails;
|
||||
try {
|
||||
companyDetails = typeof fields.companyDetails === 'string' ? JSON.parse(fields.companyDetails) : fields.companyDetails;
|
||||
} catch {
|
||||
throw new ApiError(400, 'Invalid JSON in companyDetails.');
|
||||
}
|
||||
|
||||
const companyValidation = hostCompanyDetailsSchema.safeParse(companyDetails);
|
||||
if (!companyValidation.success) {
|
||||
const message = companyValidation.error.issues.map((e) => e.message).join(', ');
|
||||
throw new ApiError(400, `Validation failed: ${message}`);
|
||||
}
|
||||
const parsedCompany = companyValidation.data;
|
||||
|
||||
let documentsMetadata;
|
||||
try {
|
||||
documentsMetadata = typeof fields.documents === 'string' ? JSON.parse(fields.documents) : fields.documents;
|
||||
} catch {
|
||||
throw new ApiError(400, 'Invalid JSON in documents.');
|
||||
}
|
||||
|
||||
if (!Array.isArray(documentsMetadata) || documentsMetadata.length === 0)
|
||||
throw new ApiError(400, 'Documents must be a non-empty array.');
|
||||
|
||||
// ✅ 5. Map uploaded files to document metadata
|
||||
const documentMetadata = documentsMetadata.map((doc: any) => {
|
||||
const file = files.find((f) => f.fieldName === doc.fieldName);
|
||||
if (!file) throw new ApiError(400, `File not found for field: ${doc.fieldName}`);
|
||||
return { ...doc, file };
|
||||
});
|
||||
|
||||
// ✅ 6. Ensure all required document types exist
|
||||
const uploadedDocTypes = documentMetadata.map((d) => d.documentTypeXid);
|
||||
const missingDocs = Object.entries(REQUIRED_DOC_TYPES)
|
||||
.filter(([_, typeId]) => !uploadedDocTypes.includes(typeId))
|
||||
.map(([name]) => name);
|
||||
if (missingDocs.length > 0)
|
||||
throw new ApiError(400, `Missing mandatory documents: ${missingDocs.join(', ')}`);
|
||||
|
||||
// ✅ 7. Upload to S3
|
||||
const uploadedDocs: Array<{ documentTypeXid: number; documentName: string; filePath: string }> = [];
|
||||
for (const doc of documentMetadata) {
|
||||
const uniqueKey = `${userInfo.id}_${crypto.randomUUID()}_${doc.file.fileName}`;
|
||||
const s3Key = `Documents/Host/${uniqueKey}`;
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: doc.file.buffer,
|
||||
ContentType: doc.file.mimeType,
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
uploadedDocs.push({
|
||||
documentTypeXid: doc.documentTypeXid,
|
||||
documentName: doc.documentName,
|
||||
filePath: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`,
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ 8. Save company details + documents in DB via MinglarService
|
||||
const createdHost = await hostService.addCompanyDetails(parsedCompany, uploadedDocs);
|
||||
if (!createdHost) throw new ApiError(400, 'Failed to add company details.');
|
||||
|
||||
// ✅ 9. Success response
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Company details and documents uploaded successfully.',
|
||||
data: createdHost,
|
||||
}),
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('❌ Error in addCompanyDetails:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
// import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
// import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
// import { PrismaService } from '../../../common/database/prisma.service';
|
||||
// import { HostService } from '../services/host.service';
|
||||
// import { TokenService } from '../services/token.service';
|
||||
// import { GetHostLoginResponseDTO } from '../dto/host.dto';
|
||||
// import ApiError from '../../../common/utils/helper/ApiError';
|
||||
// import * as bcrypt from 'bcryptjs';
|
||||
|
||||
// const prismaService = new PrismaService();
|
||||
// const hostService = new HostService(prismaService);
|
||||
// const tokenService = new TokenService();
|
||||
|
||||
// export const handler = safeHandler(async (
|
||||
// event: APIGatewayProxyEvent,
|
||||
// context?: Context
|
||||
// ): Promise<APIGatewayProxyResult> => {
|
||||
// // Parse request body
|
||||
// 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 } = body;
|
||||
|
||||
// if (!emailAddress) {
|
||||
// throw new ApiError(400, 'Email is required');
|
||||
// }
|
||||
|
||||
// const loginForHost = await hostService.loginForHost(emailAddress);
|
||||
|
||||
// if (!loginForHost) {
|
||||
// throw new ApiError(400, 'Failed to login');
|
||||
// }
|
||||
|
||||
// if (!matchPassword) {
|
||||
// throw new ApiError(401, 'Invalid credentials');
|
||||
// }
|
||||
|
||||
// const generateTokenForHost = await tokenService.generateAuthToken(
|
||||
// loginForHost.id
|
||||
// );
|
||||
|
||||
// if (!generateTokenForHost) {
|
||||
// throw new ApiError(500, 'Failed to generate token');
|
||||
// }
|
||||
|
||||
// const loginForHostResponse = new GetHostLoginResponseDTO(
|
||||
// loginForHost,
|
||||
// generateTokenForHost.access.token,
|
||||
// generateTokenForHost.refresh.token
|
||||
// );
|
||||
|
||||
// return {
|
||||
// statusCode: 200,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'Access-Control-Allow-Origin': '*',
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// success: true,
|
||||
// message: 'Login successful',
|
||||
// data: loginForHostResponse,
|
||||
// }),
|
||||
// };
|
||||
// });
|
||||
|
||||
75
src/modules/minglaradmin/handlers/loginForMinglar.ts
Normal file
75
src/modules/minglaradmin/handlers/loginForMinglar.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
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 { TokenService } from "../services/token.service";
|
||||
import { GetMinglarLoginResponseDTO } from '../dto/minglar.dto';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarSerivce = new MinglarService(prismaService);
|
||||
const tokenService = new TokenService();
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Parse request body
|
||||
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) {
|
||||
throw new ApiError(400, 'Email is required');
|
||||
}
|
||||
|
||||
const loginForMinglar = await minglarSerivce.loginForMinglar(emailAddress ,userPassword);
|
||||
|
||||
if (!loginForMinglar) {
|
||||
throw new ApiError(400, 'Failed to login');
|
||||
}
|
||||
|
||||
const matchPassword = await bcrypt.compare(
|
||||
userPassword,
|
||||
loginForMinglar.userPassword
|
||||
);
|
||||
|
||||
if (!matchPassword) {
|
||||
throw new ApiError(401, 'Invalid credentials');
|
||||
}
|
||||
|
||||
const generateTokenForHost = await tokenService.generateAuthToken(
|
||||
loginForMinglar.id
|
||||
);
|
||||
|
||||
if (!generateTokenForHost) {
|
||||
throw new ApiError(500, 'Failed to generate token');
|
||||
}
|
||||
|
||||
const loginForHostResponse = new GetMinglarLoginResponseDTO(
|
||||
loginForMinglar,
|
||||
generateTokenForHost.access.token,
|
||||
generateTokenForHost.refresh.token
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
data: loginForHostResponse,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
75
src/modules/minglaradmin/handlers/registration.ts
Normal file
75
src/modules/minglaradmin/handlers/registration.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { MinglarService } from './../services/minglar.service';
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { generateOtpHelper } from '../../../common/utils/helper/sendOtp';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Parse request body
|
||||
let body: { email?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { email } = body;
|
||||
|
||||
if (!email) {
|
||||
throw new ApiError(400, 'Email is required');
|
||||
}
|
||||
|
||||
const user = await prismaService.user.findUnique({
|
||||
where: { emailAddress: email },
|
||||
select: { emailAddress: true, id: true, userPassword: true },
|
||||
});
|
||||
|
||||
if (user && user.userPassword) {
|
||||
throw new ApiError(404, 'User is already registered. Please login.');
|
||||
}
|
||||
|
||||
let newUser;
|
||||
|
||||
if (user && !user.userPassword) {
|
||||
// ✅ User already exists but without password → reuse record
|
||||
newUser = user;
|
||||
} else {
|
||||
// ✅ No user found → create new one
|
||||
newUser = await minglarService.createHostUser(email);
|
||||
}
|
||||
|
||||
const otpResult = await generateOtpHelper(
|
||||
Number(newUser?.id),
|
||||
newUser?.emailAddress,
|
||||
'Register',
|
||||
6,
|
||||
5
|
||||
);
|
||||
|
||||
if (!otpResult || !otpResult.otp) {
|
||||
throw new ApiError(500, 'Failed to send OTP');
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'OTP sent successfully.',
|
||||
data: {},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,6 +2,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { z } from 'zod';
|
||||
import { hostCompanyDetailsSchema } from '../../../common/utils/validation/host/hostCompanyDetails.validation';
|
||||
import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
|
||||
@Injectable()
|
||||
@@ -36,4 +40,189 @@ export class MinglarService {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async createHost(data: CreateMinglarDto) {
|
||||
return this.prisma.user.create({ data });
|
||||
}
|
||||
|
||||
async getAllHosts() {
|
||||
return this.prisma.user.findMany({ where: { roleXid: 3 } });
|
||||
}
|
||||
|
||||
async getHostById(id: number) {
|
||||
const host = await this.prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
HostHeader: {
|
||||
include: {
|
||||
currencies: true,
|
||||
cities: true,
|
||||
states: true,
|
||||
countries: true,
|
||||
HostBankDetails: true,
|
||||
HostDocuments: true,
|
||||
HostSuggestion: true,
|
||||
hostParent: true,
|
||||
HostTrack: true,
|
||||
Activities: true,
|
||||
},
|
||||
},
|
||||
UserOtp: true,
|
||||
Token: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!host || host.roleXid !== 4) {
|
||||
throw new ApiError(404, 'Host not found');
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
async updateHost(id: number, data: UpdateMinglarDto) {
|
||||
return this.prisma.user.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteHost(id: number) {
|
||||
return this.prisma.user.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async getHostByEmail(email: string): Promise<User> {
|
||||
return this.prisma.user.findUnique({ where: { emailAddress: email } });
|
||||
}
|
||||
|
||||
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { emailAddress: email },
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
UserOtp: {
|
||||
where: { isActive: true, isVerified: false },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'User not found.');
|
||||
}
|
||||
|
||||
const userOtp = user.UserOtp[0];
|
||||
|
||||
if (!userOtp) {
|
||||
throw new ApiError(400, 'No OTP found.');
|
||||
}
|
||||
|
||||
if (new Date() > userOtp.expiresOn) {
|
||||
throw new ApiError(400, 'OTP has expired.');
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
|
||||
|
||||
if (!isMatch) {
|
||||
throw new ApiError(400, 'Invalid OTP.');
|
||||
}
|
||||
|
||||
await this.prisma.userOtp.update({
|
||||
where: { id: userOtp.id },
|
||||
data: {
|
||||
isVerified: true,
|
||||
verifiedOn: new Date(),
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async loginForMinglar(emailAddress: string, userPassword: string) {
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { emailAddress: emailAddress },
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
if (existingUser.roleXid !== 4) {
|
||||
throw new ApiError(403, 'Access denied. Not a host user.');
|
||||
}
|
||||
|
||||
const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword);
|
||||
if (!matchPassword) {
|
||||
throw new ApiError(401, 'Invalid credentials');
|
||||
}
|
||||
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
async createHostUser(email: string) {
|
||||
const newUser = await this.prisma.user.create({
|
||||
data: { emailAddress: email, roleXid: 4 },
|
||||
});
|
||||
return newUser;
|
||||
}
|
||||
|
||||
|
||||
// async addCompanyDetails(
|
||||
// companyData: HostCompanyDetailsInput,
|
||||
// documents: HostDocumentInput[] // Documents with S3 URLs
|
||||
// ) {
|
||||
// return await this.prisma.$transaction(async (tx) => {
|
||||
// // ✅ Check for existing company
|
||||
// const existingHost = await tx.hostHeader.findFirst({
|
||||
// where: { registrationNumber: companyData.registrationNumber },
|
||||
// });
|
||||
|
||||
// if (existingHost) {
|
||||
// throw new ApiError(400, 'Company already exists with this registration number');
|
||||
// }
|
||||
|
||||
// // ✅ Create company record
|
||||
// const createdHost = await tx.hostHeader.create({
|
||||
// data: {
|
||||
// companyName: companyData.companyName,
|
||||
// hostRefNumber: companyData.hostRefNumber,
|
||||
// address1: companyData.address1,
|
||||
// address2: companyData.address2,
|
||||
// cityXid: companyData.cityXid,
|
||||
// stateXid: companyData.stateXid,
|
||||
// countryXid: companyData.countryXid,
|
||||
// pinCode: companyData.pinCode,
|
||||
// logoPath: companyData.logoPath,
|
||||
// isSubsidairy: companyData.isSubsidairy,
|
||||
// registrationNumber: companyData.registrationNumber,
|
||||
// panNumber: companyData.panNumber,
|
||||
// gstNumber: companyData.gstNumber,
|
||||
// formationDate: new Date(companyData.formationDate),
|
||||
// companyType: companyData.companyType,
|
||||
// websiteUrl: companyData.websiteUrl,
|
||||
// instagramUrl: companyData.instagramUrl,
|
||||
// facebookUrl: companyData.facebookUrl,
|
||||
// linkedinUrl: companyData.linkedinUrl,
|
||||
// twitterUrl: companyData.twitterUrl,
|
||||
// currencyXid: companyData.currencyXid,
|
||||
// },
|
||||
// });
|
||||
|
||||
// // ✅ Create documents (if provided)
|
||||
// if (documents && documents.length > 0) {
|
||||
// const docsData = documents.map((doc) => ({
|
||||
// hostXid: createdHost.id,
|
||||
// documentTypeXid: doc.documentTypeXid,
|
||||
// documentName: doc.documentName,
|
||||
// filePath: doc.filePath,
|
||||
// }));
|
||||
|
||||
// await tx.hostDocuments.createMany({ data: docsData });
|
||||
// }
|
||||
|
||||
// return createdHost;
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
159
src/modules/minglaradmin/services/token.service.ts
Normal file
159
src/modules/minglaradmin/services/token.service.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||
import moment from "moment";
|
||||
import config from "../../../config/config";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export class TokenService {
|
||||
private generateToken(
|
||||
user_xid: number,
|
||||
expiresIn: Date,
|
||||
type: string,
|
||||
secret: string
|
||||
): { token: string; expires: Date } {
|
||||
const token = jwt.sign(
|
||||
{
|
||||
sub: user_xid,
|
||||
iat: moment().unix(),
|
||||
exp: moment(expiresIn).unix(),
|
||||
type,
|
||||
},
|
||||
secret
|
||||
);
|
||||
|
||||
return { token, expires: expiresIn };
|
||||
}
|
||||
|
||||
async generateAuthToken(
|
||||
user_xid: number,
|
||||
): Promise<{
|
||||
access: { token: string; expires: Date };
|
||||
refresh: { token: string; expires: Date };
|
||||
}> {
|
||||
const accessTokenExpires = moment()
|
||||
.add(config.jwt.accessExpirationMinutes, "minutes")
|
||||
.toDate();
|
||||
|
||||
const refreshTokenExpires = moment()
|
||||
.add(config.jwt.refreshExpirationDays, "days")
|
||||
.toDate();
|
||||
|
||||
const accessToken = this.generateToken(
|
||||
user_xid,
|
||||
accessTokenExpires,
|
||||
"access",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
const refreshToken = this.generateToken(
|
||||
user_xid,
|
||||
refreshTokenExpires,
|
||||
"refresh",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
await prisma.token.create({
|
||||
data: {
|
||||
token: refreshToken.token,
|
||||
expiringAt: refreshToken.expires,
|
||||
tokenType: "refresh",
|
||||
isBlackListed: false,
|
||||
|
||||
user: {
|
||||
connect: { id: user_xid },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
access: accessToken,
|
||||
refresh: refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async generateAuthTokenAdmin(
|
||||
user_xid: number
|
||||
): Promise<{
|
||||
access: { token: string; expires: Date };
|
||||
refresh: { token: string; expires: Date };
|
||||
}> {
|
||||
const accessTokenExpires = moment()
|
||||
.add(config.jwt.accessExpirationMinutes, "minutes")
|
||||
.toDate();
|
||||
|
||||
const refreshTokenExpires = moment()
|
||||
.add(config.jwt.refreshExpirationDays, "days")
|
||||
.toDate();
|
||||
|
||||
const accessToken = this.generateToken(
|
||||
user_xid,
|
||||
accessTokenExpires,
|
||||
"access",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
const refreshToken = this.generateToken(
|
||||
user_xid,
|
||||
refreshTokenExpires,
|
||||
"refresh",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
await prisma.token.create({
|
||||
data: {
|
||||
token: refreshToken.token,
|
||||
expiringAt: refreshToken.expires,
|
||||
tokenType: "refresh",
|
||||
isBlackListed: false,
|
||||
user: {
|
||||
connect: { id: user_xid },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
access: accessToken,
|
||||
refresh: refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async revokeToken(user_xid: number, deviceId: string): Promise<boolean> {
|
||||
const existingToken = await prisma.token.findFirst({
|
||||
where: {
|
||||
id: user_xid,
|
||||
deviceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingToken) return false;
|
||||
|
||||
await prisma.token.delete({ where: { id: existingToken.id } });
|
||||
return true;
|
||||
}
|
||||
|
||||
async isTokenBlackListed(token: string): Promise<boolean> {
|
||||
const existing = await prisma.token.findUnique({
|
||||
where: { token },
|
||||
});
|
||||
return existing ? true : false;
|
||||
}
|
||||
|
||||
async verifyRefreshToken(
|
||||
token: string
|
||||
): Promise<string | JwtPayload | null> {
|
||||
try {
|
||||
return jwt.verify(token, config.jwt.secret);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async decodeToken(token: string): Promise<string | JwtPayload | null> {
|
||||
try {
|
||||
return jwt.decode(token);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
624
swagger.json
624
swagger.json
@@ -1,631 +1,27 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"paths": {},
|
||||
"info": {
|
||||
"title": "Minglar API",
|
||||
"description": "NestJS Backend for Minglar with AWS Lambda endpoints",
|
||||
"description": "NestJS Backend for Minglar with Lambda-ready endpoints",
|
||||
"version": "1.0.0",
|
||||
"contact": {
|
||||
"name": "API Support"
|
||||
}
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://api.minglar.com",
|
||||
"description": "Production Server"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:3000",
|
||||
"description": "Local Development Server"
|
||||
"url": "http://localhost:3000/",
|
||||
"description": "Local Server"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "Host",
|
||||
"description": "Host management endpoints"
|
||||
},
|
||||
{
|
||||
"name": "Authentication",
|
||||
"description": "Authentication and authorization endpoints"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/host": {
|
||||
"get": {
|
||||
"tags": ["Host"],
|
||||
"summary": "Get all hosts",
|
||||
"description": "Retrieves a list of all host headers with basic information",
|
||||
"operationId": "getHosts",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/HostHeader"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"hostParent": "Example Host",
|
||||
"hostRefNumber": "HOST001",
|
||||
"hostStatusDisplay": "Active",
|
||||
"accountManager": "John Doe"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/registration": {
|
||||
"post": {
|
||||
"tags": ["Authentication"],
|
||||
"summary": "Register a new host",
|
||||
"description": "Initiates host registration by sending an OTP to the provided email address",
|
||||
"operationId": "registrationOfHost",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RegistrationRequest"
|
||||
},
|
||||
"example": {
|
||||
"email": "host@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OTP sent successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SuccessResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": true,
|
||||
"message": "OTP sent successfully.",
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Email is required",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "Email is required",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 400,
|
||||
"description": "Email is required",
|
||||
"statusCode": 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User already registered",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "User is already registered. Please login.",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 404,
|
||||
"description": "User is already registered. Please login.",
|
||||
"statusCode": 404
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/verify-otp": {
|
||||
"post": {
|
||||
"tags": ["Authentication"],
|
||||
"summary": "Verify OTP",
|
||||
"description": "Verifies the OTP sent to the user's email during registration",
|
||||
"operationId": "verifyOtp",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VerifyOtpRequest"
|
||||
},
|
||||
"example": {
|
||||
"email": "host@example.com",
|
||||
"otp": "123456"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OTP verified successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SuccessResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": true,
|
||||
"message": "OTP verified successfully",
|
||||
"data": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Invalid OTP or missing fields",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"examples": {
|
||||
"missingFields": {
|
||||
"value": {
|
||||
"success": false,
|
||||
"message": "Email and OTP are required",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 400,
|
||||
"description": "Email and OTP are required",
|
||||
"statusCode": 400
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidOtp": {
|
||||
"value": {
|
||||
"success": false,
|
||||
"message": "Invalid OTP.",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 400,
|
||||
"description": "Invalid OTP.",
|
||||
"statusCode": 400
|
||||
}
|
||||
}
|
||||
},
|
||||
"expiredOtp": {
|
||||
"value": {
|
||||
"success": false,
|
||||
"message": "OTP has expired.",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 400,
|
||||
"description": "OTP has expired.",
|
||||
"statusCode": 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "User not found.",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 404,
|
||||
"description": "User not found.",
|
||||
"statusCode": 404
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/host/login": {
|
||||
"post": {
|
||||
"tags": ["Authentication"],
|
||||
"summary": "Login for host",
|
||||
"description": "Authenticates a host user and returns an access token",
|
||||
"operationId": "loginForHost",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginRequest"
|
||||
},
|
||||
"example": {
|
||||
"emailAddress": "host@example.com",
|
||||
"userPassword": "password123"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Login successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": true,
|
||||
"message": "Login successful",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"emailAddress": "host@example.com",
|
||||
"mobileNumber": "+1234567890",
|
||||
"isActive": true,
|
||||
"roleXid": 4,
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - Missing fields or failed login",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "Email and password are required",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 400,
|
||||
"description": "Email and password are required",
|
||||
"statusCode": 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized - Invalid credentials",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "Invalid credentials",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 401,
|
||||
"description": "Invalid credentials",
|
||||
"statusCode": 401
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden - Not a host user",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "Access denied. Not a host user.",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 403,
|
||||
"description": "Access denied. Not a host user.",
|
||||
"statusCode": 403
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
},
|
||||
"example": {
|
||||
"success": false,
|
||||
"message": "User not found",
|
||||
"data": null,
|
||||
"error": {
|
||||
"code": 404,
|
||||
"description": "User not found",
|
||||
"statusCode": 404
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"bearer": {
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
"description": "JWT token obtained from login endpoint"
|
||||
"type": "http"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"HostHeader": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hostParent": {
|
||||
"type": "string",
|
||||
"description": "Host parent name",
|
||||
"example": "Example Host"
|
||||
},
|
||||
"hostRefNumber": {
|
||||
"type": "string",
|
||||
"description": "Host reference number",
|
||||
"example": "HOST001"
|
||||
},
|
||||
"hostStatusDisplay": {
|
||||
"type": "string",
|
||||
"description": "Host status display",
|
||||
"example": "Active"
|
||||
},
|
||||
"accountManager": {
|
||||
"type": "string",
|
||||
"description": "Account manager name",
|
||||
"example": "John Doe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RegistrationRequest": {
|
||||
"type": "object",
|
||||
"required": ["email"],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "Email address for registration",
|
||||
"example": "host@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"VerifyOtpRequest": {
|
||||
"type": "object",
|
||||
"required": ["email", "otp"],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "Email address used during registration",
|
||||
"example": "host@example.com"
|
||||
},
|
||||
"otp": {
|
||||
"type": "string",
|
||||
"description": "6-digit OTP code sent to email",
|
||||
"pattern": "^[0-9]{6}$",
|
||||
"example": "123456"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginRequest": {
|
||||
"type": "object",
|
||||
"required": ["emailAddress", "userPassword"],
|
||||
"properties": {
|
||||
"emailAddress": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "Host user email address",
|
||||
"example": "host@example.com"
|
||||
},
|
||||
"userPassword": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "User password",
|
||||
"minLength": 8,
|
||||
"example": "password123"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResponseData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "User ID",
|
||||
"example": 1
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "User first name",
|
||||
"example": "John"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "User last name",
|
||||
"example": "Doe"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "User email address",
|
||||
"example": "host@example.com"
|
||||
},
|
||||
"mobileNumber": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "User mobile number",
|
||||
"example": "+1234567890"
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean",
|
||||
"description": "User active status",
|
||||
"example": true
|
||||
},
|
||||
"roleXid": {
|
||||
"type": "integer",
|
||||
"description": "User role ID (4 for host)",
|
||||
"example": 4
|
||||
},
|
||||
"accessToken": {
|
||||
"type": "string",
|
||||
"description": "JWT access token for authentication",
|
||||
"example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Login successful"
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/LoginResponseData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SuccessResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Operation successful"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Response data (can be null or empty object)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Error message",
|
||||
"example": "An error occurred"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"example": null
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"description": "HTTP status code",
|
||||
"example": 400
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Error description",
|
||||
"example": "Bad request"
|
||||
},
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"description": "HTTP status code",
|
||||
"example": 400
|
||||
},
|
||||
"debug": {
|
||||
"type": "string",
|
||||
"description": "Debug information (only in non-production environments)",
|
||||
"example": "Stack trace..."
|
||||
}
|
||||
},
|
||||
"required": ["code", "description", "statusCode"]
|
||||
}
|
||||
},
|
||||
"required": ["success", "message", "data", "error"]
|
||||
}
|
||||
}
|
||||
"schemas": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
test-stepper-handler.ts
Normal file
122
test-stepper-handler.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Test script for stepper handler
|
||||
* Run with: npx ts-node test-stepper-handler.ts
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function testStepperHandler() {
|
||||
console.log('🧪 Testing Stepper Handler...\n');
|
||||
|
||||
try {
|
||||
// 1. Find a host user with HostHeader data
|
||||
console.log('📍 Step 1: Finding host user with HostHeader...');
|
||||
const hostUser = await prisma.user.findFirst({
|
||||
where: { roleXid: 4 },
|
||||
include: {
|
||||
HostHeader: {
|
||||
select: {
|
||||
id: true,
|
||||
stepper: true,
|
||||
hostRefNumber: true,
|
||||
companyName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!hostUser) {
|
||||
console.log('❌ No host user found (roleXid=4) in database.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hostUser.HostHeader || hostUser.HostHeader.length === 0) {
|
||||
console.log('⚠️ Host user found but no HostHeader records.');
|
||||
console.log(` User ID: ${hostUser.id}, Email: ${hostUser.emailAddress}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const hostHeader = hostUser.HostHeader[0];
|
||||
console.log(`✅ Found host user and HostHeader\n`);
|
||||
console.log(` User ID: ${hostUser.id}`);
|
||||
console.log(` Email: ${hostUser.emailAddress}`);
|
||||
console.log(` First Name: ${hostUser.firstName}`);
|
||||
console.log(` Host ID: ${hostHeader.id}`);
|
||||
console.log(` Company: ${hostHeader.companyName}`);
|
||||
console.log(` Ref Number: ${hostHeader.hostRefNumber}`);
|
||||
console.log(` Current Stepper: ${hostHeader.stepper}\n`);
|
||||
|
||||
// 2. Simulate what the handler returns
|
||||
console.log('📍 Step 2: Simulating handler response...\n');
|
||||
|
||||
const stepDescriptions: { [key: number]: string } = {
|
||||
1: 'Basic Company Information',
|
||||
2: 'Company Documents & Verification',
|
||||
3: 'Bank & Payment Details',
|
||||
4: 'Activities Setup',
|
||||
5: 'Pricing & Services',
|
||||
6: 'Review & Approval',
|
||||
7: 'Active & Live',
|
||||
};
|
||||
|
||||
const stepperDescription =
|
||||
stepDescriptions[hostHeader.stepper] || 'Unknown Step';
|
||||
|
||||
const response = {
|
||||
success: true,
|
||||
message: 'Stepper information retrieved successfully',
|
||||
data: {
|
||||
user: {
|
||||
id: hostUser.id,
|
||||
firstName: hostUser.firstName,
|
||||
lastName: hostUser.lastName,
|
||||
emailAddress: hostUser.emailAddress,
|
||||
roleXid: hostUser.roleXid,
|
||||
},
|
||||
stepper: {
|
||||
hostId: hostHeader.id,
|
||||
currentStep: hostHeader.stepper,
|
||||
stepperDescription: stepperDescription,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
console.log('✅ Handler Response:\n');
|
||||
console.log(JSON.stringify(response, null, 2));
|
||||
|
||||
// 3. Verify stepper value is numeric
|
||||
console.log('\n📍 Step 3: Validation checks...\n');
|
||||
|
||||
if (typeof hostHeader.stepper !== 'number') {
|
||||
console.log('❌ Stepper is not a number');
|
||||
return;
|
||||
}
|
||||
console.log('✅ Stepper is numeric:', hostHeader.stepper);
|
||||
|
||||
if (hostHeader.stepper < 1 || hostHeader.stepper > 7) {
|
||||
console.log(
|
||||
'⚠️ Stepper value is out of expected range (1-7):',
|
||||
hostHeader.stepper
|
||||
);
|
||||
} else {
|
||||
console.log('✅ Stepper value in valid range (1-7)');
|
||||
}
|
||||
|
||||
if (!stepperDescription.includes('Unknown')) {
|
||||
console.log('✅ Stepper description found:', stepperDescription);
|
||||
} else {
|
||||
console.log('⚠️ Unknown stepper value');
|
||||
}
|
||||
|
||||
console.log('\n✅ Test passed! Handler is working correctly.');
|
||||
} catch (error) {
|
||||
console.error('❌ Test error:', error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
testStepperHandler();
|
||||
Reference in New Issue
Block a user