From a573d7ffb18c0f1762bf3c49dca0554d8ef0826c Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 12 Jun 2025 18:03:24 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20api=20logger=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +++++- next.config.ts | 7 ++++++- package.json | 1 + src/libs/logger.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/libs/logger.ts diff --git a/.gitignore b/.gitignore index bbffc8a..0ebef99 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,8 @@ next-env.d.ts bun.lockb pnpm-lock.yaml -pnpm-workspace.yaml \ No newline at end of file +pnpm-workspace.yaml + +# logs +logs/ +*.log diff --git a/next.config.ts b/next.config.ts index 58db81e..6733d06 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,7 +6,12 @@ const nextConfig: NextConfig = { sassOptions: { includePaths: [path.join(__dirname, './src/styles')], }, - serverExternalPackages: ['@react-pdf/renderer'], + serverExternalPackages: ['@react-pdf/renderer', 'pino'], + logging: { + fetches: { + fullUrl: true, + }, + }, async rewrites() { return [ { diff --git a/package.json b/package.json index 439c5df..e309ff6 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "next": "15.2.4", "nodemailer": "^7.0.3", "pdf-lib": "^1.17.1", + "pino": "^9.7.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-to-pdf": "^2.0.0", diff --git a/src/libs/logger.ts b/src/libs/logger.ts new file mode 100644 index 0000000..5901429 --- /dev/null +++ b/src/libs/logger.ts @@ -0,0 +1,51 @@ +import { NextRequest } from 'next/server' +import { join } from 'path' +import pino from 'pino' + +/* 로그 파일 경로 */ +const logFilePath = join('.', 'logs', 'onsite-survey.log') + +/* 로거 생성 함수 */ +const createLogger = (): pino.Logger => { + try { + return pino({ + level: 'info', + timestamp: pino.stdTimeFunctions.isoTime, + transport: { + targets: [ + { + target: 'pino/file', + options: { + destination: logFilePath, + mkdir: true, + sync: false, + }, + }, + ], + }, + }) + } catch (error) { + console.error('Pino transport 생성 실패, 기본 로거 사용:', error) + return pino({ + level: 'info', + timestamp: pino.stdTimeFunctions.isoTime, + }) + } +} + +/* 로거 인스턴스 */ +export const logger = createLogger() + +/* 로그 기록 함수 */ +export const writeLog = async (request: NextRequest, responseStatus: number): Promise => { + const logData = { + timestamp: new Date().toISOString(), + status: responseStatus, + method: request.method, + url: request.url, + // headers: Object.fromEntries(request.headers), + query: Object.fromEntries(new URL(request.url).searchParams), + body: request.body ? await request.clone().text() : undefined, + } + logger.info(logData, 'API Request') +} From 3eb59744145e93928630a2aa8eb2c3675061c3c1 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Thu, 12 Jun 2025 18:05:29 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20api=20logger=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/suitable/list/route.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/api/suitable/list/route.ts b/src/app/api/suitable/list/route.ts index c668153..c1ab794 100644 --- a/src/app/api/suitable/list/route.ts +++ b/src/app/api/suitable/list/route.ts @@ -1,5 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' +import { HttpStatusCode } from 'axios' import { prisma } from '@/libs/prisma' +import { writeLog } from '@/libs/logger' import { type Suitable } from '@/types/Suitable' /** @@ -42,6 +44,7 @@ import { type Suitable } from '@/types/Suitable' * ] */ export async function GET(request: NextRequest) { + let responseStatus: number = HttpStatusCode.InternalServerError try { const searchParams = request.nextUrl.searchParams const pageNumber = parseInt(searchParams.get('pageNumber') || '0') @@ -51,7 +54,8 @@ export async function GET(request: NextRequest) { /* 파라미터 체크 */ if (pageNumber === 0 || itemPerPage === 0) { - return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: 400 }) + responseStatus = HttpStatusCode.BadRequest + return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: responseStatus }) } let query = ` @@ -104,6 +108,7 @@ export async function GET(request: NextRequest) { const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, pageNumber, itemPerPage) + responseStatus = HttpStatusCode.Ok return NextResponse.json(suitable, { headers: { 'spinner-state': 'true', @@ -111,6 +116,9 @@ export async function GET(request: NextRequest) { }) } catch (error) { console.error(`데이터 조회 중 오류가 발생했습니다: ${error}`) - return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: 500 }) + responseStatus = HttpStatusCode.InternalServerError + return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: responseStatus }) + } finally { + await writeLog(request, responseStatus) } } From 7597700e7b96c856b84a5946eb39014e6476bd44 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Fri, 13 Jun 2025 16:50:15 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20logger=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/logger.ts | 101 ++++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/src/libs/logger.ts b/src/libs/logger.ts index 5901429..00564a7 100644 --- a/src/libs/logger.ts +++ b/src/libs/logger.ts @@ -2,43 +2,86 @@ import { NextRequest } from 'next/server' import { join } from 'path' import pino from 'pino' -/* 로그 파일 경로 */ -const logFilePath = join('.', 'logs', 'onsite-survey.log') +/* 로그 데이터 인터페이스 */ +interface LogData { + timestamp: string + status: number + method: string + url: string + // headers: { [k: string]: string } + query: { [k: string]: string } + body: string | undefined +} -/* 로거 생성 함수 */ -const createLogger = (): pino.Logger => { - try { - return pino({ - level: 'info', - timestamp: pino.stdTimeFunctions.isoTime, - transport: { - targets: [ - { - target: 'pino/file', - options: { - destination: logFilePath, - mkdir: true, - sync: false, - }, - }, - ], +/* 날짜별 로그 파일 경로 생성 함수 */ +const getLogFilePath = (): string => { + const today = new Date().toISOString().split('T')[0] // YYYY-MM-DD 형식 + return join(process.cwd(), 'logs', `onsite-survey-${today}.log`) +} + +/* 날짜별 로거 생성 클래스 */ +class DailyLogger { + private currentDate: string + private logger: pino.Logger + private destination: ReturnType + + constructor() { + this.currentDate = new Date().toISOString().split('T')[0] + this.destination = pino.destination({ + dest: getLogFilePath(), + mkdir: true, + sync: false, + }) + this.logger = this.createLogger() + + /* kill signal 핸들러 등록 */ + process.on('SIGTERM', this.handleShutdown.bind(this)) + process.on('SIGINT', this.handleShutdown.bind(this)) + } + + private async handleShutdown(): Promise { + this.destination.flushSync() + this.destination.end() + } + + private createLogger(): pino.Logger { + return pino( + { + level: 'info', + timestamp: pino.stdTimeFunctions.isoTime, }, - }) - } catch (error) { - console.error('Pino transport 생성 실패, 기본 로거 사용:', error) - return pino({ - level: 'info', - timestamp: pino.stdTimeFunctions.isoTime, - }) + this.destination, + ) + } + + public info(obj: any, msg?: string): void { + const today = new Date().toISOString().split('T')[0] + + if (today !== this.currentDate) { + /* 기존 destination 종료 */ + this.destination.flushSync() + this.destination.end() + + /* 새로운 destination 생성 */ + this.destination = pino.destination({ + dest: getLogFilePath(), + mkdir: true, + sync: false, + }) + this.currentDate = today + this.logger = this.createLogger() + } + + this.logger.info(obj, msg) } } /* 로거 인스턴스 */ -export const logger = createLogger() +const dailyLogger = new DailyLogger() /* 로그 기록 함수 */ export const writeLog = async (request: NextRequest, responseStatus: number): Promise => { - const logData = { + const logData: LogData = { timestamp: new Date().toISOString(), status: responseStatus, method: request.method, @@ -47,5 +90,5 @@ export const writeLog = async (request: NextRequest, responseStatus: number): Pr query: Object.fromEntries(new URL(request.url).searchParams), body: request.body ? await request.clone().text() : undefined, } - logger.info(logData, 'API Request') + dailyLogger.info(logData, 'API Request') } From ed8c6187d3610734a10422df912800645d5ec5f1 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Fri, 13 Jun 2025 17:26:34 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20api=20log=20wrapper=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/api-wrapper.ts | 10 ++++++++++ src/libs/logger.ts | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 src/libs/api-wrapper.ts diff --git a/src/libs/api-wrapper.ts b/src/libs/api-wrapper.ts new file mode 100644 index 0000000..ba52d52 --- /dev/null +++ b/src/libs/api-wrapper.ts @@ -0,0 +1,10 @@ +import { NextRequest, NextResponse } from 'next/server' +import { writeApiLog } from './logger' + +export function loggerWrapper(handler: (req: NextRequest) => Promise): (req: NextRequest) => Promise { + return async function (request: NextRequest) { + const response = await handler(request) + await writeApiLog(request, response.status) + return response + } +} diff --git a/src/libs/logger.ts b/src/libs/logger.ts index 00564a7..2b1aa9e 100644 --- a/src/libs/logger.ts +++ b/src/libs/logger.ts @@ -3,7 +3,7 @@ import { join } from 'path' import pino from 'pino' /* 로그 데이터 인터페이스 */ -interface LogData { +interface ApiLogData { timestamp: string status: number method: string @@ -79,9 +79,9 @@ class DailyLogger { /* 로거 인스턴스 */ const dailyLogger = new DailyLogger() -/* 로그 기록 함수 */ -export const writeLog = async (request: NextRequest, responseStatus: number): Promise => { - const logData: LogData = { +/* API 로그 기록 함수 */ +export const writeApiLog = async (request: NextRequest, responseStatus: number): Promise => { + const logData: ApiLogData = { timestamp: new Date().toISOString(), status: responseStatus, method: request.method, From c212ed0ad6717917c03d7103366cc912a062ec9e Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Mon, 16 Jun 2025 16:03:38 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20api=20log=20wrapper=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/api-wrapper.ts | 17 ++++++++++++++--- src/libs/logger.ts | 8 +++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libs/api-wrapper.ts b/src/libs/api-wrapper.ts index ba52d52..4caad47 100644 --- a/src/libs/api-wrapper.ts +++ b/src/libs/api-wrapper.ts @@ -2,9 +2,20 @@ import { NextRequest, NextResponse } from 'next/server' import { writeApiLog } from './logger' export function loggerWrapper(handler: (req: NextRequest) => Promise): (req: NextRequest) => Promise { - return async function (request: NextRequest) { - const response = await handler(request) - await writeApiLog(request, response.status) + return async function (req: NextRequest) { + const reqClone = req.clone() + + const response = await handler(req) + + await writeApiLog( + new NextRequest(req.url, { + method: req.method, + headers: req.headers, + body: reqClone.body ? await reqClone.text() : undefined, + }), + response.status, + ) + return response } } diff --git a/src/libs/logger.ts b/src/libs/logger.ts index 2b1aa9e..d75083d 100644 --- a/src/libs/logger.ts +++ b/src/libs/logger.ts @@ -4,8 +4,7 @@ import pino from 'pino' /* 로그 데이터 인터페이스 */ interface ApiLogData { - timestamp: string - status: number + responseStatus: number method: string url: string // headers: { [k: string]: string } @@ -82,13 +81,12 @@ const dailyLogger = new DailyLogger() /* API 로그 기록 함수 */ export const writeApiLog = async (request: NextRequest, responseStatus: number): Promise => { const logData: ApiLogData = { - timestamp: new Date().toISOString(), - status: responseStatus, + responseStatus: responseStatus, method: request.method, url: request.url, // headers: Object.fromEntries(request.headers), query: Object.fromEntries(new URL(request.url).searchParams), - body: request.body ? await request.clone().text() : undefined, + body: request.body ? await request.text() : undefined, } dailyLogger.info(logData, 'API Request') } From b783ca92d79eea88a252e42796da744b1eb69c44 Mon Sep 17 00:00:00 2001 From: Daseul Kim Date: Mon, 16 Jun 2025 16:49:59 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=EA=B8=B0=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=EB=90=9C=20api=EC=97=90=20api=20logger=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/auth/chg-pwd/route.ts | 5 ++- src/app/api/comm-code/route.ts | 37 +++++++++++++---------- src/app/api/qna/detail/route.ts | 5 ++- src/app/api/qna/file/route.ts | 5 ++- src/app/api/qna/list/route.ts | 5 ++- src/app/api/qna/route.ts | 5 ++- src/app/api/qna/save/route.ts | 5 ++- src/app/api/submission/admin-sub/route.ts | 7 +++-- src/app/api/submission/admin/route.ts | 5 ++- src/app/api/submission/builder/route.ts | 5 ++- src/app/api/submission/super/route.ts | 5 ++- src/app/api/suitable/list/route.ts | 17 +++++------ src/app/api/suitable/pdf/route.ts | 10 ++++-- src/app/api/suitable/pick/route.ts | 16 ++++++---- src/app/api/suitable/route.ts | 10 ++++-- 15 files changed, 93 insertions(+), 49 deletions(-) diff --git a/src/app/api/auth/chg-pwd/route.ts b/src/app/api/auth/chg-pwd/route.ts index 71e9f6b..321a69f 100644 --- a/src/app/api/auth/chg-pwd/route.ts +++ b/src/app/api/auth/chg-pwd/route.ts @@ -1,7 +1,8 @@ import { NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' import { axiosInstance } from '@/libs/axios' -export async function POST(req: Request) { +async function setChgPwd(req: Request): Promise { const { loginId, email, pwd, chgPwd } = await req.json() console.log('🚀 ~ POST ~ loginId:', loginId) console.log('🚀 ~ POST ~ email:', email) @@ -19,3 +20,5 @@ export async function POST(req: Request) { return NextResponse.json({ code: 200, data: result.data }) } + +export const POST = loggerWrapper(setChgPwd) diff --git a/src/app/api/comm-code/route.ts b/src/app/api/comm-code/route.ts index d000836..6148a33 100644 --- a/src/app/api/comm-code/route.ts +++ b/src/app/api/comm-code/route.ts @@ -1,8 +1,9 @@ import { NextRequest, NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' import { prisma } from '@/libs/prisma' import type { CommCode } from '@/types/CommCode' -export async function GET(request: NextRequest) { +async function getCommCode(request: NextRequest): Promise { try { const searchParams = request.nextUrl.searchParams const headCode = searchParams.get('headCode') @@ -20,24 +21,26 @@ export async function GET(request: NextRequest) { if (!headCd) { return NextResponse.json({ error: `${headCode}를 찾을 수 없습니다` }, { status: 404 }) } - // @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', - }, - }) + 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) } - return NextResponse.json(roofMaterials) } catch (error) { console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error) return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) @@ -60,3 +63,5 @@ const getSaleOffice = async (headCode: string) => { }) return NextResponse.json(commCodeSaleOffice) } + +export const GET = loggerWrapper(getCommCode) diff --git a/src/app/api/qna/detail/route.ts b/src/app/api/qna/detail/route.ts index 315c18e..1ac937c 100644 --- a/src/app/api/qna/detail/route.ts +++ b/src/app/api/qna/detail/route.ts @@ -1,8 +1,9 @@ import { queryStringFormatter } from '@/utils/common-utils' import axios from 'axios' import { NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' -export async function GET(request: Request) { +async function getQnaDetail(request: Request): Promise { const { searchParams } = new URL(request.url) const params = { compCd: searchParams.get('compCd'), @@ -22,3 +23,5 @@ export async function GET(request: Request) { return NextResponse.json({ error: 'route error' }, { status: 500 }) } } + +export const GET = loggerWrapper(getQnaDetail) diff --git a/src/app/api/qna/file/route.ts b/src/app/api/qna/file/route.ts index 602bbd6..49c7209 100644 --- a/src/app/api/qna/file/route.ts +++ b/src/app/api/qna/file/route.ts @@ -1,5 +1,6 @@ import axios from 'axios' import { NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' // export async function GET(request: Request) { // const { searchParams } = new URL(request.url) @@ -38,7 +39,7 @@ import { NextResponse } from 'next/server' // } // } -export async function GET(request: Request) { +async function downloadFile(request: Request): Promise { const { searchParams } = new URL(request.url) const encodeFileNo = searchParams.get('encodeFileNo') const srcFileNm = searchParams.get('srcFileNm') || 'downloaded-file' @@ -71,3 +72,5 @@ export async function GET(request: Request) { return NextResponse.json({ error: error.response?.data || 'Failed to download file' }, { status: 500 }) } } + +export const GET = loggerWrapper(downloadFile) diff --git a/src/app/api/qna/list/route.ts b/src/app/api/qna/list/route.ts index 9708e4f..12cf96e 100644 --- a/src/app/api/qna/list/route.ts +++ b/src/app/api/qna/list/route.ts @@ -3,10 +3,11 @@ import { NextResponse } from 'next/server' import { queryStringFormatter } from '@/utils/common-utils' import { getIronSession } from 'iron-session' import { cookies } from 'next/headers' +import { loggerWrapper } from '@/libs/api-wrapper' import { sessionOptions } from '@/libs/session' import { SessionData } from '@/types/Auth' -export async function GET(request: Request) { +async function getQnaList(request: Request): Promise { const cookieStore = await cookies() const session = await getIronSession(cookieStore, sessionOptions) @@ -37,3 +38,5 @@ export async function GET(request: Request) { return NextResponse.json({ error: 'route error' }, { status: 500 }) } } + +export const GET = loggerWrapper(getQnaList) diff --git a/src/app/api/qna/route.ts b/src/app/api/qna/route.ts index 994a86a..2773761 100644 --- a/src/app/api/qna/route.ts +++ b/src/app/api/qna/route.ts @@ -1,8 +1,9 @@ import { NextResponse } from 'next/server' import axios from 'axios' import { CommonCode } from '@/types/Inquiry' +import { loggerWrapper } from '@/libs/api-wrapper' -export async function GET() { +async function getCommonCodeListData(request: Request): Promise { const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/system/commonCodeListData`) const codeList: CommonCode[] = [] response.data.data.apiCommCdList.forEach((item: any) => { @@ -17,3 +18,5 @@ export async function GET() { }) return NextResponse.json({ data: codeList }) } + +export const GET = loggerWrapper(getCommonCodeListData) diff --git a/src/app/api/qna/save/route.ts b/src/app/api/qna/save/route.ts index b440ef8..8cd1ad5 100644 --- a/src/app/api/qna/save/route.ts +++ b/src/app/api/qna/save/route.ts @@ -1,7 +1,8 @@ import axios from 'axios' import { NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' -export async function POST(request: Request) { +async function setQna(request: Request): Promise { const formData = await request.formData() console.log(formData) try { @@ -19,3 +20,5 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Failed to save qna' }, { status: 500 }) } } + +export const POST = loggerWrapper(setQna) diff --git a/src/app/api/submission/admin-sub/route.ts b/src/app/api/submission/admin-sub/route.ts index c8c30f3..28e3627 100644 --- a/src/app/api/submission/admin-sub/route.ts +++ b/src/app/api/submission/admin-sub/route.ts @@ -1,5 +1,6 @@ import { prisma } from '@/libs/prisma' import { NextRequest, NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' type AdminSubPerson = { storeId: string @@ -7,8 +8,8 @@ type AdminSubPerson = { eMail: string authority: string } -// 2차점이 자신에게 매핑 된 1차 판매점과 관리자 정보 조회 -export async function GET(request: NextRequest) { +// 2차점이 자신에게 매핑 된 1차 판매점과 관리자 정보 조회 +async function getSubMissionAdminSub(request: NextRequest): Promise { try { const { searchParams } = new URL(request.url) const id = searchParams.get('id') @@ -40,3 +41,5 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) } } + +export const GET = loggerWrapper(getSubMissionAdminSub) diff --git a/src/app/api/submission/admin/route.ts b/src/app/api/submission/admin/route.ts index 0b52031..eae6881 100644 --- a/src/app/api/submission/admin/route.ts +++ b/src/app/api/submission/admin/route.ts @@ -1,5 +1,6 @@ import { prisma } from '@/libs/prisma' import { NextRequest, NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' type SuperPerson = { storeId: string @@ -8,7 +9,7 @@ type SuperPerson = { toEmail: string } -export async function GET(request: NextRequest) { +async function getSubmissionAdmin(request: NextRequest): Promise { try { const { searchParams } = new URL(request.url) const id = searchParams.get('id') @@ -44,3 +45,5 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) } } + +export const GET = loggerWrapper(getSubmissionAdmin) diff --git a/src/app/api/submission/builder/route.ts b/src/app/api/submission/builder/route.ts index 41c66ce..f1f0f93 100644 --- a/src/app/api/submission/builder/route.ts +++ b/src/app/api/submission/builder/route.ts @@ -1,6 +1,7 @@ import { prisma } from '@/libs/prisma' import { SubmitTargetResponse } from '@/types/Survey' import { NextRequest, NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' type BuilderPerson = { agencyStoreId: string @@ -11,7 +12,7 @@ type BuilderPerson = { // 2차점의 시공권한 user가 해당 판매점의 관리자 정보 조회 // N == 일반유저, S == 수퍼유저, B == 시공권한유저 -export async function GET(request: NextRequest) { +async function getSubmissionBuilder(request: NextRequest): Promise { try { const { searchParams } = new URL(request.url) const id = searchParams.get('id') @@ -46,3 +47,5 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) } } + +export const GET = loggerWrapper(getSubmissionBuilder) diff --git a/src/app/api/submission/super/route.ts b/src/app/api/submission/super/route.ts index 6eed5c7..faa66cf 100644 --- a/src/app/api/submission/super/route.ts +++ b/src/app/api/submission/super/route.ts @@ -1,5 +1,6 @@ import { prisma } from '@/libs/prisma' import { NextRequest, NextResponse } from 'next/server' +import { loggerWrapper } from '@/libs/api-wrapper' type SuperPerson = { storeId: string @@ -7,7 +8,7 @@ type SuperPerson = { eMail: string } -export async function GET(request: NextRequest) { +async function getSubmissionSuper(request: NextRequest): Promise { try { const { searchParams } = new URL(request.url) const id = searchParams.get('id') @@ -37,3 +38,5 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 }) } } + +export const GET = loggerWrapper(getSubmissionSuper) diff --git a/src/app/api/suitable/list/route.ts b/src/app/api/suitable/list/route.ts index c1ab794..c8b3719 100644 --- a/src/app/api/suitable/list/route.ts +++ b/src/app/api/suitable/list/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { HttpStatusCode } from 'axios' +import { loggerWrapper } from '@/libs/api-wrapper' import { prisma } from '@/libs/prisma' -import { writeLog } from '@/libs/logger' import { type Suitable } from '@/types/Suitable' /** @@ -43,8 +43,7 @@ import { type Suitable } from '@/types/Suitable' * } * ] */ -export async function GET(request: NextRequest) { - let responseStatus: number = HttpStatusCode.InternalServerError +async function getSuitableList(request: NextRequest): Promise { try { const searchParams = request.nextUrl.searchParams const pageNumber = parseInt(searchParams.get('pageNumber') || '0') @@ -54,8 +53,7 @@ export async function GET(request: NextRequest) { /* 파라미터 체크 */ if (pageNumber === 0 || itemPerPage === 0) { - responseStatus = HttpStatusCode.BadRequest - return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: responseStatus }) + return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: HttpStatusCode.BadRequest }) } let query = ` @@ -108,17 +106,16 @@ export async function GET(request: NextRequest) { const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, pageNumber, itemPerPage) - responseStatus = HttpStatusCode.Ok return NextResponse.json(suitable, { headers: { 'spinner-state': 'true', }, + status: HttpStatusCode.Ok, }) } catch (error) { console.error(`데이터 조회 중 오류가 발생했습니다: ${error}`) - responseStatus = HttpStatusCode.InternalServerError - return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: responseStatus }) - } finally { - await writeLog(request, responseStatus) + return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: HttpStatusCode.InternalServerError }) } } + +export const GET = loggerWrapper(getSuitableList) diff --git a/src/app/api/suitable/pdf/route.ts b/src/app/api/suitable/pdf/route.ts index 768f81e..ccef573 100644 --- a/src/app/api/suitable/pdf/route.ts +++ b/src/app/api/suitable/pdf/route.ts @@ -1,7 +1,9 @@ import React from 'react' import { NextRequest, NextResponse } from 'next/server' +import { HttpStatusCode } from 'axios' import { pdf, Document } from '@react-pdf/renderer' import { PDFDocument } from 'pdf-lib' +import { loggerWrapper } from '@/libs/api-wrapper' import { prisma } from '@/libs/prisma' import { type Suitable } from '@/types/Suitable' import SuitablePdf from '@/components/pdf/SuitablePdf' @@ -28,7 +30,7 @@ import SuitablePdf from '@/components/pdf/SuitablePdf' * * @apiSuccess {File} 지붕재 적합성 PDF 파일 */ -export async function POST(request: NextRequest) { +async function createSuitablePdf(request: NextRequest): Promise { const formData = await request.formData() const ids = formData.get('ids') as string const detailIds = formData.get('detailIds') as string @@ -36,7 +38,7 @@ export async function POST(request: NextRequest) { /* 파라미터 체크 */ if (ids === '' || detailIds === '' || fileTitle === '') { - return NextResponse.json({ error: '필수 파라미터가 누락되었습니다' }, { status: 400 }) + return NextResponse.json({ error: '필수 파라미터가 누락되었습니다' }, { status: HttpStatusCode.BadRequest }) } try { @@ -121,7 +123,7 @@ export async function POST(request: NextRequest) { }) } catch (error) { console.error(`데이터 조회 중 오류가 발생했습니다: ${error}`) - return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: 500 }) + return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: HttpStatusCode.InternalServerError }) } } @@ -142,3 +144,5 @@ async function mergePdfBuffers(buffers: Uint8Array[]) { const mergedPdfBytes = await mergedPdf.save() return mergedPdfBytes } + +export const POST = loggerWrapper(createSuitablePdf) diff --git a/src/app/api/suitable/pick/route.ts b/src/app/api/suitable/pick/route.ts index 2865777..8d345ce 100644 --- a/src/app/api/suitable/pick/route.ts +++ b/src/app/api/suitable/pick/route.ts @@ -1,23 +1,25 @@ import { NextRequest, NextResponse } from 'next/server' +import { HttpStatusCode } from 'axios' +import { loggerWrapper } from '@/libs/api-wrapper' import { prisma } from '@/libs/prisma' /** * @api {get} /api/suitable/pick 지붕재 적합성 데이터 아이디 조회 API * @apiName GetSuitablePick * @apiGroup Suitable - * + * * @apiDescription * 지붕재 적합성 데이터의 검색 조건에 맞는 main_id와 detail_id를 조회 - * + * * @apiParam {String} [category] 지붕재그룹코드 (예: RMG001) * @apiParam {String} [keyword] 검색키워드 - * + * * @apiExample {curl} Example usage: * curl -X GET \ * -G "category=RMG001" \ * -G "keyword=검색키워드" \ * http://localhost:3000/api/suitable/pick - * + * * @apiSuccess {Array} suitableIdSet 지붕재 적합성 데이터의 아이디 목록 * @apiSuccessExample {json} Success-Response: * [ @@ -27,7 +29,7 @@ import { prisma } from '@/libs/prisma' * } * ] */ -export async function GET(request: NextRequest) { +async function getSuitablePick(request: NextRequest): Promise { try { const searchParams = request.nextUrl.searchParams const category = searchParams.get('category') @@ -72,6 +74,8 @@ export async function GET(request: NextRequest) { return NextResponse.json(suitableIdSet) } catch (error) { console.error(`데이터 조회 중 오류가 발생했습니다: ${error}`) - return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: 500 }) + return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: HttpStatusCode.InternalServerError }) } } + +export const GET = loggerWrapper(getSuitablePick) diff --git a/src/app/api/suitable/route.ts b/src/app/api/suitable/route.ts index c9ca8c4..9227cfc 100644 --- a/src/app/api/suitable/route.ts +++ b/src/app/api/suitable/route.ts @@ -1,4 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' +import { HttpStatusCode } from 'axios' +import { loggerWrapper } from '@/libs/api-wrapper' import { prisma } from '@/libs/prisma' import { Suitable } from '@/types/Suitable' @@ -40,7 +42,7 @@ import { Suitable } from '@/types/Suitable' * } * ] */ -export async function POST(request: NextRequest) { +async function getSuitable(request: NextRequest): Promise { try { const body: Record = await request.json() const ids = body.ids @@ -48,7 +50,7 @@ export async function POST(request: NextRequest) { /* 파라미터 체크 */ if (ids === '') { - return NextResponse.json({ error: '필수 파라미터가 누락되었습니다' }, { status: 400 }) + return NextResponse.json({ error: '필수 파라미터가 누락되었습니다' }, { status: HttpStatusCode.BadRequest }) } let query = ` @@ -96,6 +98,8 @@ export async function POST(request: NextRequest) { return NextResponse.json(suitable) } catch (error) { console.error(`데이터 조회 중 오류가 발생했습니다: ${error}`) - return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: 500 }) + return NextResponse.json({ error: `데이터 조회 중 오류가 발생했습니다: ${error}` }, { status: HttpStatusCode.InternalServerError }) } } + +export const POST = loggerWrapper(getSuitable)