#323 (LLM 검증 어드민 UI): - api.ts: getVerifyPending / verifyAll / verifyOne / setRestaurantHidden 추가 - Restaurant 타입에 hidden / hidden_reason / verified_at 추가 - RestaurantsPanel 헤더에 "미검증 N건 + LLM 검증" 버튼 추가 - 테이블에 "검증" 컬럼 추가: - hidden=true → "숨김 (사유)" 버튼 (클릭 시 해제) - verified_at 있고 visible → "OK" 버튼 (클릭 시 숨김) - 미검증 → "미검증" 텍스트 #304 (어드민 공통 유틸): - lib/admin-utils.ts 신규 - getAdminToken(): localStorage 직접 접근 통일 - authHeaders(): 표준 Bearer 헤더 - consumeSseStream(): SSE 라인 파싱 헬퍼 - colSpan 6 → 7로 검증 컬럼 반영 후속 분리: #329 (admin/page.tsx 전체 분리 + localStorage/SSE 호출 11+곳 교체) Refs: #304 #323 #322
53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
// #304 어드민 페이지 공통 유틸.
|
|
// 결함: localStorage 직접 접근 10+곳 / SSE 파싱 코드 6곳 중복.
|
|
|
|
const TOKEN_KEY = "tasteby_token";
|
|
|
|
export function getAdminToken(): string | null {
|
|
if (typeof window === "undefined") return null;
|
|
return localStorage.getItem(TOKEN_KEY);
|
|
}
|
|
|
|
export function authHeaders(): Record<string, string> {
|
|
const token = getAdminToken();
|
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
}
|
|
|
|
/**
|
|
* SSE(Server-Sent Events) 스트림을 라인 단위로 파싱하여 onEvent 콜백을 호출.
|
|
* 호환 패턴: `data: { ...json... }` 한 줄 = 한 이벤트.
|
|
* 비어있는 줄은 무시. JSON 파싱 실패 시 콜백 skip.
|
|
*/
|
|
export async function consumeSseStream(
|
|
response: Response,
|
|
onEvent: (event: unknown) => void,
|
|
onError?: (err: unknown) => void,
|
|
): Promise<void> {
|
|
const reader = response.body?.getReader();
|
|
if (!reader) return;
|
|
const decoder = new TextDecoder();
|
|
let buffer = "";
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
buffer += decoder.decode(value, { stream: true });
|
|
const lines = buffer.split("\n");
|
|
buffer = lines.pop() ?? "";
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed.startsWith("data:")) continue;
|
|
const payload = trimmed.slice(5).trim();
|
|
if (!payload) continue;
|
|
try {
|
|
onEvent(JSON.parse(payload));
|
|
} catch {
|
|
// 무시: 일부 SSE 줄이 JSON이 아닐 수도 있음
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
onError?.(err);
|
|
}
|
|
}
|