From 9627b1861d4e42bc6ab1c95b78b0f7ba7ec53da3 Mon Sep 17 00:00:00 2001 From: SimplifiedPrivacy Date: Sat, 3 Oct 2026 14:47:38 -0400 Subject: [PATCH] Introduced new model: Plan. This differs from the legacy subscriptions because its based on time in months and supports multiple protocols at once in a list enum format --- core/Constants.py | 2 +- core/controllers/SyncController.py | 4 +- core/models/manage/migrations.py | 3 +- core/models/manage/session_management.py | 4 +- core/models/orm_models/CachedSync.py | 10 ++++- core/models/orm_models/Plan.py | 50 +++++++++++++++++++++ core/services/networking/httpx/endpoints.py | 3 +- 7 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 core/models/orm_models/Plan.py diff --git a/core/Constants.py b/core/Constants.py index bc7ea6e..5a9aadd 100644 --- a/core/Constants.py +++ b/core/Constants.py @@ -6,7 +6,7 @@ import os @dataclass(frozen=True) class Constants: - DB_VERSION_THIS_APP_WANTS = 2 + DB_VERSION_THIS_APP_WANTS = 3 # Fallback for development (running outside AppImage) fallback_non_appimage = os.path.dirname(os.path.abspath(__file__)) diff --git a/core/controllers/SyncController.py b/core/controllers/SyncController.py index 45f9898..b10e71e 100644 --- a/core/controllers/SyncController.py +++ b/core/controllers/SyncController.py @@ -16,6 +16,7 @@ from core.errors.logger import logger from core.models.orm_models.Location import Location from core.models.orm_models.Operator import Operator from core.models.orm_models.Dependency import Dependency +from core.models.orm_models.Plan import Plan from core.Constants import Constants from core.controllers.ApplicationController import ApplicationController @@ -35,7 +36,8 @@ ORM_TABLES = { "locations": Location, "operators": Operator, "application_versions": ApplicationVersion, - "dependencies": Dependency + "dependencies": Dependency, + "plans": Plan, } LEGACY_SQL_FUNCT_DICT = { diff --git a/core/models/manage/migrations.py b/core/models/manage/migrations.py index 72c92ab..7462f34 100644 --- a/core/models/manage/migrations.py +++ b/core/models/manage/migrations.py @@ -67,7 +67,8 @@ def migrate_sql() -> DatabaseOperation: from core.models.orm_models.CachedSync import CachedSync from core.models.orm_models.EncryptedProxy import EncryptedProxy from core.models.orm_models.Dependency import Dependency - MODELS = [Location, Operator, CachedSync, EncryptedProxy, Dependency] + from core.models.orm_models.Plan import Plan + MODELS = [Location, Operator, CachedSync, EncryptedProxy, Dependency, Plan] logger.info(f"[MIGRATION] Tables loaded") except: logger.error(f"[MIGRATION] Could not load models. Critical failure.") diff --git a/core/models/manage/session_management.py b/core/models/manage/session_management.py index 4aabeb7..0ebe3e0 100644 --- a/core/models/manage/session_management.py +++ b/core/models/manage/session_management.py @@ -146,9 +146,11 @@ def create_ALL_tables(): from core.models.orm_models.Operator import Operator from core.models.orm_models.CachedSync import CachedSync from core.models.orm_models.EncryptedProxy import EncryptedProxy - from core.models.SubscriptionPlan import SubscriptionPlan + from core.models.orm_models.Plan import Plan from core.models.orm_models.ApplicationVersion import ApplicationVersion from core.models.orm_models.Dependency import Dependency + # legacy: + from core.models.SubscriptionPlan import SubscriptionPlan try: BaseModel.metadata.create_all(engine, checkfirst=True) diff --git a/core/models/orm_models/CachedSync.py b/core/models/orm_models/CachedSync.py index f146a8b..3a711bf 100644 --- a/core/models/orm_models/CachedSync.py +++ b/core/models/orm_models/CachedSync.py @@ -21,5 +21,13 @@ class CachedSync(Base): client_version: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) operators: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) locations: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) - subscriptions: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) dependencies: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) + + # legacy: + subscriptions: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) + + # v2 refactor: + plans: Mapped[Optional[int]] = mapped_column(Integer, nullable=False, default=0) + + + diff --git a/core/models/orm_models/Plan.py b/core/models/orm_models/Plan.py new file mode 100644 index 0000000..52c406d --- /dev/null +++ b/core/models/orm_models/Plan.py @@ -0,0 +1,50 @@ +from core.models.enum_models import ProtocolChoice +from core.models.orm_models.Base import BaseModel +from core.Constants import Constants +from core.errors.logger import logger + +import enum +from sqlalchemy.orm import Mapped +from sqlalchemy import Column, Integer, String, Boolean, UniqueConstraint, JSON, TypeDecorator, Numeric +from sqlalchemy.orm import declarative_base + + +class EnumList(TypeDecorator): + """Stores a list of ProtocolChoice enums as a JSON list of strings.""" + impl = JSON + + def process_bind_param(self, value, dialect): + if value is not None: + # Create a set of all valid values + valid_values = {item.value for item in ProtocolChoice} + + filtered = [] + for item in value: + # 1. Get the raw value regardless of if it's an Enum or String + val = item.value if isinstance(item, enum.Enum) else item + + # 2. Only keep it if it exists in our valid_values set + if val in valid_values: + filtered.append(val) + else: + logger.error(f"Dropped an invalid item: {val}") + pass + + return filtered + return None + + def process_result_value(self, value, dialect): + # Convert strings from DB back into enum members + if value is not None: + return [ProtocolChoice(item) for item in value] + return None + + +class Plan(BaseModel): + __tablename__ = 'plans' + + id = Column(Integer, primary_key=True) + months = Column(Integer, unique=False, nullable=False) + supported_protocols = Column(EnumList, nullable=False) + cost = Column(Numeric(precision=10, scale=2), nullable=False) + diff --git a/core/services/networking/httpx/endpoints.py b/core/services/networking/httpx/endpoints.py index baa57aa..2bb5155 100644 --- a/core/services/networking/httpx/endpoints.py +++ b/core/services/networking/httpx/endpoints.py @@ -10,7 +10,8 @@ def switch_endpoint_domain(domain: str) -> dict: "subscriptions": f"https://{domain}/api/v1/subscription-plans", "applications": f"https://{domain}/api/v1/platforms/linux-x86_64/applications", "application_versions": f"https://{domain}/api/v1/platforms/linux-x86_64/application-versions", - "dependencies": f"https://{domain}/api/v1/dependencies" + "dependencies": f"https://{domain}/api/v1/dependencies", + "plans": f"https://{domain}/api/v2/time_based_plans" }