feat(phase3b): orders에 영상화(music_video) 발주 + video-product 조회

This commit is contained in:
2026-07-09 22:53:02 +09:00
parent 853f96b405
commit c9b8d2df5c
2 changed files with 38 additions and 7 deletions

View File

@@ -70,14 +70,22 @@ export async function POST(request: Request) {
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 방지
const { data: existing } = await admin
.from('orders')
.select('id')
.eq('user_id', user.id)
.eq('product_id', productId)
.eq('status', 'pending')
.maybeSingle();
const dupQuery = admin.from('orders').select('id')
.eq('user_id', user.id).eq('product_id', productId).eq('status', 'pending');
const { data: existing } = productId === 'music_video' && musicTrackId
? await dupQuery.eq('metadata->>music_track_id', musicTrackId).maybeSingle()
: await dupQuery.maybeSingle();
if (existing) {
return NextResponse.json({ orderId: existing.id, reused: true });
@@ -94,6 +102,7 @@ export async function POST(request: Request) {
metadata: {
method: 'bank_transfer',
depositor_name: depositorName,
...(musicTrackId ? { music_track_id: musicTrackId } : {}),
},
})
.select('id')
@@ -106,6 +115,12 @@ export async function POST(request: Request) {
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) 메일 발송 (실패해도 주문 유효)
try {
await sendOrderReceivedEmails({