Spaces:
Sleeping
Sleeping
File size: 9,332 Bytes
0a367ef 4c83ab6 0a367ef 4c83ab6 7da71bc 0a367ef 7da71bc 0a367ef 7da71bc 0a367ef c55abce 4c83ab6 0a367ef 4c83ab6 0a367ef 4c83ab6 0a367ef 4c83ab6 0a367ef 4c83ab6 7da71bc 0a367ef 7da71bc 0a367ef 7da71bc 0a367ef 7da71bc 0a367ef 7da71bc 0a367ef 4c83ab6 | 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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | import json
import sys
from contextlib import asynccontextmanager
from pathlib import Path
from typing import AsyncGenerator
import aiosqlite
_DDL = """
PRAGMA journal_mode=WAL;
PRAGMA foreign_keys=ON;
CREATE TABLE IF NOT EXISTS subscriptions (
id TEXT PRIMARY KEY,
folder_id TEXT NOT NULL,
folder_name TEXT NOT NULL,
expiry_datetime TEXT NOT NULL,
notification_url TEXT NOT NULL,
client_state TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_renewed_at TEXT
);
CREATE TABLE IF NOT EXISTS notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
received_at TEXT NOT NULL DEFAULT (datetime('now')),
subscription_id TEXT,
change_type TEXT,
resource TEXT,
message_id TEXT,
email_identifier TEXT,
processing_status TEXT NOT NULL DEFAULT 'pending',
error_message TEXT,
storage_path TEXT
);
CREATE TABLE IF NOT EXISTS routing_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
notification_id INTEGER NOT NULL,
processed_at TEXT NOT NULL DEFAULT (datetime('now')),
email_identifier TEXT,
attachment_filename TEXT,
document_type TEXT,
action TEXT,
destination_path TEXT,
confidence_scores TEXT,
error_message TEXT
);
CREATE TABLE IF NOT EXISTS daily_brief (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
email_identifier TEXT NOT NULL,
notification_id INTEGER NOT NULL,
sender_email TEXT,
subject TEXT,
attachment_filename TEXT,
reason TEXT NOT NULL,
confidence_scores TEXT,
error_message TEXT,
included_in_report INTEGER NOT NULL DEFAULT 0,
report_sent_at TEXT
);
CREATE TABLE IF NOT EXISTS client_domains (
domain TEXT PRIMARY KEY,
client_name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_seen_at TEXT,
notes TEXT
);
"""
async def init_db(db_path: Path) -> None:
db_path.parent.mkdir(parents=True, exist_ok=True)
async with aiosqlite.connect(db_path) as conn:
await conn.executescript(_DDL)
await conn.commit()
@asynccontextmanager
async def get_db(db_path: Path) -> AsyncGenerator[aiosqlite.Connection, None]:
async with aiosqlite.connect(db_path) as conn:
conn.row_factory = aiosqlite.Row
await conn.execute("PRAGMA journal_mode=WAL")
await conn.execute("PRAGMA foreign_keys=ON")
yield conn
async def upsert_subscription(
conn: aiosqlite.Connection,
sub_id: str,
folder_id: str,
folder_name: str,
expiry_datetime: str,
notification_url: str,
client_state: str,
) -> None:
await conn.execute(
"""
INSERT INTO subscriptions (id, folder_id, folder_name, expiry_datetime,
notification_url, client_state, is_active)
VALUES (?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(id) DO UPDATE SET
expiry_datetime = excluded.expiry_datetime,
last_renewed_at = datetime('now'),
is_active = 1
""",
(sub_id, folder_id, folder_name, expiry_datetime, notification_url, client_state),
)
await conn.commit()
async def get_active_subscription(conn: aiosqlite.Connection) -> dict | None:
cursor = await conn.execute(
"SELECT * FROM subscriptions WHERE is_active = 1 ORDER BY created_at DESC LIMIT 1"
)
row = await cursor.fetchone()
return dict(row) if row else None
async def mark_subscription_inactive(conn: aiosqlite.Connection, sub_id: str) -> None:
await conn.execute(
"UPDATE subscriptions SET is_active = 0 WHERE id = ?", (sub_id,)
)
await conn.commit()
async def log_notification(
conn: aiosqlite.Connection,
subscription_id: str | None,
change_type: str | None,
resource: str | None,
message_id: str | None,
email_identifier: str | None = None,
) -> int:
cursor = await conn.execute(
"""
INSERT INTO notifications (subscription_id, change_type, resource, message_id, email_identifier)
VALUES (?, ?, ?, ?, ?)
""",
(subscription_id, change_type, resource, message_id, email_identifier or message_id),
)
await conn.commit()
return cursor.lastrowid
async def update_subscription_expiry(
conn: aiosqlite.Connection, sub_id: str, expiry_datetime: str
) -> None:
await conn.execute(
"""
UPDATE subscriptions
SET expiry_datetime = ?, last_renewed_at = datetime('now')
WHERE id = ?
""",
(expiry_datetime, sub_id),
)
await conn.commit()
async def get_notification_by_email_identifier(
conn: aiosqlite.Connection, email_identifier: str
) -> dict | None:
cursor = await conn.execute(
"SELECT * FROM notifications WHERE email_identifier = ? LIMIT 1",
(email_identifier,),
)
row = await cursor.fetchone()
return dict(row) if row else None
async def update_notification_status(
conn: aiosqlite.Connection,
notif_id: int,
status: str,
error_message: str | None = None,
storage_path: str | None = None,
) -> None:
await conn.execute(
"""
UPDATE notifications
SET processing_status = ?, error_message = ?, storage_path = ?
WHERE id = ?
""",
(status, error_message, storage_path, notif_id),
)
await conn.commit()
async def get_fetched_notifications(conn: aiosqlite.Connection) -> list[dict]:
cursor = await conn.execute(
"SELECT * FROM notifications WHERE processing_status = 'fetched' ORDER BY received_at"
)
rows = await cursor.fetchall()
return [dict(r) for r in rows]
async def set_notification_routing_status(
conn: aiosqlite.Connection, notif_id: int, status: str
) -> None:
await conn.execute(
"UPDATE notifications SET processing_status = ? WHERE id = ?",
(status, notif_id),
)
await conn.commit()
async def log_routing_result(
conn: aiosqlite.Connection,
notification_id: int,
filename: str | None,
doc_type: str | None,
action: str,
email_identifier: str | None = None,
destination_path: str | None = None,
confidence_scores: dict | None = None,
error_message: str | None = None,
) -> None:
await conn.execute(
"""
INSERT INTO routing_results
(notification_id, email_identifier, attachment_filename, document_type, action,
destination_path, confidence_scores, error_message)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
notification_id,
email_identifier,
filename,
doc_type,
action,
destination_path,
json.dumps(confidence_scores) if confidence_scores else None,
error_message,
),
)
await conn.commit()
async def insert_daily_brief(
conn: aiosqlite.Connection,
email_identifier: str,
notification_id: int,
sender_email: str | None,
subject: str | None,
filename: str | None,
reason: str,
confidence_scores: dict | None = None,
error_message: str | None = None,
) -> None:
await conn.execute(
"""
INSERT INTO daily_brief
(email_identifier, notification_id, sender_email, subject,
attachment_filename, reason, confidence_scores, error_message)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
email_identifier,
notification_id,
sender_email,
subject,
filename,
reason,
json.dumps(confidence_scores) if confidence_scores else None,
error_message,
),
)
await conn.commit()
async def get_pending_brief_items(conn: aiosqlite.Connection) -> list[dict]:
cursor = await conn.execute(
"SELECT * FROM daily_brief WHERE included_in_report = 0 ORDER BY created_at"
)
rows = await cursor.fetchall()
return [dict(r) for r in rows]
async def mark_brief_items_sent(conn: aiosqlite.Connection, ids: list[int]) -> None:
await conn.execute(
f"UPDATE daily_brief SET included_in_report = 1, report_sent_at = datetime('now') "
f"WHERE id IN ({','.join('?' * len(ids))})",
ids,
)
await conn.commit()
async def run_retention_cleanup(conn: aiosqlite.Connection) -> None:
await conn.executescript("""
DELETE FROM notifications WHERE received_at < datetime('now', '-12 months');
DELETE FROM routing_results WHERE processed_at < datetime('now', '-12 months');
DELETE FROM daily_brief WHERE created_at < datetime('now', '-12 months');
DELETE FROM subscriptions WHERE is_active = 0 AND created_at < datetime('now', '-12 months');
""")
await conn.commit()
if __name__ == "__main__":
import asyncio
if "--init" in sys.argv:
from app.config import settings
asyncio.run(init_db(settings.database_path))
print(f"Database initialized at {settings.database_path}")
|