from datetime import datetime from typing import List, Optional from fastapi import HTTPException from app.db.database import db from bson import ObjectId from app.models.news import NewsCreate, NewsUpdate, NewsInDB news_collection = db["news"] class NewsService: @staticmethod def _to_news_in_db(doc) -> NewsInDB: return NewsInDB( id=str(doc["_id"]), title=doc["title"], markdown=doc["markdown"], preview=doc.get("preview"), tags=doc.get("tags", []), is_published=doc.get("is_published", True), created_at=doc["created_at"], updated_at=doc["updated_at"], ) async def list_news(self, limit: int = 20, skip: int = 0, include_unpublished: bool = False) -> List[NewsInDB]: query = {} if not include_unpublished: query["is_published"] = True cursor = ( news_collection .find(query) .sort("created_at", -1) .skip(skip) .limit(limit) ) docs = await cursor.to_list(length=limit) return [self._to_news_in_db(d) for d in docs] async def get_news(self, news_id: str) -> NewsInDB: try: oid = ObjectId(news_id) except: raise HTTPException(status_code=400, detail="Invalid news id") doc = await news_collection.find_one({"_id": oid}) if not doc: raise HTTPException(status_code=404, detail="News not found") return self._to_news_in_db(doc) async def create_news(self, payload: NewsCreate) -> NewsInDB: now = datetime.utcnow() doc = { "title": payload.title, "markdown": payload.markdown, "preview": payload.preview, "tags": payload.tags, "is_published": payload.is_published, "created_at": now, "updated_at": now, } result = await news_collection.insert_one(doc) doc["_id"] = result.inserted_id return self._to_news_in_db(doc) async def update_news(self, news_id: str, payload: NewsUpdate) -> NewsInDB: try: oid = ObjectId(news_id) except: raise HTTPException(status_code=400, detail="Invalid news id") update_data = {k: v for k, v in payload.dict(exclude_unset=True).items()} if not update_data: return await self.get_news(news_id) update_data["updated_at"] = datetime.utcnow() result = await news_collection.find_one_and_update( {"_id": oid}, {"$set": update_data}, return_document=True, ) if not result: raise HTTPException(status_code=404, detail="News not found") return self._to_news_in_db(result) async def delete_news(self, news_id: str): try: oid = ObjectId(news_id) except: raise HTTPException(status_code=400, detail="Invalid news id") result = await news_collection.delete_one({"_id": oid}) if result.deleted_count == 0: raise HTTPException(status_code=404, detail="News not found") return {"status": "success"}