import datetime import json from sqlalchemy import and_, desc from sqlalchemy.orm import Session from backend.models import NewsItem, NewsTranslation SUPPORTED_LANGUAGES = {"en", "ta", "ml"} def create_news( db: Session, headline: str, summary: str, source_url: str | None = None, image_url: str | None = None, image_credit: str | None = None, tldr_points: list[str] | None = None, summary_body: str | None = None, source_citation: str | None = None, summary_image_url: str | None = None, summary_image_credit: str | None = None, published_at: datetime.datetime | None = None, ) -> NewsItem: item = NewsItem( headline=headline, summary=summary, source_url=source_url, image_url=image_url, image_credit=image_credit, tldr_points=json.dumps(tldr_points) if tldr_points else None, summary_body=summary_body, source_citation=source_citation, summary_image_url=summary_image_url, summary_image_credit=summary_image_credit, published_at=published_at or datetime.datetime.utcnow(), ) db.add(item) db.commit() db.refresh(item) return item def get_recent_news(db: Session, limit: int = 10) -> list[NewsItem]: return ( db.query(NewsItem) .filter(NewsItem.archived.is_(False)) .order_by(desc(NewsItem.published_at)) .limit(limit) .all() ) def get_latest_news(db: Session) -> NewsItem | None: return ( db.query(NewsItem) .filter(NewsItem.archived.is_(False)) .order_by(desc(NewsItem.published_at)) .first() ) def create_translation( db: Session, news_item_id: int, language: str, headline: str, summary: str, tldr_points: list[str] | None = None, summary_body: str | None = None, source_citation: str | None = None, ) -> NewsTranslation: translation = NewsTranslation( news_item_id=news_item_id, language=language, headline=headline, summary=summary, tldr_points=json.dumps(tldr_points) if tldr_points else None, summary_body=summary_body, source_citation=source_citation, ) db.add(translation) db.commit() db.refresh(translation) return translation def get_translation(db: Session, news_item_id: int, language: str) -> NewsTranslation | None: return ( db.query(NewsTranslation) .filter( and_( NewsTranslation.news_item_id == news_item_id, NewsTranslation.language == language, ) ) .first() ) def translation_exists(db: Session, news_item_id: int, language: str) -> bool: return get_translation(db, news_item_id, language) is not None def get_translations_by_article(db: Session, news_item_id: int) -> list[NewsTranslation]: return ( db.query(NewsTranslation) .filter(NewsTranslation.news_item_id == news_item_id) .order_by(NewsTranslation.language.asc()) .all() ) def resolve_news_content(item: NewsItem, translation: NewsTranslation | None) -> tuple[str, str]: if translation is None: return item.headline, item.summary return translation.headline, translation.summary def resolve_tldr_points(item: NewsItem, translation: NewsTranslation | None) -> list[str] | None: raw = translation.tldr_points if translation is not None else item.tldr_points if not raw: return None try: parsed = json.loads(raw) except json.JSONDecodeError: return None if isinstance(parsed, list): return [str(x) for x in parsed if str(x).strip()] return None def resolve_summary_fields( item: NewsItem, translation: NewsTranslation | None ) -> tuple[list[str] | None, str | None, str | None]: tldr_points = resolve_tldr_points(item, translation) if translation is None: return tldr_points, item.summary_body, item.source_citation return tldr_points, translation.summary_body, translation.source_citation def normalize_language(language: str | None) -> str: if not language: return "en" lower = language.lower() if lower not in SUPPORTED_LANGUAGES: return "en" return lower def get_news_paginated( db: Session, cursor: int | None = None, limit: int = 10, exclude_id: int | None = None ) -> list[NewsItem]: query = db.query(NewsItem).filter(NewsItem.archived.is_(False)) if exclude_id is not None: query = query.filter(NewsItem.id != exclude_id) if cursor is not None: query = query.filter(NewsItem.id < cursor) return query.order_by(desc(NewsItem.id)).limit(limit).all() def headline_exists_within_24h(db: Session, headline: str) -> bool: cutoff = datetime.datetime.utcnow() - datetime.timedelta(hours=24) return ( db.query(NewsItem) .filter( and_( NewsItem.headline == headline, NewsItem.created_at >= cutoff, ) ) .first() is not None ) def archive_old_news(db: Session, retention_days: int = 30) -> int: cutoff = datetime.datetime.utcnow() - datetime.timedelta(days=retention_days) count = ( db.query(NewsItem) .filter( and_( NewsItem.created_at < cutoff, NewsItem.archived.is_(False), ) ) .update({"archived": True}) ) db.commit() return count def delete_archived_news(db: Session, days_after_archive: int = 60) -> int: cutoff = datetime.datetime.utcnow() - datetime.timedelta(days=days_after_archive) count = ( db.query(NewsItem) .filter( and_( NewsItem.archived.is_(True), NewsItem.created_at < cutoff, ) ) .delete() ) db.commit() return count