made s3 uploader and apis
This commit is contained in:
185
src/modules/minglaradmin/handlers/addCompanyDetails.ts
Normal file
185
src/modules/minglaradmin/handlers/addCompanyDetails.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
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 { verifyHostToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||
import {
|
||||
hostCompanyDetailsSchema,
|
||||
REQUIRED_DOC_TYPES,
|
||||
} from '../../../common/utils/validation/host/hostCompanyDetails.validation';
|
||||
import { uploadFilesToS3 } from '../../../common/utils/helper/s3Upload';
|
||||
import { parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
|
||||
// ✅ 1. Extract & verify token
|
||||
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);
|
||||
|
||||
// ✅ 2. Check Content-Type and parse accordingly
|
||||
const contentType = event.headers['content-type'] || event.headers['Content-Type'] || '';
|
||||
let parsedCompany: any;
|
||||
let documentsWithS3Urls: Array<{ documentTypeXid: number; documentName: string; filePath: string }> = [];
|
||||
|
||||
if (contentType.includes('multipart/form-data')) {
|
||||
// ✅ Parse multipart/form-data
|
||||
// API Gateway sets isBase64Encoded to true for binary media types
|
||||
const isBase64Encoded = (event as any).isBase64Encoded === true;
|
||||
const formData = parseMultipartFormData(event.body, contentType, isBase64Encoded);
|
||||
|
||||
// ✅ Parse companyDetails from form field (should be JSON string)
|
||||
const companyDetailsJson = formData.fields['companyDetails'];
|
||||
if (!companyDetailsJson) {
|
||||
throw new ApiError(400, 'Company details are required in form data');
|
||||
}
|
||||
|
||||
try {
|
||||
parsedCompany = JSON.parse(companyDetailsJson);
|
||||
} catch {
|
||||
throw new ApiError(400, 'Invalid JSON in companyDetails field');
|
||||
}
|
||||
|
||||
// ✅ Validate company details
|
||||
const companyValidation = hostCompanyDetailsSchema.safeParse(parsedCompany);
|
||||
if (!companyValidation.success) {
|
||||
const errorMessages = companyValidation.error.issues.map(e => e.message).join(', ');
|
||||
throw new ApiError(400, `Validation failed: ${errorMessages}`);
|
||||
}
|
||||
parsedCompany = companyValidation.data;
|
||||
|
||||
// ✅ Process uploaded files
|
||||
if (formData.files.length === 0) {
|
||||
throw new ApiError(400, 'At least one document file is required');
|
||||
}
|
||||
|
||||
// ✅ Parse documents metadata (JSON array)
|
||||
const documentsJson = formData.fields['documents'];
|
||||
if (!documentsJson) {
|
||||
throw new ApiError(400, 'Documents metadata is required in form data');
|
||||
}
|
||||
|
||||
let documentsMetadata: Array<{ documentTypeXid: number; documentName: string; fieldName: string }>;
|
||||
try {
|
||||
documentsMetadata = JSON.parse(documentsJson);
|
||||
} catch {
|
||||
throw new ApiError(400, 'Invalid JSON in documents field');
|
||||
}
|
||||
|
||||
if (!Array.isArray(documentsMetadata) || documentsMetadata.length === 0) {
|
||||
throw new ApiError(400, 'Documents must be a non-empty array');
|
||||
}
|
||||
|
||||
// ✅ Map files to document structure
|
||||
const documentMetadata: Array<{ documentTypeXid: number; documentName: string; file: typeof formData.files[0] }> = [];
|
||||
|
||||
for (const docMeta of documentsMetadata) {
|
||||
const file = formData.files.find((f) => f.fieldName === docMeta.fieldName);
|
||||
if (!file) {
|
||||
throw new ApiError(400, `File not found for field: ${docMeta.fieldName}`);
|
||||
}
|
||||
|
||||
documentMetadata.push({
|
||||
documentTypeXid: docMeta.documentTypeXid,
|
||||
documentName: docMeta.documentName,
|
||||
file,
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ Ensure all required documents exist
|
||||
const uploadedDocTypes = documentMetadata.map((doc) => doc.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(', ')}`);
|
||||
}
|
||||
|
||||
// ✅ Upload files to S3
|
||||
const filesToUpload = documentMetadata.map((doc) => ({
|
||||
fileData: doc.file.data.toString('base64'),
|
||||
fileName: doc.file.fileName,
|
||||
contentType: doc.file.contentType,
|
||||
}));
|
||||
|
||||
const s3Urls = await uploadFilesToS3(filesToUpload, `host-documents/${userInfo.id}`);
|
||||
|
||||
// ✅ Map S3 URLs to documents
|
||||
documentsWithS3Urls = documentMetadata.map((doc, index) => ({
|
||||
documentTypeXid: doc.documentTypeXid,
|
||||
documentName: doc.documentName,
|
||||
filePath: s3Urls[index],
|
||||
}));
|
||||
} else {
|
||||
// ✅ Fallback to JSON parsing (for backward compatibility)
|
||||
let body: { companyDetails?: unknown; documents?: unknown[] };
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { companyDetails, documents } = body;
|
||||
|
||||
if (!companyDetails) {
|
||||
throw new ApiError(400, 'Company details are required');
|
||||
}
|
||||
|
||||
// ✅ Validate company details
|
||||
const companyValidation = hostCompanyDetailsSchema.safeParse(companyDetails);
|
||||
if (!companyValidation.success) {
|
||||
const errorMessages = companyValidation.error.issues.map(e => e.message).join(', ');
|
||||
throw new ApiError(400, `Validation failed: ${errorMessages}`);
|
||||
}
|
||||
parsedCompany = companyValidation.data;
|
||||
|
||||
// For JSON, we still expect base64 encoded files in documents array
|
||||
// This maintains backward compatibility
|
||||
if (documents && Array.isArray(documents) && documents.length > 0) {
|
||||
const filesToUpload = documents.map((doc: any) => ({
|
||||
fileData: doc.fileData,
|
||||
fileName: doc.documentName,
|
||||
contentType: doc.contentType || 'application/pdf',
|
||||
}));
|
||||
|
||||
const s3Urls = await uploadFilesToS3(filesToUpload, `host-documents/${userInfo.id}`);
|
||||
|
||||
documentsWithS3Urls = documents.map((doc: any, index: number) => ({
|
||||
documentTypeXid: doc.documentTypeXid,
|
||||
documentName: doc.documentName,
|
||||
filePath: s3Urls[index],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 7. Pass validated data to service
|
||||
const createdHost = await minglarService.addCompanyDetails(parsedCompany, documentsWithS3Urls);
|
||||
|
||||
if (!createdHost) {
|
||||
throw new ApiError(400, 'Failed to add company details');
|
||||
}
|
||||
|
||||
// ✅ 6. 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',
|
||||
}),
|
||||
};
|
||||
});
|
||||
65
src/modules/minglaradmin/handlers/createPassword.ts
Normal file
65
src/modules/minglaradmin/handlers/createPassword.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
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 { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(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.');
|
||||
}
|
||||
|
||||
// Authenticate user using the shared authForHost function
|
||||
const userInfo = await verifyHostToken(token);
|
||||
const user_xid = userInfo.id;
|
||||
|
||||
// Parse request body
|
||||
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');
|
||||
}
|
||||
|
||||
// Validate password match
|
||||
if (password !== confirmPassword) {
|
||||
throw new ApiError(400, 'Password and confirm password do not match');
|
||||
}
|
||||
|
||||
// Validate password length
|
||||
if (password.length < 8) {
|
||||
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||
}
|
||||
|
||||
await minglarService.createPassword(user_xid, password);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Password created successfully',
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
70
src/modules/minglaradmin/handlers/loginForHost.ts
Normal file
70
src/modules/minglaradmin/handlers/loginForHost.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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,
|
||||
// }),
|
||||
// };
|
||||
// });
|
||||
|
||||
106
src/modules/minglaradmin/services/minglar.service.ts
Normal file
106
src/modules/minglaradmin/services/minglar.service.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
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';
|
||||
|
||||
type HostCompanyDetailsInput = z.infer<typeof hostCompanyDetailsSchema>;
|
||||
|
||||
// Document input after S3 upload (with S3 URL as filePath)
|
||||
interface HostDocumentInput {
|
||||
documentTypeXid: number;
|
||||
documentName: string;
|
||||
filePath: string; // S3 URL
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MinglarService {
|
||||
constructor(private prisma: PrismaService) { }
|
||||
|
||||
async createPassword(user_xid: number, password: string): Promise<boolean> {
|
||||
// Find user by id
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: user_xid },
|
||||
select: { id: true, emailAddress: true, userPassword: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
// Check if password already exists
|
||||
if (user.userPassword) {
|
||||
throw new ApiError(400, 'Password already exists. Use update password instead.');
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
const saltRounds = parseInt(process.env.SALT_ROUNDS || '10', 10);
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// Update user with hashed password
|
||||
await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { userPassword: hashedPassword },
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user