112 lines
4.2 KiB
TypeScript
112 lines
4.2 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { cookies } from 'next/headers';
|
|
import { createAdminClient } from '@/lib/supabase/admin';
|
|
import { verifyAdminTokenNode } from '@/lib/admin-auth';
|
|
|
|
export const runtime = 'nodejs';
|
|
|
|
async function checkAuth() {
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get('admin_token')?.value;
|
|
return token && verifyAdminTokenNode(token);
|
|
}
|
|
|
|
const ID_RE = /^[a-z0-9_]{2,40}$/;
|
|
|
|
function sanitizeFeatures(input: unknown): string[] | undefined {
|
|
if (!Array.isArray(input)) return undefined;
|
|
return input.filter((v): v is string => typeof v === 'string');
|
|
}
|
|
|
|
export async function GET() {
|
|
if (!(await checkAuth())) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
const supabase = createAdminClient();
|
|
const { data, error } = await supabase
|
|
.from('products')
|
|
.select('*')
|
|
.order('sort_order')
|
|
.order('id');
|
|
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
|
return NextResponse.json({ products: data ?? [] });
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
if (!(await checkAuth())) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { id, name, description, description_long, price, features, is_listed, sort_order } = body;
|
|
|
|
if (typeof id !== 'string' || !ID_RE.test(id)) {
|
|
return NextResponse.json({ error: 'id는 영소문자/숫자/언더스코어 2-40자' }, { status: 400 });
|
|
}
|
|
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
return NextResponse.json({ error: 'name 필요' }, { status: 400 });
|
|
}
|
|
if (typeof price !== 'number' || !Number.isInteger(price) || price < 0) {
|
|
return NextResponse.json({ error: 'price는 0 이상의 정수' }, { status: 400 });
|
|
}
|
|
|
|
const insert: Record<string, unknown> = {
|
|
id,
|
|
name: name.trim(),
|
|
price,
|
|
category: 'software',
|
|
pay_method: 'bank_transfer',
|
|
is_active: true,
|
|
};
|
|
if (typeof description === 'string') insert.description = description;
|
|
if (typeof description_long === 'string') insert.description_long = description_long;
|
|
const feats = sanitizeFeatures(features);
|
|
if (feats !== undefined) insert.features = feats;
|
|
if (typeof is_listed === 'boolean') insert.is_listed = is_listed;
|
|
if (typeof sort_order === 'number') insert.sort_order = sort_order;
|
|
|
|
const supabase = createAdminClient();
|
|
const { data, error } = await supabase.from('products').insert(insert).select().single();
|
|
if (error) {
|
|
if (error.code === '23505') {
|
|
return NextResponse.json({ error: '이미 존재하는 제품 id' }, { status: 409 });
|
|
}
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
return NextResponse.json({ product: data });
|
|
}
|
|
|
|
export async function PATCH(request: Request) {
|
|
if (!(await checkAuth())) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { id } = body;
|
|
if (typeof id !== 'string' || !id) {
|
|
return NextResponse.json({ error: 'id 필요' }, { status: 400 });
|
|
}
|
|
|
|
const updates: Record<string, unknown> = {};
|
|
if (typeof body.name === 'string') updates.name = body.name.trim();
|
|
if (typeof body.description === 'string') updates.description = body.description;
|
|
if (typeof body.description_long === 'string') updates.description_long = body.description_long;
|
|
if (typeof body.price === 'number' && Number.isInteger(body.price) && body.price >= 0) {
|
|
updates.price = body.price;
|
|
}
|
|
const feats = sanitizeFeatures(body.features);
|
|
if (feats !== undefined) updates.features = feats;
|
|
if (typeof body.is_listed === 'boolean') updates.is_listed = body.is_listed;
|
|
if (typeof body.is_active === 'boolean') updates.is_active = body.is_active;
|
|
if (typeof body.sort_order === 'number') updates.sort_order = body.sort_order;
|
|
|
|
if (Object.keys(updates).length === 0) {
|
|
return NextResponse.json({ error: '변경할 필드 없음' }, { status: 400 });
|
|
}
|
|
|
|
const supabase = createAdminClient();
|
|
const { error } = await supabase.from('products').update(updates).eq('id', id);
|
|
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
|
|
return NextResponse.json({ success: true });
|
|
}
|