Files
Zeya Phyo f51ac4afa4 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
2026-02-26 09:17:50 +00:00

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 });
}
}