update stuff, add fallbacks, more order to the api

This commit is contained in:
Nixietab 2025-11-10 13:53:52 -03:00
parent e777ed476d
commit ad9fab907c
5 changed files with 83 additions and 3 deletions

Binary file not shown.

Binary file not shown.

View File

@ -8,6 +8,7 @@ from urllib.parse import urljoin
import tokenext import tokenext
from healthcheck import ensure_config_exists from healthcheck import ensure_config_exists
# load config
config = ensure_config_exists() config = ensure_config_exists()
server_cfg = config["server"] server_cfg = config["server"]
@ -16,6 +17,13 @@ PASSWORD = server_cfg.get("password", "").strip()
CACHE_DIR = server_cfg.get("cache_dir", "cache") CACHE_DIR = server_cfg.get("cache_dir", "cache")
BASE_API = server_cfg.get("base_api", "https://ws1.smn.gob.ar") BASE_API = server_cfg.get("base_api", "https://ws1.smn.gob.ar")
LOG_FILE = server_cfg.get("log_file", "").strip() LOG_FILE = server_cfg.get("log_file", "").strip()
BASE_PATH = server_cfg.get("base_path", "/smn").strip()
if not BASE_PATH.startswith("/"):
BASE_PATH = "/" + BASE_PATH
if BASE_PATH.endswith("/"):
BASE_PATH = BASE_PATH[:-1]
SMN_TOKEN_FILE = "token" SMN_TOKEN_FILE = "token"
CACHE_TTL = timedelta(minutes=60) CACHE_TTL = timedelta(minutes=60)
AUTH_ENABLED = PASSWORD != "" AUTH_ENABLED = PASSWORD != ""
@ -28,6 +36,7 @@ def log(msg: str):
f.write(f"[{datetime.now().isoformat()}] {msg}\n") f.write(f"[{datetime.now().isoformat()}] {msg}\n")
print(msg) print(msg)
# cache handling
def get_cache_filename(url: str) -> str: def get_cache_filename(url: str) -> str:
h = hashlib.sha256(url.encode()).hexdigest() h = hashlib.sha256(url.encode()).hexdigest()
return os.path.join(CACHE_DIR, f"{h}.json") return os.path.join(CACHE_DIR, f"{h}.json")
@ -51,7 +60,13 @@ def save_cache(url: str, data: dict):
with open(path, "w", encoding="utf-8") as f: with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False) json.dump(data, f, indent=2, ensure_ascii=False)
# token handling
def load_smn_token(): def load_smn_token():
if not os.path.exists(SMN_TOKEN_FILE):
log("[TOKEN] Token file not found — refreshing token...")
refresh_smn_token()
if not os.path.exists(SMN_TOKEN_FILE):
raise FileNotFoundError("Token file could not be created.")
with open(SMN_TOKEN_FILE, "r") as f: with open(SMN_TOKEN_FILE, "r") as f:
return f.read().strip() return f.read().strip()
@ -69,6 +84,7 @@ def check_access_token():
header_token = request.headers.get("Authorization", "").strip() header_token = request.headers.get("Authorization", "").strip()
return header_token == PASSWORD return header_token == PASSWORD
# upstream the request
def fetch_from_smn(url: str, retry: bool = True): def fetch_from_smn(url: str, retry: bool = True):
token = load_smn_token() token = load_smn_token()
headers = { headers = {
@ -89,7 +105,8 @@ def fetch_from_smn(url: str, retry: bool = True):
return resp return resp
@app.route("/smn/<path:subpath>") # the proxy stuff
@app.route(f"{BASE_PATH}/<path:subpath>")
def smn_proxy(subpath): def smn_proxy(subpath):
if not check_access_token(): if not check_access_token():
return jsonify({"error": "Unauthorized"}), 401 return jsonify({"error": "Unauthorized"}), 401
@ -121,7 +138,37 @@ def smn_proxy(subpath):
except Exception: except Exception:
return Response("Invalid JSON from SMN", status=502) return Response("Invalid JSON from SMN", status=502)
@app.errorhandler(404)
def handle_not_found(e):
return jsonify({
"error": "Endpoint not found",
"message": f"The requested URL '{request.path}' is not a valid API endpoint.",
}), 200
@app.errorhandler(405)
def handle_method_not_allowed(e):
return jsonify({
"error": "Method not allowed",
"allowed": ["GET"],
"path": request.path
}), 200
@app.errorhandler(Exception)
def handle_general_error(e):
log(f"[ERROR] Unexpected exception: {e}")
return jsonify({
"error": "Internal error",
"message": str(e),
"path": request.path
}), 200
# === Startup ===
if __name__ == "__main__": if __name__ == "__main__":
os.makedirs(CACHE_DIR, exist_ok=True) os.makedirs(CACHE_DIR, exist_ok=True)
if not os.path.exists(SMN_TOKEN_FILE):
log("[STARTUP] No token file found — generating a new one.")
refresh_smn_token()
log(f"[STARTUP] Server starting on port {PORT}") log(f"[STARTUP] Server starting on port {PORT}")
log(f"[STARTUP] Base path set to '{BASE_PATH}/<path>'")
app.run(host="0.0.0.0", port=PORT) app.run(host="0.0.0.0", port=PORT)

View File

@ -0,0 +1,30 @@
{
"date": "2025-11-10T13:00:00-03:00",
"humidity": 55.0,
"pressure": 1020.8,
"feels_like": null,
"temperature": 24.1,
"visibility": 10.0,
"weather": {
"description": "Ligeramente nublado",
"id": 13
},
"wind": {
"direction": "Noreste",
"deg": 45.0,
"speed": 15.0
},
"station_id": 87582,
"location": {
"id": 10821,
"name": "Aeroparque Buenos Aires",
"department": "CABA",
"province": "CABA",
"type": "",
"coord": {
"lon": -58.4187,
"lat": -34.5619
},
"distance": 0.35
}
}

View File

@ -9,12 +9,14 @@ DEFAULT_CONFIG = {
"password": "debug", "password": "debug",
"cache_dir": "cache", "cache_dir": "cache",
"base_api": "https://ws1.smn.gob.ar", "base_api": "https://ws1.smn.gob.ar",
"log_file": "" "log_file": "",
"base_path": "/smn"
} }
} }
def ensure_config_exists(): def ensure_config_exists():
config = configparser.ConfigParser() config = configparser.ConfigParser()
if not os.path.exists(CONFIG_FILE): if not os.path.exists(CONFIG_FILE):
print("[CONFIG] config.ini not found - creating with default values") print("[CONFIG] config.ini not found - creating with default values")
config.read_dict(DEFAULT_CONFIG) config.read_dict(DEFAULT_CONFIG)
@ -36,4 +38,5 @@ def ensure_config_exists():
with open(CONFIG_FILE, "w") as f: with open(CONFIG_FILE, "w") as f:
config.write(f) config.write(f)
print("[CONFIG] Missing keys added to config.ini") print("[CONFIG] Missing keys added to config.ini")
return config
return config