import { NextRequest, NextResponse } from 'next/server'; const SUPABASE_URL = process.env.SUPABASE_URL || 'https://tcwdbokruvlizkxcpkzj.supabase.co'; const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || ''; const SUPABASE_TOKEN = process.env.SUPABASE_TOKEN || ''; const EDGE_FUNCTIONS_URL = `${SUPABASE_URL}/functions/v1`; const COOKIE_NAME = 'cellex_session_id'; /** * Group Buy API * * Operations: * - start: Buyer starts a new group buy for a product (creates invite link) * - join: Buyer joins an existing group buy via invite code * - status: Get status of a group buy * - active: Get active group buys for a product * - mine: Get group buys the user has joined * - enable: Seller enables group buy for a product (sets target + discount) * - disable: Seller disables group buy for a product */ function generateInviteCode(): string { return Math.random().toString(36).substring(2, 10) + Date.now().toString(36).substring(-4); } export async function POST(request: NextRequest) { if (!SUPABASE_ANON_KEY) { return NextResponse.json({ success: false, error: 'SUPABASE_ANON_KEY not set' }, { status: 500 }); } const sessionId = request.cookies.get(COOKIE_NAME)?.value || ''; let body: any; try { body = await request.json(); } catch { return NextResponse.json({ success: false, error: 'Invalid JSON' }, { status: 400 }); } const sqlHeaders: Record = { 'Authorization': `Bearer ${SUPABASE_TOKEN}`, 'Content-Type': 'application/json', }; const sqlApiUrl = `https://api.supabase.com/v1/projects/tcwdbokruvlizkxcpkzj/database/query`; // --- Operations that don't require auth --- if (body.op === 'status') { const gbId = body.groupBuyId; if (!gbId) return NextResponse.json({ success: false, error: 'groupBuyId required' }, { status: 400 }); const resp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT gb.*, p.name as product_name, p.image_url, p.price, p.group_buy_enabled, p.group_buy_target_count, p.group_buy_discount_pct FROM group_buys gb JOIN products p ON gb.product_id = p.id WHERE gb.id = '${gbId}'::uuid;` }) }); const data = await resp.json(); if (Array.isArray(data) && data.length > 0) return NextResponse.json({ success: true, groupBuy: data[0] }); return NextResponse.json({ success: false, error: 'Group buy not found' }, { status: 404 }); } if (body.op === 'open') { const productId = body.productId; if (!productId) return NextResponse.json({ success: false, error: 'productId required' }, { status: 400 }); const resp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT * FROM group_buys WHERE product_id = ${productId} AND status = 'open' ORDER BY created_at DESC;` }) }); const data = await resp.json(); return NextResponse.json({ success: true, groupBuys: Array.isArray(data) ? data : [] }); } if (body.op === 'invite') { // Get group buy by invite code (public — no auth needed) const code = body.inviteCode; if (!code) return NextResponse.json({ success: false, error: 'inviteCode required' }, { status: 400 }); const resp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT gb.*, p.name as product_name, p.image_url, p.price, p.group_buy_target_count, p.group_buy_discount_pct, u.email as initiator_email FROM group_buys gb JOIN products p ON gb.product_id = p.id LEFT JOIN auth.users u ON gb.initiator_id = u.id WHERE gb.invite_code = '${code.replace(/'/g, "''")}';` }) }); const data = await resp.json(); if (Array.isArray(data) && data.length > 0) return NextResponse.json({ success: true, groupBuy: data[0] }); return NextResponse.json({ success: false, error: 'Invalid invite code' }, { status: 404 }); } // --- Operations that require auth --- if (!sessionId) return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); // Get user const authResp = await fetch(`${EDGE_FUNCTIONS_URL}/auth`, { method: 'POST', headers: { 'apikey': SUPABASE_ANON_KEY, 'Authorization': `Bearer ${sessionId}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ op: 'session' }), }); const authData = await authResp.json(); if (!authData.success || !authData.user) return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); const userId = authData.user.id; const userEmail = authData.user.email; switch (body.op) { case 'enable': { // Seller enables group buy for a product const { productId, targetCount, discountPct } = body; if (!productId) return NextResponse.json({ success: false, error: 'productId required' }, { status: 400 }); const tc = targetCount || 3; const dp = discountPct || 20; const resp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `UPDATE products SET group_buy_enabled = true, group_buy_target_count = ${tc}, group_buy_discount_pct = ${dp} WHERE id = ${productId} AND seller_id = '${userId}'::uuid RETURNING *;` }) }); const data = await resp.json(); if (Array.isArray(data) && data.length > 0) return NextResponse.json({ success: true, product: data[0] }); return NextResponse.json({ success: false, error: 'Failed — not the seller or product not found' }, { status: 403 }); } case 'disable': { const { productId } = body; if (!productId) return NextResponse.json({ success: false, error: 'productId required' }, { status: 400 }); const resp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `UPDATE products SET group_buy_enabled = false WHERE id = ${productId} AND seller_id = '${userId}'::uuid RETURNING *;` }) }); const data = await resp.json(); if (Array.isArray(data) && data.length > 0) return NextResponse.json({ success: true, product: data[0] }); return NextResponse.json({ success: false, error: 'Failed' }, { status: 403 }); } case 'start': { // Buyer starts a new group buy for a product const { productId } = body; if (!productId) return NextResponse.json({ success: false, error: 'productId required' }, { status: 400 }); // Check if product has group buy enabled const checkResp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT group_buy_enabled, group_buy_target_count, group_buy_discount_pct, seller_id FROM products WHERE id = ${productId};` }) }); const checkData = await checkResp.json(); if (!Array.isArray(checkData) || checkData.length === 0) return NextResponse.json({ success: false, error: 'Product not found' }, { status: 404 }); const product = checkData[0]; if (!product.group_buy_enabled) return NextResponse.json({ success: false, error: 'Seller has not enabled group buy for this product' }, { status: 400 }); const inviteCode = generateInviteCode(); const targetCount = product.group_buy_target_count || 3; const discountPct = product.group_buy_discount_pct || 20; const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // 7 days // Create group buy const createResp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `INSERT INTO group_buys (product_id, seller_id, initiator_id, target_count, current_count, discount_pct, status, expires_at, invite_code) VALUES (${productId}, '${product.seller_id}'::uuid, '${userId}'::uuid, ${targetCount}, 1, ${discountPct}, 'open', '${expiresAt}', '${inviteCode}') RETURNING *;` }) }); const createData = await createResp.json(); if (!Array.isArray(createData) || createData.length === 0) return NextResponse.json({ success: false, error: 'Failed to create group buy' }, { status: 500 }); const groupBuy = createData[0]; // Add initiator as first member await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `INSERT INTO group_buy_members (group_buy_id, user_id) VALUES ('${groupBuy.id}'::uuid, '${userId}'::uuid);` }) }); // Create a conversation for this group buy (initiator ↔ seller) // Only create if initiator is NOT the seller (avoid self-conversation) let conversationId = null; if (userId !== product.seller_id) { const convResp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `INSERT INTO conversations (type, participant1, participant2, group_buy_id) VALUES ('group_buy', '${userId}'::uuid, '${product.seller_id}'::uuid, '${groupBuy.id}'::uuid) RETURNING *;` }) }); const convData = await convResp.json(); if (Array.isArray(convData) && convData.length > 0) { conversationId = convData[0].id; await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `UPDATE group_buys SET conversation_id = '${conversationId}'::uuid WHERE id = '${groupBuy.id}'::uuid;` }) }); } } return NextResponse.json({ success: true, groupBuy: { ...groupBuy, conversationId, inviteLink: `/group-buy-join?code=${inviteCode}` } }); } case 'join': { // Buyer joins a group buy via invite code const { inviteCode } = body; if (!inviteCode) return NextResponse.json({ success: false, error: 'inviteCode required' }, { status: 400 }); // Get group buy const gbResp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT * FROM group_buys WHERE invite_code = '${inviteCode.replace(/'/g, "''")}' AND status = 'open';` }) }); const gbData = await gbResp.json(); if (!Array.isArray(gbData) || gbData.length === 0) return NextResponse.json({ success: false, error: 'Group buy not found or expired' }, { status: 404 }); const groupBuy = gbData[0]; // Check if already a member const memberResp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT * FROM group_buy_members WHERE group_buy_id = '${groupBuy.id}'::uuid AND user_id = '${userId}'::uuid;` }) }); const memberData = await memberResp.json(); if (Array.isArray(memberData) && memberData.length > 0) return NextResponse.json({ success: false, error: 'You are already a member of this group buy' }, { status: 400 }); // Check if group buy is full if (groupBuy.current_count >= groupBuy.target_count) return NextResponse.json({ success: false, error: 'Group buy is full' }, { status: 400 }); // Add member await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `INSERT INTO group_buy_members (group_buy_id, user_id) VALUES ('${groupBuy.id}'::uuid, '${userId}'::uuid);` }) }); // Increment count const newCount = groupBuy.current_count + 1; const isComplete = newCount >= groupBuy.target_count; const updateResp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `UPDATE group_buys SET current_count = ${newCount}, status = '${isComplete ? 'completed' : 'open'}', completed_at = ${isComplete ? 'NOW()' : 'NULL'} WHERE id = '${groupBuy.id}'::uuid RETURNING *;` }) }); const updateData = await updateResp.json(); // Add user to the group buy's conversation (create one between joiner and initiator) // This creates a 1-on-1 conversation linked to the group buy await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `INSERT INTO conversations (type, participant1, participant2, group_buy_id) VALUES ('group_buy', '${userId}'::uuid, '${groupBuy.initiator_id}'::uuid, '${groupBuy.id}'::uuid) ON CONFLICT DO NOTHING;` }) }); return NextResponse.json({ success: true, groupBuy: Array.isArray(updateData) && updateData.length > 0 ? updateData[0] : groupBuy }); } case 'mine': { // Get group buys the user has joined const resp = await fetch(sqlApiUrl, { method: 'POST', headers: sqlHeaders, body: JSON.stringify({ query: `SELECT gb.*, p.name as product_name, p.image_url, p.price FROM group_buys gb JOIN group_buy_members gbm ON gb.id = gbm.group_buy_id JOIN products p ON gb.product_id = p.id WHERE gbm.user_id = '${userId}'::uuid ORDER BY gb.created_at DESC;` }) }); const data = await resp.json(); return NextResponse.json({ success: true, groupBuys: Array.isArray(data) ? data : [] }); } default: return NextResponse.json({ success: false, error: `Unknown op: ${body.op}` }, { status: 400 }); } }