File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import 'server-only';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import { buildFileImageUrl } from '@/lib/data/repositories/file-repository';
import {
joinProductBadges,
parseProductBadges,
type MerchantProduct,
} from '@/lib/domain/merchant-product';
import type { MerchantProductValues } from '@/lib/domain/merchant-product-form';
/**
* 가맹몰 메뉴 Repository — edupay-backend origin/develop 15d409a 기준.
*
* ```
* GET /api/v1/mngr/voc/product/list/{frcsNo} 상세가 있는 가맹점의 메뉴 전체(페이징 없음)
* GET /api/v1/mngr/voc/product/{frcsProductSn}
* POST /api/v1/mngr/voc/product @RequestBody · 가맹점 상세가 있어야 한다
* PUT /api/v1/mngr/voc/product/{frcsProductSn} @RequestBody
* DELETE /api/v1/mngr/voc/product/{frcsProductSn} 논리 삭제
* ```
*/
const PRODUCT_PATH = '/api/v1/mngr/voc/product';
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object';
}
function readString(source: Record<string, unknown>, key: string): string | null {
const value = source[key];
if (typeof value === 'string' && value !== '') return value;
if (typeof value === 'number') return String(value);
return null;
}
function readNumber(source: Record<string, unknown>, key: string): number | null {
const value = source[key];
if (typeof value === 'number' && Number.isFinite(value)) return value;
if (typeof value === 'string' && value.trim() !== '') {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function toProduct(raw: unknown): MerchantProduct[] {
if (!isRecord(raw)) return [];
const id = readString(raw, 'frcsProductSn');
const frcsNo = readString(raw, 'frcsNo');
if (!id || !frcsNo) return [];
return [
{
id,
frcsNo,
name: readString(raw, 'frcsProductSj') ?? '',
price: readNumber(raw, 'frcsProductPrice'),
description: readString(raw, 'frcsProductCn'),
badges: parseProductBadges(readString(raw, 'frcsBadge')),
imageFileId: readString(raw, 'atchFileId'),
imageUrl: buildFileImageUrl(readString(raw, 'atchFileId')),
sortOrder: readNumber(raw, 'sortOrdr'),
isVisible: (readString(raw, 'useYn') ?? 'Y').toUpperCase() !== 'N',
},
];
}
export async function fetchMerchantProducts(frcsNo: string): Promise<MerchantProduct[]> {
const accessToken = await getSessionAccessToken();
const result = await backendFetch<unknown>(
`${PRODUCT_PATH}/list/${encodeURIComponent(frcsNo)}`,
{
method: 'GET',
accessToken: accessToken ?? undefined,
cache: 'no-store',
canHaveNullData: true,
}
);
if (!result.ok) {
throw new BackendRequestError(result);
}
return Array.isArray(result.data) ? result.data.flatMap(toProduct) : [];
}
export async function fetchMerchantProduct(
productSn: string
): Promise<MerchantProduct | null> {
const accessToken = await getSessionAccessToken();
const result = await backendFetch<unknown>(
`${PRODUCT_PATH}/${encodeURIComponent(productSn)}`,
{
method: 'GET',
accessToken: accessToken ?? undefined,
cache: 'no-store',
canHaveNullData: true,
}
);
if (!result.ok) {
// 없는 메뉴는 404 봉투로 온다 — 화면이 notFound()로 넘기게 null을 준다.
if (result.code === 404) return null;
throw new BackendRequestError(result);
}
return toProduct(result.data)[0] ?? null;
}
function toWritePayload(values: MerchantProductValues): Record<string, unknown> {
return {
frcsNo: values.frcsNo,
frcsProductSj: values.name,
frcsProductPrice: values.price,
frcsProductCn: values.description,
frcsBadge: joinProductBadges(values.badges),
atchFileId: values.imageFileId || null,
sortOrdr: values.sortOrder,
useYn: values.isVisible ? 'Y' : 'N',
};
}
async function sendWrite(
path: string,
method: 'POST' | 'PUT' | 'DELETE',
body?: unknown
): Promise<void> {
const accessToken = await getSessionAccessToken();
const result = await backendFetch<unknown>(path, {
method,
...(body === undefined ? {} : { body }),
accessToken: accessToken ?? undefined,
cache: 'no-store',
canHaveNullData: true,
canHaveEmptyBody: true,
});
if (!result.ok) {
throw new BackendRequestError(result);
}
}
export async function createMerchantProduct(
values: MerchantProductValues
): Promise<void> {
await sendWrite(PRODUCT_PATH, 'POST', toWritePayload(values));
}
export async function updateMerchantProduct(
productSn: string,
values: MerchantProductValues
): Promise<void> {
await sendWrite(
`${PRODUCT_PATH}/${encodeURIComponent(productSn)}`,
'PUT',
toWritePayload(values)
);
}
export async function deleteMerchantProduct(productSn: string): Promise<void> {
await sendWrite(`${PRODUCT_PATH}/${encodeURIComponent(productSn)}`, 'DELETE');
}