#!/usr/bin/env python3
"""
Admin tools for managing burmddit articles
"""
import psycopg2
from dotenv import load_dotenv
import os
from datetime import datetime
from loguru import logger
import sys
load_dotenv()
def get_connection():
"""Get database connection"""
return psycopg2.connect(os.getenv('DATABASE_URL'))
def list_articles(status=None, limit=20):
"""List articles with optional status filter"""
conn = get_connection()
cur = conn.cursor()
if status:
cur.execute('''
SELECT id, title, status, published_at, view_count,
LENGTH(content) as content_len,
LENGTH(content_burmese) as burmese_len
FROM articles
WHERE status = %s
ORDER BY published_at DESC
LIMIT %s
''', (status, limit))
else:
cur.execute('''
SELECT id, title, status, published_at, view_count,
LENGTH(content) as content_len,
LENGTH(content_burmese) as burmese_len
FROM articles
ORDER BY published_at DESC
LIMIT %s
''', (limit,))
articles = []
for row in cur.fetchall():
articles.append({
'id': row[0],
'title': row[1][:60] + '...' if len(row[1]) > 60 else row[1],
'status': row[2],
'published_at': row[3],
'views': row[4] or 0,
'content_len': row[5],
'burmese_len': row[6]
})
cur.close()
conn.close()
return articles
def unpublish_article(article_id: int, reason: str = "Error/Quality issue"):
"""Unpublish an article (change status to draft)"""
conn = get_connection()
cur = conn.cursor()
# Get article info first
cur.execute('SELECT id, title, status FROM articles WHERE id = %s', (article_id,))
article = cur.fetchone()
if not article:
logger.error(f"Article {article_id} not found")
cur.close()
conn.close()
return False
logger.info(f"Unpublishing article {article_id}: {article[1][:60]}...")
logger.info(f"Current status: {article[2]}")
logger.info(f"Reason: {reason}")
# Update status to draft
cur.execute('''
UPDATE articles
SET status = 'draft',
updated_at = NOW()
WHERE id = %s
''', (article_id,))
conn.commit()
logger.info(f"✅ Article {article_id} unpublished successfully")
cur.close()
conn.close()
return True
def republish_article(article_id: int):
"""Republish an article (change status to published)"""
conn = get_connection()
cur = conn.cursor()
# Get article info first
cur.execute('SELECT id, title, status FROM articles WHERE id = %s', (article_id,))
article = cur.fetchone()
if not article:
logger.error(f"Article {article_id} not found")
cur.close()
conn.close()
return False
logger.info(f"Republishing article {article_id}: {article[1][:60]}...")
logger.info(f"Current status: {article[2]}")
# Update status to published
cur.execute('''
UPDATE articles
SET status = 'published',
updated_at = NOW()
WHERE id = %s
''', (article_id,))
conn.commit()
logger.info(f"✅ Article {article_id} republished successfully")
cur.close()
conn.close()
return True
def delete_article(article_id: int):
"""Permanently delete an article"""
conn = get_connection()
cur = conn.cursor()
# Get article info first
cur.execute('SELECT id, title, status FROM articles WHERE id = %s', (article_id,))
article = cur.fetchone()
if not article:
logger.error(f"Article {article_id} not found")
cur.close()
conn.close()
return False
logger.warning(f"⚠️ DELETING article {article_id}: {article[1][:60]}...")
# Delete from database
cur.execute('DELETE FROM articles WHERE id = %s', (article_id,))
conn.commit()
logger.info(f"✅ Article {article_id} deleted permanently")
cur.close()
conn.close()
return True
def find_problem_articles():
"""Find articles with potential issues"""
conn = get_connection()
cur = conn.cursor()
issues = []
# Issue 1: Translation too short (< 30% of original)
cur.execute('''
SELECT id, title,
LENGTH(content) as en_len,
LENGTH(content_burmese) as mm_len,
ROUND(100.0 * LENGTH(content_burmese) / NULLIF(LENGTH(content), 0), 1) as ratio
FROM articles
WHERE status = 'published'
AND LENGTH(content_burmese) < LENGTH(content) * 0.3
ORDER BY ratio ASC
LIMIT 10
''')
for row in cur.fetchall():
issues.append({
'id': row[0],
'title': row[1][:50],
'issue': 'Translation too short',
'details': f'EN: {row[2]} chars, MM: {row[3]} chars ({row[4]}%)'
})
# Issue 2: Missing Burmese content
cur.execute('''
SELECT id, title
FROM articles
WHERE status = 'published'
AND (content_burmese IS NULL OR LENGTH(content_burmese) < 100)
LIMIT 10
''')
for row in cur.fetchall():
issues.append({
'id': row[0],
'title': row[1][:50],
'issue': 'Missing Burmese translation',
'details': 'No or very short Burmese content'
})
# Issue 3: Very short articles (< 500 chars)
cur.execute('''
SELECT id, title, LENGTH(content) as len
FROM articles
WHERE status = 'published'
AND LENGTH(content) < 500
LIMIT 10
''')
for row in cur.fetchall():
issues.append({
'id': row[0],
'title': row[1][:50],
'issue': 'Article too short',
'details': f'Only {row[2]} chars'
})
cur.close()
conn.close()
return issues
def get_article_details(article_id: int):
"""Get detailed info about an article"""
conn = get_connection()
cur = conn.cursor()
cur.execute('''
SELECT id, title, title_burmese, slug, status,
LENGTH(content) as content_len,
LENGTH(content_burmese) as burmese_len,
category_id, author, reading_time,
published_at, view_count, created_at, updated_at,
LEFT(content, 200) as content_preview,
LEFT(content_burmese, 200) as burmese_preview
FROM articles
WHERE id = %s
''', (article_id,))
row = cur.fetchone()
if not row:
return None
article = {
'id': row[0],
'title': row[1],
'title_burmese': row[2],
'slug': row[3],
'status': row[4],
'content_length': row[5],
'burmese_length': row[6],
'translation_ratio': round(100.0 * row[6] / row[5], 1) if row[5] > 0 else 0,
'category_id': row[7],
'author': row[8],
'reading_time': row[9],
'published_at': row[10],
'view_count': row[11] or 0,
'created_at': row[12],
'updated_at': row[13],
'content_preview': row[14],
'burmese_preview': row[15]
}
cur.close()
conn.close()
return article
def print_article_table(articles):
"""Print articles in a nice table format"""
print()
print("=" * 100)
print(f"{'ID':<5} {'Title':<50} {'Status':<12} {'Views':<8} {'Ratio':<8}")
print("-" * 100)
for a in articles:
ratio = f"{100.0 * a['burmese_len'] / a['content_len']:.1f}%" if a['content_len'] > 0 else "N/A"
print(f"{a['id']:<5} {a['title']:<50} {a['status']:<12} {a['views']:<8} {ratio:<8}")
print("=" * 100)
print()
def main():
"""Main CLI interface"""
import argparse
parser = argparse.ArgumentParser(description='Burmddit Admin Tools')
subparsers = parser.add_subparsers(dest='command', help='Commands')
# List command
list_parser = subparsers.add_parser('list', help='List articles')
list_parser.add_argument('--status', choices=['published', 'draft'], help='Filter by status')
list_parser.add_argument('--limit', type=int, default=20, help='Number of articles')
# Unpublish command
unpublish_parser = subparsers.add_parser('unpublish', help='Unpublish an article')
unpublish_parser.add_argument('article_id', type=int, help='Article ID')
unpublish_parser.add_argument('--reason', default='Error/Quality issue', help='Reason for unpublishing')
# Republish command
republish_parser = subparsers.add_parser('republish', help='Republish an article')
republish_parser.add_argument('article_id', type=int, help='Article ID')
# Delete command
delete_parser = subparsers.add_parser('delete', help='Delete an article permanently')
delete_parser.add_argument('article_id', type=int, help='Article ID')
delete_parser.add_argument('--confirm', action='store_true', help='Confirm deletion')
# Find problems command
subparsers.add_parser('find-problems', help='Find articles with issues')
# Details command
details_parser = subparsers.add_parser('details', help='Show article details')
details_parser.add_argument('article_id', type=int, help='Article ID')
args = parser.parse_args()
# Configure logger
logger.remove()
logger.add(sys.stdout, format="{message}", level="INFO")
if args.command == 'list':
articles = list_articles(status=args.status, limit=args.limit)
print_article_table(articles)
print(f"Total: {len(articles)} articles")
elif args.command == 'unpublish':
unpublish_article(args.article_id, args.reason)
elif args.command == 'republish':
republish_article(args.article_id)
elif args.command == 'delete':
if not args.confirm:
logger.error("⚠️ Deletion requires --confirm flag to prevent accidents")
return
delete_article(args.article_id)
elif args.command == 'find-problems':
issues = find_problem_articles()
if not issues:
logger.info("✅ No issues found!")
else:
print()
print("=" * 100)
print(f"Found {len(issues)} potential issues:")
print("-" * 100)
for issue in issues:
print(f"ID {issue['id']}: {issue['title']}")
print(f" Issue: {issue['issue']}")
print(f" Details: {issue['details']}")
print()
print("=" * 100)
print()
print("To unpublish an article: python3 admin_tools.py unpublish ")
elif args.command == 'details':
article = get_article_details(args.article_id)
if not article:
logger.error(f"Article {args.article_id} not found")
return
print()
print("=" * 80)
print(f"Article {article['id']} Details")
print("=" * 80)
print(f"Title (EN): {article['title']}")
print(f"Title (MM): {article['title_burmese']}")
print(f"Slug: {article['slug']}")
print(f"Status: {article['status']}")
print(f"Author: {article['author']}")
print(f"Published: {article['published_at']}")
print(f"Views: {article['view_count']}")
print()
print(f"Content length: {article['content_length']} chars")
print(f"Burmese length: {article['burmese_length']} chars")
print(f"Translation ratio: {article['translation_ratio']}%")
print()
print("English preview:")
print(article['content_preview'])
print()
print("Burmese preview:")
print(article['burmese_preview'])
print("=" * 80)
else:
parser.print_help()
if __name__ == '__main__':
main()