File size: 4,967 Bytes
954286a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1c84b5
 
954286a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1c84b5
954286a
 
 
 
 
b1c84b5
954286a
 
 
 
 
 
 
 
b1c84b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954286a
 
 
 
 
 
b1c84b5
954286a
 
b1c84b5
954286a
 
 
 
 
 
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
PhilVerify β€” Firebase / Firestore Client
Initializes firebase-admin SDK and provides typed helpers for persistence.

Setup:
  1. Go to Firebase Console β†’ Project Settings β†’ Service Accounts
  2. Click "Generate new private key" β†’ save as `serviceAccountKey.json`
     in the PhilVerify project root (already in .gitignore)
  3. Set FIREBASE_PROJECT_ID in .env

Collections:
  verifications/   β€” one doc per verification run
  trends/summary   β€” aggregated entity/topic counters
"""
import logging
import os
from functools import lru_cache
from pathlib import Path

logger = logging.getLogger(__name__)

_SERVICEACCOUNT_PATH = Path(__file__).parent / "serviceAccountKey.json"
_db = None  # Firestore client singleton


def get_firestore():
    """Return the Firestore client, or None if Firebase is not configured."""
    global _db
    if _db is not None:
        return _db

    try:
        import firebase_admin
        from firebase_admin import credentials, firestore

        if firebase_admin._DEFAULT_APP_NAME in firebase_admin._apps:
            _db = firestore.client()
            return _db

        if _SERVICEACCOUNT_PATH.exists():
            # Service account key file available (local dev + CI)
            cred = credentials.Certificate(str(_SERVICEACCOUNT_PATH))
            firebase_admin.initialize_app(cred)
            logger.info("Firebase initialized via service account key")
        elif os.getenv("GOOGLE_APPLICATION_CREDENTIALS") or os.getenv("K_SERVICE"):
            # Cloud Run (K_SERVICE is always set) or explicit ADC path
            cred = credentials.ApplicationDefault()
            firebase_admin.initialize_app(cred)
            logger.info("Firebase initialized via Application Default Credentials")
        else:
            logger.warning(
                "Firebase not configured β€” no serviceAccountKey.json and no "
                "GOOGLE_APPLICATION_CREDENTIALS env var. History will use in-memory store."
            )
            return None

        _db = firestore.client()
        return _db

    except ImportError:
        logger.warning("firebase-admin not installed β€” Firestore disabled")
        return None
    except Exception as e:
        logger.error("Firebase init error: %s β€” falling back to in-memory store", e)
        return None


async def save_verification(data: dict) -> bool:
    """
    Persist a verification result to Firestore.
    Returns True on success, False if Firebase is unavailable.
    """
    db = get_firestore()
    if db is None:
        return False
    try:
        db.collection("verifications").document(data["id"]).set(data)
        logger.debug("Verification %s saved to Firestore", data["id"])
        return True
    except Exception as e:
        logger.error("Firestore write error: %s", e)
        return False


async def get_verifications(
    limit: int = 20,
    offset: int = 0,
    verdict_filter: str | None = None,
) -> list[dict]:
    """Fetch verification history from Firestore ordered by timestamp desc."""
    db = get_firestore()
    if db is None:
        return []
    try:
        from google.cloud.firestore_v1.base_query import FieldFilter
        query = (
            db.collection("verifications")
            .order_by("timestamp", direction="DESCENDING")
        )
        if verdict_filter:
            query = query.where(filter=FieldFilter("verdict", "==", verdict_filter))
        docs = query.limit(limit + offset).stream()
        results = [doc.to_dict() for doc in docs]
        return results[offset : offset + limit]
    except Exception as e:
        logger.error("Firestore read error: %s", e)
        return []


def get_all_verifications_sync() -> list[dict]:
    """Synchronously fetch ALL verification records from Firestore (used by trends aggregation)."""
    db = get_firestore()
    if db is None:
        return []
    try:
        docs = (
            db.collection("verifications")
            .order_by("timestamp", direction="DESCENDING")
            .limit(10_000)  # hard cap β€” more than enough for trends analysis
            .stream()
        )
        return [doc.to_dict() for doc in docs]
    except Exception as e:
        logger.error("Firestore get_all_verifications_sync error: %s", e)
        return []


async def get_verification_count(verdict_filter: str | None = None) -> int:
    """Return total count of verifications (with optional verdict filter)."""
    db = get_firestore()
    if db is None:
        return 0
    try:
        from google.cloud.firestore_v1.base_query import FieldFilter
        query = db.collection("verifications")
        if verdict_filter:
            query = query.where(filter=FieldFilter("verdict", "==", verdict_filter))
        # Use aggregation query (Firestore native count)
        result = query.count().get()
        return result[0][0].value
    except Exception as e:
        logger.error("Firestore count error: %s", e)
        return 0