feat(phase3b): orders에 영상화(music_video) 발주 + video-product 조회
This commit is contained in:
@@ -70,14 +70,22 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({ error: '판매 중인 상품이 아닙니다' }, { status: 404 });
|
return NextResponse.json({ error: '판매 중인 상품이 아닙니다' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// music_video 발주: 본인 트랙 검증 + 트랙별 중복 가드
|
||||||
|
let musicTrackId: string | null = null;
|
||||||
|
if (productId === 'music_video') {
|
||||||
|
musicTrackId = sanitizeStr((body as Record<string, unknown>).musicTrackId, 64) || null;
|
||||||
|
if (!musicTrackId) return NextResponse.json({ error: '영상화할 트랙이 필요합니다' }, { status: 400 });
|
||||||
|
const { data: track } = await admin
|
||||||
|
.from('music_tracks').select('id').eq('id', musicTrackId).eq('user_id', user.id).maybeSingle();
|
||||||
|
if (!track) return NextResponse.json({ error: '본인 음악 트랙만 영상화할 수 있습니다' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
// 4) 중복 pending 방지
|
// 4) 중복 pending 방지
|
||||||
const { data: existing } = await admin
|
const dupQuery = admin.from('orders').select('id')
|
||||||
.from('orders')
|
.eq('user_id', user.id).eq('product_id', productId).eq('status', 'pending');
|
||||||
.select('id')
|
const { data: existing } = productId === 'music_video' && musicTrackId
|
||||||
.eq('user_id', user.id)
|
? await dupQuery.eq('metadata->>music_track_id', musicTrackId).maybeSingle()
|
||||||
.eq('product_id', productId)
|
: await dupQuery.maybeSingle();
|
||||||
.eq('status', 'pending')
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return NextResponse.json({ orderId: existing.id, reused: true });
|
return NextResponse.json({ orderId: existing.id, reused: true });
|
||||||
@@ -94,6 +102,7 @@ export async function POST(request: Request) {
|
|||||||
metadata: {
|
metadata: {
|
||||||
method: 'bank_transfer',
|
method: 'bank_transfer',
|
||||||
depositor_name: depositorName,
|
depositor_name: depositorName,
|
||||||
|
...(musicTrackId ? { music_track_id: musicTrackId } : {}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.select('id')
|
.select('id')
|
||||||
@@ -106,6 +115,12 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
const orderId = order.id as string;
|
const orderId = order.id as string;
|
||||||
|
|
||||||
|
if (productId === 'music_video' && musicTrackId) {
|
||||||
|
await admin.from('music_tracks')
|
||||||
|
.update({ video_status: 'requested', video_order_id: orderId })
|
||||||
|
.eq('id', musicTrackId).eq('user_id', user.id);
|
||||||
|
}
|
||||||
|
|
||||||
// 6) 메일 발송 (실패해도 주문 유효)
|
// 6) 메일 발송 (실패해도 주문 유효)
|
||||||
try {
|
try {
|
||||||
await sendOrderReceivedEmails({
|
await sendOrderReceivedEmails({
|
||||||
|
|||||||
16
app/api/studio/video-product/route.ts
Normal file
16
app/api/studio/video-product/route.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { createClient } from '@/lib/supabase/server';
|
||||||
|
import { createAdminClient } from '@/lib/supabase/admin';
|
||||||
|
import { getProductById } from '@/lib/supabase/product-files';
|
||||||
|
|
||||||
|
export const runtime = 'nodejs';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const supabase = await createClient();
|
||||||
|
const { data: { user } } = await supabase.auth.getUser();
|
||||||
|
if (!user) return NextResponse.json({ error: '로그인이 필요합니다.' }, { status: 401 });
|
||||||
|
const admin = createAdminClient();
|
||||||
|
const product = await getProductById(admin, 'music_video');
|
||||||
|
if (!product || !product.is_active) return NextResponse.json({ error: '영상화 상품이 준비 중입니다.' }, { status: 404 });
|
||||||
|
return NextResponse.json({ product: { id: product.id, name: product.name, price: product.price, is_active: product.is_active } });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user