onsitesurvey/src/app/api/qna/service.ts
2025-07-03 09:28:16 +09:00

183 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { SessionData } from '@/types/Auth'
import { CommonCode, InquiryRequest } from '@/types/Inquiry'
import { ERROR_MESSAGE } from '@/hooks/useAlertMsg'
import { HttpStatusCode } from 'axios'
import { ApiError } from 'next/dist/server/api-utils'
import { prisma } from '@/libs/prisma'
import { sendEmail } from '@/libs/mailer'
export class QnaService {
private session?: SessionData
constructor(session?: SessionData) {
this.session = session
}
/**
* @description API ROUTE 에러 처리
* @param {any} error 에러 객체
* @returns {ApiError} 에러 객체
*/
private handleRouteError(error: any): ApiError {
if (error === undefined) return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGE.SERVER_ERROR)
console.error('❌ API ROUTE ERROR : ', error)
return new ApiError(error.response.status ?? HttpStatusCode.InternalServerError, error.response.data.result.message ?? ERROR_MESSAGE.FETCH_ERROR)
}
/**
* @description 비동기 함수 try-catch 처리 함수
* @param {() => Promise<any>} func 비동기 함수
* @returns {Promise<ApiError | any>} 에러 객체 또는 함수 결과
*/
async tryFunction(func: () => Promise<any>, shouldThrowResult?: boolean): Promise<ApiError | any> {
if (this.session !== undefined && !this.session?.isLoggedIn) {
return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGE.UNAUTHORIZED)
}
try {
const response = await func()
if (shouldThrowResult) return response
return this.handleResult(response)
} catch (error) {
return this.handleRouteError(error)
}
}
/**
* @description 함수 결과 처리 함수
* @param {any} result 함수 결과
* @returns {ApiError | any} 에러 객체 또는 함수 결과
*/
private handleResult(response: any): ApiError | any {
if (response.status === HttpStatusCode.Ok) {
if (response.data.data !== null) return response.data
return new ApiError(HttpStatusCode.NotFound, ERROR_MESSAGE.NOT_FOUND)
}
return new ApiError(response.result.code, response.result.message)
}
/**
* @description 문의 유형 타입 목록 조회
* @param {string[]} responseList 문의 유형 타입 목록
* @returns {CommonCode[]} 문의 유형 타입 목록
*/
getInquiryTypeList(responseList: string[]): CommonCode[] {
const codeList: CommonCode[] = []
responseList.forEach((item: any) => {
if (item.headCd === '204200' || item.headCd === '204300' || item.headCd === '204400') {
codeList.push({
headCd: item.headCd,
code: item.code,
name: item.codeJp,
refChar1: item.refChr1,
})
}
})
return codeList
}
/**
* @description 문의 목록 조회 파라미터 처리
* @param {URLSearchParams} searchParams URLSearchParams 객체
* @returns {Record<string, string>} 문의 목록 조회 파라미터
*/
getSearchParams(searchParams: URLSearchParams): Record<string, string> {
const params: Record<string, string> = {}
searchParams.forEach((value, key) => {
const match = key.match(/inquiryListRequest\[(.*)\]/)
if (match) {
params[match[1]] = value
} else {
params[key] = value
}
})
return params
}
/**
* @description 문의 메일 발송
* @param {FormData} formData 문의 메일 발송 요청 파라미터 - qnaClsLrgCd, title, contents, files
* @returns {ApiError | null} 에러 객체 또는 null
*/
async sendMail(formData: FormData): Promise<ApiError | void> {
const receivers: string[] = await this.getReceiver(formData.get('qnaClsLrgCd') as string)
if (receivers.length === 0) {
return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGE.EMAIL_SEND_ERROR)
}
const files = formData.getAll('files') as File[]
const fileBuffers = await Promise.all(files.map((file) => file.arrayBuffer()))
const attachments = files.map((file, index) => ({
filename: file.name,
content: Buffer.from(fileBuffers[index]),
contentType: file.type || 'application/octet-stream',
}))
return this.tryFunction(() => {
return sendEmail({
from: 'test@test.com',
to: receivers,
subject: `[HANASYS お問い合わせ] ${formData.get('title')}`,
content: this.generateEmailContent(formData),
attachments: attachments,
})
}, true)
}
/**
* @description 문의 수신자 조회
* @param {string} qnaClsLrgCd 문의 유형 대분류 코드
* @returns {string[]} 문의 수신자 목록
*/
async getReceiver(qnaClsLrgCd: string): Promise<string[]> {
const query = `
OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP;
SELECT CONVERT(NVARCHAR(100), DecryptByKey(bu.e_mail)) AS email
FROM BC_USER bu
WHERE bu.user_id IN (
SELECT user_id
FROM SY_POLICY_U spu
WHERE policy_cd = (
SELECT bcl.ref_chr2
FROM BC_COMM_L bcl
WHERE bcl.head_cd = (SELECT head_cd FROM BC_COMM_H bch WHERE head_id = 'QNA_CLS_MID_CD')
AND bcl.ref_chr1 = '${qnaClsLrgCd}'
GROUP BY bcl.ref_chr2
)
)
CLOSE SYMMETRIC KEY SYMMETRICKEY;`
const receivers: { email: string }[] = await prisma.$queryRawUnsafe(query)
return receivers.map((receiver) => receiver.email)
}
/**
* @description 문의 이메일 내용 생성
* @param {FormData} formData 문의 메일 발송 요청 파라미터 - qnaClsLrgCd, title, contents, files
* @returns {string} 문의 이메일 내용
*/
generateEmailContent = (formData: FormData) => `
<div>
<p style="font-size: 13px; font-weight: 400; color: #2e3a59; margin-bottom: 15px;">
お問い合わせが登録されました。<br/>
{ QSP > System mgt. > お問い合わせ } でお問い合わせ内容を確認してください。
</p>
<p style="font-size: 13px; font-weight: 400; color: #2e3a59; margin-bottom: 3px;">
-登録者: <span style="color: #417DDC;">${formData.get('regUserNm')}</span>
</p>
<p style="font-size: 13px; font-weight: 400; color: #2e3a59; margin-bottom: 3px;">
-販売店ID
<span style="color: #417DDC;">
${formData.get('storeId')}
</span>
</p>
<p style="font-size: 13px; font-weight: 400; color: #2e3a59; margin-bottom: 15px;">
-販売店名:
<span style="color: #417DDC;">${this.session?.storeNm ?? ' - '}</span>
</p>
<p style="font-size: 13px; font-weight: 400; color: #2e3a59; margin-bottom: 15px;">
-お問い合わせ内容:
</p>
<p style="font-size: 13px; font-weight: 400; color: #2e3a59;">
${formData.get('contents')}
</p>
</div>
`
}