feat(roles): add admin store roles page, permission i18n, and menu integration
Some checks failed
Some checks failed
- Add admin store roles page with merchant→store cascading for superadmin and store-only selection for platform admin - Add permission catalog API with translated labels/descriptions (en/fr/de/lb) - Add permission translations to all 15 module locale files (60 files total) - Add info icon tooltips for permission descriptions in role editor - Add store roles menu item and admin menu item in module definition - Fix store-selector.js URL construction bug when apiEndpoint has query params - Add admin store roles API (CRUD + platform scoping) - Add integration tests for admin store roles and permission catalog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -476,12 +476,17 @@ class AdminService:
|
||||
search: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
is_verified: bool | None = None,
|
||||
merchant_id: int | None = None,
|
||||
) -> tuple[list[Store], int]:
|
||||
"""Get paginated list of all stores with filtering."""
|
||||
try:
|
||||
# Eagerly load merchant relationship to avoid N+1 queries
|
||||
query = db.query(Store).options(joinedload(Store.merchant))
|
||||
|
||||
# Filter by merchant
|
||||
if merchant_id is not None:
|
||||
query = query.filter(Store.merchant_id == merchant_id)
|
||||
|
||||
# Apply search filter
|
||||
if search:
|
||||
search_term = f"%{search}%"
|
||||
@@ -501,6 +506,8 @@ class AdminService:
|
||||
|
||||
# Get total count (without joinedload for performance)
|
||||
count_query = db.query(Store)
|
||||
if merchant_id is not None:
|
||||
count_query = count_query.filter(Store.merchant_id == merchant_id)
|
||||
if search:
|
||||
search_term = f"%{search}%"
|
||||
count_query = count_query.filter(
|
||||
|
||||
@@ -35,7 +35,7 @@ from app.modules.tenancy.exceptions import (
|
||||
TeamMemberAlreadyExistsException,
|
||||
UserNotFoundException,
|
||||
)
|
||||
from app.modules.tenancy.models import Role, Store, StoreUser, User
|
||||
from app.modules.tenancy.models import Role, Store, StorePlatform, StoreUser, User
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -724,6 +724,59 @@ class StoreTeamService:
|
||||
details={"role_name": role_name, "store_id": store_id},
|
||||
)
|
||||
|
||||
# ========================================================================
|
||||
# Admin Access Validation
|
||||
# ========================================================================
|
||||
|
||||
def validate_admin_store_access(
|
||||
self,
|
||||
db: Session,
|
||||
user_context,
|
||||
store_id: int,
|
||||
) -> Store:
|
||||
"""
|
||||
Verify an admin user can access the given store.
|
||||
|
||||
Super admins can access any store. Platform admins can only access
|
||||
stores that belong to one of their assigned platforms.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
user_context: UserContext of the admin user
|
||||
store_id: Store ID to validate access to
|
||||
|
||||
Returns:
|
||||
The Store object if access is granted
|
||||
|
||||
Raises:
|
||||
InvalidRoleException: If store not found or admin lacks access
|
||||
"""
|
||||
store = db.query(Store).filter(Store.id == store_id).first()
|
||||
if not store:
|
||||
raise InvalidRoleException(f"Store {store_id} not found")
|
||||
|
||||
# Super admins (accessible_platform_ids is None) can access all stores
|
||||
platform_ids = user_context.get_accessible_platform_ids()
|
||||
if platform_ids is None:
|
||||
return store
|
||||
|
||||
# Platform admins: store must belong to one of their platforms
|
||||
store_in_platform = (
|
||||
db.query(StorePlatform)
|
||||
.filter(
|
||||
StorePlatform.store_id == store_id,
|
||||
StorePlatform.platform_id.in_(platform_ids),
|
||||
StorePlatform.is_active == True,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not store_in_platform:
|
||||
raise InvalidRoleException(
|
||||
"You do not have access to this store's roles"
|
||||
)
|
||||
|
||||
return store
|
||||
|
||||
# Private helper methods
|
||||
|
||||
def _generate_invitation_token(self) -> str:
|
||||
|
||||
Reference in New Issue
Block a user