Files
nas-tools/dashboard/backend/routers/info_engine.py
Gan, Jimmy a91a1132c9
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m10s
feat: add info engine MVP pipeline and dashboard page
Introduce a standalone scheduled info-engine worker with SQLite persistence and expose read-only dashboard APIs/UI so curated intelligence items can be collected and viewed with minimal architecture changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-02 23:35:10 +08:00

112 lines
2.7 KiB
Python

import json
import aiosqlite
from fastapi import APIRouter, HTTPException, Query
from config import INFO_ENGINE_DB
router = APIRouter()
async def _db():
return aiosqlite.connect(INFO_ENGINE_DB)
def _row_to_item(row):
tags = []
if row[5]:
try:
tags = json.loads(row[5])
except Exception:
tags = []
return {
"id": row[0],
"source": row[1],
"title": row[2],
"summary": row[3],
"url": row[4],
"tags": tags,
"published_at": row[6],
"collected_at": row[7],
"content_text": row[8],
"content_html": row[9],
}
@router.get("/items")
async def list_items(
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
source: str = Query(""),
tag: str = Query(""),
since: str = Query(""),
):
where = []
params = []
if source:
where.append("source = ?")
params.append(source)
if tag:
where.append("tags LIKE ?")
params.append(f'%"{tag}"%')
if since:
where.append("collected_at >= ?")
params.append(since)
where_clause = f"WHERE {' AND '.join(where)}" if where else ""
async with await _db() as db:
count_cursor = await db.execute(
f"SELECT COUNT(*) FROM info_items {where_clause}",
tuple(params),
)
total = (await count_cursor.fetchone())[0]
cursor = await db.execute(
f"""
SELECT id, source, title, summary, url, tags, published_at, collected_at, content_text, content_html
FROM info_items
{where_clause}
ORDER BY COALESCE(published_at, collected_at) DESC, id DESC
LIMIT ? OFFSET ?
""",
tuple(params + [limit, offset]),
)
rows = await cursor.fetchall()
return {"items": [_row_to_item(row) for row in rows], "total": total}
@router.get("/items/{item_id}")
async def get_item(item_id: int):
async with await _db() as db:
cursor = await db.execute(
"""
SELECT id, source, title, summary, url, tags, published_at, collected_at, content_text, content_html
FROM info_items
WHERE id = ?
""",
(item_id,),
)
row = await cursor.fetchone()
if not row:
raise HTTPException(404, "Item not found")
return _row_to_item(row)
@router.get("/health")
async def health():
async with await _db() as db:
cursor = await db.execute("SELECT COUNT(*), MAX(collected_at) FROM info_items")
row = await cursor.fetchone()
return {
"status": "ok",
"item_count": row[0] if row else 0,
"last_collect_at": row[1] if row else None,
}