This commit is contained in:
valenti 2026-04-08 15:48:58 -04:00
parent 40ec5b1137
commit 2108e1be4f
22 changed files with 93 additions and 2849 deletions

View File

@ -1,5 +1,5 @@
venv/bin/python main.py .venv/bin/python main.py

View File

@ -1,6 +0,0 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.8.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -0,0 +1,29 @@
#!/bin/bash
# Check if the .venv directory exists
if [ ! -d ".venv" ]; then
echo ".venv directory does not exist. Creating a virtual environment..."
# Create a virtual environment
python3 -m venv .venv
echo "Virtual environment created."
else
echo ".venv directory already exists."
fi
# Activate the virtual environment
source .venv/bin/activate
# Upgrade pip (optional)
pip install --upgrade pip
# Install requirements from requirements.txt
if [ -f "requirements.txt" ]; then
echo "Installing requirements from requirements.txt..."
pip install -r requirements.txt
else
echo "No requirements.txt file found."
fi
echo "Setup complete."

View File

@ -1,5 +1,5 @@
venv/bin/python main.py .venv/bin/python main.py

View File

@ -1,6 +1,7 @@
pymongo==4.6.0
fastapi==0.104.1 fastapi==0.104.1
uvicorn[standard]==0.24.0 uvicorn[standard]==0.24.0
pydantic==2.8.0
requests==2.31.0 requests==2.31.0
python-dotenv==1.0.0 python-dotenv==1.0.0
pymongo==4.6.0 python-multipart==0.0.6
pydantic==2.8.0

View File

@ -1,6 +0,0 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.8.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -0,0 +1,29 @@
#!/bin/bash
# Check if the .venv directory exists
if [ ! -d ".venv" ]; then
echo ".venv directory does not exist. Creating a virtual environment..."
# Create a virtual environment
python3 -m venv .venv
echo "Virtual environment created."
else
echo ".venv directory already exists."
fi
# Activate the virtual environment
source .venv/bin/activate
# Upgrade pip (optional)
pip install --upgrade pip
# Install requirements from requirements.txt
if [ -f "requirements.txt" ]; then
echo "Installing requirements from requirements.txt..."
pip install -r requirements.txt
else
echo "No requirements.txt file found."
fi
echo "Setup complete."

View File

@ -1,7 +0,0 @@
SHERIFF_V2_BASE_URL=sheriff.example.com
SHERIFF_V2_ACCESS_KEY=your_access_key
SHERIFF_V2_ACCESS_SECRET=your_access_secret
SHERIFF_V2_CLIENT_IDENTIFIER=SheriffSecureClient-v1
SHERIFF_V2_MONGODB_URI=mongodb://localhost:27017/duxiter
SHERIFF_V2_CACHE_TTL_SECONDS=604800
SHERIFF_V2_TOKEN_TTL_SECONDS=1800

View File

@ -1,708 +0,0 @@
{
"info": {
"_postman_id": "db3f9c3b-5e0b-4dc4-bdb8-665fab04b4b9",
"name": "Sheriff API v2",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "31069565"
},
"item": [
{
"name": "🔒 Autenticación y Tokens",
"item": [
{
"name": "🔑 getToken (Obtener Token de Acceso)",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var response = pm.response.json(); // Utiliza el método moderno para obtener el JSON",
"",
"if (response && response.data) {",
" // La respuesta contiene el token en la propiedad 'data'",
" pm.environment.set(\"bearer_token\", response.data);",
" ",
" // Opcional: Imprime el token en la Consola de Postman",
" console.log(\"Token guardado automáticamente en 'bearer_token': \" + response.data);",
"} else {",
" console.log(\"Error: No se pudo obtener el token de la respuesta. Verifica la estructura JSON.\");",
"}"
],
"type": "text/javascript",
"packages": {},
"requests": {}
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"accessKey\": \"{{accessKey}}\",\n \"accessSecret\": \"{{accessSecret}}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://{{base_url}}/api/clients/v2/apiCredentials/getToken",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"apiCredentials",
"getToken"
]
}
},
"response": []
}
]
},
{
"name": "👤 Plataforma",
"item": [
{
"name": "Perfil - Detalle",
"item": [
{
"name": "⚖️ Judiciales",
"item": [
{
"name": "civil (Causas Civiles)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/judicial/{{rut}}/civil",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"judicial",
"{{rut}}",
"civil"
]
}
},
"response": []
},
{
"name": "cobranza (Causas Cobranza)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/judicial/{{rut}}/cobranza",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"judicial",
"{{rut}}",
"cobranza"
]
}
},
"response": []
},
{
"name": "laboral (Causas Laborales)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/judicial/{{rut}}/laboral",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"judicial",
"{{rut}}",
"laboral"
]
}
},
"response": []
}
]
},
{
"name": "💼 Cobranza Laboral",
"item": [
{
"name": "multaLaboral (Multas Laborales)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/cobranzaLaboral/{{rut}}/multaLaboral",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"cobranzaLaboral",
"{{rut}}",
"multaLaboral"
]
}
},
"response": []
},
{
"name": "moraPrevisional (Mora Previsional)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/cobranzaLaboral/{{rut}}/moraPrevisional",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"cobranzaLaboral",
"{{rut}}",
"moraPrevisional"
]
}
},
"response": []
}
]
},
{
"name": "🛡️ Compliance",
"item": [
{
"name": "compliance (Detalle de Cumplimiento)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/compliance/{{rut}}",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"compliance",
"{{rut}}"
]
}
},
"response": []
}
]
},
{
"name": "🔗 Legal",
"item": [
{
"name": "mallaSocietaria (Malla Societaria)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/legal/{{rut}}/mallaSocietaria",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"legal",
"{{rut}}",
"mallaSocietaria"
]
}
},
"response": []
}
]
},
{
"name": "💯 Score",
"item": [
{
"name": "💳 creditScore (Puntaje Crediticio)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/creditScore/{{rut}}",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"creditScore",
"{{rut}}"
]
}
},
"response": []
}
]
}
]
},
{
"name": "📈 Uso y Plan",
"item": [
{
"name": "usage (Consumo de la Cuenta)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/usage",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"usage"
]
}
},
"response": []
}
]
},
{
"name": "↩️ Webhooks/Callbacks (Asíncronos)",
"item": [
{
"name": "📋 Reportes (summaryByRut)",
"item": [
{
"name": "summaryByRut (PDF de Resumen por RUT)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/pdf"
},
{
"key": "Content-Disposition",
"value": "attachment; filename={{rut}}.pdf"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/webhook/profile/{{rut}}/resumen?format=pdf",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"webhook",
"profile",
"{{rut}}",
"resumen"
],
"query": [
{
"key": "format",
"value": "pdf"
}
]
}
},
"response": []
}
]
},
{
"name": "🛡️ Compliance (Asíncrono)",
"item": [
{
"name": "complianceByName (Cargar Perfil Compliance por Nombre)",
"request": {
"method": "POST",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"fullName\": \"Nombre Empresa\",\n \"isMonitoring\": true,\n \"isJuridicalPerson\": true,\n \"searchErrors\": 0\n }",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://{{base_url}}/api/clients/v2/webhook/compliance/cargarPerfil",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"webhook",
"compliance",
"cargarPerfil"
]
}
},
"response": []
}
]
}
]
},
{
"name": "📄 resumen (Obtener Resumen del Perfil)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/resumen?rut={{rut}}",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"resumen"
],
"query": [
{
"key": "rut",
"value": "{{rut}}"
}
]
}
},
"response": []
},
{
"name": "📜 loadedProfiles (Perfiles Cargados)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/profiles",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"profiles"
]
}
},
"response": []
},
{
"name": " cargarRut (Cargar Perfil)",
"request": {
"method": "POST",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"rut\": \"{{rut}}\",\n \"isMonitoring\": false,\n \"includeEquifax\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/cargarRut",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"cargarRut"
]
}
},
"response": []
}
]
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{bearer_token}}",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
}

View File

@ -1,5 +0,0 @@
venv/bin/python main.py

View File

@ -1,723 +0,0 @@
#!/usr/bin/env python3
"""
FastAPI Sheriff v2 Provider
"""
import os
import json
import time
import asyncio
import logging
import hashlib
import glob
import shutil
import threading
import re
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
# 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()
CACHE_TTL_SECONDS = int(os.getenv("SHERIFF_V2_CACHE_TTL_SECONDS", "604800"))
SHERIFF_RESPONSES_DIR = Path(__file__).resolve().parents[2] / "sheriff_v2_response"
# Json Cache Manager Implementation
class JsonCacheManager:
def __init__(self, directory: Path):
self.directory = directory
self.backup_directory = directory / "backups"
self.directory.mkdir(parents=True, exist_ok=True)
self.backup_directory.mkdir(parents=True, exist_ok=True)
self.lock = threading.RLock()
def _get_safe_filename_prefix(self, key: str) -> str:
return hashlib.sha256(key.encode()).hexdigest()
def get(self, key: str) -> Optional[Dict[str, Any]]:
with self.lock:
try:
safe_prefix = self._get_safe_filename_prefix(key)
pattern = str(self.directory / f"{safe_prefix}_v*.json")
files = glob.glob(pattern)
if not files:
return None
# Filter and parse valid files
valid_files = []
for f_path in files:
try:
name = Path(f_path).name
# Regex to parse: hash_v(\d+)_(\d+)_(\d+).json
match = re.match(r"^[0-9a-f]+_v(\d+)_(\d+)_(\d+)\.json$", name)
if match:
version = int(match.group(1))
ts = int(match.group(2))
exp = int(match.group(3))
valid_files.append((version, ts, exp, f_path))
except Exception:
continue
if not valid_files:
return None
# Pick latest version
valid_files.sort(key=lambda x: (x[0], x[1]), reverse=True)
latest = valid_files[0]
version, ts, exp, f_path = latest
# Check expiry
if exp < time.time():
# logger.info(f"Cache expired for key: {key}")
return None
# Read file
with open(f_path, 'r', encoding='utf-8') as f:
data = json.load(f)
logger.info(f"Cache hit for key: {key} (file: {Path(f_path).name})")
return data.get("result")
except Exception as e:
logger.error(f"Error reading from file cache: {e}")
return None
def save(self, key: str, result: Dict[str, Any], ttl_seconds: int) -> None:
with self.lock:
try:
safe_prefix = self._get_safe_filename_prefix(key)
pattern = str(self.directory / f"{safe_prefix}_v*.json")
files = glob.glob(pattern)
next_version = 1
# Handle backups and versioning
if files:
# Parse to find existing versions
current_versions = []
for f_path in files:
name = Path(f_path).name
match = re.match(r"^[0-9a-f]+_v(\d+)_(\d+)_(\d+)\.json$", name)
if match:
current_versions.append((int(match.group(1)), f_path))
if current_versions:
current_versions.sort(key=lambda x: x[0], reverse=True)
last_ver, last_path = current_versions[0]
next_version = last_ver + 1
# Backup previous files
for _, path in current_versions:
try:
dest = self.backup_directory / Path(path).name
shutil.move(path, dest)
logger.info(f"Backed up {Path(path).name}")
except Exception as e:
logger.error(f"Failed to backup {path}: {e}")
now_ts = int(time.time())
exp_ts = now_ts + ttl_seconds
filename = f"{safe_prefix}_v{next_version}_{now_ts}_{exp_ts}.json"
filepath = self.directory / filename
data = {
"_id": key,
"result": result,
"cached_at": datetime.utcnow().isoformat(),
"expires_at": (datetime.utcnow() + timedelta(seconds=ttl_seconds)).isoformat(),
"version": next_version
}
# Atomic write
temp_path = filepath.with_suffix('.tmp')
with open(temp_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
os.replace(temp_path, filepath)
logger.info(f"Transaction result cached with key: {key}, file: {filename}")
except Exception as e:
logger.error(f"Error saving to file cache: {e}")
def clear(self) -> int:
with self.lock:
try:
pattern = str(self.directory / "*_v*_*.json")
files = glob.glob(pattern)
count = 0
for f in files:
try:
name = Path(f).name
if re.match(r"^[0-9a-f]+_v\d+_\d+_\d+\.json$", name):
os.remove(f)
count += 1
except Exception:
pass
logger.info(f"Cleared {count} cached transactions")
return count
except Exception as e:
logger.error(f"Error clearing cache: {e}")
return 0
def stats(self) -> Dict[str, Any]:
with self.lock:
try:
total = 0
expired = 0
pattern = str(self.directory / "*_v*_*.json")
files = glob.glob(pattern)
now_ts = time.time()
for f in files:
name = Path(f).name
match = re.match(r"^[0-9a-f]+_v\d+_\d+_(\d+)\.json$", name)
if match:
total += 1
exp = int(match.group(1))
if exp < now_ts:
expired += 1
return {
"total_cached_transactions": total,
"expired_transactions": expired,
"active_transactions": total - expired,
"cache_ttl_seconds": CACHE_TTL_SECONDS,
"backend": "json_file"
}
except Exception as e:
logger.error(f"Error getting cache stats: {e}")
return {"error": str(e), "backend": "json_file"}
# Initialize Cache Manager
cache_manager = JsonCacheManager(SHERIFF_RESPONSES_DIR)
TOKEN_CACHE_FILE = Path(".token_cache.json")
SHERIFF_TOKEN_TTL_SECONDS = int(os.getenv("SHERIFF_V2_TOKEN_TTL_SECONDS", "1800"))
# 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]]:
return cache_manager.get(cache_key)
def save_transaction_to_cache(cache_key: str, result: Dict[str, Any]) -> None:
cache_manager.save(cache_key, result, CACHE_TTL_SECONDS)
def clear_cache() -> int:
return cache_manager.clear()
def get_cache_stats() -> Dict[str, Any]:
return cache_manager.stats()
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:
dest_dir = SHERIFF_RESPONSES_DIR
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)

View File

@ -1,7 +0,0 @@
pymongo==4.6.0
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -1,6 +0,0 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -1,7 +0,0 @@
SHERIFF_V2_BASE_URL=sheriff.example.com
SHERIFF_V2_ACCESS_KEY=your_access_key
SHERIFF_V2_ACCESS_SECRET=your_access_secret
SHERIFF_V2_CLIENT_IDENTIFIER=SheriffSecureClient-v1
SHERIFF_V2_MONGODB_URI=mongodb://localhost:27017/duxiter
SHERIFF_V2_CACHE_TTL_SECONDS=604800
SHERIFF_V2_TOKEN_TTL_SECONDS=1800

View File

@ -1,708 +0,0 @@
{
"info": {
"_postman_id": "db3f9c3b-5e0b-4dc4-bdb8-665fab04b4b9",
"name": "Sheriff API v2",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "31069565"
},
"item": [
{
"name": "🔒 Autenticación y Tokens",
"item": [
{
"name": "🔑 getToken (Obtener Token de Acceso)",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var response = pm.response.json(); // Utiliza el método moderno para obtener el JSON",
"",
"if (response && response.data) {",
" // La respuesta contiene el token en la propiedad 'data'",
" pm.environment.set(\"bearer_token\", response.data);",
" ",
" // Opcional: Imprime el token en la Consola de Postman",
" console.log(\"Token guardado automáticamente en 'bearer_token': \" + response.data);",
"} else {",
" console.log(\"Error: No se pudo obtener el token de la respuesta. Verifica la estructura JSON.\");",
"}"
],
"type": "text/javascript",
"packages": {},
"requests": {}
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"accessKey\": \"{{accessKey}}\",\n \"accessSecret\": \"{{accessSecret}}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://{{base_url}}/api/clients/v2/apiCredentials/getToken",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"apiCredentials",
"getToken"
]
}
},
"response": []
}
]
},
{
"name": "👤 Plataforma",
"item": [
{
"name": "Perfil - Detalle",
"item": [
{
"name": "⚖️ Judiciales",
"item": [
{
"name": "civil (Causas Civiles)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/judicial/{{rut}}/civil",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"judicial",
"{{rut}}",
"civil"
]
}
},
"response": []
},
{
"name": "cobranza (Causas Cobranza)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/judicial/{{rut}}/cobranza",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"judicial",
"{{rut}}",
"cobranza"
]
}
},
"response": []
},
{
"name": "laboral (Causas Laborales)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/judicial/{{rut}}/laboral",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"judicial",
"{{rut}}",
"laboral"
]
}
},
"response": []
}
]
},
{
"name": "💼 Cobranza Laboral",
"item": [
{
"name": "multaLaboral (Multas Laborales)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/cobranzaLaboral/{{rut}}/multaLaboral",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"cobranzaLaboral",
"{{rut}}",
"multaLaboral"
]
}
},
"response": []
},
{
"name": "moraPrevisional (Mora Previsional)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/cobranzaLaboral/{{rut}}/moraPrevisional",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"cobranzaLaboral",
"{{rut}}",
"moraPrevisional"
]
}
},
"response": []
}
]
},
{
"name": "🛡️ Compliance",
"item": [
{
"name": "compliance (Detalle de Cumplimiento)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/compliance/{{rut}}",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"compliance",
"{{rut}}"
]
}
},
"response": []
}
]
},
{
"name": "🔗 Legal",
"item": [
{
"name": "mallaSocietaria (Malla Societaria)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/legal/{{rut}}/mallaSocietaria",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"legal",
"{{rut}}",
"mallaSocietaria"
]
}
},
"response": []
}
]
},
{
"name": "💯 Score",
"item": [
{
"name": "💳 creditScore (Puntaje Crediticio)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/creditScore/{{rut}}",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"creditScore",
"{{rut}}"
]
}
},
"response": []
}
]
}
]
},
{
"name": "📈 Uso y Plan",
"item": [
{
"name": "usage (Consumo de la Cuenta)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/usage",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"usage"
]
}
},
"response": []
}
]
},
{
"name": "↩️ Webhooks/Callbacks (Asíncronos)",
"item": [
{
"name": "📋 Reportes (summaryByRut)",
"item": [
{
"name": "summaryByRut (PDF de Resumen por RUT)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/pdf"
},
{
"key": "Content-Disposition",
"value": "attachment; filename={{rut}}.pdf"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/webhook/profile/{{rut}}/resumen?format=pdf",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"webhook",
"profile",
"{{rut}}",
"resumen"
],
"query": [
{
"key": "format",
"value": "pdf"
}
]
}
},
"response": []
}
]
},
{
"name": "🛡️ Compliance (Asíncrono)",
"item": [
{
"name": "complianceByName (Cargar Perfil Compliance por Nombre)",
"request": {
"method": "POST",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"fullName\": \"Nombre Empresa\",\n \"isMonitoring\": true,\n \"isJuridicalPerson\": true,\n \"searchErrors\": 0\n }",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://{{base_url}}/api/clients/v2/webhook/compliance/cargarPerfil",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"webhook",
"compliance",
"cargarPerfil"
]
}
},
"response": []
}
]
}
]
},
{
"name": "📄 resumen (Obtener Resumen del Perfil)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/resumen?rut={{rut}}",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"resumen"
],
"query": [
{
"key": "rut",
"value": "{{rut}}"
}
]
}
},
"response": []
},
{
"name": "📜 loadedProfiles (Perfiles Cargados)",
"request": {
"method": "GET",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
}
],
"url": {
"raw": "https://{{base_url}}/api/clients/v2/profiles",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"profiles"
]
}
},
"response": []
},
{
"name": " cargarRut (Cargar Perfil)",
"request": {
"method": "POST",
"header": [
{
"key": "accept",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{bearer_token}}"
},
{
"key": "x-client-identifier",
"value": "SheriffSecureClient-v1"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"rut\": \"{{rut}}\",\n \"isMonitoring\": false,\n \"includeEquifax\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://{{base_url}}/api/clients/v2/helper/cargarRut",
"protocol": "https",
"host": [
"{{base_url}}"
],
"path": [
"api",
"clients",
"v2",
"helper",
"cargarRut"
]
}
},
"response": []
}
]
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{bearer_token}}",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
}

View File

@ -1,5 +0,0 @@
venv/bin/python main.py

View File

@ -1,637 +0,0 @@
#!/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

View File

@ -1,7 +0,0 @@
pymongo==4.6.0
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -1,6 +0,0 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -1,5 +1,5 @@
venv/bin/python main.py .venv/bin/python main.py

View File

@ -1,6 +0,0 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.8.0
requests==2.31.0
python-dotenv==1.0.0
python-multipart==0.0.6

View File

@ -0,0 +1,29 @@
#!/bin/bash
# Check if the .venv directory exists
if [ ! -d ".venv" ]; then
echo ".venv directory does not exist. Creating a virtual environment..."
# Create a virtual environment
python3 -m venv .venv
echo "Virtual environment created."
else
echo ".venv directory already exists."
fi
# Activate the virtual environment
source .venv/bin/activate
# Upgrade pip (optional)
pip install --upgrade pip
# Install requirements from requirements.txt
if [ -f "requirements.txt" ]; then
echo "Installing requirements from requirements.txt..."
pip install -r requirements.txt
else
echo "No requirements.txt file found."
fi
echo "Setup complete."