File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import 'server-only';
import type {
PointAccount,
PointHistoryEntry,
} from '@/lib/domain/point-account';
import type { PointAccountQuery } from '@/lib/domain/point-account-query';
// TODO(백엔드): 누적포인트 API가 아직 없다. 아래 mock 생성기를 지우고 다른 Repository처럼
// backendFetch + getSessionAccessToken으로 교체한다.
// 이슈: 구분(사이트)은 기획대로 FOX PAY App. 하나뿐이다 — 확장은 백엔드가 사이트를 실어 준 뒤다.
const TOTAL_ACCOUNTS = 3290;
const SITE_NAME = 'FOX PAY App.';
const SCHOOL_NAMES = [
'폭스고등학교',
'여우고등학교',
'한빛고등학교',
'새롬고등학교',
];
const EVENT_TITLES = ['EVENT-1', 'EVENT-2', 'EVENT-3'];
const BASE_DATE = Date.UTC(2026, 1, 5);
const DAY_MS = 24 * 60 * 60 * 1000;
function dateAt(offsetDays: number): string {
return new Date(BASE_DATE - offsetDays * DAY_MS).toISOString().slice(0, 10);
}
function timestampAt(index: number): string {
const hour = 9 + (index % 9);
const minute = (index * 7) % 60;
const pad = (value: number) => String(value).padStart(2, '0');
return `${dateAt(index % 400)} ${pad(hour)}:${pad(minute)}`;
}
/** 인덱스로만 만든다 — 난수를 쓰면 새로고침마다 목록이 흔들린다. */
function buildAccount(index: number): PointAccount {
const serial = TOTAL_ACCOUNTS - index;
const totalPoint = 120 + ((index * 37) % 880);
const deductedPoint = (index * 13) % 60;
const usedPoint = (index * 29) % 300;
return {
id: `PA-${String(serial).padStart(5, '0')}`,
memberNo: `NO_${String(10000 + serial)}`,
loginId: `user_${String(serial).padStart(4, '0')}`,
studentName: `학생${serial}`,
schoolName: SCHOOL_NAMES[index % SCHOOL_NAMES.length],
grade: (index % 3) + 1,
classNo: (index % 10) + 1,
studentNo: (index % 30) + 1,
totalPoint,
deductedPoint,
usedPoint,
currentPoint: totalPoint - deductedPoint - usedPoint,
updatedAt: timestampAt(index),
siteName: SITE_NAME,
};
}
const ACCOUNTS: PointAccount[] = Array.from(
{ length: TOTAL_ACCOUNTS },
(_, index) => buildAccount(index)
);
function matchesQuery(account: PointAccount, query: PointAccountQuery): boolean {
if (
query.schoolName &&
!account.schoolName.toLowerCase().includes(query.schoolName.toLowerCase())
) {
return false;
}
if (query.grade !== null && account.grade !== query.grade) {
return false;
}
if (query.classNo !== null && account.classNo !== query.classNo) {
return false;
}
if (query.studentNo !== null && account.studentNo !== query.studentNo) {
return false;
}
if (query.keyword) {
const needle = query.keyword.toLowerCase();
const haystack =
query.searchField === 'loginId' ? account.loginId : account.studentName;
if (!haystack.toLowerCase().includes(needle)) {
return false;
}
}
return true;
}
/** 기획 PTS10200001 ② — 최근 수정일시로 정렬. */
function byUpdatedAtDesc(a: PointAccount, b: PointAccount): number {
return b.updatedAt.localeCompare(a.updatedAt);
}
export async function fetchPointAccounts(
query: PointAccountQuery
): Promise<{ items: PointAccount[]; totalCount: number }> {
const filtered = ACCOUNTS.filter((account) => matchesQuery(account, query));
filtered.sort(byUpdatedAtDesc);
const start = (query.page - 1) * query.pageSize;
return {
items: filtered.slice(start, start + query.pageSize),
totalCount: filtered.length,
};
}
export async function fetchPointAccount(
id: string
): Promise<PointAccount | null> {
return ACCOUNTS.find((account) => account.id === id) ?? null;
}
function buildHistory(account: PointAccount): PointHistoryEntry[] {
const seed = Number(account.id.slice(3));
const length = 12 + (seed % 9);
return Array.from({ length }, (_, index) => {
const isPurchase = (seed + index) % 4 === 3;
return {
id: `${account.id}-H${String(index + 1).padStart(3, '0')}`,
title: isPurchase
? `배지 이미지 구입 - BADGE_${String(((seed + index) % 20) + 1).padStart(3, '0')}`
: EVENT_TITLES[(seed + index) % EVENT_TITLES.length],
point: isPurchase ? -100 : 10,
siteName: SITE_NAME,
date: dateAt(index),
};
});
}
/** 기획 PTS10300001 ② — 최근 날짜순 정렬. */
export async function fetchPointHistory(
accountId: string,
page: number,
pageSize: number
): Promise<{ items: PointHistoryEntry[]; totalCount: number }> {
const account = await fetchPointAccount(accountId);
if (!account) {
return { items: [], totalCount: 0 };
}
const entries = buildHistory(account);
entries.sort((a, b) => b.date.localeCompare(a.date));
const start = (page - 1) * pageSize;
return {
items: entries.slice(start, start + pageSize),
totalCount: entries.length,
};
}