Security:
- DEBT-008: remove query-string token auth; header-only Bearer now enforced
- DEBT-013: add regex constraint ^[a-z0-9\-]{1,64}$ on decky_name path param
- DEBT-015: stop leaking raw exception detail to API clients; log server-side
- DEBT-016: validate search (max_length=512) and datetime params with regex
Reliability:
- DEBT-014: wrap SSE event_generator in try/except; yield error frame on failure
- DEBT-017: emit log.warning/error on DB init retry; silent failures now visible
Observability / Docs:
- DEBT-020: add 401/422 response declarations to all route decorators
Infrastructure:
- DEBT-018: add HEALTHCHECK to all 24 template Dockerfiles
- DEBT-019: add USER decnet + setcap cap_net_bind_service to all 24 Dockerfiles
- DEBT-024: bump Redis template version 7.0.12 → 7.2.7
Config:
- DEBT-012: validate DECNET_API_PORT and DECNET_WEB_PORT range (1-65535)
Code quality:
- DEBT-010: delete 22 duplicate decnet_logging.py copies; deployer injects canonical
- DEBT-022: closed as false positive (print only in module docstring)
- DEBT-009: closed as false positive (templates already use structured syslog_line)
Build:
- DEBT-025: generate requirements.lock via pip freeze
Testing:
- DEBT-005/006/007: comprehensive test suite added across tests/api/
- conftest: in-memory SQLite + StaticPool + monkeypatched session_factory
- fuzz mark added; default run excludes fuzz; -n logical parallelism
DEBT.md updated: 23/25 items closed; DEBT-011 (Alembic) and DEBT-023 (digest pinning) remain
35 lines
1.3 KiB
Python
35 lines
1.3 KiB
Python
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
|
from sqlalchemy import create_engine
|
|
from sqlmodel import SQLModel
|
|
from pathlib import Path
|
|
|
|
# We need both sync and async engines for SQLite
|
|
# Sync for initialization (DDL) and async for standard queries
|
|
|
|
def get_async_engine(db_path: str):
|
|
# If it's a memory URI, don't add the extra slash that turns it into a relative file
|
|
prefix = "sqlite+aiosqlite:///"
|
|
if db_path.startswith("file:"):
|
|
prefix = "sqlite+aiosqlite:///"
|
|
return create_async_engine(f"{prefix}{db_path}", echo=False, connect_args={"uri": True})
|
|
|
|
def get_sync_engine(db_path: str):
|
|
prefix = "sqlite:///"
|
|
return create_engine(f"{prefix}{db_path}", echo=False, connect_args={"uri": True})
|
|
|
|
def init_db(db_path: str):
|
|
"""Synchronously create all tables."""
|
|
engine = get_sync_engine(db_path)
|
|
# Ensure WAL mode is set
|
|
with engine.connect() as conn:
|
|
conn.exec_driver_sql("PRAGMA journal_mode=WAL")
|
|
conn.exec_driver_sql("PRAGMA synchronous=NORMAL")
|
|
SQLModel.metadata.create_all(engine)
|
|
|
|
async def get_session(engine) -> AsyncSession:
|
|
async_session = async_sessionmaker(
|
|
engine, class_=AsyncSession, expire_on_commit=False
|
|
)
|
|
async with async_session() as session:
|
|
yield session
|