feat(04-01): UnsplashService + Settings unsplash_api_key

- Crea UnsplashService con search, cache disco, traduzione IT->EN
- ~30 keyword B2B italiane tradotte in dizionario statico
- Cache in-memory + persistenza su disco (unsplash_cache.json)
- Retry automatico su errori di rete, no-retry su 401/403
- Rate limiting awareness via X-Ratelimit-Remaining header
- Aggiunge campo unsplash_api_key a Settings schema
- Router settings espone unsplash_api_key_masked + configured
- Merge None-preserving per unsplash_api_key nel PUT
This commit is contained in:
Michele
2026-03-09 08:07:06 +01:00
parent 6078c75c22
commit afba4c5e9e
3 changed files with 347 additions and 1 deletions

View File

@@ -35,6 +35,7 @@ class SettingsStatusResponse(BaseModel):
"""Risposta per GET /status — usata dal frontend per abilitare/disabilitare il pulsante genera."""
api_key_configured: bool
llm_model: str
unsplash_api_key_configured: bool
class SettingsResponse(BaseModel):
@@ -46,6 +47,7 @@ class SettingsResponse(BaseModel):
frequenza_post: int
brand_name: Optional[str]
tono: Optional[str]
unsplash_api_key_masked: Optional[str] # Solo ultimi 4 caratteri o None
# ---------------------------------------------------------------------------
@@ -103,6 +105,7 @@ async def get_settings_status() -> SettingsStatusResponse:
return SettingsStatusResponse(
api_key_configured=bool(settings.api_key),
llm_model=settings.llm_model,
unsplash_api_key_configured=bool(settings.unsplash_api_key),
)
@@ -125,6 +128,7 @@ async def get_settings() -> SettingsResponse:
frequenza_post=settings.frequenza_post,
brand_name=settings.brand_name,
tono=settings.tono,
unsplash_api_key_masked=_mask_api_key(settings.unsplash_api_key),
)
@@ -149,8 +153,12 @@ async def update_settings(new_settings: Settings) -> SettingsResponse:
if new_settings.api_key is None:
new_settings = new_settings.model_copy(update={"api_key": existing.api_key})
# Se la nuova unsplash_api_key è None, mantieni quella esistente (stessa logica)
if new_settings.unsplash_api_key is None:
new_settings = new_settings.model_copy(update={"unsplash_api_key": existing.unsplash_api_key})
_save_settings(new_settings)
logger.info("Settings aggiornate | model=%s | brand=%s", new_settings.llm_model, new_settings.brand_name)
logger.info("Settings aggiornate | model=%s | brand=%s | unsplash=%s", new_settings.llm_model, new_settings.brand_name, bool(new_settings.unsplash_api_key))
return SettingsResponse(
api_key_masked=_mask_api_key(new_settings.api_key),
@@ -160,4 +168,5 @@ async def update_settings(new_settings: Settings) -> SettingsResponse:
frequenza_post=new_settings.frequenza_post,
brand_name=new_settings.brand_name,
tono=new_settings.tono,
unsplash_api_key_masked=_mask_api_key(new_settings.unsplash_api_key),
)