File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import 'server-only';
import type { AdminMember } from '@/lib/domain/admin-member';
/**
* 관리자 회원 등록/수정/삭제의 **mock 저장소** — 백엔드에 해당 API가 없어 임시로 두는 계층이다.
*
* 백엔드(edupay-backend develop b742bb4)의 관리자 도메인에는 목록 조회
* (`GET /api/v1/mngr/admin/pagination`)와 로그인 조회밖에 없다 — 등록·수정·삭제·단건 조회
* 어느 것도 컨트롤러·서비스·매퍼에 존재하지 않는다(패키지 전체를 읽어 확인). 그래서 시안
* (ADM_ADM_101 ③⑤ / 102_p / 103_p)의 쓰기 흐름은 이 파일이 대신 처리한다.
*
* **동작 방식은 "덮어쓰기 오버레이"다.** 목록의 원본은 여전히 백엔드이고, 이 저장소는 그 위에
* 얹을 변경분(추가된 행 / 필드 패치 / 삭제된 id)만 들고 있다. Repository가 백엔드 응답을 받은 뒤
* `applyMockOverlay`로 합쳐 화면에 넘긴다.
*
* **한계를 분명히 해 둔다 — 이건 데모용이지 저장소가 아니다.**
* - 서버 프로세스 메모리에만 있다. 재시작하면 사라지고, 인스턴스가 여럿이면 공유되지 않는다.
* - 비밀번호는 **보관하지 않는다.** mock이라 검증할 곳이 없고, 평문 비밀번호를 메모리에 들고
* 있는 형태를 코드에 남기고 싶지 않다. 등록/수정 시 형식 검증만 하고 값은 버린다.
*
* 백엔드 API가 생기면 이 파일을 삭제하고 Repository의 쓰기 함수 본문만 실제 호출로 교체한다 —
* 화면·Server Action은 그대로다.
*/
/** 신규 등록 행의 id 접두사 — 백엔드 `admUserId`와 섞이지 않게 구분한다. */
const MOCK_ID_PREFIX = 'mock-admin-';
type AdminMemberPatch = Partial<
Pick<
AdminMember,
'phoneNumber' | 'email' | 'roleCode' | 'menuCodes' | 'isLocked'
>
>;
type MockStoreState = {
created: AdminMember[];
patches: Map<string, AdminMemberPatch>;
deletedIds: Set<string>;
nextId: number;
};
/**
* 개발 중 HMR로 모듈이 다시 평가돼도 상태가 초기화되지 않도록 globalThis에 붙인다 — 저장한
* 내용이 코드 수정 때마다 사라지면 mock으로 흐름을 확인하는 의미가 없다. mock 전용 편법이며,
* 실제 데이터 계층에는 쓰지 않는다.
*/
const globalStore = globalThis as typeof globalThis & {
__adminMemberMockStore?: MockStoreState;
};
function getState(): MockStoreState {
globalStore.__adminMemberMockStore ??= {
created: [],
patches: new Map(),
deletedIds: new Set(),
nextId: 1,
};
return globalStore.__adminMemberMockStore;
}
/** 오늘 날짜(YYYY-MM-DD) — 신규 등록 행의 생성일. */
function today(): string {
return new Date().toISOString().slice(0, 10);
}
export type CreateAdminMemberInput = {
name: string;
loginId: string;
phoneNumber: string;
email: string;
roleCode: string;
menuCodes: string[];
};
export type UpdateAdminMemberInput = {
phoneNumber: string;
email: string;
roleCode: string;
menuCodes: string[];
isLocked: boolean;
};
/**
* 백엔드 목록에 mock 변경분을 얹는다 — 삭제된 행을 걷어내고, 수정된 행에 패치를 입히고,
* 신규 등록 행을 **앞에** 붙인다(기본 정렬이 생성일 최신순이라 방금 만든 행이 맨 위에 온다).
*
* 정렬·검색·페이징은 이 함수가 하지 않는다 — 합쳐진 전체 목록을 대상으로 Repository가 처리한다.
*/
export function applyMockOverlay(backendItems: AdminMember[]): AdminMember[] {
const { created, patches, deletedIds } = getState();
const merged = [...created, ...backendItems];
return merged
.filter((item) => !deletedIds.has(item.id))
.map((item) => {
const patch = patches.get(item.id);
return patch ? { ...item, ...patch } : item;
});
}
/** mock으로 등록된 행들의 로그인 ID — 중복 확인이 백엔드 목록과 함께 검사할 수 있게 노출한다. */
export function listMockCreatedLoginIds(): string[] {
return getState().created.map((item) => item.loginId);
}
export function createMockAdminMember(
input: CreateAdminMemberInput
): AdminMember {
const state = getState();
const id = `${MOCK_ID_PREFIX}${state.nextId++}`;
const member: AdminMember = {
id,
name: input.name,
loginId: input.loginId,
phoneNumber: input.phoneNumber,
email: input.email || null,
roleCode: input.roleCode,
menuCodes: input.menuCodes,
createdAt: today(),
isLocked: false,
isActive: true,
loginFailCount: 0,
};
state.created.unshift(member);
return member;
}
/**
* 수정 — 신규 등록 행은 원본을 직접 고치고, 백엔드에서 온 행은 패치로 기록해 둔다(원본을 우리가
* 소유하지 않으므로 매 조회마다 덧입힌다).
*/
export function updateMockAdminMember(
id: string,
input: UpdateAdminMemberInput
): void {
const state = getState();
const patch: AdminMemberPatch = {
phoneNumber: input.phoneNumber,
email: input.email || null,
roleCode: input.roleCode,
menuCodes: input.menuCodes,
isLocked: input.isLocked,
};
const createdIndex = state.created.findIndex((item) => item.id === id);
if (createdIndex >= 0) {
state.created[createdIndex] = { ...state.created[createdIndex], ...patch };
return;
}
state.patches.set(id, { ...state.patches.get(id), ...patch });
}
export function deleteMockAdminMember(id: string): void {
const state = getState();
state.created = state.created.filter((item) => item.id !== id);
state.patches.delete(id);
// 백엔드에서 오는 행은 매 조회마다 다시 등장하므로 삭제 사실을 따로 기억해야 한다.
state.deletedIds.add(id);
}