From ef39d01e1679d4378ac461cf87d5807df4bf240b Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Tue, 31 Mar 2026 01:51:08 +0200 Subject: [PATCH] fixed database issue subscriptions --- modules/auth/csrf.py | 15 ++++++++------ modules/interfaces/interfaceDbApp.py | 25 ++++++++++++---------- modules/routes/routeDataMandates.py | 31 +++++++++++++++++++++++++++- modules/routes/routeStore.py | 11 ++++++---- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/modules/auth/csrf.py b/modules/auth/csrf.py index 7cc0c07c..bac4b0c3 100644 --- a/modules/auth/csrf.py +++ b/modules/auth/csrf.py @@ -88,12 +88,15 @@ class CSRFMiddleware(BaseHTTPMiddleware): content={"detail": "Invalid CSRF token format"} ) - # Additional CSRF validation could be added here: - # - Check token against session - # - Validate token expiration - # - Verify token origin - - return await call_next(request) + try: + return await call_next(request) + except Exception as exc: + logger.error("Unhandled exception in %s %s: %s", request.method, request.url.path, exc) + from fastapi.responses import JSONResponse + return JSONResponse( + status_code=500, + content={"detail": "Internal server error"}, + ) def _is_valid_csrf_token(self, token: str) -> bool: """ diff --git a/modules/interfaces/interfaceDbApp.py b/modules/interfaces/interfaceDbApp.py index d980eb56..2ac768fd 100644 --- a/modules/interfaces/interfaceDbApp.py +++ b/modules/interfaces/interfaceDbApp.py @@ -1728,8 +1728,10 @@ class AppObjects: self.db.recordDelete(UserMandate, um.get("id")) logger.info(f"Cascade: deleted {len(memberships)} UserMandates for mandate {mandateId}") - # 3. Cancel Stripe subscriptions + delete MandateSubscription records - subs = self.db.getRecordset(MandateSubscription, recordFilter={"mandateId": mandateId}) + # 3. Cancel Stripe subscriptions + delete MandateSubscription records (poweron_billing) + from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot + subInterface = _getSubRoot() + subs = subInterface.listForMandate(mandateId) for sub in subs: subId = sub.get("id") stripeSubId = sub.get("stripeSubscriptionId") @@ -1741,20 +1743,21 @@ class AppObjects: logger.info(f"Cancelled Stripe subscription {stripeSubId} for mandate {mandateId}") except Exception as e: logger.warning(f"Failed to cancel Stripe sub {stripeSubId}: {e}") - self.db.recordDelete(MandateSubscription, subId) + subInterface.db.recordDelete(MandateSubscription, subId) logger.info(f"Cascade: deleted {len(subs)} subscriptions for mandate {mandateId}") - # 3b. Delete Billing data - billingTxs = self.db.getRecordset(BillingTransaction, recordFilter={"mandateId": mandateId}) if hasattr(BillingTransaction, '__table_name__') else [] - billingAccounts = self.db.getRecordset(BillingAccount, recordFilter={"mandateId": mandateId}) + # 3b. Delete Billing data (poweron_billing) + from modules.interfaces.interfaceDbBilling import _getRootInterface as _getBillingRoot + billingDb = _getBillingRoot().db + billingAccounts = billingDb.getRecordset(BillingAccount, recordFilter={"mandateId": mandateId}) for acc in billingAccounts: - accTxs = self.db.getRecordset(BillingTransaction, recordFilter={"accountId": acc.get("id")}) + accTxs = billingDb.getRecordset(BillingTransaction, recordFilter={"accountId": acc.get("id")}) for tx in accTxs: - self.db.recordDelete(BillingTransaction, tx.get("id")) - self.db.recordDelete(BillingAccount, acc.get("id")) - billingSettings = self.db.getRecordset(BillingSettings, recordFilter={"mandateId": mandateId}) + billingDb.recordDelete(BillingTransaction, tx.get("id")) + billingDb.recordDelete(BillingAccount, acc.get("id")) + billingSettings = billingDb.getRecordset(BillingSettings, recordFilter={"mandateId": mandateId}) for bs in billingSettings: - self.db.recordDelete(BillingSettings, bs.get("id")) + billingDb.recordDelete(BillingSettings, bs.get("id")) if billingAccounts or billingSettings: logger.info(f"Cascade: deleted billing data for mandate {mandateId}") diff --git a/modules/routes/routeDataMandates.py b/modules/routes/routeDataMandates.py index 2c2bd31c..e98fd1cc 100644 --- a/modules/routes/routeDataMandates.py +++ b/modules/routes/routeDataMandates.py @@ -318,7 +318,36 @@ def create_mandate( logger.warning( f"Could not create default billing settings for mandate {newMandate.id}: {billingErr}" ) - + + try: + from modules.datamodels.datamodelSubscription import ( + MandateSubscription, SubscriptionStatusEnum, BUILTIN_PLANS, + ) + from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot + from datetime import datetime, timezone, timedelta + + planKey = mandateData.get("planKey", "TRIAL_7D") + plan = BUILTIN_PLANS.get(planKey) + if plan: + now = datetime.now(timezone.utc) + targetStatus = SubscriptionStatusEnum.TRIALING if plan.trialDays else SubscriptionStatusEnum.ACTIVE + sub = MandateSubscription( + mandateId=str(newMandate.id), + planKey=planKey, + status=targetStatus, + recurring=plan.autoRenew and not plan.trialDays, + startedAt=now, + currentPeriodStart=now, + ) + if plan.trialDays: + sub.trialEndsAt = now + timedelta(days=plan.trialDays) + sub.currentPeriodEnd = now + timedelta(days=plan.trialDays) + subInterface = _getSubRoot() + subInterface.createSubscription(sub) + logger.info(f"Created {targetStatus.value} subscription ({planKey}) for mandate {newMandate.id}") + except Exception as subErr: + logger.error(f"Failed to create subscription for mandate {newMandate.id}: {subErr}") + logger.info(f"Mandate {newMandate.id} created by SysAdmin {currentUser.id}") return newMandate diff --git a/modules/routes/routeStore.py b/modules/routes/routeStore.py index 0d6e68ec..4af0f6b7 100644 --- a/modules/routes/routeStore.py +++ b/modules/routes/routeStore.py @@ -187,9 +187,12 @@ def getSubscriptionInfo( "budgetAiCHF": None, } - from modules.datamodels.datamodelSubscription import MandateSubscription, BUILTIN_PLANS - subs = db.getRecordset(MandateSubscription, recordFilter={"mandateId": mandateId}) - if not subs: + from modules.datamodels.datamodelSubscription import BUILTIN_PLANS + from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot + + subInterface = _getSubRoot() + allSubs = subInterface.listForMandate(mandateId) + if not allSubs: return { "plan": None, "maxDataVolumeMB": None, @@ -197,7 +200,7 @@ def getSubscriptionInfo( "budgetAiCHF": None, } - sub = subs[0] + sub = allSubs[0] plan = BUILTIN_PLANS.get(sub.get("planKey")) currentInstances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": mandateId})