SecBrief / backend /db.py
ADI576's picture
Merge branch 'teammate/Dev' and resolve conflicts in backend/db.py, backend/main.py, and frontend/src/app/page.tsx
2d7c4d1
Raw History Blame Contribute Delete
7.97 kB
"""SQLite audit log — sessions, plans, and enforcement decisions."""
from __future__ import annotations
import json
import sqlite3
import uuid
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
DB_PATH = Path(__file__).resolve().parent / "data" / "secbrief.db"
_schema_ready = False
def _utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
def get_db_connection():
init_db()
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db() -> None:
"""Create tables once. Safe to call multiple times."""
global _schema_ready
if _schema_ready:
return
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(DB_PATH)
try:
conn.executescript(
"""
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
user_email TEXT NOT NULL,
created_at TEXT NOT NULL,
input_mode TEXT,
summary TEXT
);
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
event_type TEXT NOT NULL,
payload TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY (session_id) REFERENCES sessions(id)
);
CREATE TABLE IF NOT EXISTS api_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
api_key TEXT NOT NULL UNIQUE,
plan TEXT DEFAULT 'free',
created_at TEXT NOT NULL,
is_active INTEGER DEFAULT 1
);
CREATE TABLE IF NOT EXISTS osv_scan_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT DEFAULT (datetime('now')),
scan_type TEXT,
target TEXT,
vulns_found INTEGER,
critical_count INTEGER,
armoriq_receipt TEXT,
user_email TEXT
);
CREATE TABLE IF NOT EXISTS scan_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT DEFAULT (datetime('now')),
scan_type TEXT,
target TEXT,
vulns_found INTEGER,
critical_count INTEGER,
armoriq_receipt TEXT,
user_email TEXT
);
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
CREATE INDEX IF NOT EXISTS idx_sessions_email ON sessions(user_email);
CREATE INDEX IF NOT EXISTS idx_api_keys_email ON api_keys(email);
CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key);
"""
)
conn.commit()
_schema_ready = True
finally:
conn.close()
@contextmanager
def _connect():
init_db()
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
finally:
conn.close()
def create_session(user_email: str, input_mode: str = "", summary: str = "") -> str:
sid = str(uuid.uuid4())
with _connect() as conn:
conn.execute(
"INSERT INTO sessions (id, user_email, created_at, input_mode, summary) VALUES (?, ?, ?, ?, ?)",
(sid, user_email.strip().lower(), _utc_now(), input_mode, summary[:500]),
)
return sid
def log_event(session_id: str, event_type: str, payload: dict[str, Any]) -> None:
with _connect() as conn:
conn.execute(
"INSERT INTO events (session_id, event_type, payload, created_at) VALUES (?, ?, ?, ?)",
(session_id, event_type, json.dumps(payload), _utc_now()),
)
def get_session(session_id: str) -> dict[str, Any] | None:
with _connect() as conn:
row = conn.execute("SELECT * FROM sessions WHERE id = ?", (session_id,)).fetchone()
if not row:
return None
events = conn.execute(
"SELECT event_type, payload, created_at FROM events WHERE session_id = ? ORDER BY id",
(session_id,),
).fetchall()
return {
"id": row["id"],
"user_email": row["user_email"],
"created_at": row["created_at"],
"input_mode": row["input_mode"],
"summary": row["summary"],
"events": [
{
"event_type": e["event_type"],
"payload": json.loads(e["payload"]),
"created_at": e["created_at"],
}
for e in events
],
}
def list_sessions(user_email: str, limit: int = 20) -> list[dict[str, Any]]:
with _connect() as conn:
rows = conn.execute(
"SELECT id, user_email, created_at, input_mode, summary FROM sessions "
"WHERE user_email = ? ORDER BY created_at DESC LIMIT ?",
(user_email.strip().lower(), limit),
).fetchall()
return [dict(r) for r in rows]
def get_api_key_by_email(email: str) -> dict[str, Any] | None:
with _connect() as conn:
row = conn.execute(
"SELECT * FROM api_keys WHERE email = ?",
(email.strip().lower(),),
).fetchone()
return dict(row) if row else None
def get_api_key_by_key(api_key: str) -> dict[str, Any] | None:
with _connect() as conn:
row = conn.execute(
"SELECT * FROM api_keys WHERE api_key = ?",
(api_key,),
).fetchone()
return dict(row) if row else None
def create_api_key(email: str, api_key: str, plan: str = "free") -> dict[str, Any]:
with _connect() as conn:
conn.execute(
"INSERT INTO api_keys (email, api_key, plan, created_at, is_active) VALUES (?, ?, ?, ?, ?)",
(email.strip().lower(), api_key, plan, _utc_now(), 1),
)
row = conn.execute(
"SELECT * FROM api_keys WHERE email = ?",
(email.strip().lower(),),
).fetchone()
return dict(row)
def save_osv_scan_log(
scan_type: str,
target: str,
vulns_found: int,
critical_count: int,
receipt: str,
user_email: str | None = None,
):
with _connect() as conn:
conn.execute(
"INSERT INTO osv_scan_logs (scan_type, target, vulns_found, critical_count, armoriq_receipt, user_email) VALUES (?,?,?,?,?,?)",
(scan_type, target, vulns_found, critical_count, receipt, user_email),
)
def get_osv_scan_logs(user_email: str | None = None, limit: int = 20):
with _connect() as conn:
if user_email:
rows = conn.execute(
"SELECT * FROM osv_scan_logs WHERE user_email=? ORDER BY timestamp DESC LIMIT ?",
(user_email, limit),
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM osv_scan_logs ORDER BY timestamp DESC LIMIT ?", (limit,)
).fetchall()
return [dict(r) for r in rows]
def save_scan_log(scan_type, target, vulns_found, critical_count, receipt, user_email=None):
conn = get_db_connection()
conn.execute(
"INSERT INTO scan_logs (scan_type,target,vulns_found,critical_count,armoriq_receipt,user_email) VALUES (?,?,?,?,?,?)",
(scan_type, target, vulns_found, critical_count, receipt, user_email),
)
conn.commit()
conn.close()
def get_scan_logs(user_email=None, limit=20):
conn = get_db_connection()
if user_email:
rows = conn.execute(
"SELECT * FROM scan_logs WHERE user_email=? ORDER BY timestamp DESC LIMIT ?",
(user_email, limit),
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM scan_logs ORDER BY timestamp DESC LIMIT ?", (limit,)
).fetchall()
conn.close()
return [dict(r) for r in rows]