export const config = { api: { bodyParser: true, // ✅ 이게 false면 안 됨! }, }; import type { NextApiRequest, NextApiResponse } from "next"; import path from "path"; import fs from "fs"; import * as z from "zod"; import db from "@/db/db"; import { GetPOSchema } from "@/lib/po/validations"; import { unstable_cache } from "@/lib/unstable-cache"; import { filterColumns } from "@/lib/filter-columns"; import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq, count, } from "drizzle-orm"; import { countPos, selectPos } from "@/lib/po/repository"; import { contractEnvelopes, contractsDetailView, contractSigners, contracts, } from "@/db/schema/contract"; import { vendors, vendorContacts } from "@/db/schema/vendors"; import dayjs from "dayjs"; import { POContent } from "@/lib/docuSign/types"; import { downloadContractFile, getRecipients } from "@/lib/docuSign/docuSignFns"; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== "POST") { return res.status(405).end(); } try { const { event, data = {} } = req.body; //recipientId === "1" 첫번째 서명자 서명 완료 //recipientId === "2" 두번쨰 서명자 서명 완료 const { envelopeId, recipientId } = data; console.log(req.body) const contractList = [ { dbName: "contract_envelopes", dbSchma: contractEnvelopes, recipients: [ { recipientId: "1", role: "VENDOR", }, { recipientId: "2", role: "REQUESTER", }, ], }, ]; //서명 요청 발송 if (event === "recipient-sent") { await db.transaction(async (tx) => { for (const targetDB of contractList) { const { dbSchma, dbName, recipients = [] } = targetDB; const [targetContract] = await tx .select() .from(dbSchma) .where(eq(dbSchma.envelopeId, envelopeId)) .limit(1); if (!targetContract) { continue; } const { contractId, id } = targetContract; if (contractId === null || contractId === undefined) { continue; } const { result: sendResult, message } = await getRecipients( envelopeId, recipientId ); if (!sendResult) { const targetRole = recipients.find( (c) => c.recipientId === recipientId ); if (targetRole) { const { role } = targetRole; const safeRole = role as "REQUESTER" | "VENDOR"; await tx .update(contracts) .set({ status: `$FAILED_${safeRole}_SEND_MAIL(${message})` }) .where(eq(contracts.id, contractId)); await tx .update(dbSchma) .set({ envelopeStatus: `${safeRole}_${message}` }) .where(eq(dbSchma.id, id)); } continue; } // const createContractFile = await downloadContractFile(envelopeId); // const { result, buffer, error } = createContractFile; // if (!result || !buffer) { // console.error( // `${dbName}: ${envelopeId} can not download contract file, ${error}` // ); // continue; // } // const fullFilePath = createFolderTree(fileName, filePath); // fs.writeFileSync(fullFilePath, buffer); } }); } //서명 요청 수신 if (event === "recipient-delivered") { } //서명 부분 거절 if (event === "recipient-declined") { await db.transaction(async (tx) => { for (const targetDB of contractList) { const { dbSchma, recipients = [] } = targetDB; const [targetContract] = await tx .select() .from(dbSchma) .where(eq(dbSchma.envelopeId, envelopeId)) .limit(1); const { id, contractId } = targetContract; if (contractId === null || contractId === undefined) { continue; } const targetRole = recipients.find( (c) => c.recipientId === recipientId ); if (targetRole) { const { role } = targetRole; const safeRole = role as "REQUESTER" | "VENDOR"; await tx .update(contracts) .set({ status: `${safeRole}_DECLINED_SIGNATURE` }) .where(eq(contracts.id, contractId)); if (["REQUESTER", "VENDOR"].includes(role)) { await tx .update(contractEnvelopes) .set({ envelopeStatus: `${role}_DECLINED` }) .where(and(eq(contractEnvelopes.envelopeId, envelopeId))); await tx .update(contractSigners) .set({ signerStatus: "DECLINED", signedAt: new Date(), }) .where( and( eq(contractSigners.envelopeId, id), eq(contractSigners.signerType, safeRole) ) ); } } } }); } //서명 부분 완료 if (event === "recipient-completed") { await db.transaction(async (tx) => { for (const targetDB of contractList) { const { dbSchma, dbName, recipients = [] } = targetDB; const [targetContract] = await tx .select() .from(dbSchma) .where(eq(dbSchma.envelopeId, envelopeId)) .limit(1); const { id, contractId, fileName, filePath } = targetContract; if (contractId === null || contractId === undefined) { continue; } const createContractFile = await downloadContractFile(envelopeId); const { result, buffer, error } = createContractFile; if (!result || !buffer) { console.error( `${dbName}: ${envelopeId} can not download contract file, ${error}` ); continue; } const fullFilePath = createFolderTree(fileName, filePath); fs.writeFileSync(fullFilePath, buffer); const targetRole = recipients.find( (c) => c.recipientId === recipientId ); if (targetRole) { const { role } = targetRole; const safeRole = role as "REQUESTER" | "VENDOR"; await tx .update(contracts) .set({ status: `${safeRole}_FINISHED_SIGNATURE` }) .where(eq(contracts.id, contractId)); if (["REQUESTER", "VENDOR"].includes(role)) { await tx .update(contractEnvelopes) .set({ envelopeStatus: `${role}_FINISHED` }) .where(and(eq(contractEnvelopes.envelopeId, envelopeId))); await tx .update(contractSigners) .set({ signerStatus: "FINISHED", signedAt: new Date() }) .where( and( eq(contractSigners.envelopeId, id), eq(contractSigners.signerType, safeRole) ) ); } } } }); } if (event === "envelope-completed") { await db.transaction(async (tx) => { for (const targetDB of contractList) { const { dbSchma, dbName } = targetDB; const [targetContract] = await tx .select() .from(dbSchma) .where(eq(dbSchma.envelopeId, envelopeId)) .limit(1); const { contractId, fileName, filePath } = targetContract; if (contractId === null || contractId === undefined) { continue; } const createContractFile = await downloadContractFile(envelopeId); const { result, buffer, error } = createContractFile; if (!result || !buffer) { console.error( `${dbName}: ${envelopeId} can not download contract file, ${error}` ); continue; } const fullFilePath = createFolderTree(fileName, filePath); fs.writeFileSync(fullFilePath, buffer); await tx .update(contracts) .set({ status: "COMPLETED_SIGNATURE" }) .where(eq(contracts.id, contractId)); await tx .update(contractEnvelopes) .set({ envelopeStatus: "COMPLETED_SIGNATURE" }) .where(and(eq(contractEnvelopes.envelopeId, envelopeId))); } }); } if (event === "envelope-declined") { } return res.status(200).json("OK"); } catch (error: any) { return res .status(500) .json({ success: false, message: error?.message || "Unknown error" }); } } const createFolderTree = (fileName: string, filePath: string): string => { const cleanedPath = filePath.replace(/^\/+/, ""); const dirPath = path.resolve(process.cwd(), "public", cleanedPath); // 예: 'contracts/185/signatures' const fullFilePath = path.join(dirPath, fileName); // 예: 'contracts/185/signatures/xxx.pdf' if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } return fullFilePath; };