made update profile for minglar admin
This commit is contained in:
@@ -15,12 +15,14 @@ model User {
|
||||
firstName String? @map("first_name")
|
||||
lastName String? @map("last_name")
|
||||
roleXid Int? @map("role_xid")
|
||||
dateOfBirth DateTime? @map("date_of_birth")
|
||||
role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict)
|
||||
emailAddress String @unique @map("email_address")
|
||||
isdCode String? @map("isd_code")
|
||||
mobileNumber String? @map("mobile_number")
|
||||
userPassword String? @map("user_password")
|
||||
userPasscode String? @map("user_passcode")
|
||||
profileImage String? @map("profile_image")
|
||||
isEmailVerfied Boolean? @default(false) @map("is_email_verified")
|
||||
isMobileVerfied Boolean? @default(false) @map("is_mobile_verified")
|
||||
isActive Boolean? @default(true) @map("is_active")
|
||||
@@ -55,11 +57,53 @@ model User {
|
||||
connectDetails ConnectDetails[]
|
||||
friends Friends[]
|
||||
friendOf Friends[] @relation("FriendUser")
|
||||
userAddressDetails UserAddressDetails[]
|
||||
userDocuments UserDocuments[]
|
||||
|
||||
@@map("users")
|
||||
@@schema("usr")
|
||||
}
|
||||
|
||||
model UserAddressDetails {
|
||||
id Int @id @default(autoincrement())
|
||||
userXid Int @map("user_xid")
|
||||
user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
|
||||
address1 String @map("address_1")
|
||||
address2 String? @map("address_2")
|
||||
countryXid Int @map("country_xid")
|
||||
country Countries @relation(fields: [countryXid], references: [id], onDelete: Restrict)
|
||||
stateXid Int @map("state_xid")
|
||||
states States @relation(fields: [stateXid], references: [id], onDelete: Restrict)
|
||||
cityXid Int @map("city_xid")
|
||||
cities Cities @relation(fields: [cityXid], references: [id], onDelete: Restrict)
|
||||
pinCode String @map("pin_code")
|
||||
locationName String? @map("location_name")
|
||||
locationAddress String? @map("location_address")
|
||||
locationLat Float? @map("location_lat")
|
||||
locationLong Float? @map("location_long")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
@@map("user_address_details")
|
||||
@@schema("usr")
|
||||
}
|
||||
|
||||
model UserDocuments {
|
||||
id Int @id @default(autoincrement())
|
||||
userXid Int @map("user_xid")
|
||||
user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
|
||||
fileName String @map("file_name")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
@@map("user_documents")
|
||||
@@schema("usr")
|
||||
}
|
||||
|
||||
model UserOtp {
|
||||
id Int @id @default(autoincrement())
|
||||
userXid Int @map("user_xid")
|
||||
@@ -160,20 +204,21 @@ model UserInterests {
|
||||
}
|
||||
|
||||
model Countries {
|
||||
id Int @id @default(autoincrement())
|
||||
countryName String @unique @map("country_name")
|
||||
countryCode String @unique @map("country_code")
|
||||
countryFlag String @map("country_flag")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
Currencies Currencies[]
|
||||
States States[]
|
||||
Taxes Taxes[]
|
||||
Banks Banks[]
|
||||
HostHeader HostHeader[]
|
||||
hostParent HostParent[]
|
||||
id Int @id @default(autoincrement())
|
||||
countryName String @unique @map("country_name")
|
||||
countryCode String @unique @map("country_code")
|
||||
countryFlag String @map("country_flag")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
Currencies Currencies[]
|
||||
States States[]
|
||||
Taxes Taxes[]
|
||||
Banks Banks[]
|
||||
HostHeader HostHeader[]
|
||||
hostParent HostParent[]
|
||||
userAddressDetails UserAddressDetails[]
|
||||
|
||||
@@map("countries")
|
||||
@@schema("mst")
|
||||
@@ -197,35 +242,37 @@ model Currencies {
|
||||
}
|
||||
|
||||
model States {
|
||||
id Int @id @default(autoincrement())
|
||||
countryXid Int @map("country_xid")
|
||||
country Countries @relation(fields: [countryXid], references: [id], onDelete: Cascade)
|
||||
stateName String @unique @map("state_name")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
Cities Cities[]
|
||||
BankBranches BankBranches[]
|
||||
HostHeader HostHeader[]
|
||||
hostParent HostParent[]
|
||||
id Int @id @default(autoincrement())
|
||||
countryXid Int @map("country_xid")
|
||||
country Countries @relation(fields: [countryXid], references: [id], onDelete: Cascade)
|
||||
stateName String @unique @map("state_name")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
Cities Cities[]
|
||||
BankBranches BankBranches[]
|
||||
HostHeader HostHeader[]
|
||||
hostParent HostParent[]
|
||||
userAddressDetails UserAddressDetails[]
|
||||
|
||||
@@map("states")
|
||||
@@schema("mst")
|
||||
}
|
||||
|
||||
model Cities {
|
||||
id Int @id @default(autoincrement())
|
||||
stateXid Int @map("state_xid")
|
||||
states States @relation(fields: [stateXid], references: [id], onDelete: Cascade)
|
||||
cityName String @unique @map("city_name")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
BankBranches BankBranches[]
|
||||
HostHeader HostHeader[]
|
||||
hostParent HostParent[]
|
||||
id Int @id @default(autoincrement())
|
||||
stateXid Int @map("state_xid")
|
||||
states States @relation(fields: [stateXid], references: [id], onDelete: Cascade)
|
||||
cityName String @unique @map("city_name")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
BankBranches BankBranches[]
|
||||
HostHeader HostHeader[]
|
||||
hostParent HostParent[]
|
||||
userAddressDetails UserAddressDetails[]
|
||||
|
||||
@@map("cities")
|
||||
@@schema("mst")
|
||||
|
||||
@@ -258,6 +258,23 @@ functions:
|
||||
path: /minglaradmin/create-password
|
||||
method: post
|
||||
|
||||
updateMinglarProfile:
|
||||
handler: src/modules/minglaradmin/handlers/updateProfile.handler
|
||||
package:
|
||||
patterns:
|
||||
- "src/modules/host/handlers/addCompanyDetails.*"
|
||||
- "src/modules/host/services/**"
|
||||
- "common/**"
|
||||
- "src/common/**"
|
||||
- "node_modules/@prisma/client/**"
|
||||
- "node_modules/.prisma/**"
|
||||
- "node_modules/@aws-sdk/**"
|
||||
- "node_modules/@smithy/**"
|
||||
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/update-profile
|
||||
method: patch
|
||||
|
||||
addCompanyDetails:
|
||||
handler: src/modules/host/handlers/addCompanyDetails.handler
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
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;
|
||||
}
|
||||
});
|
||||
151
src/modules/minglaradmin/handlers/updateProfile.ts
Normal file
151
src/modules/minglaradmin/handlers/updateProfile.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
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 { parseMultipartFormData, parseJsonField } from '../../../common/utils/helper/parseMultipartFormData';
|
||||
import AWS from 'aws-sdk';
|
||||
import crypto from 'crypto';
|
||||
import config from '@/config/config';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const minglarService = new MinglarService(prismaService);
|
||||
|
||||
const s3 = new AWS.S3({
|
||||
region: config.aws.region,
|
||||
});
|
||||
|
||||
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 verifyMinglarAdminToken(token);
|
||||
const userId = Number(userInfo.id);
|
||||
|
||||
if (!userId || isNaN(userId)) {
|
||||
throw new ApiError(400, 'Invalid user ID');
|
||||
}
|
||||
|
||||
// Parse multipart form data
|
||||
const contentType = event.headers['Content-Type'] || event.headers['content-type'];
|
||||
const isBase64Encoded = event.isBase64Encoded || false;
|
||||
|
||||
const { fields, files } = parseMultipartFormData(
|
||||
event.body,
|
||||
contentType,
|
||||
isBase64Encoded
|
||||
);
|
||||
|
||||
// Parse JSON fields
|
||||
const userData = parseJsonField(fields, 'userData') || {};
|
||||
const addressData = parseJsonField(fields, 'addressData') || {};
|
||||
|
||||
// Extract user fields
|
||||
const { firstName, lastName, mobileNumber, dateOfBirth, profileImage } = userData;
|
||||
|
||||
// Extract address fields
|
||||
const { address1, address2, stateXid, countryXid, cityXid, pinCode } = addressData;
|
||||
|
||||
// Handle file uploads (profileImage, aadharCard, panCard)
|
||||
const uploadedFiles: Array<{ fileName: string; filePath: string; documentType?: string }> = [];
|
||||
let profileImagePath: string | undefined = profileImage;
|
||||
|
||||
// Upload profile image if provided as file
|
||||
const profileImageFile = files.find(f => f.fieldName === 'profileImage');
|
||||
if (profileImageFile) {
|
||||
const uniqueKey = `${userId}_${crypto.randomUUID()}_${profileImageFile.fileName}`;
|
||||
const s3Key = `MinglarAdmin/ProfileImages/${uniqueKey}`;
|
||||
|
||||
await s3.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: profileImageFile.data,
|
||||
ContentType: profileImageFile.contentType,
|
||||
ACL: 'private',
|
||||
}).promise();
|
||||
|
||||
profileImagePath = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
}
|
||||
|
||||
// Upload documents (aadharCard, panCard)
|
||||
const aadharFile = files.find(f => f.fieldName === 'aadharCard');
|
||||
const panFile = files.find(f => f.fieldName === 'panCard');
|
||||
|
||||
if (aadharFile) {
|
||||
const uniqueKey = `${userId}_${crypto.randomUUID()}_${aadharFile.fileName}`;
|
||||
const s3Key = `MinglarAdmin/Documents/${uniqueKey}`;
|
||||
|
||||
await s3.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: aadharFile.data,
|
||||
ContentType: aadharFile.contentType,
|
||||
ACL: 'private',
|
||||
}).promise();
|
||||
|
||||
const filePath = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
uploadedFiles.push({ fileName: aadharFile.fileName, filePath, documentType: 'aadhar' });
|
||||
}
|
||||
|
||||
if (panFile) {
|
||||
const uniqueKey = `${userId}_${crypto.randomUUID()}_${panFile.fileName}`;
|
||||
const s3Key = `MinglarAdmin/${userId}/documents/pan_${uniqueKey}`;
|
||||
|
||||
await s3.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: s3Key,
|
||||
Body: panFile.data,
|
||||
ContentType: panFile.contentType,
|
||||
ACL: 'private',
|
||||
}).promise();
|
||||
|
||||
const filePath = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||
uploadedFiles.push({ fileName: panFile.fileName, filePath, documentType: 'pan' });
|
||||
}
|
||||
|
||||
// Update profile using service
|
||||
const result = await minglarService.updateProfile(
|
||||
userId,
|
||||
{
|
||||
firstName,
|
||||
lastName,
|
||||
mobileNumber,
|
||||
dateOfBirth,
|
||||
profileImage: profileImagePath,
|
||||
},
|
||||
{
|
||||
address1,
|
||||
address2,
|
||||
stateXid,
|
||||
countryXid,
|
||||
cityXid,
|
||||
pinCode,
|
||||
},
|
||||
uploadedFiles.filter(f => f.documentType).map(f => ({
|
||||
fileName: f.fileName,
|
||||
filePath: f.filePath,
|
||||
}))
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Profile updated successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -131,4 +131,161 @@ export class MinglarService {
|
||||
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
async updateProfile(
|
||||
userId: number,
|
||||
userData: {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
mobileNumber?: string;
|
||||
dateOfBirth?: string;
|
||||
profileImage?: string;
|
||||
},
|
||||
addressData: {
|
||||
address1?: string;
|
||||
address2?: string;
|
||||
stateXid?: number;
|
||||
countryXid?: number;
|
||||
cityXid?: number;
|
||||
pinCode?: string;
|
||||
},
|
||||
documents: Array<{ fileName: string; filePath: string }>
|
||||
) {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
// 1. Update User table
|
||||
const userUpdateData: any = {};
|
||||
if (userData.firstName !== undefined) userUpdateData.firstName = userData.firstName;
|
||||
if (userData.lastName !== undefined) userUpdateData.lastName = userData.lastName;
|
||||
if (userData.mobileNumber !== undefined) userUpdateData.mobileNumber = userData.mobileNumber;
|
||||
if (userData.dateOfBirth !== undefined) userUpdateData.dateOfBirth = new Date(userData.dateOfBirth);
|
||||
if (userData.profileImage !== undefined) userUpdateData.profileImage = userData.profileImage;
|
||||
|
||||
if (Object.keys(userUpdateData).length > 0) {
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: userUpdateData,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Update or create UserAddressDetails
|
||||
if (Object.keys(addressData).length > 0) {
|
||||
const existingAddress = await tx.userAddressDetails.findFirst({
|
||||
where: { userXid: userId, isActive: true },
|
||||
});
|
||||
|
||||
const addressUpdateData: any = {};
|
||||
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
|
||||
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
|
||||
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
|
||||
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
|
||||
if (addressData.cityXid !== undefined) addressUpdateData.cityXid = addressData.cityXid;
|
||||
if (addressData.pinCode !== undefined) addressUpdateData.pinCode = addressData.pinCode;
|
||||
|
||||
if (existingAddress) {
|
||||
await tx.userAddressDetails.update({
|
||||
where: { id: existingAddress.id },
|
||||
data: addressUpdateData,
|
||||
});
|
||||
} else {
|
||||
if (!addressData.address1 || !addressData.stateXid || !addressData.countryXid || !addressData.cityXid || !addressData.pinCode) {
|
||||
throw new ApiError(400, 'All address fields are required for new address');
|
||||
}
|
||||
await tx.userAddressDetails.create({
|
||||
data: {
|
||||
userXid: userId,
|
||||
...addressUpdateData,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update or create UserDocuments (store S3 URL in fileName field)
|
||||
if (documents && documents.length > 0) {
|
||||
const existingDocs = await tx.userDocuments.findMany({
|
||||
where: { userXid: userId, isActive: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
|
||||
// Update existing documents or create new ones
|
||||
for (let i = 0; i < documents.length; i++) {
|
||||
const doc = documents[i];
|
||||
if (existingDocs[i]) {
|
||||
// Update existing document
|
||||
await tx.userDocuments.update({
|
||||
where: { id: existingDocs[i].id },
|
||||
data: { fileName: doc.filePath }, // Store S3 URL in fileName
|
||||
});
|
||||
} else {
|
||||
// Create new document
|
||||
await tx.userDocuments.create({
|
||||
data: {
|
||||
userXid: userId,
|
||||
fileName: doc.filePath, // Store S3 URL in fileName
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Fetch updated user data to calculate percentage
|
||||
const updatedUser = await tx.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
userAddressDetails: {
|
||||
where: { isActive: true },
|
||||
take: 1,
|
||||
},
|
||||
userDocuments: {
|
||||
where: { isActive: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!updatedUser) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
// 5. Calculate profile completion percentage
|
||||
let percentage = 0;
|
||||
|
||||
// Profile Image: 15%
|
||||
if (updatedUser.profileImage) {
|
||||
percentage += 15;
|
||||
}
|
||||
|
||||
// Name and Phone Number: 15%
|
||||
if (updatedUser.firstName && updatedUser.lastName && updatedUser.mobileNumber) {
|
||||
percentage += 15;
|
||||
}
|
||||
|
||||
// Location Info: 25%
|
||||
if (updatedUser.userAddressDetails && updatedUser.userAddressDetails.length > 0) {
|
||||
const address = updatedUser.userAddressDetails[0];
|
||||
if (address.address1 && address.stateXid && address.countryXid && address.cityXid && address.pinCode) {
|
||||
percentage += 25;
|
||||
}
|
||||
}
|
||||
|
||||
// Documents (Aadhar and PAN): 45%
|
||||
if (updatedUser.userDocuments && updatedUser.userDocuments.length >= 2) {
|
||||
percentage += 45;
|
||||
} else if (updatedUser.userDocuments && updatedUser.userDocuments.length === 1) {
|
||||
percentage += 22.5; // Half if only one document
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: updatedUser.id,
|
||||
firstName: updatedUser.firstName,
|
||||
lastName: updatedUser.lastName,
|
||||
mobileNumber: updatedUser.mobileNumber,
|
||||
dateOfBirth: updatedUser.dateOfBirth,
|
||||
profileImage: updatedUser.profileImage,
|
||||
},
|
||||
address: updatedUser.userAddressDetails[0] || null,
|
||||
documents: updatedUser.userDocuments,
|
||||
profileCompletionPercentage: Math.min(percentage, 100),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user