feat(products): orders 기반 제품/파일 조회 헬퍼
This commit is contained in:
84
lib/supabase/product-files.ts
Normal file
84
lib/supabase/product-files.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
import { expandProductAccess } from '@/lib/product-access';
|
||||||
|
|
||||||
|
export interface ProductRow {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
description_long: string | null;
|
||||||
|
price: number;
|
||||||
|
category: string;
|
||||||
|
is_active: boolean;
|
||||||
|
is_listed: boolean;
|
||||||
|
sort_order: number;
|
||||||
|
features: string[] | null;
|
||||||
|
pay_method: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductFile {
|
||||||
|
id: string;
|
||||||
|
product_id: string | null;
|
||||||
|
label: string;
|
||||||
|
file_path: string;
|
||||||
|
filename: string;
|
||||||
|
size_bytes: number;
|
||||||
|
sort_order: number;
|
||||||
|
uploaded_at: string;
|
||||||
|
deleted_at: string | null;
|
||||||
|
min_tier: string; // 레거시 컬럼 — 신규 로직에서는 미사용
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 카탈로그 노출 제품 (is_listed && is_active, sort_order 순) */
|
||||||
|
export async function getListedProducts(supabase: SupabaseClient): Promise<ProductRow[]> {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('products')
|
||||||
|
.select('*')
|
||||||
|
.eq('is_listed', true)
|
||||||
|
.eq('is_active', true)
|
||||||
|
.order('sort_order')
|
||||||
|
.order('id');
|
||||||
|
if (error) throw error;
|
||||||
|
return (data ?? []) as ProductRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProductById(supabase: SupabaseClient, id: string): Promise<ProductRow | null> {
|
||||||
|
const { data, error } = await supabase.from('products').select('*').eq('id', id).maybeSingle();
|
||||||
|
if (error) throw error;
|
||||||
|
return (data as ProductRow) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 사용자의 결제 완료 product_id 목록 (orders 단일 소스) */
|
||||||
|
export async function getUserPaidProductIds(supabase: SupabaseClient, userId: string): Promise<string[]> {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('orders')
|
||||||
|
.select('product_id')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.eq('status', 'paid')
|
||||||
|
.not('product_id', 'is', null);
|
||||||
|
if (error) throw error;
|
||||||
|
return Array.from(new Set((data ?? []).map((r) => r.product_id as string)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 접근 확장 포함 — 사용자가 다운로드 가능한 product_id 집합 */
|
||||||
|
export async function getUserAccessibleProductIds(supabase: SupabaseClient, userId: string): Promise<string[]> {
|
||||||
|
return expandProductAccess(await getUserPaidProductIds(supabase, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFilesByProductIds(supabase: SupabaseClient, productIds: string[]): Promise<ProductFile[]> {
|
||||||
|
if (productIds.length === 0) return [];
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('pack_files')
|
||||||
|
.select('*')
|
||||||
|
.in('product_id', productIds)
|
||||||
|
.is('deleted_at', null)
|
||||||
|
.order('product_id')
|
||||||
|
.order('sort_order');
|
||||||
|
if (error) throw error;
|
||||||
|
return (data ?? []) as ProductFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileById(supabase: SupabaseClient, id: string): Promise<ProductFile | null> {
|
||||||
|
const { data, error } = await supabase.from('pack_files').select('*').eq('id', id).maybeSingle();
|
||||||
|
if (error) throw error;
|
||||||
|
return (data as ProductFile) ?? null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user