refactor(arch): move auth schemas to tenancy module and add cross-module service methods
Some checks failed
Some checks failed
Move all auth schemas (UserContext, UserLogin, LoginResponse, etc.) from legacy models/schema/auth.py to app/modules/tenancy/schemas/auth.py per MOD-019. Update 84 import sites across 14 modules. Legacy file now re-exports for backwards compatibility. Add missing tenancy service methods for cross-module consumers: - merchant_service.get_merchant_by_owner_id() - merchant_service.get_merchant_count_for_owner() - admin_service.get_user_by_id() (public, was private-only) - platform_service.get_active_store_count() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,10 +56,10 @@ from app.modules.tenancy.exceptions import (
|
|||||||
)
|
)
|
||||||
from app.modules.tenancy.models import Store
|
from app.modules.tenancy.models import Store
|
||||||
from app.modules.tenancy.models import User as UserModel
|
from app.modules.tenancy.models import User as UserModel
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
from middleware.rate_limiter import RateLimiter
|
from middleware.rate_limiter import RateLimiter
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# Initialize dependencies
|
# Initialize dependencies
|
||||||
security = HTTPBearer(auto_error=False) # auto_error=False prevents automatic 403
|
security = HTTPBearer(auto_error=False) # auto_error=False prevents automatic 403
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from app.modules.billing.services import (
|
|||||||
subscription_service,
|
subscription_service,
|
||||||
)
|
)
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from app.modules.billing.schemas import (
|
|||||||
from app.modules.billing.services.feature_aggregator import feature_aggregator
|
from app.modules.billing.services.feature_aggregator import feature_aggregator
|
||||||
from app.modules.billing.services.feature_service import feature_service
|
from app.modules.billing.services.feature_service import feature_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_features_router = APIRouter(
|
admin_features_router = APIRouter(
|
||||||
prefix="/features",
|
prefix="/features",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from app.modules.billing.schemas.billing import (
|
|||||||
)
|
)
|
||||||
from app.modules.billing.services import billing_service, subscription_service
|
from app.modules.billing.services import billing_service, subscription_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from app.core.config import settings
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.billing.services import billing_service
|
from app.modules.billing.services import billing_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_addons_router = APIRouter(
|
store_addons_router = APIRouter(
|
||||||
prefix="/addons",
|
prefix="/addons",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from app.modules.billing.schemas.billing import (
|
|||||||
from app.modules.billing.services import billing_service
|
from app.modules.billing.services import billing_service
|
||||||
from app.modules.billing.services.subscription_service import subscription_service
|
from app.modules.billing.services.subscription_service import subscription_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_checkout_router = APIRouter(
|
store_checkout_router = APIRouter(
|
||||||
dependencies=[Depends(require_module_access("billing", FrontendType.STORE))],
|
dependencies=[Depends(require_module_access("billing", FrontendType.STORE))],
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from app.modules.billing.services.feature_aggregator import feature_aggregator
|
|||||||
from app.modules.billing.services.feature_service import feature_service
|
from app.modules.billing.services.feature_service import feature_service
|
||||||
from app.modules.billing.services.subscription_service import subscription_service
|
from app.modules.billing.services.subscription_service import subscription_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_features_router = APIRouter(
|
store_features_router = APIRouter(
|
||||||
prefix="/features",
|
prefix="/features",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from app.api.deps import get_current_store_api, require_module_access
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.billing.services.usage_service import usage_service
|
from app.modules.billing.services.usage_service import usage_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_usage_router = APIRouter(
|
store_usage_router = APIRouter(
|
||||||
prefix="/usage",
|
prefix="/usage",
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ from app.api.deps import get_current_merchant_from_cookie_or_header
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.core.utils.page_context import get_context_for_frontend
|
from app.modules.core.utils.page_context import get_context_for_frontend
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.templates_config import templates
|
from app.templates_config import templates
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
ROUTE_CONFIG = {
|
ROUTE_CONFIG = {
|
||||||
"prefix": "/billing",
|
"prefix": "/billing",
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ from app.modules.billing.models import (
|
|||||||
SubscriptionTier,
|
SubscriptionTier,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.models import Merchant, Platform, User
|
from app.modules.tenancy.models import Merchant, Platform, User
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from app.modules.catalog.schemas import (
|
|||||||
)
|
)
|
||||||
from app.modules.catalog.services.store_product_service import store_product_service
|
from app.modules.catalog.services.store_product_service import store_product_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_router = APIRouter(
|
admin_router = APIRouter(
|
||||||
prefix="/store-products",
|
prefix="/store-products",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from app.modules.catalog.schemas import (
|
|||||||
from app.modules.catalog.services.product_service import product_service
|
from app.modules.catalog.services.product_service import product_service
|
||||||
from app.modules.catalog.services.store_product_service import store_product_service
|
from app.modules.catalog.services.store_product_service import store_product_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_router = APIRouter(
|
store_router = APIRouter(
|
||||||
prefix="/products",
|
prefix="/products",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from app.api.deps import get_current_admin_api
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.cms.schemas.image import ImageStorageStats
|
from app.modules.cms.schemas.image import ImageStorageStats
|
||||||
from app.modules.cms.services.media_service import media_service
|
from app.modules.cms.services.media_service import media_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_images_router = APIRouter(prefix="/images")
|
admin_images_router = APIRouter(prefix="/images")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from app.modules.cms.schemas.media import (
|
|||||||
MediaUploadResponse,
|
MediaUploadResponse,
|
||||||
)
|
)
|
||||||
from app.modules.cms.services.media_service import media_service
|
from app.modules.cms.services.media_service import media_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_media_router = APIRouter(prefix="/media")
|
admin_media_router = APIRouter(prefix="/media")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from app.modules.cms.schemas.store_theme import (
|
|||||||
ThemePresetResponse,
|
ThemePresetResponse,
|
||||||
)
|
)
|
||||||
from app.modules.cms.services.store_theme_service import store_theme_service
|
from app.modules.cms.services.store_theme_service import store_theme_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_store_themes_router = APIRouter(prefix="/store-themes")
|
admin_store_themes_router = APIRouter(prefix="/store-themes")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from app.modules.cms.schemas.media import (
|
|||||||
UploadedFileInfo,
|
UploadedFileInfo,
|
||||||
)
|
)
|
||||||
from app.modules.cms.services.media_service import media_service
|
from app.modules.cms.services.media_service import media_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_media_router = APIRouter(prefix="/media")
|
store_media_router = APIRouter(prefix="/media")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from app.modules.core.schemas.dashboard import (
|
|||||||
)
|
)
|
||||||
from app.modules.core.services.stats_aggregator import stats_aggregator
|
from app.modules.core.services.stats_aggregator import stats_aggregator
|
||||||
from app.modules.core.services.widget_aggregator import widget_aggregator
|
from app.modules.core.services.widget_aggregator import widget_aggregator
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_dashboard_router = APIRouter(prefix="/dashboard")
|
admin_dashboard_router = APIRouter(prefix="/dashboard")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ from app.api.deps import (
|
|||||||
)
|
)
|
||||||
from app.modules.core.services.menu_service import MenuItemConfig, menu_service
|
from app.modules.core.services.menu_service import MenuItemConfig, menu_service
|
||||||
from app.modules.enums import FrontendType # API-007 - Enum for type safety
|
from app.modules.enums import FrontendType # API-007 - Enum for type safety
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.platform_service import platform_service
|
from app.modules.tenancy.services.platform_service import platform_service
|
||||||
from app.utils.i18n import DEFAULT_LANGUAGE, translate
|
from app.utils.i18n import DEFAULT_LANGUAGE, translate
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/menu-config")
|
router = APIRouter(prefix="/menu-config")
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from app.modules.tenancy.schemas.admin import (
|
|||||||
RowsPerPageResponse,
|
RowsPerPageResponse,
|
||||||
RowsPerPageUpdateResponse,
|
RowsPerPageUpdateResponse,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_settings_router = APIRouter(prefix="/settings")
|
admin_settings_router = APIRouter(prefix="/settings")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_merchant_from_cookie_or_header, get_db
|
from app.api.deps import get_current_merchant_from_cookie_or_header, get_db
|
||||||
from app.modules.core.services.menu_service import menu_service
|
from app.modules.core.services.menu_service import menu_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.utils.i18n import translate
|
from app.utils.i18n import translate
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from app.modules.core.schemas.dashboard import (
|
|||||||
)
|
)
|
||||||
from app.modules.core.services.stats_aggregator import stats_aggregator
|
from app.modules.core.services.stats_aggregator import stats_aggregator
|
||||||
from app.modules.tenancy.exceptions import StoreNotActiveException
|
from app.modules.tenancy.exceptions import StoreNotActiveException
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_dashboard_router = APIRouter(prefix="/dashboard")
|
store_dashboard_router = APIRouter(prefix="/dashboard")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ from app.api.deps import get_current_store_api, get_user_permissions
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.core.services.menu_service import menu_service
|
from app.modules.core.services.menu_service import menu_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from app.utils.i18n import translate
|
from app.utils.i18n import translate
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from app.core.database import get_db
|
|||||||
from app.modules.core.services.platform_settings_service import (
|
from app.modules.core.services.platform_settings_service import (
|
||||||
platform_settings_service,
|
platform_settings_service,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_settings_router = APIRouter(prefix="/settings")
|
store_settings_router = APIRouter(prefix="/settings")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ from app.api.deps import (
|
|||||||
)
|
)
|
||||||
from app.modules.core.utils.page_context import get_context_for_frontend
|
from app.modules.core.utils.page_context import get_context_for_frontend
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.templates_config import templates
|
from app.templates_config import templates
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
ROUTE_CONFIG = {
|
ROUTE_CONFIG = {
|
||||||
"prefix": "",
|
"prefix": "",
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ from app.modules.tenancy.exceptions import (
|
|||||||
UserNotActiveException,
|
UserNotActiveException,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.models import Store, StoreUser, User
|
from app.modules.tenancy.models import Store, StoreUser, User
|
||||||
|
from app.modules.tenancy.schemas.auth import UserLogin
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
from models.schema.auth import UserLogin
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ from app.modules.billing.models import (
|
|||||||
)
|
)
|
||||||
from app.modules.customers.models.customer import Customer
|
from app.modules.customers.models.customer import Customer
|
||||||
from app.modules.tenancy.models import Merchant, Platform, Store, StoreUser, User
|
from app.modules.tenancy.models import Merchant, Platform, Store, StoreUser, User
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from app.modules.billing.models import (
|
|||||||
)
|
)
|
||||||
from app.modules.tenancy.models import Merchant, Platform, User
|
from app.modules.tenancy.models import Merchant, Platform, User
|
||||||
from app.modules.tenancy.models.platform_module import PlatformModule
|
from app.modules.tenancy.models.platform_module import PlatformModule
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import pytest
|
|||||||
from app.api.deps import get_current_store_from_cookie_or_header
|
from app.api.deps import get_current_store_from_cookie_or_header
|
||||||
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
||||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from app.modules.customers.schemas import (
|
|||||||
)
|
)
|
||||||
from app.modules.customers.services import admin_customer_service
|
from app.modules.customers.services import admin_customer_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
# Create module-aware router
|
# Create module-aware router
|
||||||
admin_router = APIRouter(
|
admin_router = APIRouter(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from app.modules.customers.schemas import (
|
|||||||
)
|
)
|
||||||
from app.modules.customers.services import customer_service
|
from app.modules.customers.services import customer_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
# Create module-aware router
|
# Create module-aware router
|
||||||
store_router = APIRouter(
|
store_router = APIRouter(
|
||||||
@@ -150,4 +150,3 @@ def toggle_customer_status(
|
|||||||
|
|
||||||
status = "activated" if customer.is_active else "deactivated"
|
status = "activated" if customer.is_active else "deactivated"
|
||||||
return CustomerMessageResponse(message=f"Customer {status} successfully")
|
return CustomerMessageResponse(message=f"Customer {status} successfully")
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ from app.modules.messaging.services.email_service import (
|
|||||||
EmailService, # MOD-004 - Core email service
|
EmailService, # MOD-004 - Core email service
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.exceptions import StoreNotFoundException
|
from app.modules.tenancy.exceptions import StoreNotFoundException
|
||||||
from models.schema.auth import (
|
from app.modules.tenancy.schemas.auth import (
|
||||||
LogoutResponse,
|
LogoutResponse,
|
||||||
PasswordResetRequestResponse,
|
PasswordResetRequestResponse,
|
||||||
PasswordResetResponse,
|
PasswordResetResponse,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ from app.modules.inventory.services.inventory_service import inventory_service
|
|||||||
from app.modules.inventory.services.inventory_transaction_service import (
|
from app.modules.inventory.services.inventory_transaction_service import (
|
||||||
inventory_transaction_service,
|
inventory_transaction_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_router = APIRouter(
|
admin_router = APIRouter(
|
||||||
prefix="/inventory",
|
prefix="/inventory",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from app.modules.inventory.services.inventory_service import inventory_service
|
|||||||
from app.modules.inventory.services.inventory_transaction_service import (
|
from app.modules.inventory.services.inventory_transaction_service import (
|
||||||
inventory_transaction_service,
|
inventory_transaction_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_router = APIRouter(
|
store_router = APIRouter(
|
||||||
prefix="/inventory",
|
prefix="/inventory",
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from app.modules.core.utils.page_context import get_context_for_frontend
|
|||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from app.modules.loyalty.services import program_service
|
from app.modules.loyalty.services import program_service
|
||||||
from app.modules.tenancy.models import Merchant
|
from app.modules.tenancy.models import Merchant
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.templates_config import templates
|
from app.templates_config import templates
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ from app.modules.loyalty.models.loyalty_program import LoyaltyType
|
|||||||
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
||||||
from app.modules.tenancy.models.store import StoreUser
|
from app.modules.tenancy.models.store import StoreUser
|
||||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ from app.modules.orders.exceptions import OrderHasUnresolvedExceptionsException
|
|||||||
from app.modules.orders.services.order_item_exception_service import (
|
from app.modules.orders.services.order_item_exception_service import (
|
||||||
order_item_exception_service,
|
order_item_exception_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_letzshop_router = APIRouter(
|
admin_letzshop_router = APIRouter(
|
||||||
prefix="/letzshop",
|
prefix="/letzshop",
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ from app.modules.marketplace.schemas import (
|
|||||||
from app.modules.marketplace.services.marketplace_import_job_service import (
|
from app.modules.marketplace.services.marketplace_import_job_service import (
|
||||||
marketplace_import_job_service,
|
marketplace_import_job_service,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_marketplace_router = APIRouter(
|
admin_marketplace_router = APIRouter(
|
||||||
prefix="/marketplace-import-jobs",
|
prefix="/marketplace-import-jobs",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from app.modules.enums import FrontendType
|
|||||||
from app.modules.marketplace.services.marketplace_product_service import (
|
from app.modules.marketplace.services.marketplace_product_service import (
|
||||||
marketplace_product_service,
|
marketplace_product_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_products_router = APIRouter(
|
admin_products_router = APIRouter(
|
||||||
prefix="/products",
|
prefix="/products",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ from app.modules.orders.exceptions import OrderHasUnresolvedExceptionsException
|
|||||||
from app.modules.orders.services.order_item_exception_service import (
|
from app.modules.orders.services.order_item_exception_service import (
|
||||||
order_item_exception_service,
|
order_item_exception_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_letzshop_router = APIRouter(
|
store_letzshop_router = APIRouter(
|
||||||
prefix="/letzshop",
|
prefix="/letzshop",
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ from app.modules.marketplace.schemas import (
|
|||||||
from app.modules.marketplace.services.marketplace_import_job_service import (
|
from app.modules.marketplace.services.marketplace_import_job_service import (
|
||||||
marketplace_import_job_service,
|
marketplace_import_job_service,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from middleware.decorators import rate_limit
|
from middleware.decorators import rate_limit
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_marketplace_router = APIRouter(
|
store_marketplace_router = APIRouter(
|
||||||
prefix="/marketplace",
|
prefix="/marketplace",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from app.modules.marketplace.schemas import (
|
|||||||
ProductImportConfigResponse,
|
ProductImportConfigResponse,
|
||||||
)
|
)
|
||||||
from app.modules.marketplace.services.onboarding_service import OnboardingService
|
from app.modules.marketplace.services.onboarding_service import OnboardingService
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_onboarding_router = APIRouter(
|
store_onboarding_router = APIRouter(
|
||||||
prefix="/onboarding",
|
prefix="/onboarding",
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ from app.modules.marketplace.models import OnboardingStatus, StoreOnboarding
|
|||||||
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
||||||
from app.modules.tenancy.models.platform_module import PlatformModule
|
from app.modules.tenancy.models.platform_module import PlatformModule
|
||||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from app.modules.marketplace.routes.pages.store import (
|
|||||||
store_marketplace_page,
|
store_marketplace_page,
|
||||||
store_onboarding_page,
|
store_onboarding_page,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
|
|
||||||
def _make_user_context(store_id: int = 1, store_code: str = "teststore") -> UserContext:
|
def _make_user_context(store_id: int = 1, store_code: str = "teststore") -> UserContext:
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from app.api.deps import get_current_admin_api
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.messaging.services.email_service import EmailService
|
from app.modules.messaging.services.email_service import EmailService
|
||||||
from app.modules.messaging.services.email_template_service import EmailTemplateService
|
from app.modules.messaging.services.email_template_service import EmailTemplateService
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_email_templates_router = APIRouter(prefix="/email-templates")
|
admin_email_templates_router = APIRouter(prefix="/email-templates")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ from app.modules.messaging.services.message_attachment_service import (
|
|||||||
message_attachment_service,
|
message_attachment_service,
|
||||||
)
|
)
|
||||||
from app.modules.messaging.services.messaging_service import messaging_service
|
from app.modules.messaging.services.messaging_service import messaging_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_messages_router = APIRouter(prefix="/messages")
|
admin_messages_router = APIRouter(prefix="/messages")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from app.modules.tenancy.schemas.admin import (
|
|||||||
PlatformAlertResolve,
|
PlatformAlertResolve,
|
||||||
PlatformAlertResponse,
|
PlatformAlertResponse,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_notifications_router = APIRouter(prefix="/notifications")
|
admin_notifications_router = APIRouter(prefix="/notifications")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from app.modules.billing.services.subscription_service import subscription_servi
|
|||||||
from app.modules.messaging.services.store_email_settings_service import (
|
from app.modules.messaging.services.store_email_settings_service import (
|
||||||
store_email_settings_service,
|
store_email_settings_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_email_settings_router = APIRouter(prefix="/email-settings")
|
store_email_settings_router = APIRouter(prefix="/email-settings")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ from app.api.deps import get_current_store_api
|
|||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.messaging.services.email_service import EmailService
|
from app.modules.messaging.services.email_service import EmailService
|
||||||
from app.modules.messaging.services.email_template_service import EmailTemplateService
|
from app.modules.messaging.services.email_template_service import EmailTemplateService
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_email_templates_router = APIRouter(prefix="/email-templates")
|
store_email_templates_router = APIRouter(prefix="/email-templates")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ from app.modules.messaging.services.message_attachment_service import (
|
|||||||
message_attachment_service,
|
message_attachment_service,
|
||||||
)
|
)
|
||||||
from app.modules.messaging.services.messaging_service import messaging_service
|
from app.modules.messaging.services.messaging_service import messaging_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_messages_router = APIRouter(prefix="/messages")
|
store_messages_router = APIRouter(prefix="/messages")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ from app.modules.messaging.schemas import (
|
|||||||
TestNotificationRequest,
|
TestNotificationRequest,
|
||||||
UnreadCountResponse,
|
UnreadCountResponse,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_notifications_router = APIRouter(prefix="/notifications")
|
store_notifications_router = APIRouter(prefix="/notifications")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from app.modules.tenancy.schemas.admin import (
|
|||||||
AdminAuditLogListResponse,
|
AdminAuditLogListResponse,
|
||||||
AdminAuditLogResponse,
|
AdminAuditLogResponse,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_audit_router = APIRouter(prefix="/audit")
|
admin_audit_router = APIRouter(prefix="/audit")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from app.modules.monitoring.exceptions import (
|
|||||||
ScanNotFoundException,
|
ScanNotFoundException,
|
||||||
ViolationNotFoundException,
|
ViolationNotFoundException,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_code_quality_router = APIRouter(prefix="/code-quality")
|
admin_code_quality_router = APIRouter(prefix="/code-quality")
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from app.modules.tenancy.schemas.admin import (
|
|||||||
LogSettingsUpdateResponse,
|
LogSettingsUpdateResponse,
|
||||||
LogStatistics,
|
LogStatistics,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_logs_router = APIRouter(prefix="/logs")
|
admin_logs_router = APIRouter(prefix="/logs")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from app.core.database import get_db
|
|||||||
from app.modules.monitoring.services.platform_health_service import (
|
from app.modules.monitoring.services.platform_health_service import (
|
||||||
platform_health_service,
|
platform_health_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_platform_health_router = APIRouter(prefix="/platform")
|
admin_platform_health_router = APIRouter(prefix="/platform")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from app.core.database import get_db
|
|||||||
from app.modules.monitoring.services.background_tasks_service import (
|
from app.modules.monitoring.services.background_tasks_service import (
|
||||||
background_tasks_service,
|
background_tasks_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_tasks_router = APIRouter(prefix="/tasks")
|
admin_tasks_router = APIRouter(prefix="/tasks")
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.dev_tools.services.test_runner_service import test_runner_service
|
from app.modules.dev_tools.services.test_runner_service import test_runner_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
admin_tests_router = APIRouter(prefix="/tests")
|
admin_tests_router = APIRouter(prefix="/tests")
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from app.modules.orders.schemas import (
|
|||||||
ShippingLabelInfo,
|
ShippingLabelInfo,
|
||||||
)
|
)
|
||||||
from app.modules.orders.services.order_service import order_service
|
from app.modules.orders.services.order_service import order_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
# Base router for orders
|
# Base router for orders
|
||||||
_orders_router = APIRouter(
|
_orders_router = APIRouter(
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from app.modules.orders.schemas import (
|
|||||||
from app.modules.orders.services.order_item_exception_service import (
|
from app.modules.orders.services.order_item_exception_service import (
|
||||||
order_item_exception_service,
|
order_item_exception_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from app.modules.orders.schemas import (
|
|||||||
)
|
)
|
||||||
from app.modules.orders.services.order_inventory_service import order_inventory_service
|
from app.modules.orders.services.order_inventory_service import order_inventory_service
|
||||||
from app.modules.orders.services.order_service import order_service
|
from app.modules.orders.services.order_service import order_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
# Base router for orders
|
# Base router for orders
|
||||||
_orders_router = APIRouter(
|
_orders_router = APIRouter(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from app.core.database import get_db
|
|||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from app.modules.orders.services.customer_order_service import customer_order_service
|
from app.modules.orders.services.customer_order_service import customer_order_service
|
||||||
from app.modules.orders.services.order_metrics import order_metrics_provider
|
from app.modules.orders.services.order_metrics import order_metrics_provider
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from app.modules.orders.schemas import (
|
|||||||
from app.modules.orders.services.order_item_exception_service import (
|
from app.modules.orders.services.order_item_exception_service import (
|
||||||
order_item_exception_service,
|
order_item_exception_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ from app.modules.orders.schemas import (
|
|||||||
StoreInvoiceSettingsUpdate,
|
StoreInvoiceSettingsUpdate,
|
||||||
)
|
)
|
||||||
from app.modules.orders.services.invoice_service import invoice_service
|
from app.modules.orders.services.invoice_service import invoice_service
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
store_invoices_router = APIRouter(
|
store_invoices_router = APIRouter(
|
||||||
prefix="/invoices",
|
prefix="/invoices",
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ from app.modules.payments.schemas import (
|
|||||||
from app.modules.payments.schemas import (
|
from app.modules.payments.schemas import (
|
||||||
PaymentRefundResponse as RefundResponse,
|
PaymentRefundResponse as RefundResponse,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_router = APIRouter(
|
store_router = APIRouter(
|
||||||
prefix="/payments",
|
prefix="/payments",
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ from app.modules.core.services.auth_service import auth_service
|
|||||||
from app.modules.tenancy.exceptions import (
|
from app.modules.tenancy.exceptions import (
|
||||||
InvalidCredentialsException,
|
InvalidCredentialsException,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.services.admin_platform_service import admin_platform_service
|
from app.modules.tenancy.schemas.auth import (
|
||||||
from middleware.auth import AuthManager
|
|
||||||
from models.schema.auth import (
|
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
LogoutResponse,
|
LogoutResponse,
|
||||||
PlatformSelectResponse,
|
PlatformSelectResponse,
|
||||||
@@ -31,6 +29,8 @@ from models.schema.auth import (
|
|||||||
UserLogin,
|
UserLogin,
|
||||||
UserResponse,
|
UserResponse,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.services.admin_platform_service import admin_platform_service
|
||||||
|
from middleware.auth import AuthManager
|
||||||
|
|
||||||
admin_auth_router = APIRouter(prefix="/auth")
|
admin_auth_router = APIRouter(prefix="/auth")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.merchant_domain import (
|
from app.modules.tenancy.schemas.merchant_domain import (
|
||||||
MerchantDomainCreate,
|
MerchantDomainCreate,
|
||||||
MerchantDomainDeletionResponse,
|
MerchantDomainDeletionResponse,
|
||||||
@@ -30,7 +31,6 @@ from app.modules.tenancy.schemas.store_domain import (
|
|||||||
from app.modules.tenancy.services.merchant_domain_service import (
|
from app.modules.tenancy.services.merchant_domain_service import (
|
||||||
merchant_domain_service,
|
merchant_domain_service,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_merchant_domains_router = APIRouter(prefix="/merchants")
|
admin_merchant_domains_router = APIRouter(prefix="/merchants")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from app.modules.tenancy.exceptions import (
|
|||||||
ConfirmationRequiredException,
|
ConfirmationRequiredException,
|
||||||
MerchantHasStoresException,
|
MerchantHasStoresException,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.merchant import (
|
from app.modules.tenancy.schemas.merchant import (
|
||||||
MerchantCreate,
|
MerchantCreate,
|
||||||
MerchantCreateResponse,
|
MerchantCreateResponse,
|
||||||
@@ -27,7 +28,6 @@ from app.modules.tenancy.schemas.merchant import (
|
|||||||
MerchantUpdate,
|
MerchantUpdate,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.services.merchant_service import merchant_service
|
from app.modules.tenancy.services.merchant_service import merchant_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_merchants_router = APIRouter(prefix="/merchants")
|
admin_merchants_router = APIRouter(prefix="/merchants")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ from app.api.deps import get_current_super_admin, get_db
|
|||||||
from app.exceptions import ValidationException
|
from app.exceptions import ValidationException
|
||||||
from app.modules.registry import MODULES
|
from app.modules.registry import MODULES
|
||||||
from app.modules.service import module_service
|
from app.modules.service import module_service
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.platform_service import platform_service
|
from app.modules.tenancy.services.platform_service import platform_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/module-config")
|
router = APIRouter(prefix="/module-config")
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_super_admin, get_db
|
from app.api.deps import get_current_super_admin, get_db
|
||||||
from app.modules.registry import MODULES, get_core_module_codes
|
from app.modules.registry import MODULES, get_core_module_codes
|
||||||
from app.modules.service import module_service
|
from app.modules.service import module_service
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.platform_service import platform_service
|
from app.modules.tenancy.services.platform_service import platform_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/modules")
|
router = APIRouter(prefix="/modules")
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.core.services.stats_aggregator import stats_aggregator
|
from app.modules.core.services.stats_aggregator import stats_aggregator
|
||||||
from app.modules.tenancy.services.admin_service import admin_service
|
from app.modules.tenancy.schemas.auth import (
|
||||||
from models.schema.auth import (
|
|
||||||
OwnedMerchantSummary,
|
OwnedMerchantSummary,
|
||||||
StoreMembershipSummary,
|
StoreMembershipSummary,
|
||||||
UserContext,
|
UserContext,
|
||||||
@@ -29,6 +28,7 @@ from models.schema.auth import (
|
|||||||
UserStatusToggleResponse,
|
UserStatusToggleResponse,
|
||||||
UserUpdate,
|
UserUpdate,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.services.admin_service import admin_service
|
||||||
|
|
||||||
admin_platform_users_router = APIRouter(prefix="/users")
|
admin_platform_users_router = APIRouter(prefix="/users")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ from pydantic import BaseModel, Field
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import get_current_admin_from_cookie_or_header, get_db
|
from app.api.deps import get_current_admin_from_cookie_or_header, get_db
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.platform_service import platform_service
|
from app.modules.tenancy.services.platform_service import platform_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
admin_platforms_router = APIRouter(prefix="/platforms")
|
admin_platforms_router = APIRouter(prefix="/platforms")
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.store_domain import (
|
from app.modules.tenancy.schemas.store_domain import (
|
||||||
DomainDeletionResponse,
|
DomainDeletionResponse,
|
||||||
DomainVerificationInstructions,
|
DomainVerificationInstructions,
|
||||||
@@ -27,7 +28,6 @@ from app.modules.tenancy.schemas.store_domain import (
|
|||||||
)
|
)
|
||||||
from app.modules.tenancy.services.store_domain_service import store_domain_service
|
from app.modules.tenancy.services.store_domain_service import store_domain_service
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_store_domains_router = APIRouter(prefix="/stores")
|
admin_store_domains_router = APIRouter(prefix="/stores")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.team import (
|
from app.modules.tenancy.schemas.team import (
|
||||||
PermissionCatalogResponse,
|
PermissionCatalogResponse,
|
||||||
RoleCreate,
|
RoleCreate,
|
||||||
@@ -33,7 +34,6 @@ from app.modules.tenancy.services.permission_discovery_service import (
|
|||||||
)
|
)
|
||||||
from app.modules.tenancy.services.store_team_service import store_team_service
|
from app.modules.tenancy.services.store_team_service import store_team_service
|
||||||
from app.utils.i18n import translate
|
from app.utils.i18n import translate
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_store_roles_router = APIRouter(prefix="/store-roles")
|
admin_store_roles_router = APIRouter(prefix="/store-roles")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_admin_api
|
from app.api.deps import get_current_admin_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.tenancy.exceptions import ConfirmationRequiredException
|
from app.modules.tenancy.exceptions import ConfirmationRequiredException
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.store import (
|
from app.modules.tenancy.schemas.store import (
|
||||||
StoreCreate,
|
StoreCreate,
|
||||||
StoreCreateResponse,
|
StoreCreateResponse,
|
||||||
@@ -26,7 +27,6 @@ from app.modules.tenancy.schemas.store import (
|
|||||||
)
|
)
|
||||||
from app.modules.tenancy.services.admin_service import admin_service
|
from app.modules.tenancy.services.admin_service import admin_service
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_stores_router = APIRouter(prefix="/stores")
|
admin_stores_router = APIRouter(prefix="/stores")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ from app.exceptions import ValidationException
|
|||||||
from app.modules.tenancy.models import (
|
from app.modules.tenancy.models import (
|
||||||
User, # API-007 - Internal helper uses User model
|
User, # API-007 - Internal helper uses User model
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.admin_platform_service import admin_platform_service
|
from app.modules.tenancy.services.admin_platform_service import admin_platform_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
admin_users_router = APIRouter(prefix="/admin-users")
|
admin_users_router = APIRouter(prefix="/admin-users")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ from app.modules.tenancy.schemas import (
|
|||||||
MerchantPortalProfileUpdate,
|
MerchantPortalProfileUpdate,
|
||||||
MerchantPortalStoreListResponse,
|
MerchantPortalStoreListResponse,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.services.merchant_service import merchant_service
|
from app.modules.tenancy.services.merchant_service import merchant_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
from .email_verification import email_verification_api_router
|
from .email_verification import email_verification_api_router
|
||||||
from .merchant_auth import merchant_auth_router
|
from .merchant_auth import merchant_auth_router
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ from app.modules.core.services.auth_service import auth_service
|
|||||||
from app.modules.tenancy.models.user_password_reset_token import (
|
from app.modules.tenancy.models.user_password_reset_token import (
|
||||||
UserPasswordResetToken, # noqa: API-007
|
UserPasswordResetToken, # noqa: API-007
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.services.user_auth_service import user_auth_service
|
from app.modules.tenancy.schemas.auth import (
|
||||||
from models.schema.auth import (
|
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
LogoutResponse,
|
LogoutResponse,
|
||||||
UserContext,
|
UserContext,
|
||||||
UserLogin,
|
UserLogin,
|
||||||
UserResponse,
|
UserResponse,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.services.user_auth_service import user_auth_service
|
||||||
|
|
||||||
merchant_auth_router = APIRouter(prefix="/auth")
|
merchant_auth_router = APIRouter(prefix="/auth")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -26,10 +26,15 @@ from app.modules.tenancy.exceptions import InvalidCredentialsException
|
|||||||
from app.modules.tenancy.models.user_password_reset_token import (
|
from app.modules.tenancy.models.user_password_reset_token import (
|
||||||
UserPasswordResetToken, # noqa: API-007
|
UserPasswordResetToken, # noqa: API-007
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import (
|
||||||
|
LogoutResponse,
|
||||||
|
StoreUserResponse,
|
||||||
|
UserContext,
|
||||||
|
UserLogin,
|
||||||
|
)
|
||||||
from app.modules.tenancy.services.user_auth_service import user_auth_service
|
from app.modules.tenancy.services.user_auth_service import user_auth_service
|
||||||
from middleware.platform_context import get_current_platform
|
from middleware.platform_context import get_current_platform
|
||||||
from middleware.store_context import get_current_store
|
from middleware.store_context import get_current_store
|
||||||
from models.schema.auth import LogoutResponse, StoreUserResponse, UserContext, UserLogin
|
|
||||||
|
|
||||||
store_auth_router = APIRouter(prefix="/auth")
|
store_auth_router = APIRouter(prefix="/auth")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.api.deps import get_current_store_api
|
from app.api.deps import get_current_store_api
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.store import StoreResponse, StoreUpdate
|
from app.modules.tenancy.schemas.store import StoreResponse, StoreUpdate
|
||||||
from app.modules.tenancy.services.store_service import store_service
|
from app.modules.tenancy.services.store_service import store_service
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_profile_router = APIRouter(prefix="/profile")
|
store_profile_router = APIRouter(prefix="/profile")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from app.api.deps import (
|
|||||||
require_store_permission,
|
require_store_permission,
|
||||||
)
|
)
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.modules.tenancy.schemas.team import (
|
from app.modules.tenancy.schemas.team import (
|
||||||
BulkRemoveRequest,
|
BulkRemoveRequest,
|
||||||
BulkRemoveResponse,
|
BulkRemoveResponse,
|
||||||
@@ -48,7 +49,6 @@ from app.modules.tenancy.services.permission_discovery_service import (
|
|||||||
)
|
)
|
||||||
from app.modules.tenancy.services.store_team_service import store_team_service
|
from app.modules.tenancy.services.store_team_service import store_team_service
|
||||||
from app.utils.i18n import translate
|
from app.utils.i18n import translate
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
store_team_router = APIRouter(prefix="/team")
|
store_team_router = APIRouter(prefix="/team")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ from sqlalchemy.orm import Session
|
|||||||
from app.api.deps import get_current_merchant_from_cookie_or_header, get_db
|
from app.api.deps import get_current_merchant_from_cookie_or_header, get_db
|
||||||
from app.modules.core.utils.page_context import get_context_for_frontend
|
from app.modules.core.utils.page_context import get_context_for_frontend
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from app.templates_config import templates
|
from app.templates_config import templates
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Tenancy module Pydantic schemas.
|
|||||||
Request/response schemas for platform, merchant, store, admin user, and team management.
|
Request/response schemas for platform, merchant, store, admin user, and team management.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Merchant schemas
|
# Auth schemas
|
||||||
# Admin schemas
|
# Admin schemas
|
||||||
from app.modules.tenancy.schemas.admin import (
|
from app.modules.tenancy.schemas.admin import (
|
||||||
AdminAuditLogFilters,
|
AdminAuditLogFilters,
|
||||||
@@ -49,6 +49,27 @@ from app.modules.tenancy.schemas.admin import (
|
|||||||
RowsPerPageUpdateResponse,
|
RowsPerPageUpdateResponse,
|
||||||
SystemHealthResponse,
|
SystemHealthResponse,
|
||||||
)
|
)
|
||||||
|
from app.modules.tenancy.schemas.auth import (
|
||||||
|
LoginResponse,
|
||||||
|
LogoutResponse,
|
||||||
|
OwnedMerchantSummary,
|
||||||
|
PasswordResetRequestResponse,
|
||||||
|
PasswordResetResponse,
|
||||||
|
PlatformSelectResponse,
|
||||||
|
StoreMembershipSummary,
|
||||||
|
StoreUserResponse,
|
||||||
|
UserContext,
|
||||||
|
UserCreate,
|
||||||
|
UserDeleteResponse,
|
||||||
|
UserDetailResponse,
|
||||||
|
UserListResponse,
|
||||||
|
UserLogin,
|
||||||
|
UserResponse,
|
||||||
|
UserSearchItem,
|
||||||
|
UserSearchResponse,
|
||||||
|
UserStatusToggleResponse,
|
||||||
|
UserUpdate,
|
||||||
|
)
|
||||||
from app.modules.tenancy.schemas.merchant import (
|
from app.modules.tenancy.schemas.merchant import (
|
||||||
MerchantBase,
|
MerchantBase,
|
||||||
MerchantCreate,
|
MerchantCreate,
|
||||||
@@ -112,6 +133,26 @@ from app.modules.tenancy.schemas.team import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
# Auth
|
||||||
|
"LoginResponse",
|
||||||
|
"LogoutResponse",
|
||||||
|
"OwnedMerchantSummary",
|
||||||
|
"PasswordResetRequestResponse",
|
||||||
|
"PasswordResetResponse",
|
||||||
|
"PlatformSelectResponse",
|
||||||
|
"StoreMembershipSummary",
|
||||||
|
"StoreUserResponse",
|
||||||
|
"UserContext",
|
||||||
|
"UserCreate",
|
||||||
|
"UserDeleteResponse",
|
||||||
|
"UserDetailResponse",
|
||||||
|
"UserListResponse",
|
||||||
|
"UserLogin",
|
||||||
|
"UserResponse",
|
||||||
|
"UserSearchItem",
|
||||||
|
"UserSearchResponse",
|
||||||
|
"UserStatusToggleResponse",
|
||||||
|
"UserUpdate",
|
||||||
# Merchant
|
# Merchant
|
||||||
"MerchantBase",
|
"MerchantBase",
|
||||||
"MerchantCreate",
|
"MerchantCreate",
|
||||||
|
|||||||
343
app/modules/tenancy/schemas/auth.py
Normal file
343
app/modules/tenancy/schemas/auth.py
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
# app/modules/tenancy/schemas/auth.py
|
||||||
|
"""
|
||||||
|
Authentication and user context schemas.
|
||||||
|
|
||||||
|
UserContext is the primary schema for dependency injection in API endpoints,
|
||||||
|
replacing direct use of the User database model in routes.
|
||||||
|
|
||||||
|
Migrated from models/schema/auth.py per MOD-019 / MOD-025.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
||||||
|
|
||||||
|
|
||||||
|
class UserLogin(BaseModel):
|
||||||
|
email_or_username: str = Field(..., description="Username or email address")
|
||||||
|
password: str = Field(..., description="Password")
|
||||||
|
store_code: str | None = Field(
|
||||||
|
None, description="Optional store code for context"
|
||||||
|
)
|
||||||
|
|
||||||
|
@field_validator("email_or_username")
|
||||||
|
@classmethod
|
||||||
|
def validate_email_or_username(cls, v):
|
||||||
|
return v.strip()
|
||||||
|
|
||||||
|
|
||||||
|
class UserResponse(BaseModel):
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
id: int
|
||||||
|
email: str
|
||||||
|
username: str
|
||||||
|
role: str
|
||||||
|
is_active: bool
|
||||||
|
preferred_language: str | None = None
|
||||||
|
last_login: datetime | None = None
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class LoginResponse(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str = "bearer"
|
||||||
|
expires_in: int
|
||||||
|
user: UserResponse
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformSelectResponse(BaseModel):
|
||||||
|
"""Response for platform selection (no user data - client already has it)."""
|
||||||
|
|
||||||
|
access_token: str
|
||||||
|
token_type: str = "bearer"
|
||||||
|
expires_in: int
|
||||||
|
platform_id: int
|
||||||
|
platform_code: str
|
||||||
|
|
||||||
|
|
||||||
|
class OwnedMerchantSummary(BaseModel):
|
||||||
|
"""Summary of a merchant owned by a user."""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
is_active: bool
|
||||||
|
store_count: int
|
||||||
|
|
||||||
|
|
||||||
|
class StoreMembershipSummary(BaseModel):
|
||||||
|
"""Summary of a user's store membership."""
|
||||||
|
|
||||||
|
store_id: int
|
||||||
|
store_code: str
|
||||||
|
store_name: str
|
||||||
|
role: str
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UserDetailResponse(UserResponse):
|
||||||
|
"""Extended user response with additional details."""
|
||||||
|
|
||||||
|
first_name: str | None = None
|
||||||
|
last_name: str | None = None
|
||||||
|
full_name: str | None = None
|
||||||
|
is_email_verified: bool = False
|
||||||
|
owned_merchants_count: int = 0
|
||||||
|
store_memberships_count: int = 0
|
||||||
|
owned_merchants: list[OwnedMerchantSummary] = []
|
||||||
|
store_memberships: list[StoreMembershipSummary] = []
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
"""Schema for updating user information."""
|
||||||
|
|
||||||
|
username: str | None = Field(None, min_length=3, max_length=50)
|
||||||
|
email: EmailStr | None = None
|
||||||
|
first_name: str | None = Field(None, max_length=100)
|
||||||
|
last_name: str | None = Field(None, max_length=100)
|
||||||
|
role: str | None = Field(None, pattern="^(super_admin|platform_admin|merchant_owner|store_member)$")
|
||||||
|
is_active: bool | None = None
|
||||||
|
is_email_verified: bool | None = None
|
||||||
|
preferred_language: str | None = Field(
|
||||||
|
None, description="Preferred language (en, fr, de, lb)"
|
||||||
|
)
|
||||||
|
|
||||||
|
@field_validator("username")
|
||||||
|
@classmethod
|
||||||
|
def validate_username(cls, v):
|
||||||
|
if v and not re.match(r"^[a-zA-Z0-9_]+$", v):
|
||||||
|
raise ValueError(
|
||||||
|
"Username must contain only letters, numbers, or underscores"
|
||||||
|
)
|
||||||
|
return v.lower().strip() if v else v
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreate(BaseModel):
|
||||||
|
"""Schema for creating a new user (admin only)."""
|
||||||
|
|
||||||
|
email: EmailStr = Field(..., description="Valid email address")
|
||||||
|
username: str = Field(..., min_length=3, max_length=50)
|
||||||
|
password: str = Field(..., min_length=6, description="Password")
|
||||||
|
first_name: str | None = Field(None, max_length=100)
|
||||||
|
last_name: str | None = Field(None, max_length=100)
|
||||||
|
role: str = Field(default="store_member", pattern="^(super_admin|platform_admin|merchant_owner|store_member)$")
|
||||||
|
preferred_language: str | None = Field(
|
||||||
|
None, description="Preferred language (en, fr, de, lb)"
|
||||||
|
)
|
||||||
|
|
||||||
|
@field_validator("username")
|
||||||
|
@classmethod
|
||||||
|
def validate_username(cls, v):
|
||||||
|
if not re.match(r"^[a-zA-Z0-9_]+$", v):
|
||||||
|
raise ValueError(
|
||||||
|
"Username must contain only letters, numbers, or underscores"
|
||||||
|
)
|
||||||
|
return v.lower().strip()
|
||||||
|
|
||||||
|
|
||||||
|
class UserListResponse(BaseModel):
|
||||||
|
"""Schema for paginated user list."""
|
||||||
|
|
||||||
|
items: list[UserResponse]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
per_page: int
|
||||||
|
pages: int
|
||||||
|
|
||||||
|
|
||||||
|
class UserSearchItem(BaseModel):
|
||||||
|
"""Schema for a single user search result."""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UserSearchResponse(BaseModel):
|
||||||
|
"""Schema for user search results."""
|
||||||
|
|
||||||
|
users: list[UserSearchItem]
|
||||||
|
|
||||||
|
|
||||||
|
class UserStatusToggleResponse(BaseModel):
|
||||||
|
"""Schema for user status toggle response."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UserDeleteResponse(BaseModel):
|
||||||
|
"""Schema for user delete response."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class LogoutResponse(BaseModel):
|
||||||
|
"""Schema for logout response."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetRequestResponse(BaseModel):
|
||||||
|
"""Schema for password reset request response."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetResponse(BaseModel):
|
||||||
|
"""Schema for password reset response."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class StoreUserResponse(BaseModel):
|
||||||
|
"""Schema for store user info in auth context."""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
role: str
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
model_config = {"from_attributes": True}
|
||||||
|
|
||||||
|
|
||||||
|
class UserContext(BaseModel):
|
||||||
|
"""
|
||||||
|
User context for dependency injection in API endpoints.
|
||||||
|
|
||||||
|
This schema replaces direct use of the User database model in API routes,
|
||||||
|
following the principle that routes should not import database models directly.
|
||||||
|
|
||||||
|
Used by:
|
||||||
|
- get_current_admin_api / get_current_admin_from_cookie_or_header
|
||||||
|
- get_current_store_api / get_current_store_from_cookie_or_header
|
||||||
|
- get_current_super_admin
|
||||||
|
|
||||||
|
For admin users:
|
||||||
|
- is_super_admin indicates full platform access
|
||||||
|
- accessible_platform_ids is None for super admins (all platforms)
|
||||||
|
- accessible_platform_ids is a list for platform admins
|
||||||
|
|
||||||
|
For store users:
|
||||||
|
- token_store_id/code/role come from JWT token
|
||||||
|
- These indicate which store context the user is operating in
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Core user fields
|
||||||
|
id: int
|
||||||
|
email: str
|
||||||
|
username: str
|
||||||
|
role: str # super_admin, platform_admin, merchant_owner, or store_member
|
||||||
|
is_active: bool = True
|
||||||
|
|
||||||
|
# Admin-specific fields
|
||||||
|
accessible_platform_ids: list[int] | None = None # None = all platforms (super admin)
|
||||||
|
|
||||||
|
# Admin platform context (from JWT token after platform selection)
|
||||||
|
token_platform_id: int | None = None
|
||||||
|
token_platform_code: str | None = None
|
||||||
|
|
||||||
|
# Store-specific fields (from JWT token)
|
||||||
|
token_store_id: int | None = None
|
||||||
|
token_store_code: str | None = None
|
||||||
|
token_store_role: str | None = None
|
||||||
|
|
||||||
|
# Optional profile fields
|
||||||
|
first_name: str | None = None
|
||||||
|
last_name: str | None = None
|
||||||
|
preferred_language: str | None = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self) -> str:
|
||||||
|
"""Returns the full name of the user."""
|
||||||
|
if self.first_name and self.last_name:
|
||||||
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_super_admin(self) -> bool:
|
||||||
|
"""Check if user is a super admin."""
|
||||||
|
return self.role == "super_admin"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_admin(self) -> bool:
|
||||||
|
"""Check if user is an admin (super_admin or platform_admin)."""
|
||||||
|
return self.role in ("super_admin", "platform_admin")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_store_user(self) -> bool:
|
||||||
|
"""Check if user is a store user (merchant_owner or store_member)."""
|
||||||
|
return self.role in ("merchant_owner", "store_member")
|
||||||
|
|
||||||
|
def can_access_platform(self, platform_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
Check if user can access a specific platform.
|
||||||
|
|
||||||
|
Super admins (accessible_platform_ids=None) can access all platforms.
|
||||||
|
Platform admins can only access their assigned platforms.
|
||||||
|
"""
|
||||||
|
if self.is_super_admin:
|
||||||
|
return True
|
||||||
|
if self.accessible_platform_ids is None:
|
||||||
|
return True # Super admin fallback
|
||||||
|
return platform_id in self.accessible_platform_ids
|
||||||
|
|
||||||
|
def get_accessible_platform_ids(self) -> list[int] | None:
|
||||||
|
"""
|
||||||
|
Get list of platform IDs this user can access.
|
||||||
|
|
||||||
|
Returns None for super admins (all platforms accessible).
|
||||||
|
Returns list of platform IDs for platform admins.
|
||||||
|
"""
|
||||||
|
return self.accessible_platform_ids
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_user(cls, user, include_store_context: bool = True) -> "UserContext":
|
||||||
|
"""
|
||||||
|
Create UserContext from a User database model.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user: User database model instance
|
||||||
|
include_store_context: Whether to include token_store_* fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserContext instance
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"id": user.id,
|
||||||
|
"email": user.email,
|
||||||
|
"username": user.username,
|
||||||
|
"role": user.role,
|
||||||
|
"is_active": user.is_active,
|
||||||
|
"first_name": getattr(user, "first_name", None),
|
||||||
|
"last_name": getattr(user, "last_name", None),
|
||||||
|
"preferred_language": getattr(user, "preferred_language", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add admin platform access info
|
||||||
|
if user.is_admin:
|
||||||
|
if user.is_super_admin:
|
||||||
|
data["accessible_platform_ids"] = None # All platforms
|
||||||
|
else:
|
||||||
|
# Get platform IDs from admin_platforms relationship
|
||||||
|
admin_platforms = getattr(user, "admin_platforms", [])
|
||||||
|
data["accessible_platform_ids"] = [
|
||||||
|
ap.platform_id for ap in admin_platforms if ap.is_active
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add platform context from JWT token (for platform admins after selection)
|
||||||
|
data["token_platform_id"] = getattr(user, "token_platform_id", None)
|
||||||
|
data["token_platform_code"] = getattr(user, "token_platform_code", None)
|
||||||
|
|
||||||
|
# Add store context from JWT token if present
|
||||||
|
if include_store_context:
|
||||||
|
data["token_store_id"] = getattr(user, "token_store_id", None)
|
||||||
|
data["token_store_code"] = getattr(user, "token_store_code", None)
|
||||||
|
data["token_store_role"] = getattr(user, "token_store_role", None)
|
||||||
|
|
||||||
|
return cls(**data)
|
||||||
@@ -23,7 +23,7 @@ from app.modules.tenancy.exceptions import (
|
|||||||
UserNotFoundException,
|
UserNotFoundException,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.models import AdminPlatform, Platform, User
|
from app.modules.tenancy.models import AdminPlatform, Platform, User
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -786,6 +786,22 @@ class AdminService:
|
|||||||
# PRIVATE HELPER METHODS
|
# PRIVATE HELPER METHODS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
def get_user_by_id(self, db: Session, user_id: int) -> User | None:
|
||||||
|
"""
|
||||||
|
Get user by ID.
|
||||||
|
|
||||||
|
Public method for cross-module consumers that need to look up a user.
|
||||||
|
Returns None if not found (does not raise).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Database session
|
||||||
|
user_id: User ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User object or None
|
||||||
|
"""
|
||||||
|
return db.query(User).filter(User.id == user_id).first()
|
||||||
|
|
||||||
def _get_user_by_id_or_raise(self, db: Session, user_id: int) -> User:
|
def _get_user_by_id_or_raise(self, db: Session, user_id: int) -> User:
|
||||||
"""Get user by ID or raise UserNotFoundException."""
|
"""Get user by ID or raise UserNotFoundException."""
|
||||||
user = db.query(User).filter(User.id == user_id).first()
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
|
|||||||
@@ -329,6 +329,49 @@ class MerchantService:
|
|||||||
|
|
||||||
return merchant, old_owner, new_owner
|
return merchant, old_owner, new_owner
|
||||||
|
|
||||||
|
def get_merchant_by_owner_id(
|
||||||
|
self, db: Session, owner_user_id: int
|
||||||
|
) -> Merchant | None:
|
||||||
|
"""
|
||||||
|
Get merchant by owner user ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Database session
|
||||||
|
owner_user_id: Owner user ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
First active merchant owned by the user, or None
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
db.query(Merchant)
|
||||||
|
.filter(
|
||||||
|
Merchant.owner_user_id == owner_user_id,
|
||||||
|
Merchant.is_active == True, # noqa: E712
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_merchant_count_for_owner(
|
||||||
|
self, db: Session, owner_user_id: int, active_only: bool = True
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Count merchants owned by a user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Database session
|
||||||
|
owner_user_id: Owner user ID
|
||||||
|
active_only: Only count active merchants
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of merchants
|
||||||
|
"""
|
||||||
|
query = db.query(func.count(Merchant.id)).filter(
|
||||||
|
Merchant.owner_user_id == owner_user_id
|
||||||
|
)
|
||||||
|
if active_only:
|
||||||
|
query = query.filter(Merchant.is_active == True) # noqa: E712
|
||||||
|
return query.scalar() or 0
|
||||||
|
|
||||||
def get_merchant_stores(
|
def get_merchant_stores(
|
||||||
self, db: Session, merchant_id: int, skip: int = 0, limit: int = 100
|
self, db: Session, merchant_id: int, skip: int = 0, limit: int = 100
|
||||||
) -> tuple[list, int]:
|
) -> tuple[list, int]:
|
||||||
|
|||||||
@@ -142,6 +142,31 @@ class PlatformService:
|
|||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_active_store_count(db: Session, platform_id: int) -> int:
|
||||||
|
"""
|
||||||
|
Get count of active stores on a platform.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Database session
|
||||||
|
platform_id: Platform ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Active store count
|
||||||
|
"""
|
||||||
|
from app.modules.tenancy.models import Store
|
||||||
|
|
||||||
|
return (
|
||||||
|
db.query(func.count(StorePlatform.store_id))
|
||||||
|
.join(Store, Store.id == StorePlatform.store_id)
|
||||||
|
.filter(
|
||||||
|
StorePlatform.platform_id == platform_id,
|
||||||
|
Store.is_active == True, # noqa: E712
|
||||||
|
)
|
||||||
|
.scalar()
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_platform_pages_count(db: Session, platform_id: int) -> int:
|
def get_platform_pages_count(db: Session, platform_id: int) -> int:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import pytest
|
|||||||
|
|
||||||
from app.api.deps import get_current_merchant_api, get_merchant_for_current_user
|
from app.api.deps import get_current_merchant_api, get_merchant_for_current_user
|
||||||
from app.modules.tenancy.models import Merchant, Store, User
|
from app.modules.tenancy.models import Merchant, Store, User
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import pytest
|
|||||||
|
|
||||||
from app.api.deps import get_current_store_from_cookie_or_header
|
from app.api.deps import get_current_store_from_cookie_or_header
|
||||||
from app.modules.tenancy.models import Merchant, Role, Store, StoreUser, User
|
from app.modules.tenancy.models import Merchant, Role, Store, StoreUser, User
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from main import app
|
from main import app
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Routes should not import database models directly (architecture rule API-007). I
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# CORRECT: Use UserContext from dependencies
|
# CORRECT: Use UserContext from dependencies
|
||||||
from models.schema.auth import UserContext
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
|
|
||||||
@router.get("/endpoint")
|
@router.get("/endpoint")
|
||||||
def my_endpoint(current_user: UserContext = Depends(get_current_admin_api)):
|
def my_endpoint(current_user: UserContext = Depends(get_current_admin_api)):
|
||||||
|
|||||||
@@ -1,334 +1,34 @@
|
|||||||
# auth.py - Keep security-critical validation
|
# models/schema/auth.py
|
||||||
import re
|
"""
|
||||||
from datetime import datetime
|
LEGACY LOCATION — re-exports from canonical location.
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
All auth schemas have been moved to app/modules/tenancy/schemas/auth.py
|
||||||
|
per MOD-019 (schemas belong in their module).
|
||||||
|
|
||||||
|
This file provides backwards compatibility re-exports.
|
||||||
|
New code should import from: app.modules.tenancy.schemas.auth
|
||||||
|
|
||||||
class UserLogin(BaseModel):
|
Schemas use Pydantic Field and field_validator for input validation.
|
||||||
email_or_username: str = Field(..., description="Username or email address")
|
"""
|
||||||
password: str = Field(..., description="Password")
|
|
||||||
store_code: str | None = Field(
|
from app.modules.tenancy.schemas.auth import ( # noqa: F401
|
||||||
None, description="Optional store code for context"
|
LoginResponse,
|
||||||
|
LogoutResponse,
|
||||||
|
OwnedMerchantSummary,
|
||||||
|
PasswordResetRequestResponse,
|
||||||
|
PasswordResetResponse,
|
||||||
|
PlatformSelectResponse,
|
||||||
|
StoreMembershipSummary,
|
||||||
|
StoreUserResponse,
|
||||||
|
UserContext,
|
||||||
|
UserCreate,
|
||||||
|
UserDeleteResponse,
|
||||||
|
UserDetailResponse,
|
||||||
|
UserListResponse,
|
||||||
|
UserLogin,
|
||||||
|
UserResponse,
|
||||||
|
UserSearchItem,
|
||||||
|
UserSearchResponse,
|
||||||
|
UserStatusToggleResponse,
|
||||||
|
UserUpdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@field_validator("email_or_username")
|
|
||||||
@classmethod
|
|
||||||
def validate_email_or_username(cls, v):
|
|
||||||
return v.strip()
|
|
||||||
|
|
||||||
|
|
||||||
class UserResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
id: int
|
|
||||||
email: str
|
|
||||||
username: str
|
|
||||||
role: str
|
|
||||||
is_active: bool
|
|
||||||
preferred_language: str | None = None
|
|
||||||
last_login: datetime | None = None
|
|
||||||
created_at: datetime
|
|
||||||
updated_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class LoginResponse(BaseModel):
|
|
||||||
access_token: str
|
|
||||||
token_type: str = "bearer"
|
|
||||||
expires_in: int
|
|
||||||
user: UserResponse
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformSelectResponse(BaseModel):
|
|
||||||
"""Response for platform selection (no user data - client already has it)."""
|
|
||||||
|
|
||||||
access_token: str
|
|
||||||
token_type: str = "bearer"
|
|
||||||
expires_in: int
|
|
||||||
platform_id: int
|
|
||||||
platform_code: str
|
|
||||||
|
|
||||||
|
|
||||||
class OwnedMerchantSummary(BaseModel):
|
|
||||||
"""Summary of a merchant owned by a user."""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
name: str
|
|
||||||
is_active: bool
|
|
||||||
store_count: int
|
|
||||||
|
|
||||||
|
|
||||||
class StoreMembershipSummary(BaseModel):
|
|
||||||
"""Summary of a user's store membership."""
|
|
||||||
|
|
||||||
store_id: int
|
|
||||||
store_code: str
|
|
||||||
store_name: str
|
|
||||||
role: str
|
|
||||||
is_active: bool
|
|
||||||
|
|
||||||
|
|
||||||
class UserDetailResponse(UserResponse):
|
|
||||||
"""Extended user response with additional details."""
|
|
||||||
|
|
||||||
first_name: str | None = None
|
|
||||||
last_name: str | None = None
|
|
||||||
full_name: str | None = None
|
|
||||||
is_email_verified: bool = False
|
|
||||||
owned_merchants_count: int = 0
|
|
||||||
store_memberships_count: int = 0
|
|
||||||
owned_merchants: list[OwnedMerchantSummary] = []
|
|
||||||
store_memberships: list[StoreMembershipSummary] = []
|
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(BaseModel):
|
|
||||||
"""Schema for updating user information."""
|
|
||||||
|
|
||||||
username: str | None = Field(None, min_length=3, max_length=50)
|
|
||||||
email: EmailStr | None = None
|
|
||||||
first_name: str | None = Field(None, max_length=100)
|
|
||||||
last_name: str | None = Field(None, max_length=100)
|
|
||||||
role: str | None = Field(None, pattern="^(super_admin|platform_admin|merchant_owner|store_member)$")
|
|
||||||
is_active: bool | None = None
|
|
||||||
is_email_verified: bool | None = None
|
|
||||||
preferred_language: str | None = Field(
|
|
||||||
None, description="Preferred language (en, fr, de, lb)"
|
|
||||||
)
|
|
||||||
|
|
||||||
@field_validator("username")
|
|
||||||
@classmethod
|
|
||||||
def validate_username(cls, v):
|
|
||||||
if v and not re.match(r"^[a-zA-Z0-9_]+$", v):
|
|
||||||
raise ValueError(
|
|
||||||
"Username must contain only letters, numbers, or underscores"
|
|
||||||
)
|
|
||||||
return v.lower().strip() if v else v
|
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(BaseModel):
|
|
||||||
"""Schema for creating a new user (admin only)."""
|
|
||||||
|
|
||||||
email: EmailStr = Field(..., description="Valid email address")
|
|
||||||
username: str = Field(..., min_length=3, max_length=50)
|
|
||||||
password: str = Field(..., min_length=6, description="Password")
|
|
||||||
first_name: str | None = Field(None, max_length=100)
|
|
||||||
last_name: str | None = Field(None, max_length=100)
|
|
||||||
role: str = Field(default="store_member", pattern="^(super_admin|platform_admin|merchant_owner|store_member)$")
|
|
||||||
preferred_language: str | None = Field(
|
|
||||||
None, description="Preferred language (en, fr, de, lb)"
|
|
||||||
)
|
|
||||||
|
|
||||||
@field_validator("username")
|
|
||||||
@classmethod
|
|
||||||
def validate_username(cls, v):
|
|
||||||
if not re.match(r"^[a-zA-Z0-9_]+$", v):
|
|
||||||
raise ValueError(
|
|
||||||
"Username must contain only letters, numbers, or underscores"
|
|
||||||
)
|
|
||||||
return v.lower().strip()
|
|
||||||
|
|
||||||
|
|
||||||
class UserListResponse(BaseModel):
|
|
||||||
"""Schema for paginated user list."""
|
|
||||||
|
|
||||||
items: list[UserResponse]
|
|
||||||
total: int
|
|
||||||
page: int
|
|
||||||
per_page: int
|
|
||||||
pages: int
|
|
||||||
|
|
||||||
|
|
||||||
class UserSearchItem(BaseModel):
|
|
||||||
"""Schema for a single user search result."""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
username: str
|
|
||||||
email: str
|
|
||||||
is_active: bool
|
|
||||||
|
|
||||||
|
|
||||||
class UserSearchResponse(BaseModel):
|
|
||||||
"""Schema for user search results."""
|
|
||||||
|
|
||||||
users: list[UserSearchItem]
|
|
||||||
|
|
||||||
|
|
||||||
class UserStatusToggleResponse(BaseModel):
|
|
||||||
"""Schema for user status toggle response."""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
is_active: bool
|
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteResponse(BaseModel):
|
|
||||||
"""Schema for user delete response."""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutResponse(BaseModel):
|
|
||||||
"""Schema for logout response."""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetRequestResponse(BaseModel):
|
|
||||||
"""Schema for password reset request response."""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetResponse(BaseModel):
|
|
||||||
"""Schema for password reset response."""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
|
|
||||||
|
|
||||||
class StoreUserResponse(BaseModel):
|
|
||||||
"""Schema for store user info in auth context."""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
username: str
|
|
||||||
email: str
|
|
||||||
role: str
|
|
||||||
is_active: bool
|
|
||||||
|
|
||||||
model_config = {"from_attributes": True}
|
|
||||||
|
|
||||||
|
|
||||||
class UserContext(BaseModel):
|
|
||||||
"""
|
|
||||||
User context for dependency injection in API endpoints.
|
|
||||||
|
|
||||||
This schema replaces direct use of the User database model in API routes,
|
|
||||||
following the principle that routes should not import database models directly.
|
|
||||||
|
|
||||||
Used by:
|
|
||||||
- get_current_admin_api / get_current_admin_from_cookie_or_header
|
|
||||||
- get_current_store_api / get_current_store_from_cookie_or_header
|
|
||||||
- get_current_super_admin
|
|
||||||
|
|
||||||
For admin users:
|
|
||||||
- is_super_admin indicates full platform access
|
|
||||||
- accessible_platform_ids is None for super admins (all platforms)
|
|
||||||
- accessible_platform_ids is a list for platform admins
|
|
||||||
|
|
||||||
For store users:
|
|
||||||
- token_store_id/code/role come from JWT token
|
|
||||||
- These indicate which store context the user is operating in
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Core user fields
|
|
||||||
id: int
|
|
||||||
email: str
|
|
||||||
username: str
|
|
||||||
role: str # super_admin, platform_admin, merchant_owner, or store_member
|
|
||||||
is_active: bool = True
|
|
||||||
|
|
||||||
# Admin-specific fields
|
|
||||||
accessible_platform_ids: list[int] | None = None # None = all platforms (super admin)
|
|
||||||
|
|
||||||
# Admin platform context (from JWT token after platform selection)
|
|
||||||
token_platform_id: int | None = None
|
|
||||||
token_platform_code: str | None = None
|
|
||||||
|
|
||||||
# Store-specific fields (from JWT token)
|
|
||||||
token_store_id: int | None = None
|
|
||||||
token_store_code: str | None = None
|
|
||||||
token_store_role: str | None = None
|
|
||||||
|
|
||||||
# Optional profile fields
|
|
||||||
first_name: str | None = None
|
|
||||||
last_name: str | None = None
|
|
||||||
preferred_language: str | None = None
|
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def full_name(self) -> str:
|
|
||||||
"""Returns the full name of the user."""
|
|
||||||
if self.first_name and self.last_name:
|
|
||||||
return f"{self.first_name} {self.last_name}"
|
|
||||||
return self.username
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_super_admin(self) -> bool:
|
|
||||||
"""Check if user is a super admin."""
|
|
||||||
return self.role == "super_admin"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_admin(self) -> bool:
|
|
||||||
"""Check if user is an admin (super_admin or platform_admin)."""
|
|
||||||
return self.role in ("super_admin", "platform_admin")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_store_user(self) -> bool:
|
|
||||||
"""Check if user is a store user (merchant_owner or store_member)."""
|
|
||||||
return self.role in ("merchant_owner", "store_member")
|
|
||||||
|
|
||||||
def can_access_platform(self, platform_id: int) -> bool:
|
|
||||||
"""
|
|
||||||
Check if user can access a specific platform.
|
|
||||||
|
|
||||||
Super admins (accessible_platform_ids=None) can access all platforms.
|
|
||||||
Platform admins can only access their assigned platforms.
|
|
||||||
"""
|
|
||||||
if self.is_super_admin:
|
|
||||||
return True
|
|
||||||
if self.accessible_platform_ids is None:
|
|
||||||
return True # Super admin fallback
|
|
||||||
return platform_id in self.accessible_platform_ids
|
|
||||||
|
|
||||||
def get_accessible_platform_ids(self) -> list[int] | None:
|
|
||||||
"""
|
|
||||||
Get list of platform IDs this user can access.
|
|
||||||
|
|
||||||
Returns None for super admins (all platforms accessible).
|
|
||||||
Returns list of platform IDs for platform admins.
|
|
||||||
"""
|
|
||||||
return self.accessible_platform_ids
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_user(cls, user, include_store_context: bool = True) -> "UserContext":
|
|
||||||
"""
|
|
||||||
Create UserContext from a User database model.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user: User database model instance
|
|
||||||
include_store_context: Whether to include token_store_* fields
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
UserContext instance
|
|
||||||
"""
|
|
||||||
data = {
|
|
||||||
"id": user.id,
|
|
||||||
"email": user.email,
|
|
||||||
"username": user.username,
|
|
||||||
"role": user.role,
|
|
||||||
"is_active": user.is_active,
|
|
||||||
"first_name": getattr(user, "first_name", None),
|
|
||||||
"last_name": getattr(user, "last_name", None),
|
|
||||||
"preferred_language": getattr(user, "preferred_language", None),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add admin platform access info
|
|
||||||
if user.is_admin:
|
|
||||||
if user.is_super_admin:
|
|
||||||
data["accessible_platform_ids"] = None # All platforms
|
|
||||||
else:
|
|
||||||
# Get platform IDs from admin_platforms relationship
|
|
||||||
admin_platforms = getattr(user, "admin_platforms", [])
|
|
||||||
data["accessible_platform_ids"] = [
|
|
||||||
ap.platform_id for ap in admin_platforms if ap.is_active
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add platform context from JWT token (for platform admins after selection)
|
|
||||||
data["token_platform_id"] = getattr(user, "token_platform_id", None)
|
|
||||||
data["token_platform_code"] = getattr(user, "token_platform_code", None)
|
|
||||||
|
|
||||||
# Add store context from JWT token if present
|
|
||||||
if include_store_context:
|
|
||||||
data["token_store_id"] = getattr(user, "token_store_id", None)
|
|
||||||
data["token_store_code"] = getattr(user, "token_store_code", None)
|
|
||||||
data["token_store_role"] = getattr(user, "token_store_role", None)
|
|
||||||
|
|
||||||
return cls(**data)
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ from app.modules.tenancy.exceptions import (
|
|||||||
UnauthorizedStoreAccessException,
|
UnauthorizedStoreAccessException,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.models import User
|
from app.modules.tenancy.models import User
|
||||||
|
from app.modules.tenancy.schemas.auth import UserContext
|
||||||
from middleware.auth import AuthManager
|
from middleware.auth import AuthManager
|
||||||
from models.schema.auth import UserContext
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from models.schema.auth import (
|
from app.modules.tenancy.schemas.auth import (
|
||||||
UserCreate,
|
UserCreate,
|
||||||
UserLogin,
|
UserLogin,
|
||||||
UserResponse,
|
UserResponse,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from app.modules.tenancy.exceptions import (
|
|||||||
InvalidCredentialsException,
|
InvalidCredentialsException,
|
||||||
UserNotActiveException,
|
UserNotActiveException,
|
||||||
)
|
)
|
||||||
from models.schema.auth import UserLogin
|
from app.modules.tenancy.schemas.auth import UserLogin
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|||||||
Reference in New Issue
Block a user