| 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 : |
import os
import json
import time
import secrets
from datetime import datetime, timezone
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS
import requests
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, non billing)
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")
# ================== 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.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.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", "")
# PATCH: curl cookie-jar può salvare il valore 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}
})
def get_pricing():
ensure_pricing_file()
data = _json_load(PRICING_FILE, {"default_op_cost_abo": 1.0, "operations": {"generic": 1.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 FileNotFoundError:
return False
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})
def summarize_usage(days=30):
since = time.time() - int(days) * 86400
res = {"by_user": {}, "total_requests": 0, "total_ok": 0, "total_cost_eur": 0.0}
if not os.path.exists(USAGE_LOG):
return {**res, "days": int(days), "from": datetime.fromtimestamp(since, timezone.utc).isoformat(), "to": _now_iso()}
with open(USAGE_LOG, "r", encoding="utf-8") as f:
for line in f:
try:
row = json.loads(line)
ts = datetime.fromisoformat(row["ts"]).timestamp()
if ts < since:
continue
uid = str(row.get("user_id", "unknown"))
u = res["by_user"].setdefault(uid, {"requests": 0, "ok": 0, "cost_eur": 0.0, "providers": {}})
u["requests"] += 1
res["total_requests"] += 1
if row.get("ok"):
u["ok"] += 1
res["total_ok"] += 1
cost = float(row.get("cost_eur", 0.0) or 0.0)
u["cost_eur"] += cost
res["total_cost_eur"] += cost
p = row.get("provider", "unknown")
u["providers"][p] = u["providers"].get(p, 0) + 1
except Exception:
pass
return {
**res,
"days": int(days),
"from": datetime.fromtimestamp(since, timezone.utc).isoformat(),
"to": _now_iso()
}
# ================== 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)
})
@APP.get("/openapi.json")
def openapi_json():
base = "/api/ai"
return jsonify({
"openapi": "3.0.0",
"info": {"title": "CCP AI Gateway", "version": "1.0.0"},
"servers": [{"url": base}],
"paths": {
"/health": {"get": {"summary": "Health", "responses": {"200": {"description": "OK"}}}},
"/tower/login": {"post": {"summary": "Tower login", "responses": {"200": {"description": "OK"}}}},
"/tower/users/upsert": {"post": {"summary": "Upsert user", "responses": {"200": {"description": "OK"}}}},
"/tower/users/list": {"get": {"summary": "List users", "responses": {"200": {"description": "OK"}}}},
"/tower/ai/stats": {"get": {"summary": "AI stats", "responses": {"200": {"description": "OK"}}}},
"/tower/balances": {"get": {"summary": "Balances dump", "responses": {"200": {"description": "OK"}}}},
"/tower/balances/topup": {"post": {"summary": "Topup", "responses": {"200": {"description": "OK"}}}},
"/tower/balances/set": {"post": {"summary": "Set balance", "responses": {"200": {"description": "OK"}}}},
"/tower/pricing": {"get": {"summary": "Get pricing", "responses": {"200": {"description": "OK"}}}},
"/tower/pricing/set": {"post": {"summary": "Set pricing", "responses": {"200": {"description": "OK"}}}},
"/me/balance": {"get": {"summary": "My balance", "responses": {"200": {"description": "OK"}}}},
"/cert/debit": {"post": {"summary": "Debit certificate (base/blockchain)", "responses": {"200": {"description": "OK"}}}},
"/text": {"post": {"summary": "AI text", "responses": {"200": {"description": "OK"}}}}
}
})
@APP.get("/docs")
def docs():
html = """<!doctype html><html><head><meta charset="utf-8"><title>CCP AI Gateway Docs</title></head>
<body style="font-family:Arial,sans-serif;padding:20px">
<h2>CCP AI Gateway</h2>
<ul>
<li><a href="/api/ai/health">/api/ai/health</a></li>
<li><a href="/api/ai/openapi.json">/api/ai/openapi.json</a></li>
</ul>
</body></html>"""
return make_response(html, 200, {"Content-Type": "text/html; charset=utf-8"})
@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}))
resp.set_cookie("ccp_tower", tok, httponly=True, secure=True, samesite="Lax", path="/")
return resp
@APP.post("/tower/users/upsert")
def tower_users_upsert():
g = _tower_guard()
if g:
return g
data = request.get_json(silent=True) or {}
if not data.get("id"):
return jsonify({"error": "missing_id"}), 400
user = {
"id": str(data["id"]),
"token": data.get("token", ""),
"email": data.get("email", ""),
"name": data.get("name", ""),
"wallet": data.get("wallet", ""),
"plan": data.get("plan", "free"),
# mantenuto solo per compatibilità UI, ma NON usato per bloccare
"ai_limit_monthly": int(data.get("ai_limit_monthly", 0) or 0),
"updated_at": _now_iso()
}
upsert_user(user)
return jsonify({"ok": True})
@APP.get("/tower/users/list")
def tower_users_list():
g = _tower_guard()
if g:
return g
ensure_users_file()
return jsonify(_json_load(USERS_FILE, {"users": []}))
@APP.get("/tower/ai/stats")
def tower_ai_stats():
g = _tower_guard()
if g:
return g
days = int(request.args.get("days", "30") or "30")
days = max(1, min(days, 365))
return jsonify(summarize_usage(days))
@APP.get("/tower/balances")
def tower_balances():
g = _tower_guard()
if g:
return g
ensure_balances_file()
return jsonify(_json_load(BALANCES_FILE, {"balances": {}}))
@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 {}
# valida e salva
default_cost = data.get("default_op_cost_abo", None)
operations = data.get("operations", None)
cur = get_pricing()
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.get("/me/balance")
def me_balance():
tok = request.headers.get("X-CCP-Token", "")
user = find_user_by_token(tok)
if not user:
return jsonify({"error": "unauthorized"}), 401
uid = str(user.get("id"))
return jsonify({"ok": True, "user_id": uid, "balance_abo": get_balance(uid), "pricing": get_pricing()})
@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
cert_type = str(payload.get("cert_type", "base")).strip().lower()
request_id = str(payload.get("request_id", "")).strip()
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
op = "cert_blockchain" if cert_type == "blockchain" else "cert_base"
uid = str(user.get("id"))
# idempotenza: se già visto, non scalare
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)
})
# 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
# ledger certificati (idempotenza)
cert_log_request(
user_id=uid,
request_id=request_id,
op=op,
cert_type=cert_type,
abo_cost=cost_abo
)
# log generale
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)
})
@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"))
# 1) scala ABO prima della chiamata
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": prompt},
timeout=120
)
ms = int((time.time() - t0) * 1000)
# 2) se OpenAI fallisce: rimborsa ABO
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
)
# risposta testo
try:
j = r.json()
except Exception:
j = {}
return jsonify({"ok": True, "text": (j.get("output_text") or "").strip()})
if __name__ == "__main__":
APP.run("127.0.0.1", 5060)