Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Feature gate module for VoIdea tariff-based access control.
|
||||
|
||||
Usage:
|
||||
features = await get_user_features(user, db)
|
||||
if not features.get("has_voice"):
|
||||
raise HTTPException(403, "Feature not available on your plan")
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.models.tariff import TariffPlan, UserSubscription
|
||||
from app.models.user import User
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
DEFAULT_FREE_FEATURES: dict[str, Any] = {
|
||||
"limit_ideas": None, # None = unlimited
|
||||
"limit_sessions_per_day": None,
|
||||
"limit_agents": None,
|
||||
"limit_team_members": None,
|
||||
"limit_storage_mb": 100,
|
||||
"has_voice": True,
|
||||
"has_voice_commands": True,
|
||||
"has_drive": True,
|
||||
"has_advanced_analytics": True,
|
||||
"has_api_access": True,
|
||||
"has_priority_support": True,
|
||||
"has_custom_branding": True,
|
||||
"_promo": True,
|
||||
}
|
||||
|
||||
|
||||
async def get_user_features(
|
||||
user: User, db: AsyncSession
|
||||
) -> dict[str, Any]:
|
||||
"""Get feature flags and limits for a user based on their tariff plan.
|
||||
|
||||
When tariffs_enabled=False (promo mode), returns all features as available.
|
||||
"""
|
||||
if not settings.tariffs_enabled:
|
||||
return dict(DEFAULT_FREE_FEATURES)
|
||||
|
||||
result = await db.execute(
|
||||
select(UserSubscription).where(UserSubscription.user_id == user.id)
|
||||
)
|
||||
sub = result.scalar_one_or_none()
|
||||
|
||||
if sub is None or sub.status != "active":
|
||||
return _get_plan_features(db, settings.tariffs_free_code)
|
||||
|
||||
result = await db.execute(
|
||||
select(TariffPlan).where(TariffPlan.id == sub.plan_id)
|
||||
)
|
||||
plan = result.scalar_one_or_none()
|
||||
|
||||
if plan is None or not plan.is_active:
|
||||
return _get_plan_features(db, settings.tariffs_free_code)
|
||||
|
||||
return plan.features or dict(DEFAULT_FREE_FEATURES)
|
||||
|
||||
|
||||
async def _get_plan_features(
|
||||
db: AsyncSession, code: str
|
||||
) -> dict[str, Any]:
|
||||
"""Get features dict for a plan by code, falling back to defaults."""
|
||||
result = await db.execute(
|
||||
select(TariffPlan).where(
|
||||
TariffPlan.code == code, TariffPlan.is_active.is_(True)
|
||||
)
|
||||
)
|
||||
plan = result.scalar_one_or_none()
|
||||
if plan and plan.features:
|
||||
return plan.features
|
||||
return dict(DEFAULT_FREE_FEATURES)
|
||||
|
||||
|
||||
def check_feature(features: dict[str, Any], key: str) -> bool:
|
||||
"""Check if a boolean feature is enabled."""
|
||||
return bool(features.get(key, False))
|
||||
|
||||
|
||||
def check_limit(
|
||||
features: dict[str, Any], key: str, current: int = 0
|
||||
) -> bool:
|
||||
"""Check if a numeric limit is not exceeded (None = unlimited)."""
|
||||
limit = features.get(key)
|
||||
if limit is None:
|
||||
return True
|
||||
return current < int(limit)
|
||||
Reference in New Issue
Block a user