fix(billing): JWT token incompatibility — use saas_auth require_auth + fix table names HRT-54
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -24,9 +24,9 @@ import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import stripe
|
||||
from flask import Blueprint, g, jsonify, request
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from auth import jwt_required_middleware
|
||||
from saas_auth import require_auth as jwt_required_middleware
|
||||
from billing_db import get_db, migrate_billing_tables
|
||||
|
||||
logger = logging.getLogger("turf_saas.billing")
|
||||
@@ -73,18 +73,18 @@ def _sget(obj, key, default=None):
|
||||
return default
|
||||
|
||||
|
||||
def _get_active_subscription(db, user_id: int):
|
||||
def _get_active_subscription(db, user_id):
|
||||
"""Return the most recent active subscription row for a user."""
|
||||
return db.execute(
|
||||
"""SELECT * FROM subscriptions
|
||||
"""SELECT * FROM saas_subscriptions
|
||||
WHERE user_id = ?
|
||||
ORDER BY start_date DESC
|
||||
LIMIT 1""",
|
||||
(user_id,),
|
||||
(str(user_id),),
|
||||
).fetchone()
|
||||
|
||||
|
||||
def _upsert_subscription(db, user_id: int, **fields):
|
||||
def _upsert_subscription(db, user_id, **fields):
|
||||
"""
|
||||
Update existing subscription or insert a new one.
|
||||
fields: plan, stripe_customer_id, stripe_subscription_id,
|
||||
@@ -95,19 +95,19 @@ def _upsert_subscription(db, user_id: int, **fields):
|
||||
# Build SET clause dynamically from provided fields
|
||||
set_parts = ", ".join(f"{k} = ?" for k in fields)
|
||||
values = list(fields.values()) + [existing["id"]]
|
||||
db.execute(f"UPDATE subscriptions SET {set_parts} WHERE id = ?", values)
|
||||
db.execute(f"UPDATE saas_subscriptions SET {set_parts} WHERE id = ?", values)
|
||||
else:
|
||||
cols = ", ".join(["user_id"] + list(fields.keys()))
|
||||
placeholders = ", ".join(["?"] * (1 + len(fields)))
|
||||
values = [user_id] + list(fields.values())
|
||||
values = [str(user_id)] + list(fields.values())
|
||||
db.execute(
|
||||
f"INSERT INTO subscriptions ({cols}) VALUES ({placeholders})", values
|
||||
f"INSERT INTO saas_subscriptions ({cols}) VALUES ({placeholders})", values
|
||||
)
|
||||
|
||||
|
||||
def _update_user_plan(db, user_id: int, plan: str):
|
||||
"""Sync users.plan field to match active subscription."""
|
||||
db.execute("UPDATE users SET plan = ? WHERE id = ?", (plan, user_id))
|
||||
def _update_user_plan(db, user_id, plan: str):
|
||||
"""Sync saas_users.plan field to match active subscription."""
|
||||
db.execute("UPDATE saas_users SET plan = ? WHERE id = ?", (plan, str(user_id)))
|
||||
|
||||
|
||||
def _get_or_create_stripe_customer(user, db) -> str:
|
||||
@@ -198,7 +198,7 @@ def create_checkout():
|
||||
if not price_id:
|
||||
return jsonify({"error": f"Prix Stripe non configuré pour le plan {plan}"}), 503
|
||||
|
||||
user = g.current_user
|
||||
user = request.current_user
|
||||
if user["plan"] == plan:
|
||||
return jsonify({"error": f"Vous êtes déjà sur le plan {plan}"}), 400
|
||||
|
||||
@@ -263,7 +263,7 @@ def create_portal():
|
||||
if not stripe.api_key:
|
||||
return jsonify({"error": "Stripe non configuré"}), 503
|
||||
|
||||
user = g.current_user
|
||||
user = request.current_user
|
||||
db = get_db()
|
||||
try:
|
||||
sub = _get_active_subscription(db, user["id"])
|
||||
@@ -309,7 +309,7 @@ def billing_status():
|
||||
200:
|
||||
description: Subscription status
|
||||
"""
|
||||
user = g.current_user
|
||||
user = request.current_user
|
||||
db = get_db()
|
||||
try:
|
||||
sub = _get_active_subscription(db, user["id"])
|
||||
@@ -428,7 +428,7 @@ def stripe_webhook():
|
||||
def _resolve_user_from_customer(db, customer_id: str):
|
||||
"""Look up user_id via subscriptions.stripe_customer_id."""
|
||||
row = db.execute(
|
||||
"SELECT user_id FROM subscriptions WHERE stripe_customer_id = ? LIMIT 1",
|
||||
"SELECT user_id FROM saas_subscriptions WHERE stripe_customer_id = ? LIMIT 1",
|
||||
(customer_id,),
|
||||
).fetchone()
|
||||
if row:
|
||||
@@ -465,7 +465,7 @@ def _handle_checkout_completed(db, event):
|
||||
user_id = _sget(metadata, "user_id")
|
||||
|
||||
if user_id:
|
||||
user_id = int(user_id)
|
||||
user_id = str(user_id)
|
||||
else:
|
||||
user_id = _resolve_user_from_customer(db, customer_id)
|
||||
|
||||
@@ -531,7 +531,7 @@ def _handle_subscription_updated(db, event):
|
||||
meta = _sget(sub_obj, "metadata") or {}
|
||||
meta_uid = _sget(meta, "user_id")
|
||||
if meta_uid:
|
||||
user_id = int(meta_uid)
|
||||
user_id = str(meta_uid)
|
||||
|
||||
if not user_id:
|
||||
logger.error(
|
||||
@@ -565,7 +565,7 @@ def _handle_subscription_deleted(db, event):
|
||||
meta = _sget(sub_obj, "metadata") or {}
|
||||
meta_uid = _sget(meta, "user_id")
|
||||
if meta_uid:
|
||||
user_id = int(meta_uid)
|
||||
user_id = str(meta_uid)
|
||||
|
||||
if not user_id:
|
||||
logger.error(
|
||||
|
||||
@@ -76,14 +76,30 @@ def migrate_billing_tables():
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
stripe_event_id TEXT NOT NULL UNIQUE,
|
||||
event_type TEXT NOT NULL,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
user_id TEXT,
|
||||
payload TEXT,
|
||||
processed_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_billing_events_user ON billing_events(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_billing_events_type ON billing_events(event_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe ON subscriptions(stripe_subscription_id);
|
||||
CREATE TABLE IF NOT EXISTS saas_subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
plan TEXT NOT NULL DEFAULT 'free',
|
||||
start_date DATETIME DEFAULT (datetime('now')),
|
||||
end_date DATETIME,
|
||||
stripe_customer_id TEXT,
|
||||
stripe_subscription_id TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
grace_period_end DATETIME,
|
||||
current_period_end DATETIME
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_billing_events_user ON billing_events(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_billing_events_type ON billing_events(event_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_saas_subs_user ON saas_subscriptions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_saas_subs_customer ON saas_subscriptions(stripe_customer_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_saas_subs_stripe ON saas_subscriptions(stripe_subscription_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe ON subscriptions(stripe_subscription_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer ON subscriptions(stripe_customer_id);
|
||||
""")
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import sqlite3
|
||||
import re
|
||||
import os
|
||||
|
||||
DB_PATH = "/home/h3r7/turf_scraper/turf.db"
|
||||
DB_PATH = "/home/h3r7/turf_saas/turf_saas.db"
|
||||
HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept-Language': 'fr-FR,fr;q=0.9,en;q=0.8',
|
||||
|
||||
@@ -38,7 +38,7 @@ from pathlib import Path
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# CONFIG
|
||||
# ─────────────────────────────────────────────────────────
|
||||
DB_PATH = "/home/h3r7/turf_scraper/turf.db"
|
||||
DB_PATH = "/home/h3r7/turf_saas/turf_saas.db"
|
||||
OUTPUT_DIR = Path("/home/h3r7/turf_scraper")
|
||||
API_BASE = "https://online.turfinfo.api.pmu.fr/rest/client/7"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from flask import Blueprint, request, jsonify
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime
|
||||
from .saas_auth import require_auth
|
||||
from saas_auth import require_auth
|
||||
|
||||
DB_PATH = os.environ.get("TURF_SAAS_DB", "/home/h3r7/turf_saas/turf_saas.db")
|
||||
|
||||
@@ -255,3 +255,28 @@ def export_csv():
|
||||
"Content-Disposition": f"attachment; filename=turf_ia_{date_param}.csv"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ─── Billing Blueprint (Stripe) + JWT init — HRT-49 ─────────────────────────
|
||||
# Registers /api/v1/billing/* routes via nested Blueprint (Flask 2.0+)
|
||||
# Also initializes JWTManager on the Flask app (required for jwt_required_middleware)
|
||||
try:
|
||||
from flask_jwt_extended import JWTManager
|
||||
from api_v1.routes.billing import billing_bp
|
||||
|
||||
# Initialize JWTManager on the Flask app when api_v1_bp is registered
|
||||
@api_v1_bp.record_once
|
||||
def _init_jwt(state):
|
||||
app = state.app
|
||||
if not app.config.get('JWT_SECRET_KEY'):
|
||||
import os
|
||||
app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY', 'turf-saas-secret-key-change-in-prod')
|
||||
if 'flask_jwt_extended' not in app.extensions:
|
||||
JWTManager(app)
|
||||
|
||||
# Register billing blueprint with url_prefix='/billing'
|
||||
# (parent api_v1_bp has '/api/v1', so result is /api/v1/billing/*)
|
||||
api_v1_bp.register_blueprint(billing_bp, url_prefix='/billing')
|
||||
print('[saas_api_v1] Billing blueprint (Stripe) + JWT registered ✅')
|
||||
except Exception as _billing_err:
|
||||
print(f'[saas_api_v1] Warning: billing blueprint not loaded: {_billing_err}')
|
||||
|
||||
@@ -10,7 +10,7 @@ import json
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
DB_PATH = "/home/h3r7/turf_scraper/turf.db"
|
||||
DB_PATH = "/home/h3r7/turf_saas/turf_saas.db"
|
||||
HEADERS = {'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json'}
|
||||
|
||||
def get_cote_from_db(horse_name, date_course):
|
||||
|
||||
Reference in New Issue
Block a user