refactor: FastAPI best practices — return types, Pydantic schemas, middleware, code dedup

- Все 18 роутов получили return type annotations
- Создан schemas.py с Pydantic-моделями (ScanOut, PackageOut, FindingOut, ...)
- API-роуты: response_model на list/detail/export/stats
- 404 через HTTPException(404) вместо {'detail':'Not found'} (200)
- RequestLoggingMiddleware: method, path, status, duration_ms
- Глобальный exception handler: ловит необработанные исключения → 500
- _parse_flagged(): вынесен дублирующийся string→bool
- parse_package_path(): общий для web.py и api_packages.py
- selectinload: вынесены в top-level imports
- harvester: makedirs/mkdtemp/rmtree обёрнуты в asyncio.to_thread()
This commit is contained in:
Marker689
2026-05-10 12:53:33 +03:00
parent 935d96b35a
commit c1258dde99
11 changed files with 188 additions and 55 deletions

102
guarddog_nexus/schemas.py Normal file
View File

@@ -0,0 +1,102 @@
"""Pydantic schemas for API request/response models."""
from datetime import datetime
from pydantic import BaseModel
class ScanOut(BaseModel):
id: int
package_name: str
package_version: str
ecosystem: str
repository: str
status: str
total_findings: int
flagged: bool
started_at: datetime | None = None
finished_at: datetime | None = None
error_message: str | None = None
model_config = {"from_attributes": True}
class ScanListResponse(BaseModel):
total: int
limit: int
offset: int
scans: list[ScanOut]
class ScanDetailOut(ScanOut):
nexus_asset_url: str | None = None
sha256: str | None = None
initiator: str | None = None
source_ip: str | None = None
findings: list[dict] = []
class FindingOut(BaseModel):
id: int
scan_id: int
rule: str = ""
severity: str = ""
message: str = ""
location: str = ""
code: str = ""
report: dict | None = None
created_at: datetime | None = None
model_config = {"from_attributes": True}
class FindingsListResponse(BaseModel):
total: int
limit: int
offset: int
findings: list[FindingOut]
class PackageOut(BaseModel):
name: str
version: str
ecosystem: str
repository: str
last_scanned_at: datetime | None = None
flagged: bool
total_findings: int
latest_scan_id: int
class PackageListResponse(BaseModel):
total: int
limit: int
offset: int
packages: list[PackageOut]
class PackageScanOut(BaseModel):
id: int
status: str
total_findings: int
flagged: bool
started_at: datetime | None = None
class PackageDetailOut(BaseModel):
name: str
version: str
ecosystem: str
repository: str
flagged: bool
scans: list[PackageScanOut]
findings: list[dict]
class StatsResponse(BaseModel):
total_scans: int
flagged_scans: int
recent_flagged: int
total_findings: int
top_rules: list[dict]
latest_scan_at: datetime | None = None