made submit pqq answer for host api

This commit is contained in:
2025-11-19 16:55:54 +05:30
parent 99cbe55a70
commit 13ffee5f7e
4 changed files with 414 additions and 195 deletions

View File

@@ -0,0 +1,166 @@
import config from '@/config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk';
import Busboy from 'busboy';
import crypto from 'crypto';
import { PrismaService } from '../../../common/database/prisma.service';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError';
import { HostService } from '../services/host.service';
const prisma = new PrismaService();
const pqqService = new HostService(prisma);
const s3 = new AWS.S3({ region: config.aws.region });
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string): Promise<string> {
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
const s3Key = `${prefix}/${uniqueKey}`;
await s3.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: buffer,
ContentType: mimeType,
ACL: 'private'
}).promise();
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
}
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
// 1) Auth
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.');
const user = await verifyHostToken(token);
// 2) Content-Type check
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, "Body must be base64 encoded");
const bodyBuffer = Buffer.from(event.body!, "base64");
const fields: any = {};
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
// 3) Parse multipart data
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,
});
});
file.on('error', (err) => {
reject(new ApiError(400, `File upload error: ${err.message}`));
});
});
bb.on('field', (fieldname, val) => {
try {
fields[fieldname] = JSON.parse(val);
} catch {
fields[fieldname] = val;
}
});
bb.on('close', () => {
console.log("✅ Busboy parsing completed");
resolve();
});
bb.on('error', (err) => {
console.error("❌ Busboy error:", err);
reject(new ApiError(400, `Multipart parsing error: ${err.message}`));
});
bb.end(bodyBuffer);
});
console.log("📌 Parsed Files:", files);
// 4) Extract required fields
const activityXid = Number(fields.activityXid);
const pqqQuestionXid = Number(fields.pqqQuestionXid);
const pqqAnswerXid = Number(fields.pqqAnswerXid);
const comments = fields.comments || null;
if (!activityXid) throw new ApiError(400, "activityXid is required");
if (!pqqQuestionXid) throw new ApiError(400, "pqqQuestionXid is required");
if (!pqqAnswerXid) throw new ApiError(400, "pqqAnswerXid is required");
// 5) Create or update header
const header = await pqqService.createOrUpdateHeader(
activityXid,
pqqQuestionXid,
pqqAnswerXid,
comments
);
// 6) Upload files
const uploadedFiles: any[] = [];
for (const file of files) {
const url = await uploadToS3(
file.buffer,
file.mimeType,
file.fileName,
`ActivityOnboarding/Activity_${activityXid}/supportings`
);
const supporting = await pqqService.addSupportingFile(
header.id,
file.mimeType,
url
);
uploadedFiles.push(supporting);
}
return {
statusCode: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({
success: true,
message: "PQQ answer submitted successfully",
data: {
headerId: header.id,
uploadedFiles
}
})
};
} catch (error: any) {
console.error("❌ Error in submitPqqAnswer:", error);
throw error;
}
});

View File

@@ -450,4 +450,59 @@ export class HostService {
return `HOSTREFNO-${String(nextId).padStart(6, '0')}`;
}
async createOrUpdateHeader(
activityXid: number,
pqqQuestionXid: number,
pqqAnswerXid: number,
comments: string | null
) {
// find existing header
const existing = await this.prisma.activityPQQheader.findFirst({
where: { activityXid, pqqQuestionXid, deletedAt: null }
});
if (!existing) {
return await this.prisma.activityPQQheader.create({
data: {
activityXid,
pqqQuestionXid,
pqqAnswerXid,
comments
}
});
}
// mark old supportings deleted
await this.prisma.activityPQQSupportings.updateMany({
where: { activityPqqHeaderXid: existing.id },
data: {
isActive: false,
deletedAt: new Date()
}
});
// update header
return await this.prisma.activityPQQheader.update({
where: { id: existing.id },
data: {
pqqAnswerXid,
comments
}
});
}
async addSupportingFile(
headerId: number,
mimeType: string,
fileUrl: string
) {
return await this.prisma.activityPQQSupportings.create({
data: {
activityPqqHeaderXid: headerId,
mediaType: mimeType,
mediaFileName: fileUrl
}
});
}
}