- app/work/saju/layout.tsx: isServiceVisible 가드 제거, 사주 서비스 공개 전환 - lib/service-visibility.ts: HideableService에서 saju 제거 - app/api/admin/services/route.ts: DEFAULT_SERVICES에서 saju 행 제거 - app/api/saju/analyze/route.ts: saju_detail 결제 게이트(403) 제거, 로그인(401) + 서버측 일일 1회 제한(429, ai_usage_log 기반)으로 교체. recordUsage는 실제 Gemini 해석 성공 반환 직전에만 호출(MOCK 폴백 제외) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
32 lines
1.2 KiB
TypeScript
32 lines
1.2 KiB
TypeScript
import { cookies } from 'next/headers';
|
|
import { createAdminClient } from '@/lib/supabase/admin';
|
|
import { verifyAdminTokenNode } from '@/lib/admin-auth';
|
|
|
|
/** 숨김 가능 서비스 id (service_settings.id와 일치) */
|
|
export type HideableService = 'music' | 'gyeol' | 'lotto';
|
|
|
|
/**
|
|
* 서비스 노출 여부. admin_token 세션이면 항상 true.
|
|
* service_settings 조회 실패(테이블 미생성 등) 시 안전하게 숨김(false).
|
|
* @warning 레거시 숨김 전용 — 일반 공개 서비스(products 등) 가드에 재사용 금지.
|
|
* fail-closed 정책이라 DB 일시 장애 시 404가 됨. 캐싱 없음(매 렌더 DB 조회).
|
|
*/
|
|
export async function isServiceVisible(id: HideableService): Promise<boolean> {
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get('admin_token')?.value;
|
|
if (token && verifyAdminTokenNode(token)) return true;
|
|
|
|
try {
|
|
const supabase = createAdminClient();
|
|
const { data, error } = await supabase
|
|
.from('service_settings')
|
|
.select('is_active')
|
|
.eq('id', id)
|
|
.maybeSingle();
|
|
if (error || !data) return false;
|
|
return data.is_active === true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|