changes done on subscription module with saas
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
"""add max users allowed to subscription plans
|
||||||
|
|
||||||
|
Revision ID: 7b2c4d5e6f77
|
||||||
|
Revises: 6a1b2c3d4e55
|
||||||
|
Create Date: 2026-04-28 10:30:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "7b2c4d5e6f77"
|
||||||
|
down_revision: Union[str, Sequence[str], None] = "6a1b2c3d4e55"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute(
|
||||||
|
"ALTER TABLE subscription_plans ADD COLUMN IF NOT EXISTS max_users_allowed INTEGER"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.execute(
|
||||||
|
"ALTER TABLE subscription_plans DROP COLUMN IF EXISTS max_users_allowed"
|
||||||
|
)
|
||||||
@@ -8,6 +8,9 @@ from app.config.security import security
|
|||||||
from app.models.auth.user_model import User
|
from app.models.auth.user_model import User
|
||||||
from app.models.auth.access_model import Access
|
from app.models.auth.access_model import Access
|
||||||
from app.models.auth.tenant_model import Tenant
|
from app.models.auth.tenant_model import Tenant
|
||||||
|
from app.services.auth.subscription_entitlement_service import (
|
||||||
|
SubscriptionEntitlementService,
|
||||||
|
)
|
||||||
|
|
||||||
security_scheme = HTTPBearer(auto_error=False)
|
security_scheme = HTTPBearer(auto_error=False)
|
||||||
|
|
||||||
@@ -45,6 +48,8 @@ def get_current_user(
|
|||||||
detail="User not found"
|
detail="User not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setattr(user, "_saas_db_session", db)
|
||||||
|
|
||||||
if user.status != "active":
|
if user.status != "active":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
@@ -83,19 +88,19 @@ def require_active_user(current_user: User = Depends(get_current_user)) -> User:
|
|||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
def has_access(user: User, access_code: str) -> bool:
|
def has_access(user: User, access_code: str) -> bool:
|
||||||
if not user.role:
|
db = getattr(user, "_saas_db_session", None)
|
||||||
|
if db is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
user_access_codes = {ra.access.access_code for ra in user.role.role_accesses}
|
user_access_codes = SubscriptionEntitlementService.get_effective_access_codes(
|
||||||
|
db, user
|
||||||
|
)
|
||||||
return access_code in user_access_codes
|
return access_code in user_access_codes
|
||||||
|
|
||||||
def can_access(user: User, access_code: str, db: Session) -> bool:
|
def can_access(user: User, access_code: str, db: Session) -> bool:
|
||||||
if not user.role:
|
user_access_codes = SubscriptionEntitlementService.get_effective_access_codes(
|
||||||
return False
|
db, user
|
||||||
|
)
|
||||||
user_access_codes = {ra.access.access_code for ra in user.role.role_accesses}
|
|
||||||
|
|
||||||
if access_code in user_access_codes:
|
if access_code in user_access_codes:
|
||||||
return True
|
return True
|
||||||
requested_access = db.query(Access).filter(
|
requested_access = db.query(Access).filter(
|
||||||
@@ -114,10 +119,11 @@ def can_access(user: User, access_code: str, db: Session) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_user_accesses(user: User) -> List[str]:
|
def get_user_accesses(user: User) -> List[str]:
|
||||||
if not user.role:
|
db = getattr(user, "_saas_db_session", None)
|
||||||
|
if db is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return [ra.access.access_code for ra in user.role.role_accesses]
|
return sorted(SubscriptionEntitlementService.get_effective_access_codes(db, user))
|
||||||
|
|
||||||
def require_access(access_code: str):
|
def require_access(access_code: str):
|
||||||
def check_permission(current_user: User = Depends(get_current_user)) -> bool:
|
def check_permission(current_user: User = Depends(get_current_user)) -> bool:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class SubscriptionPlan(Base):
|
|||||||
description = Column(String, nullable=True)
|
description = Column(String, nullable=True)
|
||||||
price = Column(Numeric(10, 2), nullable=True)
|
price = Column(Numeric(10, 2), nullable=True)
|
||||||
duration_days = Column(Integer, nullable=True)
|
duration_days = Column(Integer, nullable=True)
|
||||||
|
max_users_allowed = Column(Integer, nullable=True)
|
||||||
is_public = Column(Boolean, default=True)
|
is_public = Column(Boolean, default=True)
|
||||||
status = Column(String, default="active")
|
status = Column(String, default="active")
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ def get_plan(
|
|||||||
description=plan.description,
|
description=plan.description,
|
||||||
price=float(plan.price) if plan.price is not None else None,
|
price=float(plan.price) if plan.price is not None else None,
|
||||||
duration_days=plan.duration_days,
|
duration_days=plan.duration_days,
|
||||||
|
max_users_allowed=plan.max_users_allowed,
|
||||||
is_public=plan.is_public,
|
is_public=plan.is_public,
|
||||||
status=plan.status,
|
status=plan.status,
|
||||||
created_at=plan.created_at,
|
created_at=plan.created_at,
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class AccessInRole(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
|
|
||||||
class RoleInUser(BaseModel):
|
class RoleInUser(BaseModel):
|
||||||
id: uuid.UUID
|
id: Optional[uuid.UUID] = None
|
||||||
role_name: str
|
role_name: Optional[str] = None
|
||||||
accesses: List[str] = []
|
accesses: List[str] = []
|
||||||
|
|
||||||
class UserResponse(UserBase):
|
class UserResponse(UserBase):
|
||||||
@@ -34,6 +34,7 @@ class UserResponse(UserBase):
|
|||||||
tenant_id: Optional[uuid.UUID] = None
|
tenant_id: Optional[uuid.UUID] = None
|
||||||
tenant_name: Optional[str] = None
|
tenant_name: Optional[str] = None
|
||||||
tenant_logo_url: Optional[str] = None
|
tenant_logo_url: Optional[str] = None
|
||||||
|
subscription_details: Optional[dict] = None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
role: Optional[RoleInUser] = None
|
role: Optional[RoleInUser] = None
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class SubscriptionPlanBase(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
price: Optional[float] = None
|
price: Optional[float] = None
|
||||||
duration_days: Optional[int] = Field(None, ge=1)
|
duration_days: Optional[int] = Field(None, ge=1)
|
||||||
|
max_users_allowed: Optional[int] = Field(None, ge=0)
|
||||||
is_public: bool = True
|
is_public: bool = True
|
||||||
status: str = "active"
|
status: str = "active"
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ class SubscriptionPlanUpdate(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
price: Optional[float] = None
|
price: Optional[float] = None
|
||||||
duration_days: Optional[int] = Field(None, ge=1)
|
duration_days: Optional[int] = Field(None, ge=1)
|
||||||
|
max_users_allowed: Optional[int] = Field(None, ge=0)
|
||||||
is_public: Optional[bool] = None
|
is_public: Optional[bool] = None
|
||||||
status: Optional[str] = None
|
status: Optional[str] = None
|
||||||
access_ids: Optional[List[uuid.UUID]] = None
|
access_ids: Optional[List[uuid.UUID]] = None
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import jwt
|
|||||||
from app.config.settings import settings
|
from app.config.settings import settings
|
||||||
from app.services.auth.email_service import EmailService
|
from app.services.auth.email_service import EmailService
|
||||||
from app.core.redis import sync_redis_client
|
from app.core.redis import sync_redis_client
|
||||||
|
from app.services.auth.subscription_entitlement_service import (
|
||||||
|
SubscriptionEntitlementService,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -53,12 +56,15 @@ class AuthService:
|
|||||||
if user.status != "active":
|
if user.status != "active":
|
||||||
raise HTTPException(status_code=403, detail="User inactive")
|
raise HTTPException(status_code=403, detail="User inactive")
|
||||||
|
|
||||||
|
effective_accesses = sorted(
|
||||||
|
SubscriptionEntitlementService.get_effective_access_codes(db, user)
|
||||||
|
)
|
||||||
role_data = None
|
role_data = None
|
||||||
if user.role:
|
if user.role or effective_accesses:
|
||||||
role_data = {
|
role_data = {
|
||||||
"id": str(user.role.id),
|
"id": str(user.role.id) if user.role else None,
|
||||||
"role_name": user.role.role_name,
|
"role_name": user.role.role_name if user.role else "subscription",
|
||||||
"accesses": [ra.access.access_code for ra in user.role.role_accesses],
|
"accesses": effective_accesses,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -79,6 +85,9 @@ class AuthService:
|
|||||||
"tenant_id": user.tenant_id,
|
"tenant_id": user.tenant_id,
|
||||||
"tenant_name": user.tenant.tenant_name if user.tenant else None,
|
"tenant_name": user.tenant.tenant_name if user.tenant else None,
|
||||||
"tenant_logo_url": user.tenant.tenant_logo_url if user.tenant else None,
|
"tenant_logo_url": user.tenant.tenant_logo_url if user.tenant else None,
|
||||||
|
"subscription_details": SubscriptionEntitlementService.get_subscription_summary(
|
||||||
|
db, user.tenant_id
|
||||||
|
),
|
||||||
"created_at": user.created_at,
|
"created_at": user.created_at,
|
||||||
"updated_at": user.updated_at,
|
"updated_at": user.updated_at,
|
||||||
"role": role_data,
|
"role": role_data,
|
||||||
@@ -108,11 +117,38 @@ class AuthService:
|
|||||||
{"sub": str(user.id)}, tenant_id=user.tenant_id
|
{"sub": str(user.id)}, tenant_id=user.tenant_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
effective_accesses = sorted(
|
||||||
|
SubscriptionEntitlementService.get_effective_access_codes(db, user)
|
||||||
|
)
|
||||||
|
role_data = None
|
||||||
|
if user.role or effective_accesses:
|
||||||
|
role_data = {
|
||||||
|
"id": str(user.role.id) if user.role else None,
|
||||||
|
"role_name": user.role.role_name if user.role else "subscription",
|
||||||
|
"accesses": effective_accesses,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"access_token": new_access_token,
|
"access_token": new_access_token,
|
||||||
"refresh_token": new_refresh_token,
|
"refresh_token": new_refresh_token,
|
||||||
"token_type": "bearer",
|
"token_type": "bearer",
|
||||||
"user": user,
|
"user": {
|
||||||
|
"id": str(user.id),
|
||||||
|
"email": user.email,
|
||||||
|
"first_name": user.first_name,
|
||||||
|
"last_name": user.last_name,
|
||||||
|
"phone_number": user.phone_number,
|
||||||
|
"status": user.status,
|
||||||
|
"tenant_id": user.tenant_id,
|
||||||
|
"tenant_name": user.tenant.tenant_name if user.tenant else None,
|
||||||
|
"tenant_logo_url": user.tenant.tenant_logo_url if user.tenant else None,
|
||||||
|
"subscription_details": SubscriptionEntitlementService.get_subscription_summary(
|
||||||
|
db, user.tenant_id
|
||||||
|
),
|
||||||
|
"created_at": user.created_at,
|
||||||
|
"updated_at": user.updated_at,
|
||||||
|
"role": role_data,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -180,15 +216,15 @@ class AuthService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def me(db: Session, current_user: User):
|
def me(db: Session, current_user: User):
|
||||||
|
effective_accesses = sorted(
|
||||||
|
SubscriptionEntitlementService.get_effective_access_codes(db, current_user)
|
||||||
|
)
|
||||||
role_data = None
|
role_data = None
|
||||||
|
if current_user.role or effective_accesses:
|
||||||
if current_user.role:
|
|
||||||
role_data = {
|
role_data = {
|
||||||
"id": str(current_user.role.id),
|
"id": str(current_user.role.id) if current_user.role else None,
|
||||||
"role_name": current_user.role.role_name,
|
"role_name": current_user.role.role_name if current_user.role else "subscription",
|
||||||
"accesses": [
|
"accesses": effective_accesses,
|
||||||
ra.access.access_code for ra in current_user.role.role_accesses
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -205,6 +241,9 @@ class AuthService:
|
|||||||
"tenant_logo_url": (
|
"tenant_logo_url": (
|
||||||
current_user.tenant.tenant_logo_url if current_user.tenant else None
|
current_user.tenant.tenant_logo_url if current_user.tenant else None
|
||||||
),
|
),
|
||||||
|
"subscription_details": SubscriptionEntitlementService.get_subscription_summary(
|
||||||
|
db, current_user.tenant_id
|
||||||
|
),
|
||||||
"created_at": current_user.created_at,
|
"created_at": current_user.created_at,
|
||||||
"updated_at": current_user.updated_at,
|
"updated_at": current_user.updated_at,
|
||||||
"role": role_data,
|
"role": role_data,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class EventService:
|
|||||||
for env in targets:
|
for env in targets:
|
||||||
base = env.backend_base_url.rstrip('/')
|
base = env.backend_base_url.rstrip('/')
|
||||||
|
|
||||||
if event_type == "TENANT_PROVISION_REQUESTED" and env.provisioning_endpoint:
|
if event_type in {"TENANT_PROVISION_REQUESTED", "TENANT_UPDATED", "TENANT_STATUS_CHANGED", "TENANT_DEPROVISION_REQUESTED"} and env.provisioning_endpoint:
|
||||||
endpoint = env.provisioning_endpoint.lstrip('/')
|
endpoint = env.provisioning_endpoint.lstrip('/')
|
||||||
logger.info(f"Trace: base='{base}', endpoint='{endpoint}'")
|
logger.info(f"Trace: base='{base}', endpoint='{endpoint}'")
|
||||||
target_url = f"{base}/{endpoint}"
|
target_url = f"{base}/{endpoint}"
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ from app.services.auth.trust_service import TrustService
|
|||||||
from app.core.redis import sync_redis_client
|
from app.core.redis import sync_redis_client
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
from app.services.auth.subscription_entitlement_service import (
|
||||||
|
SubscriptionEntitlementService,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -120,25 +123,32 @@ class SSOService:
|
|||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=404, detail="User not found")
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
permissions = []
|
permissions = sorted(
|
||||||
if user.role:
|
SubscriptionEntitlementService.get_plan_module_access_codes(
|
||||||
for ra in user.role.role_accesses:
|
db, tenant_id, module.id
|
||||||
if ra.access:
|
)
|
||||||
pass
|
)
|
||||||
|
if not permissions and user.role and user.role.role_module_accesses:
|
||||||
if user.role.role_module_accesses:
|
permissions = sorted(
|
||||||
for rma in user.role.role_module_accesses:
|
{
|
||||||
if rma.module_access and rma.module_access.module_id == module.id:
|
rma.module_access.access_code
|
||||||
permissions.append(rma.module_access.access_code)
|
for rma in user.role.role_module_accesses
|
||||||
|
if rma.module_access and rma.module_access.module_id == module.id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
timestamp = int(time.time() * 1000)
|
timestamp = int(time.time() * 1000)
|
||||||
|
subscription_details = SubscriptionEntitlementService.get_subscription_summary(
|
||||||
|
db, tenant_id
|
||||||
|
)
|
||||||
|
|
||||||
payload_data = {
|
payload_data = {
|
||||||
"user_id": str(user.id),
|
"user_id": str(user.id),
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"tenant_id": str(tenant_id) if tenant_id else None,
|
"tenant_id": str(tenant_id) if tenant_id else None,
|
||||||
"permissions": permissions,
|
"permissions": permissions,
|
||||||
|
"tenant_name": user.tenant.tenant_name if user.tenant else None,
|
||||||
|
"subscription": subscription_details,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
"first_name": user.first_name,
|
"first_name": user.first_name,
|
||||||
"last_name": user.last_name,
|
"last_name": user.last_name,
|
||||||
@@ -221,12 +231,21 @@ class SSOService:
|
|||||||
detail="Tenant mismatch for SSO grant"
|
detail="Tenant mismatch for SSO grant"
|
||||||
)
|
)
|
||||||
|
|
||||||
permissions = []
|
permissions = sorted(
|
||||||
if user.role:
|
SubscriptionEntitlementService.get_plan_module_access_codes(
|
||||||
if user.role.role_module_accesses:
|
db,
|
||||||
for rma in user.role.role_module_accesses:
|
uuid.UUID(grant_tenant_id) if grant_tenant_id else None,
|
||||||
if rma.module_access and rma.module_access.module_id == module.id:
|
module.id,
|
||||||
permissions.append(rma.module_access.access_code)
|
)
|
||||||
|
)
|
||||||
|
if not permissions and user.role and user.role.role_module_accesses:
|
||||||
|
permissions = sorted(
|
||||||
|
{
|
||||||
|
rma.module_access.access_code
|
||||||
|
for rma in user.role.role_module_accesses
|
||||||
|
if rma.module_access and rma.module_access.module_id == module.id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
token_payload = {
|
token_payload = {
|
||||||
"sub": str(user.id),
|
"sub": str(user.id),
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from typing import Any, Dict, Optional, Set
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.models.auth.access_model import Access
|
||||||
|
from app.models.auth.module_access_model import ModuleAccess
|
||||||
|
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.user_model import User
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionEntitlementService:
|
||||||
|
@staticmethod
|
||||||
|
def get_tenant(db: Session, tenant_id: Optional[uuid.UUID]) -> Optional[Tenant]:
|
||||||
|
if not tenant_id:
|
||||||
|
return None
|
||||||
|
return db.query(Tenant).filter(Tenant.id == tenant_id).first()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_plan_access_codes(
|
||||||
|
db: Session,
|
||||||
|
tenant_id: Optional[uuid.UUID],
|
||||||
|
) -> Set[str]:
|
||||||
|
tenant = SubscriptionEntitlementService.get_tenant(db, tenant_id)
|
||||||
|
if not tenant or not tenant.plan_id:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
rows = (
|
||||||
|
db.query(Access.access_code)
|
||||||
|
.join(PlanAccess, PlanAccess.access_id == Access.id)
|
||||||
|
.filter(PlanAccess.plan_id == tenant.plan_id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return {row[0] for row in rows}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_plan_module_access_codes(
|
||||||
|
db: Session,
|
||||||
|
tenant_id: Optional[uuid.UUID],
|
||||||
|
module_id: Optional[uuid.UUID] = None,
|
||||||
|
) -> Set[str]:
|
||||||
|
tenant = SubscriptionEntitlementService.get_tenant(db, tenant_id)
|
||||||
|
if not tenant or not tenant.plan_id:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
query = (
|
||||||
|
db.query(ModuleAccess.access_code)
|
||||||
|
.join(
|
||||||
|
PlanModuleAccess,
|
||||||
|
PlanModuleAccess.module_access_id == ModuleAccess.id,
|
||||||
|
)
|
||||||
|
.filter(PlanModuleAccess.plan_id == tenant.plan_id)
|
||||||
|
)
|
||||||
|
if module_id:
|
||||||
|
query = query.filter(ModuleAccess.module_id == module_id)
|
||||||
|
|
||||||
|
rows = query.all()
|
||||||
|
return {row[0] for row in rows}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_effective_access_codes(db: Session, user: User) -> Set[str]:
|
||||||
|
role_codes = set()
|
||||||
|
if user.role:
|
||||||
|
role_codes = {
|
||||||
|
ra.access.access_code
|
||||||
|
for ra in user.role.role_accesses
|
||||||
|
if ra.access is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
plan_codes = SubscriptionEntitlementService.get_plan_access_codes(
|
||||||
|
db, user.tenant_id
|
||||||
|
)
|
||||||
|
return role_codes | plan_codes
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_subscription_summary(
|
||||||
|
db: Session, tenant_id: Optional[uuid.UUID]
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
tenant = SubscriptionEntitlementService.get_tenant(db, tenant_id)
|
||||||
|
if not tenant:
|
||||||
|
return None
|
||||||
|
|
||||||
|
plan_name = tenant.plan.name if tenant.plan else None
|
||||||
|
return {
|
||||||
|
"plan_id": str(tenant.plan_id) if tenant.plan_id else None,
|
||||||
|
"plan_name": plan_name,
|
||||||
|
"max_users_allowed": tenant.plan.max_users_allowed if tenant.plan else None,
|
||||||
|
"start_date": tenant.start_date.isoformat() if tenant.start_date else None,
|
||||||
|
"end_date": tenant.end_date.isoformat() if tenant.end_date else None,
|
||||||
|
"status": tenant.status,
|
||||||
|
"is_active": tenant.is_active,
|
||||||
|
}
|
||||||
@@ -7,6 +7,13 @@ from fastapi import HTTPException, status
|
|||||||
from app.models.auth.subscription_plan_model import SubscriptionPlan
|
from app.models.auth.subscription_plan_model import SubscriptionPlan
|
||||||
from app.models.auth.plan_access_model import PlanAccess
|
from app.models.auth.plan_access_model import PlanAccess
|
||||||
from app.models.auth.plan_module_access_model import PlanModuleAccess
|
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 (
|
from app.schemas.auth.subscription_plan_schema import (
|
||||||
SubscriptionPlanCreate,
|
SubscriptionPlanCreate,
|
||||||
SubscriptionPlanUpdate,
|
SubscriptionPlanUpdate,
|
||||||
@@ -15,6 +22,108 @@ from app.schemas.auth.subscription_plan_schema import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
class SubscriptionPlanService:
|
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
|
@staticmethod
|
||||||
def create_plan(db: Session, plan_data: SubscriptionPlanCreate) -> SubscriptionPlan:
|
def create_plan(db: Session, plan_data: SubscriptionPlanCreate) -> SubscriptionPlan:
|
||||||
@@ -27,6 +136,7 @@ class SubscriptionPlanService:
|
|||||||
description=plan_data.description,
|
description=plan_data.description,
|
||||||
price=plan_data.price,
|
price=plan_data.price,
|
||||||
duration_days=plan_data.duration_days,
|
duration_days=plan_data.duration_days,
|
||||||
|
max_users_allowed=plan_data.max_users_allowed,
|
||||||
is_public=plan_data.is_public,
|
is_public=plan_data.is_public,
|
||||||
status=plan_data.status
|
status=plan_data.status
|
||||||
)
|
)
|
||||||
@@ -77,6 +187,8 @@ class SubscriptionPlanService:
|
|||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(plan)
|
db.refresh(plan)
|
||||||
|
SubscriptionPlanService._sync_tenant_default_roles_for_plan(db, plan.id)
|
||||||
|
db.commit()
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ class TenantService:
|
|||||||
"tenant_name": tenant.tenant_name,
|
"tenant_name": tenant.tenant_name,
|
||||||
"tenant_domain": tenant.tenant_domain,
|
"tenant_domain": tenant.tenant_domain,
|
||||||
"tenant_logo_url": tenant.tenant_logo_url,
|
"tenant_logo_url": tenant.tenant_logo_url,
|
||||||
|
"max_users_allowed": plan.max_users_allowed,
|
||||||
"provisioning_id": provisioning_id,
|
"provisioning_id": provisioning_id,
|
||||||
"targets": event_targets
|
"targets": event_targets
|
||||||
}
|
}
|
||||||
@@ -386,6 +387,7 @@ class TenantService:
|
|||||||
payload = {
|
payload = {
|
||||||
"tenant_id": str(tenant.id),
|
"tenant_id": str(tenant.id),
|
||||||
"tenant_name": tenant.tenant_name,
|
"tenant_name": tenant.tenant_name,
|
||||||
|
"max_users_allowed": plan.max_users_allowed,
|
||||||
"provisioning_id": provisioning_id,
|
"provisioning_id": provisioning_id,
|
||||||
"targets": event_targets
|
"targets": event_targets
|
||||||
}
|
}
|
||||||
@@ -428,6 +430,7 @@ class TenantService:
|
|||||||
"tenant_name": tenant.tenant_name,
|
"tenant_name": tenant.tenant_name,
|
||||||
"tenant_domain": tenant.tenant_domain,
|
"tenant_domain": tenant.tenant_domain,
|
||||||
"tenant_logo_url": tenant.tenant_logo_url,
|
"tenant_logo_url": tenant.tenant_logo_url,
|
||||||
|
"max_users_allowed": tenant.plan.max_users_allowed if tenant.plan else None,
|
||||||
"targets": broadcast_targets
|
"targets": broadcast_targets
|
||||||
}
|
}
|
||||||
EventService.emit_event(
|
EventService.emit_event(
|
||||||
|
|||||||
Reference in New Issue
Block a user