filter functionality added on all modules.
This commit is contained in:
@@ -89,6 +89,10 @@ class RoleController:
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
page_size: int = 10,
|
page_size: int = 10,
|
||||||
search: Optional[str] = None,
|
search: Optional[str] = None,
|
||||||
|
filter_role_names: Optional[List[str]] = None,
|
||||||
|
filter_tenant_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
sort_by: Optional[str] = None,
|
||||||
|
sort_order: Optional[str] = None,
|
||||||
) -> RolePaginatedResponse:
|
) -> RolePaginatedResponse:
|
||||||
tenant_id = None if current_user_tenant_id is None else current_user_tenant_id
|
tenant_id = None if current_user_tenant_id is None else current_user_tenant_id
|
||||||
return RoleService.get_roles_paginated(
|
return RoleService.get_roles_paginated(
|
||||||
@@ -97,4 +101,8 @@ class RoleController:
|
|||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
search=search,
|
search=search,
|
||||||
|
filter_role_names=filter_role_names,
|
||||||
|
filter_tenant_ids=filter_tenant_ids,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from app.schemas.auth.subscription_plan_schema import SubscriptionPlanCreate, SubscriptionPlanUpdate
|
from app.schemas.auth.subscription_plan_schema import SubscriptionPlanCreate, SubscriptionPlanUpdate
|
||||||
from app.services.auth.subscription_plan_service import SubscriptionPlanService
|
from app.services.auth.subscription_plan_service import SubscriptionPlanService
|
||||||
@@ -23,8 +23,28 @@ class SubscriptionPlanController:
|
|||||||
return SubscriptionPlanService.get_all_plans(db, is_public, status)
|
return SubscriptionPlanService.get_all_plans(db, is_public, status)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_paginated_plans(db: Session, page: int, page_size: int, search: Optional[str]):
|
def get_paginated_plans(
|
||||||
return SubscriptionPlanService.get_paginated_plans(db, page, page_size, search)
|
db: Session,
|
||||||
|
page: int,
|
||||||
|
page_size: int,
|
||||||
|
search: Optional[str],
|
||||||
|
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,
|
||||||
|
):
|
||||||
|
return SubscriptionPlanService.get_paginated_plans(
|
||||||
|
db,
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
search,
|
||||||
|
filter_names=filter_names,
|
||||||
|
statuses=statuses,
|
||||||
|
visibility=visibility,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_plan(db: Session, plan_id: uuid.UUID):
|
def delete_plan(db: Session, plan_id: uuid.UUID):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from sqlalchemy.orm import Session
|
|||||||
from app.schemas.auth.tenant_schema import TenantCreate, TenantUpdate
|
from app.schemas.auth.tenant_schema import TenantCreate, TenantUpdate
|
||||||
from app.services.auth.tenant_service import TenantService
|
from app.services.auth.tenant_service import TenantService
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
class TenantController:
|
class TenantController:
|
||||||
|
|
||||||
@@ -33,6 +33,12 @@ class TenantController:
|
|||||||
page_size: int,
|
page_size: int,
|
||||||
search: Optional[str],
|
search: Optional[str],
|
||||||
is_active: Optional[bool],
|
is_active: Optional[bool],
|
||||||
|
filter_tenant_names: Optional[List[str]] = None,
|
||||||
|
filter_tenant_domains: Optional[List[str]] = None,
|
||||||
|
filter_plan_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
statuses: Optional[List[bool]] = None,
|
||||||
|
sort_by: Optional[str] = None,
|
||||||
|
sort_order: Optional[str] = None,
|
||||||
):
|
):
|
||||||
return TenantService.get_tenants_paginated(
|
return TenantService.get_tenants_paginated(
|
||||||
db=db,
|
db=db,
|
||||||
@@ -40,4 +46,10 @@ class TenantController:
|
|||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
search=search,
|
search=search,
|
||||||
is_active=is_active,
|
is_active=is_active,
|
||||||
|
filter_tenant_names=filter_tenant_names,
|
||||||
|
filter_tenant_domains=filter_tenant_domains,
|
||||||
|
filter_plan_ids=filter_plan_ids,
|
||||||
|
statuses=statuses,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
)
|
)
|
||||||
@@ -55,7 +55,13 @@ class UserController:
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
page_size: int = 10,
|
page_size: int = 10,
|
||||||
search: Optional[str] = None,
|
search: Optional[str] = None,
|
||||||
status: Optional[str] = None,
|
filter_names: Optional[List[str]] = None,
|
||||||
|
filter_emails: Optional[List[str]] = None,
|
||||||
|
statuses: Optional[List[str]] = None,
|
||||||
|
filter_tenant_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
filter_role_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
sort_by: Optional[str] = None,
|
||||||
|
sort_order: Optional[str] = None,
|
||||||
):
|
):
|
||||||
tenant_id = current_user.tenant_id
|
tenant_id = current_user.tenant_id
|
||||||
return UserService.get_users_paginated(
|
return UserService.get_users_paginated(
|
||||||
@@ -64,5 +70,11 @@ class UserController:
|
|||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
search=search,
|
search=search,
|
||||||
status=status,
|
filter_names=filter_names,
|
||||||
|
filter_emails=filter_emails,
|
||||||
|
statuses=statuses,
|
||||||
|
filter_tenant_ids=filter_tenant_ids,
|
||||||
|
filter_role_ids=filter_role_ids,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
)
|
)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy import asc, desc, or_, cast, String
|
||||||
|
|
||||||
from app.config.database import get_db
|
from app.config.database import get_db
|
||||||
from app.middleware.auth_middleware import get_current_user, User
|
from app.middleware.auth_middleware import get_current_user, User
|
||||||
@@ -16,6 +17,12 @@ def get_audit_logs(
|
|||||||
module_name: Optional[str] = Query(None, description="Filter by module name"),
|
module_name: Optional[str] = Query(None, description="Filter by module name"),
|
||||||
action_type: Optional[str] = Query(None, description="CREATE | UPDATE | DELETE"),
|
action_type: Optional[str] = Query(None, description="CREATE | UPDATE | DELETE"),
|
||||||
performed_by_email: Optional[str] = Query(None, description="Filter by actor email"),
|
performed_by_email: Optional[str] = Query(None, description="Filter by actor email"),
|
||||||
|
search: Optional[str] = Query(None, description="Search audit logs"),
|
||||||
|
module_names: Optional[List[str]] = Query(None, description="Filter by module names"),
|
||||||
|
action_types: Optional[List[str]] = Query(None, description="Filter by action types"),
|
||||||
|
performed_by_emails: Optional[List[str]] = Query(None, description="Filter by actor emails"),
|
||||||
|
sort_by: Optional[str] = Query("created_at", description="Sort by created_at, module_name, action_type, or performed_by_email"),
|
||||||
|
sort_order: Optional[str] = Query("desc", description="Sort order: asc or desc"),
|
||||||
limit: int = Query(50, ge=1, le=500),
|
limit: int = Query(50, ge=1, le=500),
|
||||||
offset: int = Query(0, ge=0),
|
offset: int = Query(0, ge=0),
|
||||||
):
|
):
|
||||||
@@ -27,16 +34,44 @@ def get_audit_logs(
|
|||||||
|
|
||||||
if module_name:
|
if module_name:
|
||||||
query = query.filter(AuditLog.module_name == module_name)
|
query = query.filter(AuditLog.module_name == module_name)
|
||||||
|
if module_names:
|
||||||
|
query = query.filter(AuditLog.module_name.in_(module_names))
|
||||||
if action_type:
|
if action_type:
|
||||||
query = query.filter(AuditLog.action_type == action_type.upper())
|
query = query.filter(AuditLog.action_type == action_type.upper())
|
||||||
|
if action_types:
|
||||||
|
normalized_action_types = [item.upper() for item in action_types]
|
||||||
|
query = query.filter(AuditLog.action_type.in_(normalized_action_types))
|
||||||
if performed_by_email:
|
if performed_by_email:
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
AuditLog.performed_by_email.ilike(f"%{performed_by_email}%")
|
AuditLog.performed_by_email.ilike(f"%{performed_by_email}%")
|
||||||
)
|
)
|
||||||
|
if performed_by_emails:
|
||||||
|
query = query.filter(AuditLog.performed_by_email.in_(performed_by_emails))
|
||||||
|
if search:
|
||||||
|
search_term = search.strip()
|
||||||
|
query = query.filter(
|
||||||
|
or_(
|
||||||
|
AuditLog.module_name.ilike(f"%{search_term}%"),
|
||||||
|
AuditLog.action_type.ilike(f"%{search_term}%"),
|
||||||
|
AuditLog.entity_name.ilike(f"%{search_term}%"),
|
||||||
|
AuditLog.description.ilike(f"%{search_term}%"),
|
||||||
|
AuditLog.performed_by_email.ilike(f"%{search_term}%"),
|
||||||
|
AuditLog.ip_address.ilike(f"%{search_term}%"),
|
||||||
|
cast(AuditLog.entity_id, String).ilike(f"%{search_term}%"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
total = query.count()
|
total = query.count()
|
||||||
|
sort_column_map = {
|
||||||
|
"created_at": AuditLog.created_at,
|
||||||
|
"module_name": AuditLog.module_name,
|
||||||
|
"action_type": AuditLog.action_type,
|
||||||
|
"performed_by_email": AuditLog.performed_by_email,
|
||||||
|
}
|
||||||
|
sort_column = sort_column_map.get(sort_by or "created_at", AuditLog.created_at)
|
||||||
|
order_fn = asc if (sort_order or "").lower() == "asc" else desc
|
||||||
items = (
|
items = (
|
||||||
query.order_by(AuditLog.created_at.desc())
|
query.order_by(order_fn(sort_column))
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ def list_roles(
|
|||||||
page: int = Query(1, ge=1, description="Page number"),
|
page: int = Query(1, ge=1, description="Page number"),
|
||||||
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
||||||
search: Optional[str] = Query(None, description="Search in role name or ID"),
|
search: Optional[str] = Query(None, description="Search in role name or ID"),
|
||||||
|
filter_role_names: Optional[List[str]] = Query(None, description="Filter by role names"),
|
||||||
|
filter_tenant_ids: Optional[List[uuid.UUID]] = Query(None, description="Filter by tenant IDs"),
|
||||||
|
sort_by: Optional[str] = Query(None, description="Sort by role name or tenant"),
|
||||||
|
sort_order: Optional[str] = Query(None, description="Sort order: asc or desc"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
_=Depends(require_access("admin.role.read")),
|
_=Depends(require_access("admin.role.read")),
|
||||||
@@ -145,4 +149,8 @@ def list_roles(
|
|||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
search=search,
|
search=search,
|
||||||
|
filter_role_names=filter_role_names,
|
||||||
|
filter_tenant_ids=filter_tenant_ids,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
)
|
)
|
||||||
@@ -93,11 +93,26 @@ def list_plans(
|
|||||||
page: int = Query(1, ge=1),
|
page: int = Query(1, ge=1),
|
||||||
page_size: int = Query(10, ge=1, le=100),
|
page_size: int = Query(10, ge=1, le=100),
|
||||||
search: Optional[str] = Query(None),
|
search: Optional[str] = Query(None),
|
||||||
|
filter_names: Optional[List[str]] = Query(None),
|
||||||
|
statuses: Optional[List[str]] = Query(None),
|
||||||
|
visibility: Optional[List[bool]] = Query(None, description="Filter by public/private visibility"),
|
||||||
|
sort_by: Optional[str] = Query(None, description="Sort by name, status, visibility, or price"),
|
||||||
|
sort_order: Optional[str] = Query(None, description="Sort order: asc or desc"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
_ = Depends(require_access("superadmin.plan.read"))
|
_ = Depends(require_access("superadmin.plan.read"))
|
||||||
):
|
):
|
||||||
return SubscriptionPlanController.get_paginated_plans(db, page, page_size, search)
|
return SubscriptionPlanController.get_paginated_plans(
|
||||||
|
db,
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
search,
|
||||||
|
filter_names=filter_names,
|
||||||
|
statuses=statuses,
|
||||||
|
visibility=visibility,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/all", response_model=List[SubscriptionPlanResponse])
|
@router.get("/all", response_model=List[SubscriptionPlanResponse])
|
||||||
def all_plans(
|
def all_plans(
|
||||||
|
|||||||
@@ -133,6 +133,12 @@ def list_tenants(
|
|||||||
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
||||||
search: Optional[str] = Query(None, description="Search in name, domain, or ID"),
|
search: Optional[str] = Query(None, description="Search in name, domain, or ID"),
|
||||||
is_active: Optional[bool] = Query(None, description="Filter by active status"),
|
is_active: Optional[bool] = Query(None, description="Filter by active status"),
|
||||||
|
filter_tenant_names: Optional[List[str]] = Query(None, description="Filter by tenant names"),
|
||||||
|
filter_tenant_domains: Optional[List[str]] = Query(None, description="Filter by tenant domains"),
|
||||||
|
filter_plan_ids: Optional[List[uuid.UUID]] = Query(None, description="Filter by plan IDs"),
|
||||||
|
statuses: Optional[List[bool]] = Query(None, description="Filter by tenant statuses"),
|
||||||
|
sort_by: Optional[str] = Query(None, description="Sort by name, domain, status, or plan"),
|
||||||
|
sort_order: Optional[str] = Query(None, description="Sort order: asc or desc"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
_ = Depends(require_access("superadmin.tenant.read")),
|
_ = Depends(require_access("superadmin.tenant.read")),
|
||||||
@@ -143,4 +149,10 @@ def list_tenants(
|
|||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
search=search,
|
search=search,
|
||||||
is_active=is_active,
|
is_active=is_active,
|
||||||
|
filter_tenant_names=filter_tenant_names,
|
||||||
|
filter_tenant_domains=filter_tenant_domains,
|
||||||
|
filter_plan_ids=filter_plan_ids,
|
||||||
|
statuses=statuses,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
)
|
)
|
||||||
+15
-3
@@ -121,7 +121,13 @@ def list_users(
|
|||||||
page: int = Query(1, ge=1, description="Page number"),
|
page: int = Query(1, ge=1, description="Page number"),
|
||||||
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
|
||||||
search: Optional[str] = Query(None, description="Search in email, name, phone, or ID"),
|
search: Optional[str] = Query(None, description="Search in email, name, phone, or ID"),
|
||||||
status: Optional[str] = Query(None, description="Filter by user status (e.g. active, inactive)"),
|
filter_names: Optional[List[str]] = Query(None, description="Filter by full names"),
|
||||||
|
filter_emails: Optional[List[str]] = Query(None, description="Filter by emails"),
|
||||||
|
statuses: Optional[List[str]] = Query(None, description="Filter by user statuses"),
|
||||||
|
filter_tenant_ids: Optional[List[uuid.UUID]] = Query(None, description="Filter by tenant IDs"),
|
||||||
|
filter_role_ids: Optional[List[uuid.UUID]] = Query(None, description="Filter by role IDs"),
|
||||||
|
sort_by: Optional[str] = Query(None, description="Sort by name, email, status, tenant, or role"),
|
||||||
|
sort_order: Optional[str] = Query(None, description="Sort order: asc or desc"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
_ = Depends(require_access("admin.user.read")),
|
_ = Depends(require_access("admin.user.read")),
|
||||||
@@ -132,5 +138,11 @@ def list_users(
|
|||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
search=search,
|
search=search,
|
||||||
status=status,
|
filter_names=filter_names,
|
||||||
)
|
filter_emails=filter_emails,
|
||||||
|
statuses=statuses,
|
||||||
|
filter_tenant_ids=filter_tenant_ids,
|
||||||
|
filter_role_ids=filter_role_ids,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import or_, cast, String
|
from sqlalchemy import or_, cast, String, asc, desc
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from app.models.auth.role_model import Role
|
from app.models.auth.role_model import Role
|
||||||
from app.models.auth.role_access_model import RoleAccess
|
from app.models.auth.role_access_model import RoleAccess
|
||||||
@@ -313,6 +313,10 @@ class RoleService:
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
page_size: int = 10,
|
page_size: int = 10,
|
||||||
search: Optional[str] = None,
|
search: Optional[str] = None,
|
||||||
|
filter_role_names: Optional[List[str]] = None,
|
||||||
|
filter_tenant_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
sort_by: Optional[str] = None,
|
||||||
|
sort_order: Optional[str] = None,
|
||||||
) -> RolePaginatedResponse:
|
) -> RolePaginatedResponse:
|
||||||
|
|
||||||
query = db.query(Role)
|
query = db.query(Role)
|
||||||
@@ -320,6 +324,12 @@ class RoleService:
|
|||||||
if tenant_id is not None:
|
if tenant_id is not None:
|
||||||
query = query.filter(Role.tenant_id == tenant_id)
|
query = query.filter(Role.tenant_id == tenant_id)
|
||||||
|
|
||||||
|
if filter_role_names:
|
||||||
|
query = query.filter(Role.role_name.in_(filter_role_names))
|
||||||
|
|
||||||
|
if filter_tenant_ids:
|
||||||
|
query = query.filter(Role.tenant_id.in_(filter_tenant_ids))
|
||||||
|
|
||||||
if search and search.strip():
|
if search and search.strip():
|
||||||
search_term = search.strip()
|
search_term = search.strip()
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
@@ -329,6 +339,15 @@ class RoleService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sort_column_map = {
|
||||||
|
"name": Role.role_name,
|
||||||
|
"tenant": Role.tenant_id,
|
||||||
|
}
|
||||||
|
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()
|
total = query.count()
|
||||||
|
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
from sqlalchemy.orm import Session, joinedload
|
from sqlalchemy.orm import Session, joinedload
|
||||||
from sqlalchemy import or_, cast, String
|
from sqlalchemy import or_, cast, String, asc, desc
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
|
|
||||||
from app.models.auth.subscription_plan_model import SubscriptionPlan
|
from app.models.auth.subscription_plan_model import SubscriptionPlan
|
||||||
@@ -103,8 +103,28 @@ class SubscriptionPlanService:
|
|||||||
return query.all()
|
return query.all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_paginated_plans(db: Session, page: int = 1, page_size: int = 10, search: Optional[str] = None) -> SubscriptionPlanPaginatedResponse:
|
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)
|
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:
|
if search:
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
or_(
|
or_(
|
||||||
@@ -112,6 +132,18 @@ class SubscriptionPlanService:
|
|||||||
cast(SubscriptionPlan.id, String).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()
|
total = query.count()
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
plans = query.offset(offset).limit(page_size).all()
|
plans = query.offset(offset).limit(page_size).all()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import or_, cast, String
|
from sqlalchemy import or_, cast, String, asc, desc
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from app.models.auth.tenant_model import Tenant
|
from app.models.auth.tenant_model import Tenant
|
||||||
from app.models.auth.tenant_module_model import TenantModule
|
from app.models.auth.tenant_module_model import TenantModule
|
||||||
@@ -13,7 +13,7 @@ from app.schemas.auth.tenant_schema import TenantCreate, TenantUpdate, TenantPag
|
|||||||
from app.schemas.auth.role_schema import RoleCreate, RoleUpdate
|
from app.schemas.auth.role_schema import RoleCreate, RoleUpdate
|
||||||
from app.services.auth.role_service import RoleService
|
from app.services.auth.role_service import RoleService
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
from app.services.auth.event_service import EventService
|
from app.services.auth.event_service import EventService
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -411,10 +411,25 @@ class TenantService:
|
|||||||
page_size: int = 10,
|
page_size: int = 10,
|
||||||
search: Optional[str] = None,
|
search: Optional[str] = None,
|
||||||
is_active: Optional[bool] = None,
|
is_active: Optional[bool] = None,
|
||||||
|
filter_tenant_names: Optional[List[str]] = None,
|
||||||
|
filter_tenant_domains: Optional[List[str]] = None,
|
||||||
|
filter_plan_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
statuses: Optional[List[bool]] = None,
|
||||||
|
sort_by: Optional[str] = None,
|
||||||
|
sort_order: Optional[str] = None,
|
||||||
) -> TenantPaginatedResponse:
|
) -> TenantPaginatedResponse:
|
||||||
|
|
||||||
query = db.query(Tenant)
|
query = db.query(Tenant)
|
||||||
|
|
||||||
|
if filter_tenant_names:
|
||||||
|
query = query.filter(Tenant.tenant_name.in_(filter_tenant_names))
|
||||||
|
|
||||||
|
if filter_tenant_domains:
|
||||||
|
query = query.filter(Tenant.tenant_domain.in_(filter_tenant_domains))
|
||||||
|
|
||||||
|
if filter_plan_ids:
|
||||||
|
query = query.filter(Tenant.plan_id.in_(filter_plan_ids))
|
||||||
|
|
||||||
if search and search.strip():
|
if search and search.strip():
|
||||||
search_term = search.strip()
|
search_term = search.strip()
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
@@ -428,6 +443,20 @@ class TenantService:
|
|||||||
if is_active is not None:
|
if is_active is not None:
|
||||||
query = query.filter(Tenant.is_active == is_active)
|
query = query.filter(Tenant.is_active == is_active)
|
||||||
|
|
||||||
|
if statuses is not None and len(statuses) > 0:
|
||||||
|
query = query.filter(Tenant.is_active.in_(statuses))
|
||||||
|
|
||||||
|
sort_column_map = {
|
||||||
|
"name": Tenant.tenant_name,
|
||||||
|
"domain": Tenant.tenant_domain,
|
||||||
|
"status": Tenant.is_active,
|
||||||
|
"plan": Tenant.plan_id,
|
||||||
|
}
|
||||||
|
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()
|
total = query.count()
|
||||||
|
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import or_, cast, String
|
from sqlalchemy import or_, cast, String, asc, desc, func
|
||||||
from fastapi import HTTPException, status, BackgroundTasks
|
from fastapi import HTTPException, status, BackgroundTasks
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
@@ -14,6 +14,7 @@ from app.models.auth.role_module_access_model import RoleModuleAccess
|
|||||||
from app.models.auth.module_access_model import ModuleAccess
|
from app.models.auth.module_access_model import ModuleAccess
|
||||||
from app.models.auth.tenant_module_model import TenantModule
|
from app.models.auth.tenant_module_model import TenantModule
|
||||||
from app.models.auth.role_model import Role
|
from app.models.auth.role_model import Role
|
||||||
|
from app.models.auth.tenant_model import Tenant
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -235,11 +236,17 @@ class UserService:
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
page_size: int = 10,
|
page_size: int = 10,
|
||||||
search: Optional[str] = None,
|
search: Optional[str] = None,
|
||||||
status: Optional[str] = None,
|
filter_names: Optional[List[str]] = None,
|
||||||
|
filter_emails: Optional[List[str]] = None,
|
||||||
|
statuses: Optional[List[str]] = None,
|
||||||
|
filter_tenant_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
filter_role_ids: Optional[List[uuid.UUID]] = None,
|
||||||
|
sort_by: Optional[str] = None,
|
||||||
|
sort_order: Optional[str] = None,
|
||||||
) -> UserPaginatedResponse:
|
) -> UserPaginatedResponse:
|
||||||
|
query = db.query(User).outerjoin(Tenant, User.tenant_id == Tenant.id).outerjoin(Role, User.role_id == Role.id)
|
||||||
query = db.query(User)
|
|
||||||
|
|
||||||
|
# Scope to tenant if not superadmin
|
||||||
if tenant_id:
|
if tenant_id:
|
||||||
query = query.filter(User.tenant_id == tenant_id)
|
query = query.filter(User.tenant_id == tenant_id)
|
||||||
|
|
||||||
@@ -256,8 +263,73 @@ class UserService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if status is not None:
|
if filter_names:
|
||||||
query = query.filter(User.status == status)
|
normalized_names = [name.strip() for name in filter_names if isinstance(name, str) and name.strip()]
|
||||||
|
if normalized_names:
|
||||||
|
query = query.filter(
|
||||||
|
func.trim(
|
||||||
|
func.concat(
|
||||||
|
User.first_name,
|
||||||
|
" ",
|
||||||
|
func.coalesce(User.last_name, ""),
|
||||||
|
)
|
||||||
|
).in_(normalized_names)
|
||||||
|
)
|
||||||
|
|
||||||
|
if filter_emails:
|
||||||
|
normalized_emails = [email.strip() for email in filter_emails if isinstance(email, str) and email.strip()]
|
||||||
|
if normalized_emails:
|
||||||
|
query = query.filter(User.email.in_(normalized_emails))
|
||||||
|
|
||||||
|
if statuses:
|
||||||
|
normalized_statuses = [status for status in statuses if status]
|
||||||
|
if normalized_statuses:
|
||||||
|
query = query.filter(User.status.in_(normalized_statuses))
|
||||||
|
|
||||||
|
if filter_tenant_ids:
|
||||||
|
tenant_ids: List[uuid.UUID] = []
|
||||||
|
for tenant_value in filter_tenant_ids:
|
||||||
|
try:
|
||||||
|
tenant_ids.append(
|
||||||
|
tenant_value if isinstance(tenant_value, uuid.UUID) else uuid.UUID(str(tenant_value))
|
||||||
|
)
|
||||||
|
except (ValueError, AttributeError, TypeError):
|
||||||
|
continue
|
||||||
|
if tenant_ids:
|
||||||
|
query = query.filter(User.tenant_id.in_(tenant_ids))
|
||||||
|
|
||||||
|
if filter_role_ids:
|
||||||
|
role_ids: List[uuid.UUID] = []
|
||||||
|
for role_value in filter_role_ids:
|
||||||
|
try:
|
||||||
|
role_ids.append(
|
||||||
|
role_value if isinstance(role_value, uuid.UUID) else uuid.UUID(str(role_value))
|
||||||
|
)
|
||||||
|
except (ValueError, AttributeError, TypeError):
|
||||||
|
continue
|
||||||
|
if role_ids:
|
||||||
|
query = query.filter(User.role_id.in_(role_ids))
|
||||||
|
|
||||||
|
normalized_sort_by = (sort_by or "").strip().lower()
|
||||||
|
normalized_sort_order = (sort_order or "asc").strip().lower()
|
||||||
|
sort_fn = desc if normalized_sort_order == "desc" else asc
|
||||||
|
|
||||||
|
if normalized_sort_by == "name":
|
||||||
|
query = query.order_by(
|
||||||
|
sort_fn(func.lower(User.first_name)),
|
||||||
|
sort_fn(func.lower(func.coalesce(User.last_name, ""))),
|
||||||
|
asc(User.email),
|
||||||
|
)
|
||||||
|
elif normalized_sort_by == "email":
|
||||||
|
query = query.order_by(sort_fn(func.lower(User.email)))
|
||||||
|
elif normalized_sort_by == "status":
|
||||||
|
query = query.order_by(sort_fn(func.lower(User.status)), asc(User.email))
|
||||||
|
elif normalized_sort_by == "tenant":
|
||||||
|
query = query.order_by(sort_fn(func.lower(func.coalesce(Tenant.tenant_name, ""))), asc(User.email))
|
||||||
|
elif normalized_sort_by == "role":
|
||||||
|
query = query.order_by(sort_fn(func.lower(func.coalesce(Role.role_name, ""))), asc(User.email))
|
||||||
|
else:
|
||||||
|
query = query.order_by(desc(User.created_at))
|
||||||
|
|
||||||
total = query.count()
|
total = query.count()
|
||||||
|
|
||||||
@@ -276,10 +348,6 @@ class UserService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolve_targets_for_role(db: Session, role_id: uuid.UUID, tenant_id: uuid.UUID = None) -> List[Dict[str, Any]]:
|
def _resolve_targets_for_role(db: Session, role_id: uuid.UUID, tenant_id: uuid.UUID = None) -> List[Dict[str, Any]]:
|
||||||
"""
|
|
||||||
Helper to resolve which modules/envs a role targets.
|
|
||||||
Mirrors logic in RoleService.
|
|
||||||
"""
|
|
||||||
active_modules = (
|
active_modules = (
|
||||||
db.query(ModuleAccess.module_id)
|
db.query(ModuleAccess.module_id)
|
||||||
.join(RoleModuleAccess, RoleModuleAccess.module_access_id == ModuleAccess.id)
|
.join(RoleModuleAccess, RoleModuleAccess.module_access_id == ModuleAccess.id)
|
||||||
@@ -298,7 +366,7 @@ class UserService:
|
|||||||
tm_assignments = db.query(TenantModule).filter(
|
tm_assignments = db.query(TenantModule).filter(
|
||||||
TenantModule.tenant_id == tenant_id,
|
TenantModule.tenant_id == tenant_id,
|
||||||
TenantModule.module_id.in_(module_ids),
|
TenantModule.module_id.in_(module_ids),
|
||||||
TenantModule.is_active == True
|
TenantModule.is_active == True
|
||||||
).all()
|
).all()
|
||||||
for tm in tm_assignments:
|
for tm in tm_assignments:
|
||||||
env_map[str(tm.module_id)] = tm.assigned_environment_slug or "prod"
|
env_map[str(tm.module_id)] = tm.assigned_environment_slug or "prod"
|
||||||
|
|||||||
Reference in New Issue
Block a user