638 lines
23 KiB
Python
638 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
FastAPI Sheriff v2 Provider
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import asyncio
|
|
import logging
|
|
import hashlib
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional
|
|
from datetime import datetime, timedelta
|
|
|
|
import requests
|
|
from fastapi import FastAPI, HTTPException, Header
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
from dotenv import load_dotenv
|
|
from pymongo import MongoClient
|
|
from pymongo.errors import ConnectionFailure
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
load_dotenv()
|
|
|
|
MONGODB_URI = os.getenv("MONGODB_URI", "mongodb://localhost:27017/duxiter")
|
|
print(MONGODB_URI)
|
|
CACHE_TTL_SECONDS = int(os.getenv("SHERIFF_V2_CACHE_TTL_SECONDS", "604800"))
|
|
|
|
try:
|
|
mongo_client = MongoClient(MONGODB_URI)
|
|
# Test connection
|
|
mongo_client.admin.command('ping')
|
|
db = mongo_client.get_database()
|
|
cache_collection = db.sheriff_v2_cache
|
|
|
|
# Create TTL index for automatic document expiration (ignore auth errors)
|
|
try:
|
|
cache_collection.create_index("expires_at", expireAfterSeconds=0)
|
|
except Exception as e:
|
|
logger.warning(f"Could not create TTL index (possibly requires auth): {e}")
|
|
logger.info(f"MongoDB connected successfully. Cache TTL: {CACHE_TTL_SECONDS} seconds")
|
|
except ConnectionFailure as e:
|
|
logger.error(f"MongoDB connection failed: {e}")
|
|
mongo_client = None
|
|
db = None
|
|
cache_collection = None
|
|
|
|
TOKEN_CACHE_FILE = Path(".token_cache.json")
|
|
SHERIFF_TOKEN_TTL_SECONDS = int(os.getenv("SHERIFF_V2_TOKEN_TTL_SECONDS", "1800"))
|
|
SHERIFF_RESPONSES_DIR = Path(__file__).resolve().parents[2] / "sheriff_v2_response"
|
|
|
|
# Pydantic Models
|
|
class TokenResponse(BaseModel):
|
|
token: str
|
|
cached: bool
|
|
|
|
class HealthResponse(BaseModel):
|
|
status: str
|
|
timestamp: str
|
|
version: str
|
|
|
|
class ErrorResponse(BaseModel):
|
|
error: str
|
|
detail: str
|
|
timestamp: str
|
|
|
|
# FastAPI App
|
|
app = FastAPI(
|
|
title="Sheriff v2 Provider API",
|
|
description="REST API for Sheriff v2 client endpoints",
|
|
version="1.0.0",
|
|
docs_url="/docs",
|
|
redoc_url="/redoc"
|
|
)
|
|
|
|
# CORS middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"], # Configure appropriately for production
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
class QueryRutRequest(BaseModel):
|
|
rut: str
|
|
isMonitoring: bool = False
|
|
includeEquifax: bool = False
|
|
|
|
def generate_cache_key(env_key: str, payload: Dict[str, Any]) -> str:
|
|
payload_str = json.dumps(payload, sort_keys=True, separators=(',', ':'))
|
|
payload_hash = hashlib.sha256(payload_str.encode()).hexdigest()
|
|
return f"sheriff_v2_cache:{env_key}:{payload_hash}"
|
|
|
|
def get_cached_transaction(cache_key: str) -> Optional[Dict[str, Any]]:
|
|
if cache_collection is None:
|
|
return None
|
|
|
|
try:
|
|
cached_doc = cache_collection.find_one({"_id": cache_key})
|
|
if cached_doc:
|
|
logger.info(f"Cache hit for key: {cache_key}")
|
|
return cached_doc.get("result")
|
|
else:
|
|
# logger.info(f"Cache miss for key: {cache_key}")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Error retrieving from cache: {e}")
|
|
return None
|
|
|
|
def save_transaction_to_cache(cache_key: str, result: Dict[str, Any]) -> None:
|
|
if cache_collection is None:
|
|
return
|
|
|
|
try:
|
|
expires_at = datetime.utcnow() + timedelta(seconds=CACHE_TTL_SECONDS)
|
|
cache_doc = {
|
|
"_id": cache_key,
|
|
"result": result,
|
|
"cached_at": datetime.utcnow(),
|
|
"expires_at": expires_at
|
|
}
|
|
|
|
# Use upsert to replace existing cache entry
|
|
cache_collection.replace_one(
|
|
{"_id": cache_key},
|
|
cache_doc,
|
|
upsert=True
|
|
)
|
|
logger.info(f"Transaction result cached with key: {cache_key}, expires at: {expires_at}")
|
|
except Exception as e:
|
|
logger.error(f"Error saving to cache: {e}")
|
|
|
|
def clear_cache() -> int:
|
|
if cache_collection is None:
|
|
return 0
|
|
|
|
try:
|
|
result = cache_collection.delete_many({})
|
|
logger.info(f"Cleared {result.deleted_count} cached transactions")
|
|
return result.deleted_count
|
|
except Exception as e:
|
|
logger.error(f"Error clearing cache: {e}")
|
|
return 0
|
|
|
|
def get_cache_stats() -> Dict[str, Any]:
|
|
if cache_collection is None:
|
|
return {"error": "Cache not available"}
|
|
|
|
try:
|
|
total_docs = cache_collection.count_documents({})
|
|
expired_docs = cache_collection.count_documents({
|
|
"expires_at": {"$lt": datetime.utcnow()}
|
|
})
|
|
|
|
return {
|
|
"total_cached_transactions": total_docs,
|
|
"expired_transactions": expired_docs,
|
|
"active_transactions": total_docs - expired_docs,
|
|
"cache_ttl_seconds": CACHE_TTL_SECONDS,
|
|
"mongodb_connected": True
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Error getting cache stats: {e}")
|
|
return {"error": str(e), "mongodb_connected": False}
|
|
|
|
def now() -> float:
|
|
return time.time()
|
|
|
|
def read_cache() -> Optional[dict]:
|
|
if TOKEN_CACHE_FILE.exists():
|
|
try:
|
|
with TOKEN_CACHE_FILE.open("r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return None
|
|
return None
|
|
|
|
def write_cache(data: dict) -> None:
|
|
try:
|
|
with TOKEN_CACHE_FILE.open("w", encoding="utf-8") as f:
|
|
json.dump(data, f, indent=2)
|
|
except Exception:
|
|
pass
|
|
|
|
def sheriff_base_url() -> str:
|
|
base_url = os.getenv("SHERIFF_V2_BASE_URL", "").strip()
|
|
if not base_url:
|
|
raise HTTPException(status_code=500, detail="Missing SHERIFF_V2_BASE_URL")
|
|
return base_url
|
|
|
|
def get_client_identifier() -> str:
|
|
return os.getenv("SHERIFF_V2_CLIENT_IDENTIFIER", "SheriffSecureClient-v1").strip()
|
|
|
|
def get_api_credentials() -> Dict[str, str]:
|
|
access_key = os.getenv("SHERIFF_V2_ACCESS_KEY", "").strip()
|
|
access_secret = os.getenv("SHERIFF_V2_ACCESS_SECRET", "").strip()
|
|
if not access_key or not access_secret:
|
|
raise HTTPException(status_code=500, detail="Missing Sheriff API credentials")
|
|
return {"accessKey": access_key, "accessSecret": access_secret}
|
|
|
|
def get_cached_token(env_key: str) -> Optional[str]:
|
|
cache = read_cache()
|
|
if not cache:
|
|
return None
|
|
item = cache.get(env_key)
|
|
if not item:
|
|
return None
|
|
token = item.get("token")
|
|
expires_at = item.get("expires_at", 0)
|
|
if not token:
|
|
return None
|
|
current_time = now()
|
|
if current_time < (expires_at - 60):
|
|
return token
|
|
return None
|
|
|
|
def save_cached_token(env_key: str, token: str, ttl_secs: int) -> None:
|
|
cache = read_cache() or {}
|
|
current_time = now()
|
|
cache[env_key] = {
|
|
"token": token,
|
|
"expires_at": current_time + int(ttl_secs),
|
|
"saved_at": current_time,
|
|
}
|
|
write_cache(cache)
|
|
|
|
def fetch_token(base_url: str, access_key: str, access_secret: str) -> str:
|
|
url = base_url + "/api/clients/v2/apiCredentials/getToken"
|
|
client_id = get_client_identifier()
|
|
headers = {"Content-Type": "application/json","x-client-identifier": client_id }
|
|
body_data = {"accessKey": access_key, "accessSecret": access_secret}
|
|
body_json = json.dumps(body_data)
|
|
|
|
# Print headers as JSON to show double quotes in logs
|
|
print(f"headers {headers}")
|
|
print(f"body {body_json}")
|
|
logger.info(f"Fetching token from {url}")
|
|
|
|
resp = requests.post(url, headers=headers, data=body_json, timeout=30)
|
|
if resp.status_code == 200:
|
|
payload = resp.json()
|
|
token = payload.get("data") or payload.get("token") or ""
|
|
if not token:
|
|
raise HTTPException(status_code=502, detail="Token response invalid")
|
|
save_cached_token(base_url, token, SHERIFF_TOKEN_TTL_SECONDS)
|
|
return token
|
|
raise HTTPException(status_code=resp.status_code, detail=resp.text)
|
|
|
|
def get_token() -> str:
|
|
base_url = sheriff_base_url()
|
|
cached = get_cached_token(base_url)
|
|
if cached:
|
|
return cached
|
|
creds = get_api_credentials()
|
|
return fetch_token(base_url, creds["accessKey"], creds["accessSecret"])
|
|
|
|
def execute_sheriff_get(path: str, query: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
base_url = sheriff_base_url()
|
|
token = get_token()
|
|
client_id = get_client_identifier()
|
|
if base_url.startswith("http"):
|
|
url = f"{base_url}{path}"
|
|
else:
|
|
url = f"https://{base_url}{path}"
|
|
params = query or {}
|
|
cache_key = generate_cache_key("sheriff_v2_get", {"url": url, "params": params})
|
|
cached = get_cached_transaction(cache_key)
|
|
if cached:
|
|
cached["_cache_info"] = {"cached": True, "cache_key": cache_key, "retrieved_at": datetime.utcnow().isoformat()}
|
|
return cached
|
|
headers = {
|
|
"accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"x-client-identifier": client_id,
|
|
}
|
|
print(f"Executing GET {url} with headers {headers} and params {params}")
|
|
resp = requests.get(url, headers=headers, params=params, timeout=60)
|
|
if resp.status_code == 200:
|
|
try:
|
|
data = resp.json()
|
|
except Exception:
|
|
data = {"raw": resp.text}
|
|
save_transaction_to_cache(cache_key, data)
|
|
data["_cache_info"] = {"cached": False, "cache_key": cache_key, "cached_at": datetime.utcnow().isoformat()}
|
|
return data
|
|
if resp.status_code == 401:
|
|
token = fetch_token(base_url, get_api_credentials()["accessKey"], get_api_credentials()["accessSecret"])
|
|
headers["Authorization"] = f"Bearer {token}"
|
|
resp = requests.get(url, headers=headers, params=params, timeout=60)
|
|
if resp.status_code == 200:
|
|
try:
|
|
data = resp.json()
|
|
except Exception:
|
|
data = {"raw": resp.text}
|
|
save_transaction_to_cache(cache_key, data)
|
|
data["_cache_info"] = {"cached": False, "cache_key": cache_key, "cached_at": datetime.utcnow().isoformat()}
|
|
return data
|
|
raise HTTPException(status_code=resp.status_code, detail=resp.text)
|
|
if resp.status_code == 403:
|
|
raise HTTPException(status_code=403, detail="Forbidden")
|
|
if resp.status_code == 404:
|
|
raise HTTPException(status_code=404, detail="Not Found")
|
|
if resp.status_code >= 500:
|
|
raise HTTPException(status_code=502, detail=f"Upstream error {resp.status_code}")
|
|
raise HTTPException(status_code=resp.status_code, detail=resp.text)
|
|
|
|
def execute_sheriff_post(path: str, body: Dict[str, Any]) -> Dict[str, Any]:
|
|
base_url = sheriff_base_url()
|
|
token = get_token()
|
|
client_id = get_client_identifier()
|
|
if base_url.startswith("http"):
|
|
url = f"{base_url}{path}"
|
|
else:
|
|
url = f"https://{base_url}{path}"
|
|
headers = {
|
|
"accept": "application/json",
|
|
"Authorization": f"Bearer {token}",
|
|
"x-client-identifier": client_id,
|
|
"Content-Type": "application/json",
|
|
}
|
|
resp = requests.post(url, headers=headers, json=body, timeout=60)
|
|
if resp.status_code == 200:
|
|
try:
|
|
return resp.json()
|
|
except Exception:
|
|
return {"raw": resp.text}
|
|
if resp.status_code == 401:
|
|
token = fetch_token(base_url, get_api_credentials()["accessKey"], get_api_credentials()["accessSecret"])
|
|
headers["Authorization"] = f"Bearer {token}"
|
|
resp = requests.post(url, headers=headers, json=body, timeout=60)
|
|
if resp.status_code == 200:
|
|
try:
|
|
return resp.json()
|
|
except Exception:
|
|
return {"raw": resp.text}
|
|
raise HTTPException(status_code=resp.status_code, detail=resp.text)
|
|
if resp.status_code == 403:
|
|
raise HTTPException(status_code=403, detail="Forbidden")
|
|
if resp.status_code == 404:
|
|
raise HTTPException(status_code=404, detail="Not Found")
|
|
if resp.status_code >= 500:
|
|
raise HTTPException(status_code=502, detail=f"Upstream error {resp.status_code}")
|
|
raise HTTPException(status_code=resp.status_code, detail=resp.text)
|
|
|
|
def persist_response(rut: str, result: Dict[str, Any]) -> str:
|
|
server_dir = Path(__file__).resolve().parents[2]
|
|
dest_dir = server_dir / "sheriff_v2_response"
|
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
ts = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
|
|
fp = dest_dir / f"{rut}_{ts}.json"
|
|
with fp.open("w", encoding="utf-8") as f:
|
|
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
return str(fp)
|
|
|
|
|
|
# Rimuoviamo completamente flusso POST/Equifax
|
|
|
|
|
|
def _clear_cached_token(env_key: str) -> None:
|
|
try:
|
|
cache_data = read_cache()
|
|
if cache_data and env_key in cache_data:
|
|
del cache_data[env_key]
|
|
write_cache(cache_data)
|
|
except Exception:
|
|
pass
|
|
|
|
# API Endpoints
|
|
@app.get("/", response_model=HealthResponse)
|
|
async def root():
|
|
return HealthResponse(status="healthy", timestamp=datetime.now().isoformat(), version="1.0.0")
|
|
|
|
@app.get("/health", response_model=HealthResponse)
|
|
async def health_check():
|
|
return HealthResponse(status="healthy", timestamp=datetime.now().isoformat(), version="1.0.0")
|
|
|
|
@app.get("/token", response_model=TokenResponse)
|
|
async def get_token_endpoint():
|
|
base_url = sheriff_base_url()
|
|
token = get_token()
|
|
cached = bool(get_cached_token(base_url))
|
|
return TokenResponse(token=token, cached=cached)
|
|
|
|
@app.get("/judicial/{rut}/civil")
|
|
async def get_civil_cases(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/judicial/{rut_sanitized}/civil"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/judicial/{rut}/cobranza")
|
|
async def get_cobranza_cases(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/judicial/{rut_sanitized}/cobranza"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/judicial/{rut}/laboral")
|
|
async def get_laboral_cases(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/judicial/{rut_sanitized}/laboral"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/cobranzaLaboral/{rut}/multaLaboral")
|
|
async def get_multa_laboral(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/cobranzaLaboral/{rut_sanitized}/multaLaboral"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/cobranzaLaboral/{rut}/moraPrevisional")
|
|
async def get_mora_previsional(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/cobranzaLaboral/{rut_sanitized}/moraPrevisional"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/compliance/{rut}")
|
|
async def get_compliance(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/compliance/{rut_sanitized}"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/legal/{rut}/mallaSocietaria")
|
|
async def get_malla_societaria(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/helper/legal/{rut_sanitized}/mallaSocietaria"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.get("/creditScore/{rut}")
|
|
async def get_credit_score(rut: str):
|
|
rut_sanitized = str(rut).strip()
|
|
path = f"/api/clients/v2/creditScore/{rut_sanitized}"
|
|
return execute_sheriff_get(path)
|
|
|
|
@app.post("/queryRut")
|
|
async def query_rut(payload: QueryRutRequest):
|
|
cargar_body = {
|
|
"rut": payload.rut.strip(),
|
|
"isMonitoring": False,
|
|
"includeEquifax": False,
|
|
}
|
|
cargar = execute_sheriff_post("/api/clients/v2/helper/cargarRut", cargar_body)
|
|
time.sleep(2)
|
|
rut_sanitized = payload.rut.strip()
|
|
logger.info(f"Querying resumen for RUT {rut_sanitized}")
|
|
url = f"/api/clients/v2/helper/resumen?rut={rut_sanitized}&complete=True"
|
|
resumen = execute_sheriff_get(url)
|
|
|
|
result: Dict[str, Any] = {"success": True, "cargarRut": cargar, "resumen": resumen}
|
|
ok = True
|
|
if isinstance(cargar, dict) and ("success" in cargar) and (not bool(cargar.get("success"))):
|
|
ok = False
|
|
if isinstance(resumen, dict) and ("success" in resumen) and (not bool(resumen.get("success"))):
|
|
ok = False
|
|
if ok:
|
|
judicial_civil = execute_sheriff_get(f"/api/clients/v2/helper/judicial/{rut_sanitized}/civil")
|
|
judicial_cobranza = execute_sheriff_get(f"/api/clients/v2/helper/judicial/{rut_sanitized}/cobranza")
|
|
judicial_laboral = execute_sheriff_get(f"/api/clients/v2/helper/judicial/{rut_sanitized}/laboral")
|
|
multa_laboral = execute_sheriff_get(f"/api/clients/v2/helper/cobranzaLaboral/{rut_sanitized}/multaLaboral")
|
|
mora_previsional = execute_sheriff_get(f"/api/clients/v2/helper/cobranzaLaboral/{rut_sanitized}/moraPrevisional")
|
|
compliance = execute_sheriff_get(f"/api/clients/v2/helper/compliance/{rut_sanitized}")
|
|
malla_societaria = execute_sheriff_get(f"/api/clients/v2/helper/legal/{rut_sanitized}/mallaSocietaria")
|
|
credit_score = execute_sheriff_get(f"/api/clients/v2/creditScore/{rut_sanitized}")
|
|
result.update({
|
|
"judicial": {
|
|
"civil": judicial_civil,
|
|
"cobranza": judicial_cobranza,
|
|
"laboral": judicial_laboral,
|
|
},
|
|
"cobranzaLaboral": {
|
|
"multaLaboral": multa_laboral,
|
|
"moraPrevisional": mora_previsional,
|
|
},
|
|
"compliance": compliance,
|
|
"legal": {
|
|
"mallaSocietaria": malla_societaria,
|
|
},
|
|
"creditScore": credit_score,
|
|
})
|
|
saved_path = persist_response(rut_sanitized, result)
|
|
result["_saved_file"] = saved_path
|
|
return result
|
|
|
|
@app.delete("/cache")
|
|
async def clear_sheriff_cache():
|
|
try:
|
|
deleted_count = clear_cache()
|
|
return {"message": "Cache cleared successfully", "deleted_count": deleted_count}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to clear cache: {str(e)}")
|
|
|
|
@app.get("/cache/stats")
|
|
async def get_sheriff_cache_stats():
|
|
try:
|
|
stats = get_cache_stats()
|
|
return stats
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get cache stats: {str(e)}")
|
|
|
|
# Rimuoviamo endpoints legacy transaction-cache
|
|
|
|
@app.get("/config")
|
|
async def get_environment_config():
|
|
try:
|
|
base_url = os.getenv("SHERIFF_V2_BASE_URL", "")
|
|
access_key = os.getenv("SHERIFF_V2_ACCESS_KEY", "")
|
|
access_secret = os.getenv("SHERIFF_V2_ACCESS_SECRET", "")
|
|
client_identifier = get_client_identifier()
|
|
return {
|
|
"base_url_set": bool(base_url),
|
|
"access_key_set": bool(access_key),
|
|
"access_secret_set": bool(access_secret),
|
|
"client_identifier": client_identifier,
|
|
"cache_ttl_seconds": CACHE_TTL_SECONDS,
|
|
"token_ttl_seconds": SHERIFF_TOKEN_TTL_SECONDS,
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get config: {str(e)}")
|
|
|
|
# Error handlers
|
|
@app.exception_handler(HTTPException)
|
|
async def http_exception_handler(request, exc):
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=ErrorResponse(
|
|
error=str(exc.detail),
|
|
detail=str(exc.detail),
|
|
timestamp=datetime.now().isoformat()
|
|
).model_dump()
|
|
)
|
|
|
|
@app.exception_handler(Exception)
|
|
async def general_exception_handler(request, exc):
|
|
logger.error(f"Unhandled exception: {exc}")
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content=ErrorResponse(
|
|
error="Internal server error",
|
|
detail=str(exc),
|
|
timestamp=datetime.now().isoformat()
|
|
).model_dump()
|
|
)
|
|
|
|
async def cleanup_old_responses():
|
|
"""
|
|
Periodically cleans up old response files:
|
|
1. Deletes files older than 1 week.
|
|
2. For remaining files, keeps only the most recent one for each RUT.
|
|
Runs every 10 minutes.
|
|
"""
|
|
while True:
|
|
try:
|
|
logger.info("Starting cleanup of old Sheriff V2 responses...")
|
|
if not SHERIFF_RESPONSES_DIR.exists():
|
|
logger.info(f"Directory {SHERIFF_RESPONSES_DIR} does not exist. Skipping cleanup.")
|
|
else:
|
|
files_by_group: Dict[str, list] = {}
|
|
one_week_ago = datetime.utcnow() - timedelta(weeks=1)
|
|
deleted_count = 0
|
|
|
|
for file_path in SHERIFF_RESPONSES_DIR.glob("*.json"):
|
|
try:
|
|
filename = file_path.stem
|
|
# Format: {rut}_{timestamp}
|
|
parts = filename.rsplit('_', 1)
|
|
if len(parts) != 2:
|
|
continue
|
|
|
|
rut, timestamp_str = parts
|
|
|
|
try:
|
|
# Parse timestamp: YYYYMMDDTHHMMSSZ
|
|
file_dt = datetime.strptime(timestamp_str, "%Y%m%dT%H%M%SZ")
|
|
except ValueError:
|
|
continue
|
|
|
|
# Check if older than 1 week
|
|
if file_dt < one_week_ago:
|
|
try:
|
|
file_path.unlink()
|
|
deleted_count += 1
|
|
logger.info(f"Deleted old response file (older than 1 week): {file_path.name}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete {file_path}: {e}")
|
|
continue
|
|
|
|
if rut not in files_by_group:
|
|
files_by_group[rut] = []
|
|
|
|
files_by_group[rut].append((file_dt, file_path))
|
|
except Exception as e:
|
|
logger.warning(f"Error processing file {file_path}: {e}")
|
|
|
|
# Process groups
|
|
for rut, file_list in files_by_group.items():
|
|
# Sort by timestamp descending (newest first)
|
|
file_list.sort(key=lambda x: x[0], reverse=True)
|
|
|
|
# Keep the first one, delete the rest
|
|
for _, file_to_delete in file_list[1:]:
|
|
try:
|
|
file_to_delete.unlink()
|
|
deleted_count += 1
|
|
logger.info(f"Deleted old response file (duplicate): {file_to_delete.name}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete {file_to_delete}: {e}")
|
|
|
|
if deleted_count > 0:
|
|
logger.info(f"Cleanup finished. Deleted {deleted_count} old files.")
|
|
else:
|
|
logger.info("Cleanup finished. No files to delete.")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during cleanup: {e}")
|
|
|
|
# Wait for 10 minutes (600 seconds)
|
|
await asyncio.sleep(600)
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
asyncio.create_task(cleanup_old_responses())
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8024)
|
|
class QueryRutRequest(BaseModel):
|
|
rut: str
|
|
isMonitoring: bool = False
|
|
includeEquifax: bool = False
|