403Webshell
Server IP : 54.37.205.81  /  Your IP : 216.73.216.76
Web Server : nginx/1.22.1
System : Linux vps-249481fa 6.1.0-50-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.176-1 (2026-07-02) x86_64
User : debian ( 1000)
PHP Version : 8.2.32
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /opt/ccp-ai-gateway/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/ccp-ai-gateway/app.py.bak_2026-01-23_205120
import os
import json
import time
import secrets
import tempfile
import re
from datetime import datetime, timezone

from flask import Flask, request, jsonify, make_response
from flask_cors import CORS
import requests

# PDF + QR
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors

try:
    import qrcode  # pip install "qrcode[pil]"
except Exception:
    qrcode = None

APP = Flask(__name__)
CORS(APP)

# ================== ENV ==================
ADMIN_TOKEN = os.environ.get("CCP_AI_ADMIN_TOKEN", "")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")

TOWER_PASSWORD = os.environ.get("CCP_TOWER_PASSWORD", "")
SESSION_SECRET = os.environ.get("CCP_SESSION_SECRET", "")

DATA_DIR = os.environ.get("CCP_AI_DATA_DIR", "/opt/ccp-ai-gateway/data")
USERS_FILE = os.path.join(DATA_DIR, "users.json")
USAGE_LOG = os.path.join(DATA_DIR, "ai_usage.jsonl")
CERT_USAGE_LOG = os.path.join(DATA_DIR, "cert_usage.jsonl")
BALANCES_FILE = os.path.join(DATA_DIR, "balances.json")
PRICING_FILE = os.path.join(DATA_DIR, "pricing.json")

# stima costo EUR (solo reporting)
COST_OPENAI_EUR = float(os.environ.get("CCP_COST_OPENAI_EUR_PER_REQ", "0.01"))
COST_GEMINI_EUR = float(os.environ.get("CCP_COST_GEMINI_EUR_PER_REQ", "0.04"))

os.makedirs(DATA_DIR, exist_ok=True)

# ================== UTILS ==================
def _now_iso():
    return datetime.now(timezone.utc).isoformat()

def _json_load(path, default):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return default

def _json_save(path, obj):
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(obj, f, ensure_ascii=False, indent=2)
    os.replace(tmp, path)

def _append_jsonl(path, obj):
    with open(path, "a", encoding="utf-8") as f:
        f.write(json.dumps(obj, ensure_ascii=False) + "\n")

def _safe_filename(s: str) -> str:
    s = (s or "").strip()
    s = re.sub(r"[^a-zA-Z0-9_\-\.]+", "_", s)
    return s[:120] if s else "file"

# ================== AUTH (admin header token) ==================
def require_admin_token(req):
    if not ADMIN_TOKEN:
        return True
    return req.headers.get("X-CCP-Token", "") == ADMIN_TOKEN

# ================== TOWER COOKIE SIGN ==================
def _sign(payload: dict) -> str:
    raw = json.dumps(payload, separators=(",", ":"), sort_keys=True)
    salt = secrets.token_urlsafe(12)
    import hashlib
    h = hashlib.sha256()
    h.update((SESSION_SECRET or "").encode("utf-8"))
    h.update(salt.encode("utf-8"))
    h.update(raw.encode("utf-8"))
    return f"{salt}.{h.hexdigest()}.{raw}"

def _verify(token: str):
    try:
        salt, digest, raw = token.split(".", 2)
        import hashlib
        h = hashlib.sha256()
        h.update((SESSION_SECRET or "").encode("utf-8"))
        h.update(salt.encode("utf-8"))
        h.update(raw.encode("utf-8"))
        if h.hexdigest() != digest:
            return None
        payload = json.loads(raw)
        if payload.get("exp") and time.time() > float(payload["exp"]):
            return None
        return payload
    except Exception:
        return None

def tower_session():
    tok = request.cookies.get("ccp_tower", "")
    # curl cookie-jar può salvare tra doppi apici e con virgole escaped (\054)
    tok = (tok or "").strip('"').replace("\\054", ",")
    if not tok or not SESSION_SECRET:
        return None
    payload = _verify(tok)
    if not payload or payload.get("role") != "tower_admin":
        return None
    return payload

def _tower_guard():
    if not (TOWER_PASSWORD and SESSION_SECRET and tower_session()):
        return jsonify({"error": "unauthorized"}), 401
    return None

# ================== USERS ==================
def ensure_users_file():
    if not os.path.exists(USERS_FILE):
        _json_save(USERS_FILE, {"users": []})

def find_user_by_token(tok: str):
    tok = (tok or "").strip()
    if not tok:
        return None
    ensure_users_file()
    data = _json_load(USERS_FILE, {"users": []})
    for u in data.get("users", []):
        if (u.get("token") or "").strip() == tok:
            return u
    return None

def upsert_user(user: dict):
    ensure_users_file()
    data = _json_load(USERS_FILE, {"users": []})
    users = data.get("users", [])
    uid = str(user.get("id"))
    found = False
    for i, u in enumerate(users):
        if str(u.get("id")) == uid:
            users[i] = {**u, **user}
            found = True
            break
    if not found:
        users.append(user)
    data["users"] = users
    _json_save(USERS_FILE, data)

# ================== BALANCES (ABO) ==================
def ensure_balances_file():
    if not os.path.exists(BALANCES_FILE):
        _json_save(BALANCES_FILE, {"balances": {}})

def get_balance(uid: str) -> float:
    ensure_balances_file()
    data = _json_load(BALANCES_FILE, {"balances": {}})
    b = data.get("balances", {}).get(str(uid), 0.0)
    try:
        return float(b)
    except Exception:
        return 0.0

def set_balance(uid: str, amount: float):
    ensure_balances_file()
    data = _json_load(BALANCES_FILE, {"balances": {}})
    data.setdefault("balances", {})[str(uid)] = float(amount)
    _json_save(BALANCES_FILE, data)

def add_balance(uid: str, delta: float) -> float:
    cur = get_balance(uid)
    newv = float(cur) + float(delta)
    set_balance(uid, newv)
    return newv

# ================== PRICING (ABO per operazione) ==================
def ensure_pricing_file():
    if not os.path.exists(PRICING_FILE):
        _json_save(PRICING_FILE, {
            "default_op_cost_abo": 1.0,
            "operations": {
                "generic": 1.0,
                "cert_base": 1.0,
                "cert_blockchain": 2.0
            }
        })

def get_pricing():
    ensure_pricing_file()
    data = _json_load(PRICING_FILE, {
        "default_op_cost_abo": 1.0,
        "operations": {"generic": 1.0, "cert_base": 1.0, "cert_blockchain": 2.0}
    })
    data.setdefault("default_op_cost_abo", 1.0)
    data.setdefault("operations", {"generic": data["default_op_cost_abo"]})
    return data

def op_cost_abo(op: str) -> float:
    p = get_pricing()
    op = (op or "generic").strip().lower()
    ops = p.get("operations", {}) or {}
    if op in ops:
        try:
            return float(ops[op])
        except Exception:
            return float(p.get("default_op_cost_abo", 1.0))
    return float(p.get("default_op_cost_abo", 1.0))

def debit_or_reject(uid: str, op: str):
    cost = op_cost_abo(op)
    bal = get_balance(uid)
    if bal < cost:
        return False, bal, cost
    newb = add_balance(uid, -cost)
    return True, newb, cost

# ================== USAGE (log) ==================
def cert_seen_request(uid: str, request_id: str) -> bool:
    request_id = (request_id or "").strip()
    if not request_id:
        return False
    try:
        with open(CERT_USAGE_LOG, "r", encoding="utf-8") as f:
            for line in f:
                try:
                    row = json.loads(line)
                    if str(row.get("user_id")) == str(uid) and str(row.get("request_id")) == request_id:
                        return True
                except Exception:
                    continue
    except Exception:
        return False
    return False

def cert_log_request(**kw):
    _append_jsonl(CERT_USAGE_LOG, {"ts": _now_iso(), **kw})

def log_ai_usage(**kw):
    _append_jsonl(USAGE_LOG, {"ts": _now_iso(), **kw})

# ================== PDF CERTIFICATE (BASE - template stile tuo PDF) ==================
def generate_base_certificate(
    file_hash: str,
    uid: str,
    request_id: str,
    *,
    cert_id: str = None,
    work_id: str = None,
    deposit_date: str = None,
    author: str = None,
    author_org: str = None,
    original_filename: str = None,
    verify_url: str = None
):
    out_dir = os.path.join(DATA_DIR, "certificates", "base")
    os.makedirs(out_dir, exist_ok=True)

    cert_id = (cert_id or f"CD-{request_id}").strip()
    work_id = (work_id or request_id).strip()
    deposit_date = (deposit_date or datetime.now(timezone.utc).strftime("%d/%m/%Y")).strip()
    author = (author or uid).strip()
    author_org = (author_org or "ABO Bank Ufficio Legale\nDipartimento di Proprietà\nIntellettuale e Diritto d’Autore").strip()
    original_filename = (original_filename or f"{work_id}.pdf").strip()
    verify_url = (verify_url or "").strip() or f"https://copyrightchain.it/verify?cid={cert_id}"

    filename = f"cert_base_{_safe_filename(uid)}_{_safe_filename(request_id)}.pdf"
    fullpath = os.path.join(out_dir, filename)

    # colori
    C_TEXT = colors.HexColor("#0b1220")
    C_MUTED = colors.HexColor("#667085")
    C_BORDER = colors.HexColor("#e5e7eb")
    C_PANEL = colors.HexColor("#f3f4f6")
    C_BADGE = colors.HexColor("#eef2ff")
    C_BADGE_TEXT = colors.HexColor("#1d4ed8")
    C_DARK = colors.HexColor("#111827")

    c = canvas.Canvas(fullpath, pagesize=A4)
    w, h = A4
    M = 2.0 * cm
    TOP = h - 2.2 * cm

    # icona
    shield_x = w/2 - 0.8*cm
    shield_y = TOP + 0.2*cm
    c.setFillColor(C_DARK)
    c.roundRect(shield_x, shield_y, 1.6*cm, 1.0*cm, 0.35*cm, stroke=0, fill=1)
    c.setFillColor(colors.white)
    c.setFont("Helvetica-Bold", 10)
    c.drawCentredString(w/2, shield_y + 0.32*cm, "✓")

    # titolo
    c.setFillColor(C_TEXT)
    c.setFont("Helvetica-Bold", 20)
    c.drawCentredString(w/2, TOP - 0.6*cm, "CERTIFICATO DI PROPRIETÀ")
    c.drawCentredString(w/2, TOP - 1.35*cm, "INTELLETTUALE")

    c.setFont("Helvetica", 8.5)
    c.setFillColor(C_MUTED)
    c.drawCentredString(w/2, TOP - 2.0*cm, "EMESSO DA ABO BANK LEGAL DIVISION")

    # badge proof
    badge_y = TOP - 3.2*cm
    badge_h = 1.2*cm
    badge_w = w - 2*M
    c.setFillColor(C_BADGE)
    c.roundRect(M, badge_y, badge_w, badge_h, 0.25*cm, stroke=0, fill=1)
    c.setFillColor(C_BADGE_TEXT)
    c.setFont("Helvetica-Bold", 9)
    c.drawString(M + 1.0*cm, badge_y + 0.75*cm, "DIGITAL FINGERPRINT PROOF")
    c.setFont("Helvetica", 8)
    c.setFillColor(C_MUTED)
    c.drawString(M + 1.0*cm, badge_y + 0.35*cm, "STATUS: CERTIFIED")

    # info 2 colonne
    info_top = badge_y - 0.9*cm
    col_w = (w - 2*M) / 2.0

    c.setFillColor(C_MUTED); c.setFont("Helvetica", 8)
    c.drawString(M, info_top, "TITOLO / IDENTIFICATORE")
    c.setFillColor(C_TEXT); c.setFont("Helvetica-Bold", 11)
    c.drawString(M, info_top - 0.55*cm, str(work_id))

    c.setFillColor(C_MUTED); c.setFont("Helvetica", 8)
    c.drawString(M + col_w, info_top, "ID CERTIFICATO")
    c.setFillColor(C_BADGE_TEXT); c.setFont("Helvetica-Bold", 11)
    c.drawString(M + col_w, info_top - 0.55*cm, str(cert_id))

    # riga 3 campi
    row2_y = info_top - 1.55*cm
    third = (w - 2*M) / 3.0

    c.setFillColor(C_MUTED); c.setFont("Helvetica", 8)
    c.drawString(M, row2_y, "DATA DI DEPOSITO")
    c.setFillColor(C_TEXT); c.setFont("Helvetica-Bold", 9.5)
    c.drawString(M, row2_y - 0.45*cm, str(deposit_date))

    c.setFillColor(C_MUTED); c.setFont("Helvetica", 8)
    c.drawString(M + third, row2_y, "AUTORE / REGISTRANTE")
    c.setFillColor(C_TEXT); c.setFont("Helvetica-Bold", 9.5)
    c.drawString(M + third, row2_y - 0.45*cm, str(author))

    c.setFillColor(C_MUTED); c.setFont("Helvetica", 8)
    c.drawString(M + 2*third, row2_y, "FILE ORIGINALE")
    c.setFillColor(C_TEXT); c.setFont("Helvetica-Bold", 9.5)
    c.drawString(M + 2*third, row2_y - 0.45*cm, str(original_filename))

    # separatore
    sep_y = row2_y - 0.9*cm
    c.setStrokeColor(C_BORDER)
    c.setLineWidth(1)
    c.line(M, sep_y, w - M, sep_y)

    # box attestazione
    box_y = sep_y - 8.2*cm
    box_h = 7.4*cm
    box_w = w - 2*M
    c.setFillColor(C_PANEL)
    c.roundRect(M, box_y, box_w, box_h, 0.25*cm, stroke=0, fill=1)

    c.setFillColor(C_MUTED)
    c.setFont("Helvetica-Bold", 9)
    c.drawString(M + 0.6*cm, box_y + box_h - 0.8*cm, "ATTESTAZIONE LEGALE")

    legal = (
        "Il presente documento costituisce attestazione di avvenuta registrazione con marcatura temporale "
        "e impronta digitale (SHA-256) del contenuto indicato. L’impronta digitale consente di verificare "
        "l’integrità del file: qualsiasi modifica, anche minima, produce un’impronta differente.\n\n"
        "La certificazione BASE fornisce prova di esistenza alla data indicata mediante registrazione e log "
        "di sistema. La verifica può essere effettuata confrontando l’hash riportato con quello calcolato "
        "sul file originale."
    )
    c.setFillColor(C_TEXT)
    c.setFont("Helvetica", 9)
    text = c.beginText(M + 0.6*cm, box_y + box_h - 1.5*cm)
    text.setLeading(12)
    for line in legal.split("\n"):
        text.textLine(line)
    c.drawText(text)

    # footer fingerprint + evidence + QR
    foot_y = box_y - 2.2*cm

    fp_w = (w - 2*M) * 0.62
    fp_h = 1.0*cm
    c.setFillColor(colors.white)
    c.setStrokeColor(C_BORDER)
    c.roundRect(M, foot_y + 0.9*cm, fp_w, fp_h, 0.2*cm, stroke=1, fill=1)
    c.setFillColor(C_MUTED)
    c.setFont("Helvetica", 8)
    c.drawString(M, foot_y + 2.2*cm, "DIGITAL FINGERPRINT (SHA-256 HASH)")
    c.setFillColor(C_TEXT)
    c.setFont("Helvetica", 8.5)
    c.drawString(M + 0.35*cm, foot_y + 1.22*cm, (file_hash or "")[:90])

    ev_x = M + fp_w + 0.6*cm
    ev_w = (w - 2*M) - fp_w - 0.6*cm - 4.0*cm
    c.setFillColor(C_DARK)
    c.setStrokeColor(C_DARK)
    c.roundRect(ev_x, foot_y + 0.9*cm, ev_w, fp_h, 0.2*cm, stroke=1, fill=1)
    c.setFillColor(colors.white)
    c.setFont("Helvetica", 8)
    c.drawString(ev_x + 0.35*cm, foot_y + 1.22*cm, "EVIDENCE: BASE (N/A)")

    # QR
    qr_x = w - M - 3.6*cm
    qr_y = foot_y + 0.35*cm
    qr_size = 3.2*cm

    if qrcode is not None:
        tmp_png = None
        try:
            qr_img = qrcode.make(verify_url)
            fd, tmp_png = tempfile.mkstemp(prefix="ccp_qr_", suffix=".png")
            os.close(fd)
            qr_img.save(tmp_png)
            c.drawImage(tmp_png, qr_x, qr_y, width=qr_size, height=qr_size, mask="auto")
        finally:
            if tmp_png and os.path.exists(tmp_png):
                try:
                    os.remove(tmp_png)
                except Exception:
                    pass
    else:
        # fallback: niente qr, solo testo url
        c.setFillColor(C_MUTED)
        c.setFont("Helvetica", 7.5)
        c.drawString(qr_x - 1.0*cm, qr_y + 1.5*cm, "QR DISABILITATO")
        c.drawString(qr_x - 1.0*cm, qr_y + 1.1*cm, verify_url[:55])

    c.setFillColor(C_MUTED)
    c.setFont("Helvetica-Bold", 7.5)
    c.drawCentredString(qr_x + qr_size/2, qr_y - 0.2*cm, "SCANSIONA PER VERIFICARE")

    # badge finale
    badge2_y = 1.2*cm
    c.setFillColor(colors.HexColor("#e8fff0"))
    c.setStrokeColor(colors.HexColor("#86efac"))
    c.roundRect(M, badge2_y, 6.0*cm, 0.8*cm, 0.2*cm, stroke=1, fill=1)
    c.setFillColor(colors.HexColor("#166534"))
    c.setFont("Helvetica-Bold", 8)
    c.drawCentredString(M + 3.0*cm, badge2_y + 0.27*cm, "CERTIFICAZIONE AUTENTICATA")

    c.setFillColor(C_MUTED)
    c.setFont("Helvetica", 7.5)
    c.drawString(M, 0.6*cm, "Documento generato dal gateway CopyrightChain ai sensi delle normative vigenti.")
    c.drawRightString(w - M, 0.6*cm, "ABO BANK LEGAL SERVICES • INTELLECTUAL PROPERTY DIVISION")

    c.showPage()
    c.save()
    return fullpath

# ================== CERT CORE ==================
def _resolve_uid_from_request(payload):
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok) if tok else None
    if user:
        return str(user.get("id")), user, None
    uid = payload.get("user_id")
    if uid:
        return str(uid), None, None
    return None, None, (jsonify({"error": "unauthorized"}), 401)

def _cert_debit_core(payload):
    cert_type = str(payload.get("cert_type", "base")).strip().lower()
    request_id = str(payload.get("request_id", "")).strip()
    file_hash = str(payload.get("file_hash", "")).strip()

    # campi extra per PDF (come nel tuo modello)
    cert_id = (payload.get("cert_id") or None)
    work_id = (payload.get("work_id") or None)
    deposit_date = (payload.get("deposit_date") or None)
    author = (payload.get("author") or None)
    author_org = (payload.get("author_org") or None)
    original_filename = (payload.get("original_filename") or None)
    verify_url = (payload.get("verify_url") or None)

    cert_id = str(cert_id).strip() if cert_id is not None else None
    work_id = str(work_id).strip() if work_id is not None else None
    deposit_date = str(deposit_date).strip() if deposit_date is not None else None
    author = str(author).strip() if author is not None else None
    author_org = str(author_org).strip() if author_org is not None else None
    original_filename = str(original_filename).strip() if original_filename is not None else None
    verify_url = str(verify_url).strip() if verify_url is not None else None

    if cert_type not in ("base", "blockchain"):
        return jsonify({"error": "invalid_cert_type", "allowed": ["base", "blockchain"]}), 400
    if not request_id:
        return jsonify({"error": "missing_request_id"}), 400

    uid, user, err = _resolve_uid_from_request(payload)
    if err:
        return err

    op = "cert_blockchain" if cert_type == "blockchain" else "cert_base"

    # idempotenza
    if cert_seen_request(uid, request_id):
        return jsonify({
            "ok": True,
            "user_id": uid,
            "cert_type": cert_type,
            "op": op,
            "idempotent_replay": True,
            "request_id": request_id,
            "balance_abo": get_balance(uid)
        }), 200

    # addebito
    ok_debit, bal_before, cost_abo = debit_or_reject(uid, op)
    if not ok_debit:
        return jsonify({
            "error": "insufficient_balance",
            "op": op,
            "cert_type": cert_type,
            "cost_abo": cost_abo,
            "balance_abo": bal_before
        }), 402

    certificate_path = None

    try:
        # genera PDF SOLO per base
        if cert_type == "base":
            certificate_path = generate_base_certificate(
                file_hash=file_hash,
                uid=str(uid),
                request_id=str(request_id),
                cert_id=cert_id,
                work_id=work_id,
                deposit_date=deposit_date,
                author=author,
                author_org=author_org,
                original_filename=original_filename,
                verify_url=verify_url
            )
    except Exception as e:
        # se fallisce la generazione PDF: rimborsa ABO e NON registra la request (così puoi riprovare)
        add_balance(uid, cost_abo)
        log_ai_usage(
            user_id=uid,
            provider="gateway",
            ok=False,
            status=500,
            ms=0,
            cost_eur=0.0,
            op="cert_pdf_base",
            abo_cost=0.0,
            cert_type=cert_type,
            request_id=request_id,
            error="pdf_generation_failed",
            details=str(e)
        )
        return jsonify({"error": "pdf_generation_failed", "details": str(e)}), 500

    # registra request SOLO se tutto ok
    cert_log_request(
        user_id=uid,
        request_id=request_id,
        op=op,
        cert_type=cert_type,
        abo_cost=cost_abo,
        file_hash=file_hash
    )

    log_ai_usage(
        user_id=uid,
        provider="gateway",
        ok=True,
        status=200,
        ms=0,
        cost_eur=0.0,
        op=op,
        abo_cost=cost_abo,
        cert_type=cert_type,
        request_id=request_id
    )

    return jsonify({
        "ok": True,
        "user_id": uid,
        "cert_type": cert_type,
        "op": op,
        "debited_abo": cost_abo,
        "idempotent_replay": False,
        "request_id": request_id,
        "balance_abo": get_balance(uid),
        "certificate_path": certificate_path
    }), 200

# ================== ROUTES ==================
@APP.get("/health")
def health():
    return jsonify({
        "ok": True,
        "has_openai_key": bool(OPENAI_API_KEY),
        "has_gemini_key": bool(GEMINI_API_KEY),
        "tower_configured": bool(TOWER_PASSWORD and SESSION_SECRET),
        "has_qrcode": bool(qrcode is not None)
    })

@APP.post("/tower/login")
def tower_login():
    data = request.get_json(silent=True) or {}
    if data.get("password") != TOWER_PASSWORD:
        return jsonify({"error": "unauthorized"}), 401
    tok = _sign({"role": "tower_admin", "exp": time.time() + 86400})
    resp = make_response(jsonify({"ok": True}))
    # NOTA: secure=False per permettere curl su http://127.0.0.1
    resp.set_cookie("ccp_tower", tok, httponly=True, secure=False, samesite="Lax", path="/")
    return resp

@APP.post("/tower/balances/topup")
def tower_balances_topup():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    uid = str(data.get("user_id", "")).strip()
    amount = data.get("amount", None)
    if not uid or amount is None:
        return jsonify({"error": "missing_params"}), 400
    try:
        amount = float(amount)
    except Exception:
        return jsonify({"error": "invalid_amount"}), 400
    newb = add_balance(uid, amount)
    return jsonify({"ok": True, "user_id": uid, "balance_abo": newb})

@APP.post("/tower/balances/set")
def tower_balances_set():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    uid = str(data.get("user_id", "")).strip()
    balance = data.get("balance", None)
    if not uid or balance is None:
        return jsonify({"error": "missing_params"}), 400
    try:
        balance = float(balance)
    except Exception:
        return jsonify({"error": "invalid_balance"}), 400
    set_balance(uid, balance)
    return jsonify({"ok": True, "user_id": uid, "balance_abo": balance})

@APP.get("/tower/pricing")
def tower_pricing_get():
    g = _tower_guard()
    if g:
        return g
    return jsonify(get_pricing())

@APP.post("/tower/pricing/set")
def tower_pricing_set():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    cur = get_pricing()

    default_cost = data.get("default_op_cost_abo", None)
    operations = data.get("operations", None)

    if default_cost is not None:
        try:
            cur["default_op_cost_abo"] = float(default_cost)
        except Exception:
            return jsonify({"error": "invalid_default_cost"}), 400

    if operations is not None:
        if not isinstance(operations, dict):
            return jsonify({"error": "invalid_operations"}), 400
        cleaned = {}
        for k, v in operations.items():
            try:
                cleaned[str(k).strip().lower()] = float(v)
            except Exception:
                continue
        if cleaned:
            cur["operations"] = cleaned

    _json_save(PRICING_FILE, cur)
    return jsonify({"ok": True, "pricing": cur})

@APP.post("/certify")
def certify():
    payload = request.get_json(silent=True) or {}
    return _cert_debit_core(payload)

@APP.post("/cert/debit")
def cert_debit():
    payload = request.get_json(silent=True) or {}
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok)
    if not user:
        return jsonify({"error": "unauthorized"}), 401
    return _cert_debit_core(payload)

@APP.post("/text")
def ai_text():
    payload = request.get_json(silent=True) or {}
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok)
    if not user:
        return jsonify({"error": "unauthorized"}), 401

    prompt = str(payload.get("prompt", "")).strip()
    if not prompt:
        return jsonify({"error": "missing_prompt"}), 400

    op = str(payload.get("op", "generic")).strip().lower()
    uid = str(user.get("id"))

    ok_debit, bal_before, cost_abo = debit_or_reject(uid, op)
    if not ok_debit:
        return jsonify({
            "error": "insufficient_balance",
            "op": op,
            "cost_abo": cost_abo,
            "balance_abo": bal_before
        }), 402

    t0 = time.time()
    r = requests.post(
        "https://api.openai.com/v1/responses",
        headers={"Authorization": f"Bearer {OPENAI_API_KEY}"},
        json={"model": "gpt-4.1-mini", "input": [{"role": "user", "content": [{"type": "input_text", "text": prompt}]}]},
        timeout=120
    )
    ms = int((time.time() - t0) * 1000)

    if r.status_code >= 400:
        add_balance(uid, cost_abo)
        log_ai_usage(
            user_id=uid, provider="openai", ok=False, status=r.status_code, ms=ms,
            cost_eur=0.0, op=op, abo_cost=cost_abo, error="openai_error"
        )
        return jsonify({"error": "openai_error", "details": r.text}), r.status_code

    log_ai_usage(
        user_id=uid, provider="openai", ok=True, status=200, ms=ms,
        cost_eur=COST_OPENAI_EUR, op=op, abo_cost=cost_abo
    )

    try:
        j = r.json()
    except Exception:
        j = {}
    text = (j.get("output_text") or "").strip()
    if not text:
        out = j.get("output") or []
        if isinstance(out, list):
            parts = []
            for it in out:
                if isinstance(it, dict):
                    for c_ in (it.get("content") or []):
                        if isinstance(c_, dict) and (c_.get("text") or ""):
                            parts.append(c_.get("text"))
            text = ("".join(parts)).strip()
    return jsonify({"ok": True, "text": text})

if __name__ == "__main__":
    APP.run("127.0.0.1", 5060)


Youez - 2016 - github.com/yon3zu
LinuXploit