feat(api): /api/admin/packs — admin 파일 목록/편집/삭제
- GET: pack_files 목록 (deleted_at IS NULL)
- PATCH: { id, label?, sort_order?, min_tier? } 인라인 편집
- DELETE: web-backend 통한 soft delete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
72
app/api/admin/packs/route.ts
Normal file
72
app/api/admin/packs/route.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { createAdminClient } from '@/lib/supabase/admin';
|
||||
import { verifyAdminTokenNode } from '@/lib/admin-auth';
|
||||
import { deletePackFileViaBackend } from '@/lib/web-backend';
|
||||
import type { PackTier } from '@/lib/pack-assets';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
async function checkAuth() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('admin_token')?.value;
|
||||
return token && verifyAdminTokenNode(token);
|
||||
}
|
||||
|
||||
const VALID_TIERS = new Set<PackTier>(['starter', 'pro', 'master']);
|
||||
|
||||
export async function GET() {
|
||||
if (!(await checkAuth())) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
const supabase = createAdminClient();
|
||||
const { data, error } = await supabase
|
||||
.from('pack_files')
|
||||
.select('*')
|
||||
.is('deleted_at', null)
|
||||
.order('min_tier')
|
||||
.order('sort_order');
|
||||
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
return NextResponse.json({ files: data ?? [] });
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
if (!(await checkAuth())) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
const { id, label, sort_order, min_tier } = await request.json();
|
||||
if (!id) return NextResponse.json({ error: 'id 필요' }, { status: 400 });
|
||||
|
||||
const updates: Record<string, unknown> = {};
|
||||
if (typeof label === 'string') updates.label = label;
|
||||
if (typeof sort_order === 'number') updates.sort_order = sort_order;
|
||||
if (typeof min_tier === 'string' && VALID_TIERS.has(min_tier as PackTier)) {
|
||||
updates.min_tier = min_tier;
|
||||
}
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return NextResponse.json({ error: '변경할 필드 없음' }, { status: 400 });
|
||||
}
|
||||
|
||||
const supabase = createAdminClient();
|
||||
const { error } = await supabase.from('pack_files').update(updates).eq('id', id);
|
||||
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
if (!(await checkAuth())) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
const url = new URL(request.url);
|
||||
const id = url.searchParams.get('id');
|
||||
if (!id) return NextResponse.json({ error: 'id 필요' }, { status: 400 });
|
||||
|
||||
// web-backend가 soft delete 담당 (DSM 정리도 backend가 향후 추가 예정)
|
||||
try {
|
||||
await deletePackFileViaBackend(id);
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : 'unknown';
|
||||
return NextResponse.json({ error: 'backend delete 실패', detail: msg }, { status: 502 });
|
||||
}
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
Reference in New Issue
Block a user