279 lines
10 KiB
Python
279 lines
10 KiB
Python
import uuid
|
|
from typing import List, Optional
|
|
from sqlalchemy.orm import Session, joinedload
|
|
from sqlalchemy import or_, cast, String, asc, desc
|
|
from fastapi import HTTPException, status
|
|
|
|
from app.models.auth.subscription_plan_model import SubscriptionPlan
|
|
from app.models.auth.plan_access_model import PlanAccess
|
|
from app.models.auth.plan_module_access_model import PlanModuleAccess
|
|
from app.models.auth.tenant_model import Tenant
|
|
from app.models.auth.role_model import Role
|
|
from app.models.auth.tenant_module_model import TenantModule
|
|
from app.models.auth.module_environment_model import ModuleEnvironment
|
|
from app.schemas.auth.role_schema import RoleUpdate
|
|
from app.services.auth.role_service import RoleService
|
|
from app.services.auth.event_service import EventService
|
|
from app.schemas.auth.subscription_plan_schema import (
|
|
SubscriptionPlanCreate,
|
|
SubscriptionPlanUpdate,
|
|
SubscriptionPlanPaginatedResponse,
|
|
SubscriptionPlanResponse
|
|
)
|
|
|
|
class SubscriptionPlanService:
|
|
@staticmethod
|
|
def _resolve_environment_slug(
|
|
db: Session,
|
|
module_id: uuid.UUID,
|
|
assigned_environment_slug: Optional[str],
|
|
) -> Optional[str]:
|
|
if assigned_environment_slug:
|
|
return assigned_environment_slug
|
|
|
|
default_env = (
|
|
db.query(ModuleEnvironment)
|
|
.filter(
|
|
ModuleEnvironment.module_id == module_id,
|
|
ModuleEnvironment.is_default == True,
|
|
)
|
|
.first()
|
|
)
|
|
if default_env:
|
|
return default_env.slug
|
|
|
|
any_env = (
|
|
db.query(ModuleEnvironment)
|
|
.filter(ModuleEnvironment.module_id == module_id)
|
|
.first()
|
|
)
|
|
return any_env.slug if any_env else None
|
|
|
|
@staticmethod
|
|
def _sync_tenant_default_roles_for_plan(
|
|
db: Session,
|
|
plan_id: uuid.UUID,
|
|
) -> None:
|
|
plan = db.query(SubscriptionPlan).filter(SubscriptionPlan.id == plan_id).first()
|
|
if not plan:
|
|
return
|
|
|
|
plan_accesses = db.query(PlanAccess).filter(PlanAccess.plan_id == plan_id).all()
|
|
plan_module_accesses = (
|
|
db.query(PlanModuleAccess).filter(PlanModuleAccess.plan_id == plan_id).all()
|
|
)
|
|
effective_access_ids = [row.access_id for row in plan_accesses] + [
|
|
row.module_access_id for row in plan_module_accesses
|
|
]
|
|
|
|
tenants = db.query(Tenant).filter(Tenant.plan_id == plan_id).all()
|
|
for tenant in tenants:
|
|
default_role = (
|
|
db.query(Role)
|
|
.filter(Role.tenant_id == tenant.id, Role.is_default == True)
|
|
.first()
|
|
)
|
|
if default_role:
|
|
RoleService.update_role(
|
|
db,
|
|
default_role.id,
|
|
RoleUpdate(access_ids=effective_access_ids),
|
|
is_superadmin=True,
|
|
)
|
|
|
|
active_modules = (
|
|
db.query(TenantModule)
|
|
.filter(
|
|
TenantModule.tenant_id == tenant.id,
|
|
TenantModule.is_active == True,
|
|
)
|
|
.all()
|
|
)
|
|
if not active_modules:
|
|
continue
|
|
|
|
targets = []
|
|
for tm in active_modules:
|
|
env_slug = SubscriptionPlanService._resolve_environment_slug(
|
|
db,
|
|
tm.module_id,
|
|
tm.assigned_environment_slug,
|
|
)
|
|
if env_slug:
|
|
targets.append(
|
|
{
|
|
"module_id": str(tm.module_id),
|
|
"environment_slug": env_slug,
|
|
}
|
|
)
|
|
|
|
if not targets:
|
|
continue
|
|
|
|
EventService.emit_event(
|
|
db,
|
|
event_type="TENANT_UPDATED",
|
|
payload={
|
|
"tenant_id": str(tenant.id),
|
|
"tenant_name": tenant.tenant_name,
|
|
"tenant_domain": tenant.tenant_domain,
|
|
"tenant_logo_url": tenant.tenant_logo_url,
|
|
"max_users_allowed": plan.max_users_allowed,
|
|
"targets": targets,
|
|
},
|
|
tenant_id=tenant.id,
|
|
)
|
|
|
|
|
|
@staticmethod
|
|
def create_plan(db: Session, plan_data: SubscriptionPlanCreate) -> SubscriptionPlan:
|
|
existing = db.query(SubscriptionPlan).filter(SubscriptionPlan.name == plan_data.name).first()
|
|
if existing:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Plan name already exists")
|
|
|
|
plan = SubscriptionPlan(
|
|
name=plan_data.name,
|
|
description=plan_data.description,
|
|
price=plan_data.price,
|
|
duration_days=plan_data.duration_days,
|
|
max_users_allowed=plan_data.max_users_allowed,
|
|
is_public=plan_data.is_public,
|
|
status=plan_data.status
|
|
)
|
|
db.add(plan)
|
|
db.flush()
|
|
|
|
if plan_data.access_ids:
|
|
for acc_id in plan_data.access_ids:
|
|
db.add(PlanAccess(plan_id=plan.id, access_id=acc_id))
|
|
|
|
if plan_data.module_access_ids:
|
|
for macc_id in plan_data.module_access_ids:
|
|
db.add(PlanModuleAccess(plan_id=plan.id, module_access_id=macc_id))
|
|
|
|
db.commit()
|
|
db.refresh(plan)
|
|
return plan
|
|
|
|
@staticmethod
|
|
def update_plan(db: Session, plan_id: uuid.UUID, plan_data: SubscriptionPlanUpdate) -> SubscriptionPlan:
|
|
plan = db.query(SubscriptionPlan).filter(SubscriptionPlan.id == plan_id).first()
|
|
if not plan:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plan not found")
|
|
|
|
update_dict = plan_data.model_dump(exclude_unset=True)
|
|
|
|
if "name" in update_dict and update_dict["name"] != plan.name:
|
|
existing = db.query(SubscriptionPlan).filter(SubscriptionPlan.name == update_dict["name"]).first()
|
|
if existing:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Plan name already exists")
|
|
|
|
if "access_ids" in update_dict:
|
|
access_ids = update_dict.pop("access_ids")
|
|
db.query(PlanAccess).filter(PlanAccess.plan_id == plan.id).delete()
|
|
if access_ids:
|
|
for acc_id in access_ids:
|
|
db.add(PlanAccess(plan_id=plan.id, access_id=acc_id))
|
|
|
|
if "module_access_ids" in update_dict:
|
|
module_access_ids = update_dict.pop("module_access_ids")
|
|
db.query(PlanModuleAccess).filter(PlanModuleAccess.plan_id == plan.id).delete()
|
|
if module_access_ids:
|
|
for macc_id in module_access_ids:
|
|
db.add(PlanModuleAccess(plan_id=plan.id, module_access_id=macc_id))
|
|
|
|
for key, value in update_dict.items():
|
|
setattr(plan, key, value)
|
|
|
|
db.commit()
|
|
db.refresh(plan)
|
|
SubscriptionPlanService._sync_tenant_default_roles_for_plan(db, plan.id)
|
|
db.commit()
|
|
return plan
|
|
|
|
@staticmethod
|
|
def get_plan(db: Session, plan_id: uuid.UUID) -> SubscriptionPlan:
|
|
plan = (
|
|
db.query(SubscriptionPlan)
|
|
.options(
|
|
joinedload(SubscriptionPlan.plan_accesses),
|
|
joinedload(SubscriptionPlan.plan_module_accesses)
|
|
)
|
|
.filter(SubscriptionPlan.id == plan_id)
|
|
.first()
|
|
)
|
|
if not plan:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plan not found")
|
|
return plan
|
|
|
|
@staticmethod
|
|
def get_all_plans(db: Session, is_public: Optional[bool] = None, status: Optional[str] = None):
|
|
query = db.query(SubscriptionPlan)
|
|
if is_public is not None:
|
|
query = query.filter(SubscriptionPlan.is_public == is_public)
|
|
if status is not None:
|
|
query = query.filter(SubscriptionPlan.status == status)
|
|
return query.all()
|
|
|
|
@staticmethod
|
|
def get_paginated_plans(
|
|
db: Session,
|
|
page: int = 1,
|
|
page_size: int = 10,
|
|
search: Optional[str] = None,
|
|
filter_names: Optional[List[str]] = None,
|
|
statuses: Optional[List[str]] = None,
|
|
visibility: Optional[List[bool]] = None,
|
|
sort_by: Optional[str] = None,
|
|
sort_order: Optional[str] = None,
|
|
) -> SubscriptionPlanPaginatedResponse:
|
|
query = db.query(SubscriptionPlan)
|
|
|
|
if filter_names:
|
|
query = query.filter(SubscriptionPlan.name.in_(filter_names))
|
|
|
|
if statuses:
|
|
query = query.filter(SubscriptionPlan.status.in_(statuses))
|
|
|
|
if visibility is not None and len(visibility) > 0:
|
|
query = query.filter(SubscriptionPlan.is_public.in_(visibility))
|
|
|
|
if search:
|
|
query = query.filter(
|
|
or_(
|
|
SubscriptionPlan.name.ilike(f"%{search}%"),
|
|
cast(SubscriptionPlan.id, String).ilike(f"%{search}%")
|
|
)
|
|
)
|
|
|
|
sort_column_map = {
|
|
"name": SubscriptionPlan.name,
|
|
"price": SubscriptionPlan.price,
|
|
"status": SubscriptionPlan.status,
|
|
"visibility": SubscriptionPlan.is_public,
|
|
}
|
|
sort_column = sort_column_map.get(sort_by or "")
|
|
if sort_column is not None:
|
|
order_fn = desc if (sort_order or "").lower() == "desc" else asc
|
|
query = query.order_by(order_fn(sort_column))
|
|
|
|
total = query.count()
|
|
offset = (page - 1) * page_size
|
|
plans = query.offset(offset).limit(page_size).all()
|
|
total_pages = (total + page_size - 1) // page_size if total > 0 else 0
|
|
|
|
return SubscriptionPlanPaginatedResponse(
|
|
items=[SubscriptionPlanResponse.from_orm(p) for p in plans],
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size,
|
|
total_pages=total_pages
|
|
)
|
|
|
|
@staticmethod
|
|
def delete_plan(db: Session, plan_id: uuid.UUID):
|
|
plan = SubscriptionPlanService.get_plan(db, plan_id)
|
|
db.delete(plan)
|
|
db.commit()
|
|
return {"message": "Plan deleted successfully"}
|