6af863199f
- fito-nextjs 기반으로 재구성 - 로그인: MOMO 로고 + 모모유통 + 유통관리 ERP, 하단에 본사/지사 주소 표시 - 사이드바 상단: MOMO 아이콘 + 모모유통 + 유통관리 ERP - 파비콘: /src/app/icon.svg (MOMO 그린 배지) - layout.tsx title: 모모유통 | 유통관리 ERP - DB: 183.99.177.40:5432/distribution (fito 스키마 import 완료) - Traefik: Host(momo.junggomoa.com), 컨테이너 momo-erp
4.3 KiB
4.3 KiB
역할
Next.js 15 App Router API 라우트. 레거시 Java Spring Controller(*.do 엔드포인트)를 Node.js로 마이그레이션한 65개 이상의 엔드포인트. PostgreSQL raw SQL로 데이터베이스 통신.
공통 패턴
인증 체크 (모든 엔드포인트 필수)
const user = await getSession();
if (!user) return NextResponse.json({ success: false }, { status: 401 });
HTTP 메서드
- GET — 단순 조회 (메뉴, 카운트 등)
- POST — 목록 조회 + 필터링 + 저장/수정 (기본값)
actionType필드로 INSERT/UPDATE 구분:actionType === "regist"→ INSERT
동적 SQL 파라미터
const conditions = ["1=1"];
const params: unknown[] = [];
let idx = 1;
if (body.field) {
conditions.push(`TABLE.field = $${idx++}`);
params.push(body.field);
}
// LIKE 검색: COLUMN LIKE '%' || $${idx++} || '%'
응답 형식
| 유형 | 형식 |
|---|---|
| 목록 조회 | { RESULTLIST: [], TOTAL_CNT: number } |
| 단순 조회 | { success: true, data: rows } |
| 저장/수정 | { success: true, objId? } |
| 메뉴 | { RESULT: [] } |
| 페이지 권한 | { CREATE_AUTH_CNT, READ_AUTH_CNT, ... } |
| 오류 | { success: false, message } + HTTP 400/401/500 |
연결 고리
@/lib/session→getSession()(모든 엔드포인트)@/lib/db→queryRows,queryOne,execute@/lib/utils→createObjectId()(INSERT PK 생성),checkNull@/lib/auth→verifyCredentials(login 전용)@/lib/encrypt→encrypt(login 전용)
공통 조회 API (드롭다운/공용 데이터)
신규 메뉴에서 아래 엔드포인트 바로 사용 — 별도 매퍼 이식 불필요.
/api/common/code-list— 공통코드(comm_code) parent_code_id 기준. body{ codeId }→{ data: [{CODE_ID, CODE_NAME, OBJID}] }. 원본common.getCodeselect대응/api/common/supply-list— 거래처/고객사(supply_mng). body{ CHARGER_TYPE? }→{ RESULTLIST: [{OBJID, SUPPLY_NAME, SUPPLY_CODE, STATUS}] }. 원본common.getsupplyselect/api/common/project-list— 프로젝트(project_mgmt). body{ customer_cd? }→{ RESULTLIST: [{OBJID, PROJECT_NO, CUSTOMER_PROJECT_NAME, LABEL}] }. 원본common.getProjectNameList/api/common/unit-list— WBS 유닛(pms_wbs_task). body{ contract_objid }→{ RESULTLIST: [{OBJID, UNIT_NO, TASK_NAME, UNIT_NAME}] }. 원본common.getBomCodeList/api/common/product-list— 양산제품(product_mgmt). →{ RESULTLIST: [{OBJID, PRODUCT_CODE, NAME}] }. 원본common.getProductCodeselect/api/common/upg-list— 제품 UPG(product_mgmt_upg_detail). body{ PRODUCT_MGMT_OBJID }→{ RESULTLIST: [{CODE, NAME}] }. 원본common.getProductUPGNEWselect/api/admin/users— 사용자 목록. →{ RESULTLIST: [{USER_ID, USER_NAME, ...}] }. 원본common.getUserselect/api/common/files— 첨부파일 조회. body{ objId }→{ success, files: [{OBJID, REAL_FILE_NAME, DOC_TYPE, ...}] }/api/common/file-upload— 멀티파트 업로드. formData{ file, targetObjId, docType, docTypeName }→{ success }
프론트엔드에서 SearchableSelect 옵션으로 변환 패턴:
fetch("/api/common/supply-list", { method: "POST", ... })
.then(r => r.json())
.then(j => setOptions((j.RESULTLIST || []).map(r => ({
value: String(r.OBJID), label: String(r.SUPPLY_NAME),
}))));
등록 절차
새 API 추가 시 파일만 생성하면 자동 라우팅:
src/app/api/{module}/{feature}/route.ts
별도 등록 파일 없음 (Next.js App Router 자동 인식).
숨겨진 스펙
- SQL alias 대문자:
SELECT id AS "ID"(큰따옴표 필수, 없으면 PostgreSQL이 소문자 반환) - 삭제 플래그:
COALESCE(IS_DEL, 'N') != 'Y' - 날짜 포맷:
TO_CHAR(regdate, 'YYYY-MM-DD') - 기본 정렬:
ORDER BY regdate DESC - 파일 저장 경로:
{FILE_STORAGE_PATH}/{YYYYMMDD}/{createObjectId()}{ext} - 파일 DB 테이블:
ATTACH_FILE_INFO(OBJID, TARGET_OBJID, DOC_TYPE, FILE_PATH, WRITER) - objId 타입: DB에 numeric/bigint, 조회 시
objid::text AS "OBJID"문자열 변환 - 목록 LIMIT: 일반 없음(rows.length), 대시보드 300, 공통코드 500
- 관리자 권한 신속 처리:
user.isAdmin→ 모든 권한 카운트 = 1
@MISTAKES.md