Compare commits

..

No commits in common. "bc6a83705ba087aa7bec947ad4da377d5774e91d" and "b7188cf5bc16e313294ac403c116652ac55891e1" have entirely different histories.

8 changed files with 93 additions and 207 deletions

View File

@ -1,15 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { loggerWrapper } from '@/libs/api-wrapper'
import { prisma } from '@/libs/prisma'
import { CommonCode } from '@/types/Inquiry'
import type { CommCode } from '@/types/CommCode'
async function getCommCode(request: NextRequest): Promise<NextResponse> {
try {
const searchParams = request.nextUrl.searchParams
const headCode = searchParams.get('headCode')
if (headCode === 'QNA_CD') {
return getQnaCd()
}
// @ts-ignore
const headCd = await prisma.BC_COMM_H.findFirst({
@ -27,22 +24,23 @@ async function getCommCode(request: NextRequest): Promise<NextResponse> {
if (headCode === 'SALES_OFFICE_CD') {
return getSaleOffice(headCd.HEAD_CD)
} else {
// @ts-ignore
const roofMaterials: CommCode[] = await prisma.BC_COMM_L.findMany({
where: {
HEAD_CD: headCd.HEAD_CD,
},
select: {
HEAD_CD: true,
CODE: true,
CODE_JP: true,
},
orderBy: {
CODE: 'asc',
},
})
return NextResponse.json(roofMaterials)
}
// @ts-ignore
const roofMaterials: CommCode[] = await prisma.BC_COMM_L.findMany({
where: {
HEAD_CD: headCd.HEAD_CD,
},
select: {
HEAD_CD: true,
CODE: true,
CODE_JP: true,
},
orderBy: {
CODE: 'asc',
},
})
return NextResponse.json(roofMaterials)
} catch (error) {
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
@ -66,50 +64,4 @@ const getSaleOffice = async (headCode: string) => {
return NextResponse.json(commCodeSaleOffice)
}
/**
* @description QNA
* @returns {CommonCode[]} QNA
*/
const getQnaCd = async () => {
// @ts-ignore
const headCdList: { HEAD_CD: string; HEAD_ID: string }[] = await prisma.BC_COMM_H.findMany({
where: {
OR: [{ HEAD_ID: { in: ['QNA_CLS_LRG_CD'] } }, { HEAD_ID: { in: ['QNA_CLS_MID_CD'] } }, { HEAD_ID: { in: ['QNA_CLS_SML_CD'] } }],
},
select: {
HEAD_CD: true,
HEAD_ID: true,
},
})
const result: CommonCode[] = []
// @ts-ignore
const commCodeQna: CommCode[] = await prisma.BC_COMM_L.findMany({
where: {
HEAD_CD: {
in: headCdList.map((item) => item.HEAD_CD).filter(Boolean),
},
},
select: {
HEAD_CD: true,
CODE: true,
CODE_JP: true,
REF_CHR1: true,
},
})
commCodeQna.forEach((item) => {
result.push({
// @ts-ignore
headCd: item.HEAD_CD,
// @ts-ignore
headId: headCdList.find((headCd) => headCd.HEAD_CD === item.HEAD_CD)?.HEAD_ID ?? '',
// @ts-ignore
code: item.CODE,
// @ts-ignore
name: item.CODE_JP,
// @ts-ignore
refChar1: item.REF_CHR1 ?? '',
})
})
return NextResponse.json(result)
}
export const GET = loggerWrapper(getCommCode)

45
src/app/api/qna/route.ts Normal file
View File

@ -0,0 +1,45 @@
import { NextResponse } from 'next/server'
import axios from 'axios'
import { loggerWrapper } from '@/libs/api-wrapper'
import { QnaService } from './service'
import { ApiError } from 'next/dist/server/api-utils'
/**
* @api {GET} /api/qna API
* @apiName GET /api/qna
* @apiGroup Qna
* @apiDescription API
*
* @apiSuccess {Object} data
* @apiSuccess {String} data.headCd
* @apiSuccess {String} data.code
* @apiSuccess {String} data.codeJp -
* @apiSuccess {String} data.refChr1 -
*
* @apiExample {curl} Example usage:
* curl -X GET http://localhost:3000/api/qna
*
* @apiSuccessExample {json} Success-Response:
* {
* "data": [
* {
* "headCd": "204200",
* "code": "1",
* "codeJp": "1",
* "refChr1": "1"
* }
* ],
* ...
* }
*/
async function getCommonCodeListData(): Promise<NextResponse> {
const service = new QnaService()
const response = await service.tryFunction(() => axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/system/commonCodeListData`))
if (response instanceof ApiError) {
return NextResponse.json({ error: response.message }, { status: response.statusCode })
}
const result = service.getInquiryTypeList(response.data.apiCommCdList)
return NextResponse.json({ data: result })
}
export const GET = loggerWrapper(getCommonCodeListData)

View File

@ -1,4 +1,4 @@
import axios, { HttpStatusCode } from 'axios'
import axios from 'axios'
import { NextResponse } from 'next/server'
import { loggerWrapper } from '@/libs/api-wrapper'
import { QnaService } from '../service'
@ -12,9 +12,9 @@ import { cookies } from 'next/headers'
* @api {POST} /api/qna/save API
* @apiName POST /api/qna/save
* @apiGroup Qna
* @apiDescription API
* @apiDescription API
*
* @apiBody {FormData} formData
* @apiBody {InquiryRequest} inquiryRequest
*
* @apiExample {curl} Example usage:
* curl -X POST http://localhost:3000/api/qna/save
@ -32,14 +32,9 @@ import { cookies } from 'next/headers'
async function setQna(request: Request): Promise<NextResponse> {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
const service = new QnaService(session)
const formData = await request.formData()
const mailResult = await service.sendMail(formData)
if (mailResult instanceof ApiError) {
return NextResponse.json({ error: mailResult.message }, { status: mailResult.statusCode })
}
const result = await service.tryFunction(() =>
axios.post(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/qna/save`, formData, {
headers: {

View File

@ -1,10 +1,8 @@
import { SessionData } from '@/types/Auth'
import { CommonCode, InquiryRequest } from '@/types/Inquiry'
import { CommonCode } 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
@ -17,7 +15,6 @@ export class QnaService {
* @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)
}
@ -26,13 +23,13 @@ export class QnaService {
* @param {() => Promise<any>} func
* @returns {Promise<ApiError | any>}
*/
async tryFunction(func: () => Promise<any>, shouldThrowResult?: boolean): Promise<ApiError | any> {
async tryFunction(func: () => Promise<any>, isFile?: 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
if (isFile) return response
return this.handleResult(response)
} catch (error) {
return this.handleRouteError(error)
@ -57,22 +54,12 @@ export class QnaService {
* @param {string[]} responseList
* @returns {CommonCode[]}
*/
getInquiryTypeList(responseList: any): CommonCode[] {
getInquiryTypeList(responseList: string[]): CommonCode[] {
const codeList: CommonCode[] = []
const headCdList: { headCd: string; headId: string }[] = []
responseList.apiHeadCdList.forEach((item: any) => {
if (item.headId === 'QNA_CLS_LRG_CD' || item.headId === 'QNA_CLS_MID_CD' || item.headId === 'QNA_CLS_SML_CD') {
headCdList.push({
headCd: item.headCd,
headId: item.headId,
})
}
})
responseList.apiCommCdList.forEach((item: any) => {
if (headCdList.some((headCd) => headCd.headCd === item.headCd)) {
responseList.forEach((item: any) => {
if (item.headCd === '204200' || item.headCd === '204300' || item.headCd === '204400') {
codeList.push({
headCd: item.headCd,
headId: headCdList.find((headCd) => headCd.headCd === item.headCd)?.headId ?? '',
code: item.code,
name: item.codeJp,
refChar1: item.refChr1,
@ -99,94 +86,4 @@ export class QnaService {
})
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>
`
}

View File

@ -5,7 +5,7 @@ import { useSessionStore } from '@/store/session'
import { InquiryRequest } from '@/types/Inquiry'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { CONFIRM_MESSAGE, ERROR_MESSAGE, SUCCESS_MESSAGE, useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg'
import { CONFIRM_MESSAGE, SUCCESS_MESSAGE, useAlertMsg, WARNING_MESSAGE } from '@/hooks/useAlertMsg'
import { useInquiryFilterStore } from '@/store/inquiryFilterStore'
export default function RegistForm() {
@ -106,13 +106,9 @@ export default function RegistForm() {
showConfirm(
CONFIRM_MESSAGE.SAVE_INQUIRY_CONFIRM,
async () => {
try {
const res = await saveInquiry(formData)
showSuccessAlert(SUCCESS_MESSAGE.SAVE_SUCCESS)
router.push(`/inquiry/${res.qnaNo}`)
} catch (error) {
showErrorAlert(ERROR_MESSAGE.SERVER_ERROR)
}
const res = await saveInquiry(formData)
showSuccessAlert(SUCCESS_MESSAGE.SAVE_SUCCESS)
router.push(`/inquiry/${res.qnaNo}`)
},
() => null,
)
@ -153,7 +149,7 @@ export default function RegistForm() {
</option>
{commonCodeList
.filter((code) => code.headId === 'QNA_CLS_LRG_CD')
.filter((code) => code.headCd === '204200')
.map((code) => (
<option key={code.code} value={code.code}>
{code.name}
@ -161,7 +157,7 @@ export default function RegistForm() {
))}
</select>
</div>
{commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsLrgCd).length > 0 && inquiryRequest.qnaClsLrgCd && (
{commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsLrgCd).length > 0 && (
<div className="data-input mt5">
<select
className="select-form"
@ -183,7 +179,7 @@ export default function RegistForm() {
</select>
</div>
)}
{commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsMidCd).length > 0 && inquiryRequest.qnaClsMidCd && (
{commonCodeList.filter((code) => code.refChar1 === inquiryRequest.qnaClsMidCd).length > 0 && (
<div className="data-input mt5">
<select
className="select-form"

View File

@ -5,7 +5,6 @@ import { useInquiryFilterStore } from '@/store/inquiryFilterStore'
import { useMemo } from 'react'
import { useRouter } from 'next/navigation'
import { useAlertMsg } from '@/hooks/useAlertMsg'
import { CommCode } from '@/types/CommCode'
/**
* @description
@ -228,12 +227,8 @@ export function useInquiry(
const isListQuery = false
const shouldThrowError = false
const resp = await tryFunction(
() => axiosInstance(null).get<CommonCode[]>('/api/comm-code', { params: { headCode: 'QNA_CD' } }),
isListQuery,
shouldThrowError,
)
return resp?.data ?? []
const resp = await tryFunction(() => axiosInstance(null).get<{ data: CommonCode[] }>(`/api/qna`), isListQuery, shouldThrowError)
return resp.data
},
staleTime: Infinity,
gcTime: Infinity,
@ -247,6 +242,6 @@ export function useInquiry(
isSavingInquiry,
saveInquiry,
downloadFile,
commonCodeList: commonCodeList ?? [],
commonCodeList: commonCodeList?.data ?? [],
}
}

View File

@ -1,7 +1,6 @@
'use server'
import nodemailer from 'nodemailer'
import { Attachment } from 'nodemailer/lib/mailer'
interface EmailParams {
from: string
@ -9,10 +8,9 @@ interface EmailParams {
cc?: string | string[]
subject: string
content: string
attachments?: Attachment[]
}
export async function sendEmail({ from, to, cc, subject, content, attachments }: EmailParams): Promise<void> {
export async function sendEmail({ from, to, cc, subject, content }: EmailParams): Promise<void> {
// Create a transporter using SMTP
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
@ -29,7 +27,6 @@ export async function sendEmail({ from, to, cc, subject, content, attachments }:
cc: cc ? (Array.isArray(cc) ? cc.join(', ') : cc) : undefined,
subject,
html: content,
attachments: attachments || [],
}
try {
@ -40,3 +37,13 @@ export async function sendEmail({ from, to, cc, subject, content, attachments }:
throw new Error('Failed to send email')
}
}
async function sendEmailTest() {
await sendEmail({
from: 'from@test.com',
to: 'test@test.com',
cc: 'test2@test.com',
subject: 'Test Email',
content: '<h1>Hello</h1><p>This is a test email.</p>',
})
}

View File

@ -185,7 +185,6 @@ export type InquirySaveResponse = {
*/
export type CommonCode = {
headCd: string
headId: string
code: string
name: string
refChar1: string