- Все 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()
103 lines
2.0 KiB
Python
103 lines
2.0 KiB
Python
"""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
|