forked from minzeyaphyo/burmddit
Frontend changes: - Add /admin dashboard for article management - Add AdminButton component (Alt+Shift+A on articles) - Add /api/admin/article API endpoints Backend improvements: - scraper_v2.py: Multi-layer fallback extraction (newspaper → trafilatura → readability) - translator_v2.py: Better chunking, repetition detection, validation - admin_tools.py: CLI admin commands - test_scraper.py: Individual source testing Docs: - WEB-ADMIN-GUIDE.md: Web admin usage - ADMIN-GUIDE.md: CLI admin usage - SCRAPER-IMPROVEMENT-PLAN.md: Scraper fixes details - TRANSLATION-FIX.md: Translation improvements - ADMIN-FEATURES-SUMMARY.md: Implementation summary Fixes: - Article scraping from 0 → 96+ articles working - Translation quality issues (repetition, truncation) - Added 13 new RSS sources
123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
// Admin API for managing articles
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { Pool } from 'pg';
|
|
|
|
// Simple password auth (you can change this in .env)
|
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'burmddit2026';
|
|
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL,
|
|
});
|
|
|
|
// Helper to check admin auth
|
|
function checkAuth(request: NextRequest): boolean {
|
|
const authHeader = request.headers.get('authorization');
|
|
if (!authHeader) return false;
|
|
|
|
const password = authHeader.replace('Bearer ', '');
|
|
return password === ADMIN_PASSWORD;
|
|
}
|
|
|
|
// GET /api/admin/article - List articles
|
|
export async function GET(request: NextRequest) {
|
|
if (!checkAuth(request)) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const status = searchParams.get('status') || 'published';
|
|
const limit = parseInt(searchParams.get('limit') || '50');
|
|
|
|
try {
|
|
const client = await pool.connect();
|
|
|
|
const result = await client.query(
|
|
`SELECT id, title, title_burmese, slug, status,
|
|
LENGTH(content) as content_length,
|
|
LENGTH(content_burmese) as burmese_length,
|
|
published_at, view_count
|
|
FROM articles
|
|
WHERE status = $1
|
|
ORDER BY published_at DESC
|
|
LIMIT $2`,
|
|
[status, limit]
|
|
);
|
|
|
|
client.release();
|
|
|
|
return NextResponse.json({ articles: result.rows });
|
|
} catch (error) {
|
|
console.error('Database error:', error);
|
|
return NextResponse.json({ error: 'Database error' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// POST /api/admin/article - Update article status
|
|
export async function POST(request: NextRequest) {
|
|
if (!checkAuth(request)) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
try {
|
|
const body = await request.json();
|
|
const { articleId, action, reason } = body;
|
|
|
|
if (!articleId || !action) {
|
|
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
|
}
|
|
|
|
const client = await pool.connect();
|
|
|
|
if (action === 'unpublish') {
|
|
await client.query(
|
|
`UPDATE articles
|
|
SET status = 'draft', updated_at = NOW()
|
|
WHERE id = $1`,
|
|
[articleId]
|
|
);
|
|
|
|
client.release();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `Article ${articleId} unpublished`,
|
|
reason
|
|
});
|
|
|
|
} else if (action === 'publish') {
|
|
await client.query(
|
|
`UPDATE articles
|
|
SET status = 'published', updated_at = NOW()
|
|
WHERE id = $1`,
|
|
[articleId]
|
|
);
|
|
|
|
client.release();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `Article ${articleId} published`
|
|
});
|
|
|
|
} else if (action === 'delete') {
|
|
await client.query(
|
|
`DELETE FROM articles WHERE id = $1`,
|
|
[articleId]
|
|
);
|
|
|
|
client.release();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `Article ${articleId} deleted permanently`
|
|
});
|
|
}
|
|
|
|
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
|
|
|
} catch (error) {
|
|
console.error('Database error:', error);
|
|
return NextResponse.json({ error: 'Database error' }, { status: 500 });
|
|
}
|
|
}
|