import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; export async function GET(request: NextRequest) { try { const supabase = await createClient(); const { data: { user }, error: authError } = await supabase.auth.getUser(); if (authError || !user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { data: profile } = await supabase .from('user_profiles') .select('role, is_active') .eq('id', user.id) .single(); if (!profile?.is_active) return NextResponse.json({ error: 'Account is inactive' }, { status: 403 }); const allowedRoles = ['contributor', 'author', 'editor', 'administrator', 'admin']; if (!allowedRoles.includes(profile.role)) return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 }); const { data: articles, error } = await supabase .from('native_articles') .select('id, title, status, category, site_id, created_at, updated_at, scheduled_at, published_at, submitted_for_review_at, scope_flagged, editor_note') .eq('author_id', user.id) .order('created_at', { ascending: false }); if (error) return NextResponse.json({ error: error.message }, { status: 500 }); return NextResponse.json({ articles: articles || [] }); } catch (error) { console.error('Contributor posts GET error:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } export async function POST(request: NextRequest) { try { const supabase = await createClient(); const { data: { user }, error: authError } = await supabase.auth.getUser(); if (authError || !user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { data: profile } = await supabase .from('user_profiles') .select('role, is_active, full_name') .eq('id', user.id) .single(); if (!profile?.is_active) return NextResponse.json({ error: 'Account is inactive' }, { status: 403 }); const allowedRoles = ['contributor', 'author', 'editor', 'administrator', 'admin']; if (!allowedRoles.includes(profile.role)) return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 }); const body = await request.json(); const { title, content, excerpt, category, tags, featured_image, featured_image_alt, scheduled_at, site_id = 1, scope_flagged = false } = body; if (!title) return NextResponse.json({ error: 'Title is required' }, { status: 400 }); // Enforce Featured category lock for contributors and authors let safeCategory = category; if (['contributor', 'author'].includes(profile.role) && category?.toLowerCase() === 'featured') { safeCategory = 'Uncategorized'; } // ── Banned terms check (skip for editors/admins) ────────────────────────── if (['contributor', 'author'].includes(profile.role)) { const textToCheck = [title, content || '', excerpt || ''].join(' '); const { data: bannedMatch } = await supabase .rpc('check_banned_terms', { p_text: textToCheck, p_site_id: site_id }) .single(); if (bannedMatch) { if (bannedMatch.ban_level === 'hard') { // Save as draft silently, send admin notification const slug = title.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').trim() + '-' + Date.now(); await supabase.from('native_articles').insert({ slug, title, content, excerpt, category: safeCategory, tags, featured_image, featured_image_alt, author_id: user.id, author_name: profile.full_name || user.email, status: 'draft', site_id, banned_term_flagged: true, banned_term_matched: bannedMatch.term, }); // Notify admins via email (fire and forget) fetch('/api/admin/banned-term-alert', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: profile.full_name || user.email, term: bannedMatch.term }), }).catch(() => {}); return NextResponse.json({ blocked: true, error: 'This post could not be submitted. Please contact the editorial team at editor@avbeat.com for assistance.' }, { status: 422 }); } // Soft ban: flag but allow through // Will be tagged below } } // Determine status let status = 'draft'; let submittedForReviewAt = null; let bannedTermFlagged = false; let bannedTermMatched: string | null = null; // Re-check for soft ban flag if (['contributor', 'author'].includes(profile.role)) { const textToCheck = [title, content || '', excerpt || ''].join(' '); const { data: softMatch } = await supabase .rpc('check_banned_terms', { p_text: textToCheck, p_site_id: site_id }) .single(); if (softMatch && softMatch.ban_level === 'soft') { bannedTermFlagged = true; bannedTermMatched = softMatch.term; } } if (profile.role === 'contributor') { status = scheduled_at ? 'scheduled_pending_review' : 'pending'; submittedForReviewAt = new Date().toISOString(); } else if (profile.role === 'author') { status = scheduled_at ? 'scheduled' : 'published'; } else { status = scheduled_at ? 'scheduled' : (body.status || 'published'); } const slug = title.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').trim() + '-' + Date.now(); const { data: article, error: insertError } = await supabase .from('native_articles') .insert({ slug, title, content, excerpt, category: safeCategory, tags, featured_image, featured_image_alt, author_id: user.id, author_name: profile.full_name || user.email, status, site_id, scheduled_at: scheduled_at || null, submitted_for_review_at: submittedForReviewAt, published_at: status === 'published' ? new Date().toISOString() : null, scope_flagged: scope_flagged || false, banned_term_flagged: bannedTermFlagged, banned_term_matched: bannedTermMatched, }) .select() .single(); if (insertError) return NextResponse.json({ error: insertError.message }, { status: 500 }); return NextResponse.json({ article, status }, { status: 201 }); } catch (error) { console.error('Contributor posts POST error:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } }