initial commit: rocket.new export of broadcastbeat
This commit is contained in:
148
src/app/api/contributor/posts/route.ts
Normal file
148
src/app/api/contributor/posts/route.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
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@broadcastbeat.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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user