forked from minzeyaphyo/burmddit
Add web admin features + fix scraper & translator
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
This commit is contained in:
122
frontend/app/api/admin/article/route.ts
Normal file
122
frontend/app/api/admin/article/route.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user