# app/modules/cms/services/cms_features.py """ CMS feature provider for the billing feature system. Declares CMS-related billable features (page limits, SEO, scheduling, templates) and provides usage tracking queries for feature gating. Quantitative features track content page counts at both store and merchant levels. """ from __future__ import annotations import logging from typing import TYPE_CHECKING from sqlalchemy import func from app.modules.contracts.features import ( FeatureDeclaration, FeatureScope, FeatureType, FeatureUsage, ) if TYPE_CHECKING: from sqlalchemy.orm import Session logger = logging.getLogger(__name__) class CmsFeatureProvider: """Feature provider for the CMS module. Declares: - cms_pages_limit: quantitative per-store limit on total content pages - cms_custom_pages_limit: quantitative per-store limit on custom pages - cms_basic: binary merchant-level feature for basic CMS editing - cms_seo: binary merchant-level feature for SEO metadata - cms_scheduling: binary merchant-level feature for content scheduling - cms_templates: binary merchant-level feature for page templates """ @property def feature_category(self) -> str: return "cms" def get_feature_declarations(self) -> list[FeatureDeclaration]: return [ FeatureDeclaration( code="cms_pages_limit", name_key="cms.features.cms_pages_limit.name", description_key="cms.features.cms_pages_limit.description", category="cms", feature_type=FeatureType.QUANTITATIVE, scope=FeatureScope.STORE, default_limit=5, unit_key="cms.features.cms_pages_limit.unit", ui_icon="file-text", display_order=10, ), FeatureDeclaration( code="cms_custom_pages_limit", name_key="cms.features.cms_custom_pages_limit.name", description_key="cms.features.cms_custom_pages_limit.description", category="cms", feature_type=FeatureType.QUANTITATIVE, scope=FeatureScope.STORE, default_limit=2, unit_key="cms.features.cms_custom_pages_limit.unit", ui_icon="layout", display_order=20, ), FeatureDeclaration( code="cms_basic", name_key="cms.features.cms_basic.name", description_key="cms.features.cms_basic.description", category="cms", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="edit-3", display_order=30, ), FeatureDeclaration( code="cms_seo", name_key="cms.features.cms_seo.name", description_key="cms.features.cms_seo.description", category="cms", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="search", display_order=40, ), FeatureDeclaration( code="cms_scheduling", name_key="cms.features.cms_scheduling.name", description_key="cms.features.cms_scheduling.description", category="cms", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="calendar", display_order=50, ), FeatureDeclaration( code="cms_templates", name_key="cms.features.cms_templates.name", description_key="cms.features.cms_templates.description", category="cms", feature_type=FeatureType.BINARY, scope=FeatureScope.MERCHANT, ui_icon="grid", display_order=60, ), ] def get_store_usage( self, db: Session, store_id: int, ) -> list[FeatureUsage]: from app.modules.cms.models.content_page import ContentPage # Count all content pages for this store pages_count = ( db.query(func.count(ContentPage.id)) .filter(ContentPage.store_id == store_id) .scalar() or 0 ) # Count custom pages (store overrides that are not platform or default pages) custom_count = ( db.query(func.count(ContentPage.id)) .filter( ContentPage.store_id == store_id, ContentPage.is_custom == True, # noqa: E712 ) .scalar() or 0 ) return [ FeatureUsage( feature_code="cms_pages_limit", current_count=pages_count, label="Content pages", ), FeatureUsage( feature_code="cms_custom_pages_limit", current_count=custom_count, label="Custom pages", ), ] def get_merchant_usage( self, db: Session, merchant_id: int, platform_id: int, ) -> list[FeatureUsage]: from app.modules.cms.models.content_page import ContentPage from app.modules.tenancy.services.platform_service import platform_service from app.modules.tenancy.services.store_service import store_service # Get store IDs for this merchant that are on the given platform merchant_stores = store_service.get_stores_by_merchant_id(db, merchant_id) store_ids = [] for s in merchant_stores: pids = platform_service.get_active_platform_ids_for_store(db, s.id) if platform_id in pids: store_ids.append(s.id) if not store_ids: return [ FeatureUsage(feature_code="cms_pages_limit", current_count=0, label="Content pages"), FeatureUsage(feature_code="cms_custom_pages_limit", current_count=0, label="Custom pages"), ] # Aggregate content pages across all merchant's stores on this platform pages_count = ( db.query(func.count(ContentPage.id)) .filter(ContentPage.store_id.in_(store_ids)) .scalar() or 0 ) custom_count = ( db.query(func.count(ContentPage.id)) .filter( ContentPage.store_id.in_(store_ids), ContentPage.is_custom == True, # noqa: E712 ) .scalar() or 0 ) return [ FeatureUsage( feature_code="cms_pages_limit", current_count=pages_count, label="Content pages", ), FeatureUsage( feature_code="cms_custom_pages_limit", current_count=custom_count, label="Custom pages", ), ] # Singleton instance for module registration cms_feature_provider = CmsFeatureProvider() __all__ = [ "CmsFeatureProvider", "cms_feature_provider", ]