File size: 2,795 Bytes
c335df4
d85c750
 
 
 
 
 
c335df4
 
d85c750
353cfb6
d85c750
353cfb6
c335df4
 
 
d85c750
 
c335df4
ca44fa4
c335df4
 
 
 
 
d85c750
c335df4
d85c750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c79ae02
 
d85c750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from pydantic import BaseModel, HttpUrl, field_validator, Field, ConfigDict
from typing import Optional, List
from datetime import datetime
from email.utils import parsedate_to_datetime

class Article(BaseModel):
    """News article model"""
    model_config = ConfigDict(populate_by_name=True)

    title: str
    id: Optional[str] = Field(None, alias="$id") # Appwrite ID
    description: Optional[str] = ""
    url: Optional[str] = None # Relaxed validation for compatibility
    # Direct mapping to DB fields (snake_case)
    image_url: Optional[str] = ""
    published_at: datetime
    source: Optional[str] = ""
    category: Optional[str] = ""
    audio_url: Optional[str] = None # URL to audio summary
    text_summary: Optional[str] = None # Generated text summary
    
    # Engagement Stats (Side-loaded)
    likes: int = 0
    dislikes: int = Field(default=0, validation_alias="dislike") # Alias for DB 'dislike'
    views: int = 0
    
    @field_validator('published_at', mode='before')
    @classmethod
    def parse_datetime(cls, v):
        """Parse datetime from various formats including RFC 2822 (RSS feeds)"""
        if isinstance(v, datetime):
            return v
        if isinstance(v, str):
            try:
                # Try RFC 2822 format (used by RSS feeds like Google News)
                # Example: "Tue, 06 Jan 2026 19:14:27 GMT"
                return parsedate_to_datetime(v)
            except:
                try:
                    # Try ISO format
                    return datetime.fromisoformat(v.replace('Z', '+00:00'))
                except:
                    try:
                        # Fallback to dateutil parser
                        from dateutil import parser
                        return parser.parse(v)
                    except:
                        # Last resort: return current time
                        return datetime.now()
        return v

class NewsResponse(BaseModel):
    """Response model for news endpoints"""
    success: bool
    category: str
    count: int
    articles: List[Article]
    cached: bool = False
    source: Optional[str] = None  # "redis", "appwrite", "empty", or "api"
    message: Optional[str] = None  # User-friendly message for empty states

class SearchResponse(BaseModel):
    """Response model for search endpoints"""
    success: bool
    query: str
    count: int
    articles: List[Article]

class ViewCountRequest(BaseModel):
    """Request model for view count increment"""
    article_url: HttpUrl

class ViewCountResponse(BaseModel):
    """Response model for view count"""
    success: bool
    article_url: str
    view_count: int

class ErrorResponse(BaseModel):
    """Error response model"""
    success: bool = False
    error: str
    detail: Optional[str] = None