752f477a27
build / build (push) Successful in 1m16s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m9s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m51s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Bazy sa rdzeniem produktu i wlasnie zostaly kupione — a aplikacja nie miala ZADNEGO uwierzytelniania. Prezentacja to NodePort, wiec kazdy w LAN wchodzil bez logowania, a `/search` oddawal surowe wiersze do 50 000 na zapytanie. Bazy mogly wyjsc przez sama aplikacje, bez udzialu jakiegokolwiek LLM. - prezentacja: HTTP Basic (APP_USER/APP_PASSWORD) + limit zadan na IP (RATE_LIMIT_PER_MIN, domyslnie 120/min). Limit dziala TAKZE przed uwierzytelnieniem, zeby zgadywanie hasla i sondowanie API nie bylo darmowe. - logika i dane: token miedzywarstwowy X-Astrololo-Token (INTERNAL_TOKEN) — bez niego dalo sie ominac logowanie, uderzajac wprost w warstwe nizej. Warstwa danych oddaje surowe wiersze, wiec to najwrazliwszy punkt. - /search: gorny limit 50 000 -> 5000 (tyle realnie uzywa build_report). Publiczne /api/query zostaje na 200. - /health celowo publiczny (sondy k8s go nie uwierzytelnia). - swiadomie nie logujemy tresci zadan ani promptow — logi to kolejny nosnik. Fail-open przy braku konfiguracji (zgodnosc wstecz i dev), ale z GLOSNYM ostrzezeniem przy starcie, zeby nikt nie wdrozyl tego w przekonaniu, ze jest chroniony. Wlaczenie w produkcji wymaga ustawienia sekretow w repo deploy. Testy: 12 (prezentacja, nowy katalog + job w CI) i 5 (logika). Zweryfikowane na zywym stosie: bez hasla 401, z haslem 200, logika wprost bez tokenu 401, z tokenem 200, /health 200, limit 50000 odrzucony (422), a prezentacja nadal liczy horoskop przez logike. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
1.6 KiB
Python
45 lines
1.6 KiB
Python
"""Kontrakt danych warstwy bazodanowej.
|
|
|
|
Te modele są JEDYNYM publicznym interfejsem tej warstwy. Warstwa logiczna zna
|
|
wyłącznie te kształty (poprzez HTTP/JSON) — nie wie nic o Excelu, cache ani SQL.
|
|
Dzięki temu można podmienić implementację (Excel -> SQL) bez zmiany pozostałych
|
|
warstw.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class SearchQuery(BaseModel):
|
|
"""Znormalizowane zapytanie wyszukiwania przychodzące z warstwy logicznej."""
|
|
|
|
key: str = Field(..., description="Pole/kolumna kanoniczna, po której szukamy, np. 'name'.")
|
|
value: str = Field(..., description="Szukana wartość.")
|
|
exact: bool = Field(False, description="Dopasowanie dokładne vs. zawieranie (contains).")
|
|
# Górny limit celowo niski: to zapytanie oddaje SUROWE wiersze baz, więc wysoki
|
|
# pułap zamienia je w narzędzie do masowego pobrania (LOG-32). 5000 = tyle, ile
|
|
# realnie potrzebuje build_report na jeden obiekt.
|
|
limit: int = Field(50, ge=1, le=5000)
|
|
fields: list[str] | None = Field(
|
|
None, description="Lista pól kanonicznych do zwrócenia; None = wszystkie."
|
|
)
|
|
|
|
|
|
class SearchResult(BaseModel):
|
|
"""Wynik wyszukiwania zwracany w górę do warstwy logicznej."""
|
|
|
|
rows: list[dict[str, Any]]
|
|
total: int
|
|
elapsed_ms: float
|
|
cache: str = Field("miss", description="hit/miss/partial — skąd pochodzą dane.")
|
|
provider: str = Field(..., description="Nazwa aktywnej implementacji, np. 'excel' lub 'sql'.")
|
|
|
|
|
|
class HealthInfo(BaseModel):
|
|
status: str = "ok"
|
|
provider: str
|
|
indexed_files: int = 0
|
|
details: dict[str, Any] = Field(default_factory=dict)
|