# 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, FeatureProviderProtocol, 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.models import Store, StorePlatform # Aggregate content pages across all merchant's stores on this platform pages_count = ( db.query(func.count(ContentPage.id)) .join(Store, ContentPage.store_id == Store.id) .join(StorePlatform, Store.id == StorePlatform.store_id) .filter( Store.merchant_id == merchant_id, StorePlatform.platform_id == platform_id, ) .scalar() or 0 ) custom_count = ( db.query(func.count(ContentPage.id)) .join(Store, ContentPage.store_id == Store.id) .join(StorePlatform, Store.id == StorePlatform.store_id) .filter( Store.merchant_id == merchant_id, StorePlatform.platform_id == platform_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", ), ] # Singleton instance for module registration cms_feature_provider = CmsFeatureProvider() __all__ = [ "CmsFeatureProvider", "cms_feature_provider", ]