Commit fc69800a authored by Marco Schmiedel's avatar Marco Schmiedel

fix

parent ccf805f2
AWS_ACCESS_KEY_ID = "AKIATXO2FKVK3BSS3DMT" AWS_ACCESS_KEY_ID = "AKIATXO2FKVK3BSS3DMT"
AWS_SECRET_ACCESS_KEY = "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" AWS_SECRET_ACCESS_KEY = "u6CvzjBJCo6qiL0zj8txOVGRUDlsspyhfLU/YK+Q"
REGION = "eu-central-1" REGION = "eu-central-1"
BUCKET_NAME = "freenetflyer" BUCKET_NAME = "compactcdn.backoffice.online"
MAUI_USERNAME = "28009594-198" MAUI_USERNAME = "28009594-198"
MAUI_PASSWORD = "8v#5YeeQyh" MAUI_PASSWORD = "8v#5YeeQyh"
MAUI_AUTHCODE = "2D3JJNG3WWGSWRDI5KRW2MZKL3NJEZXJ" MAUI_AUTHCODE = "2D3JJNG3WWGSWRDI5KRW2MZKL3NJEZXJ"
EECCX_TOKEN_URL = "https://sts.md.de/v1/oidc/token"
EECCX_API_URL = "https://partner-api.md.de/vertragserfassung/ftpOption2Pci.php"
EECCX_CLIENT_ID = "8VyjbQZyTVdx2T2UO6mA3ZTEeiodHcp-"
EECCX_CLIENT_SECRET = "FTE3y3Hj3TXeYrR8JbbO9yRRJ5ZGmPPTHH4HpyLFd9_X6wOx"
EECCX_CF_CLIENT_ID = "e42d165bde7363f8478a157b57425fd5.access"
EECCX_CF_CLIENT_SECRET = "de141ba4a6fbf9c29f51ba86fce9e81d3479797ff010a7ba43cde01977eac565"
EECCX_HDL_NR = 28009594
EECCX_PROV_HDL_NR = 28009594
...@@ -61,7 +61,7 @@ def _require_token(): ...@@ -61,7 +61,7 @@ def _require_token():
token = request.args.get("token") token = request.args.get("token")
if token != TOKEN_VALUE: if token != TOKEN_VALUE:
return ( return (
jsonify({"status": "ERROR", "message": "Please enter a valid token."}), jsonify({"message": "Please enter a valid token."}),
401, 401,
) )
......
""" #!/usr/bin/env python3
Health-Check-Router (Caching & PDF-Rückgabe) # -*- coding: utf-8 -*-
Stellt **einen** Endpunkt bereit
--------------------------------
GET /eeccx/<id>?options=A,B,C
GET /eeccx/<id>?options=A&options=B…
Workflow
--------
1. ID + Options → SHA-256-Hash → ./cache/<hash>.pdf
• Datei vorhanden ⇒ PDF sofort senden.
2. OAuth-Token holen (client-credentials).
3. Partner-API aufrufen.
• Enthält die Antwort ein Feld **error** → Fehlerbotschaft(en)
per JSON an den Client weitergeben.
• Enthält die Antwort kein *pcsPdf/pciPdf* → Fehler aus Antwort
oder Standardmeldung zurückgeben.
4. PDF herunterladen, cachen, ausliefern.
5. Jeder andere Fehler liefert immer
`{"status":"ERROR","message":"…"}`
– **ohne** HTML-Escaping/Unicode-Escapes („ä“, „ü“ usw. bleiben sichtbar).
Hinweis
-------
• SSL-Verifikation ist zu Demo-Zwecken deaktiviert (`verify=False`).
• Die Secrets stammen 1-zu-1 aus Deinem Beispielskript.
"""
from __future__ import annotations from __future__ import annotations
import sys import sys
sys.path.append("..") sys.path.append("..")
import time
import hashlib import hashlib
import io import io
import json import json
import os import os
import tempfile
from typing import List, Tuple from typing import List, Tuple
import requests import requests
import urllib3 import urllib3
from flask import Blueprint, Response, request, send_file from flask import Blueprint, Response, request
from sqlalchemy.orm import joinedload
from config.MauiConfig import EECCX_TOKEN_URL, EECCX_API_URL, EECCX_CLIENT_ID, EECCX_CLIENT_SECRET, EECCX_CF_CLIENT_ID, EECCX_CF_CLIENT_SECRET, EECCX_HDL_NR, EECCX_PROV_HDL_NR
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Warnungen zu unsicheren HTTPS-Requests unterdrücken (nur Dev) # # Eigene Module #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) from manager.S3Manager import S3Manager
from manager.MysqlManager import MysqlManager
from models.deal_deal import DealDeal
from models.base_base import BaseBase
from models.option_opti import OptionOpti
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Blueprint # # Warnungen zu unsicheren HTTPS-Requests unterdrücken (nur Dev) #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
blueprint = Blueprint(__name__.rsplit(".", 1)[-1], __name__) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Konfiguration / Konstanten # # Konfiguration / Konstanten #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
TOKEN_URL = "https://sts.md.de/v1/oidc/token" TOKEN_URL = EECCX_TOKEN_URL
API_URL = "https://partner-api.md.de/vertragserfassung/ftpOption2Pci.php" API_URL = EECCX_API_URL
CLIENT_ID = EECCX_CLIENT_ID
CLIENT_ID = "8VyjbQZyTVdx2T2UO6mA3ZTEeiodHcp-" CLIENT_SECRET = EECCX_CLIENT_SECRET
CLIENT_SECRET = "FTE3y3Hj3TXeYrR8JbbO9yRRJ5ZGmPPTHH4HpyLFd9_X6wOx" CF_CLIENT_ID = EECCX_CF_CLIENT_ID
CF_CLIENT_SECRET = EECCX_CF_CLIENT_SECRET
CF_CLIENT_ID = "e42d165bde7363f8478a157b57425fd5.access" HDL_NR = EECCX_HDL_NR
CF_CLIENT_SECRET = "de141ba4a6fbf9c29f51ba86fce9e81d3479797ff010a7ba43cde01977eac565" PROV_HDL_NR = EECCX_PROV_HDL_NR
HDL_NR = 28009594
PROV_HDL_NR = 28009594
PRODUKT_KATEGORIE = "O" PRODUKT_KATEGORIE = "O"
CACHE_DIR = "./../cache" # --------------------------------------------------------------------------- #
# Blueprint #
# --------------------------------------------------------------------------- #
blueprint = Blueprint(__name__.rsplit(".", 1)[-1], __name__)
# S3-Manager für Uploads
s3_manager = S3Manager()
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Hilfsfunktionen # # Hilfsfunktionen #
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
def _json_error(message: str, status_code: int = 500) -> Response: def _json_error(message: str, status_code: int = 500) -> Response:
"""
Gibt eine ERROR-Antwort zurück, **ohne** dass Umlaute als \\uXXXX
escaped werden (ensure_ascii=False).
"""
payload = json.dumps({"status": "ERROR", "message": message}, ensure_ascii=False) payload = json.dumps({"status": "ERROR", "message": message}, ensure_ascii=False)
return Response(payload, status=status_code, mimetype="application/json") return Response(payload, status=status_code, mimetype="application/json")
def _extract_options() -> List[str]: def _extract_options() -> List[str]:
"""
Wandelt Query-Parameter »options« in eine Liste um.
Akzeptiert:
?options=A,B,C
?options=A&options=B…
"""
raw = request.args.getlist("options") raw = request.args.getlist("options")
if len(raw) == 1 and "," in raw[0]: if len(raw) == 1 and "," in raw[0]:
return [opt.strip() for opt in raw[0].split(",") if opt.strip()] return [opt.strip() for opt in raw[0].split(",") if opt.strip()]
...@@ -98,14 +71,10 @@ def _extract_options() -> List[str]: ...@@ -98,14 +71,10 @@ def _extract_options() -> List[str]:
def _hash_id_options(tarif_id: str, options: List[str]) -> str: def _hash_id_options(tarif_id: str, options: List[str]) -> str:
key = f"{tarif_id}:{','.join(sorted(options))}".encode("utf-8") key = f"{tarif_id}:{','.join(sorted(options))}:{int(time.time())}".encode("utf-8")
return hashlib.sha256(key).hexdigest() return hashlib.sha256(key).hexdigest()
def _ensure_cache():
os.makedirs(CACHE_DIR, exist_ok=True)
# ---------------------------- OAuth-Token ---------------------------------- # # ---------------------------- OAuth-Token ---------------------------------- #
def _get_token() -> Tuple[str | None, str | None]: def _get_token() -> Tuple[str | None, str | None]:
payload = { payload = {
...@@ -133,41 +102,99 @@ def _partner_api(token: str, tarif_id: str, options: List[str]) -> Tuple[dict | ...@@ -133,41 +102,99 @@ def _partner_api(token: str, tarif_id: str, options: List[str]) -> Tuple[dict |
"CF-Access-Client-Secret": CF_CLIENT_SECRET, "CF-Access-Client-Secret": CF_CLIENT_SECRET,
"Content-Type": "application/json", "Content-Type": "application/json",
} }
try:
session = MysqlManager().getSession()
deal_int = int(tarif_id)
deal = (
session
.query(DealDeal)
.options(joinedload(DealDeal.base))
.filter(DealDeal.id_deal == deal_int)
.one_or_none()
)
if deal is None:
return None, f"Deal {tarif_id} not found."
base_obj: BaseBase = deal.base
if not base_obj or not base_obj.providercode_base:
return None, f"Kein providercode_base für Deal id={tarif_id} gefunden."
providercode_base_value = base_obj.providercode_base
providercode_deal_value = deal.providercode_deal
finally:
session.close()
am_aktion_id: int | None = None
if providercode_deal_value:
digits = "".join(filter(str.isdigit, providercode_deal_value))
if digits:
am_aktion_id = int(digits)
service_codes: List[str] = []
try:
session = MysqlManager().getSession()
for opt_id in options:
try:
opt_int = int(opt_id)
except ValueError:
continue
opt = (
session.query(OptionOpti)
.filter(OptionOpti.id_opti == opt_int)
.one_or_none()
)
if not opt or not opt.providercode_opti:
continue
service_codes.append(opt.providercode_opti)
parent_code = opt.providercategory_opti
while parent_code:
parent_opt = (
session.query(OptionOpti)
.filter(OptionOpti.providercode_opti == parent_code)
.limit(1)
.one_or_none()
)
if not parent_opt or not parent_opt.providercode_opti:
break
service_codes.append(parent_opt.providercode_opti)
parent_code = parent_opt.providercategory_opti
finally:
session.close()
payload = { payload = {
"hdl_nr": HDL_NR, "hdl_nr": HDL_NR,
"prov_hdl_nr": PROV_HDL_NR, "prov_hdl_nr": PROV_HDL_NR,
"tarif_id": tarif_id, "tarif_id": providercode_base_value,
"produkt_kategorie": PRODUKT_KATEGORIE, "produkt_kategorie": PRODUKT_KATEGORIE,
"service_code": options, "service_code": service_codes,
} }
if am_aktion_id is not None:
payload["am_aktion_id"] = am_aktion_id
try: try:
r = requests.put(API_URL, headers=headers, json=payload, verify=False, timeout=30) r = requests.put(API_URL, headers=headers, json=payload, verify=False, timeout=30)
r.raise_for_status() r.raise_for_status()
except requests.exceptions.RequestException as exc: except requests.exceptions.RequestException as exc:
return None, f"API-Aufruf fehlgeschlagen: {exc}" return None, f"API-Aufruf fehlgeschlagen: {exc} – Payload: {payload}"
try: try:
data = r.json() data = r.json()
except ValueError: except ValueError:
return None, "Antwort der Partner-API ist kein JSON." return None, "Antwort der Partner-API ist kein JSON."
# → Fehlerrückgabe der Partner-API auswerten
err_val = data.get("error") err_val = data.get("error")
if err_val: if err_val:
# Fehler kann Array, Dict oder JSON-String sein
if isinstance(err_val, list): if isinstance(err_val, list):
msg = "; ".join(str(e) for e in err_val) msg = "; ".join(str(e) for e in err_val)
elif isinstance(err_val, dict): elif isinstance(err_val, dict):
msg = "; ".join(f"{k}: {v}" for k, v in err_val.items()) msg = "; ".join(f"{k}: {v}" for k, v in err_val.items())
elif isinstance(err_val, str): elif isinstance(err_val, str):
# eventuell weitere JSON-Ebene
try: try:
decoded = json.loads(err_val) decoded = json.loads(err_val)
if isinstance(decoded, dict): msg = decoded.get("message", str(decoded)) if isinstance(decoded, dict) else str(decoded)
msg = decoded.get("message", str(decoded))
else:
msg = str(decoded)
except ValueError: except ValueError:
msg = err_val msg = err_val
else: else:
...@@ -194,51 +221,45 @@ def _download_pdf(url: str) -> Tuple[bytes | None, str | None]: ...@@ -194,51 +221,45 @@ def _download_pdf(url: str) -> Tuple[bytes | None, str | None]:
def eeccx_pdf(tarif_id: str): def eeccx_pdf(tarif_id: str):
""" """
Beispiel: Beispiel:
/eeccx/3877325?options=G343,O3729 /freenet-eeccx/3877325?options=G343,O3729
/eeccx/3877325?options=G343&options=O3729 /freenet-eeccx/3877325?options=G343&options=B…
""" """
options = _extract_options() options = _extract_options()
_ensure_cache()
cache_file = os.path.join(CACHE_DIR, f"{_hash_id_options(tarif_id, options)}.pdf") # 1) OAuth-Token
# 1) Cache-Treffer → PDF sofort
if os.path.isfile(cache_file):
return send_file(cache_file, mimetype="application/pdf")
# 2) Token
token, err = _get_token() token, err = _get_token()
if err: if err:
return _json_error(err, 502) return _json_error(err, 502)
# 3) Partner-API # 2) Partner-API
api_json, err = _partner_api(token, tarif_id, options) api_json, err = _partner_api(token, tarif_id, options)
if err: if err:
return _json_error(err, 502) return _json_error(err, 502)
# 4) PDF-URL # 3) PDF-URL extrahieren
pdf_url: str | None = api_json.get("pcsPdf") or api_json.get("pciPdf") pdf_url = api_json.get("pcsPdf") or api_json.get("pciPdf")
if not pdf_url: if not pdf_url:
# falls API eine Message liefert, diese übernehmen
msg = api_json.get("message") or "Keine PDF-URL in der API-Antwort." msg = api_json.get("message") or "Keine PDF-URL in der API-Antwort."
return _json_error(msg, 502) return _json_error(msg, 502)
# 5) PDF laden # 4) PDF laden
pdf_bytes, err = _download_pdf(pdf_url) pdf_bytes, err = _download_pdf(pdf_url)
if err: if err:
return _json_error(err, 502) return _json_error(err, 502)
# 6) Cache speichern (Fehler ≠ KO) # 5) Temporäre Datei zum Upload schreiben
try: hash_name = _hash_id_options(tarif_id, options)
with open(cache_file, "wb") as fh: with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
fh.write(pdf_bytes) tmp.write(pdf_bytes)
except OSError: tmp_path = tmp.name
pass # Ignorieren, PDF wird dennoch geliefert
# 6) Upload zu S3
# 7) PDF senden s3_key = f"eeccx/{hash_name}.pdf"
return send_file( url = s3_manager.uploadFile(tmp_path, s3_key)
io.BytesIO(pdf_bytes), os.remove(tmp_path)
mimetype="application/pdf", if not url:
as_attachment=False, return _json_error(f"Upload zu S3 fehlgeschlagen für key={s3_key}", 502)
download_name=f"{tarif_id}.pdf",
) # 7) Download-URL als JSON zurückgeben
payload = json.dumps({"url": url}, ensure_ascii=False)
return Response(payload, status=200, mimetype="application/json")
...@@ -15,4 +15,4 @@ def index(): ...@@ -15,4 +15,4 @@ def index():
GET / GET /
Liefert einen einfachen JSON-Status. Liefert einen einfachen JSON-Status.
""" """
return jsonify({"status": "ok", "message": "The API is working."}) return jsonify({"message": "The API is working."})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment