refactor: complete module-driven architecture migration

This commit completes the migration to a fully module-driven architecture:

## Models Migration
- Moved all domain models from models/database/ to their respective modules:
  - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc.
  - cms: MediaFile, VendorTheme
  - messaging: Email, VendorEmailSettings, VendorEmailTemplate
  - core: AdminMenuConfig
- models/database/ now only contains Base and TimestampMixin (infrastructure)

## Schemas Migration
- Moved all domain schemas from models/schema/ to their respective modules:
  - tenancy: company, vendor, admin, team, vendor_domain
  - cms: media, image, vendor_theme
  - messaging: email
- models/schema/ now only contains base.py and auth.py (infrastructure)

## Routes Migration
- Moved admin routes from app/api/v1/admin/ to modules:
  - menu_config.py -> core module
  - modules.py -> tenancy module
  - module_config.py -> tenancy module
- app/api/v1/admin/ now only aggregates auto-discovered module routes

## Menu System
- Implemented module-driven menu system with MenuDiscoveryService
- Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT
- Added MenuItemDefinition and MenuSectionDefinition dataclasses
- Each module now defines its own menu items in definition.py
- MenuService integrates with MenuDiscoveryService for template rendering

## Documentation
- Updated docs/architecture/models-structure.md
- Updated docs/architecture/menu-management.md
- Updated architecture validation rules for new exceptions

## Architecture Validation
- Updated MOD-019 rule to allow base.py in models/schema/
- Created core module exceptions.py and schemas/ directory
- All validation errors resolved (only warnings remain)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 21:02:56 +01:00
parent 09d7d282c6
commit d7a0ff8818
307 changed files with 5536 additions and 3826 deletions

View File

@@ -10,6 +10,8 @@ Aggregates all admin tenancy routes:
- /platforms/* - Platform management (super admin only)
- /vendors/* - Vendor management
- /vendor-domains/* - Vendor domain configuration
- /modules/* - Platform module management
- /module-config/* - Module configuration management
The tenancy module owns identity and organizational hierarchy.
"""
@@ -23,6 +25,8 @@ from .admin_companies import admin_companies_router
from .admin_platforms import admin_platforms_router
from .admin_vendors import admin_vendors_router
from .admin_vendor_domains import admin_vendor_domains_router
from .admin_modules import router as admin_modules_router
from .admin_module_config import router as admin_module_config_router
admin_router = APIRouter()
@@ -34,3 +38,5 @@ admin_router.include_router(admin_companies_router, tags=["admin-companies"])
admin_router.include_router(admin_platforms_router, tags=["admin-platforms"])
admin_router.include_router(admin_vendors_router, tags=["admin-vendors"])
admin_router.include_router(admin_vendor_domains_router, tags=["admin-vendor-domains"])
admin_router.include_router(admin_modules_router, tags=["admin-modules"])
admin_router.include_router(admin_module_config_router, tags=["admin-module-config"])

View File

@@ -21,7 +21,7 @@ from app.modules.tenancy.exceptions import InsufficientPermissionsException, Inv
from app.modules.tenancy.services.admin_platform_service import admin_platform_service
from app.modules.core.services.auth_service import auth_service
from middleware.auth import AuthManager
from models.database.platform import Platform # noqa: API-007 - Admin needs to query platforms
from app.modules.tenancy.models import Platform # noqa: API-007 - Admin needs to query platforms
from models.schema.auth import UserContext
from models.schema.auth import LoginResponse, LogoutResponse, UserLogin, UserResponse

View File

@@ -14,7 +14,7 @@ from app.core.database import get_db
from app.modules.tenancy.exceptions import CompanyHasVendorsException, ConfirmationRequiredException
from app.modules.tenancy.services.company_service import company_service
from models.schema.auth import UserContext
from models.schema.company import (
from app.modules.tenancy.schemas.company import (
CompanyCreate,
CompanyCreateResponse,
CompanyDetailResponse,

View File

@@ -0,0 +1,418 @@
# app/modules/tenancy/routes/api/admin_module_config.py
"""
Admin API endpoints for Module Configuration Management.
Provides per-module configuration for platforms:
- GET /module-config/platforms/{platform_id}/modules/{module_code}/config - Get module config
- PUT /module-config/platforms/{platform_id}/modules/{module_code}/config - Update module config
- GET /module-config/defaults/{module_code} - Get config defaults for a module
All endpoints require super admin access.
"""
import logging
from typing import Any
from fastapi import APIRouter, Depends, Path
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.api.deps import get_current_super_admin, get_db
from app.exceptions import ValidationException
from app.modules.registry import MODULES
from app.modules.service import module_service
from app.modules.tenancy.services.platform_service import platform_service
from models.schema.auth import UserContext
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/module-config")
# =============================================================================
# Config Defaults per Module
# =============================================================================
# Default configuration options per module
MODULE_CONFIG_DEFAULTS: dict[str, dict[str, Any]] = {
"billing": {
"stripe_mode": "test",
"default_trial_days": 14,
"allow_free_tier": True,
},
"inventory": {
"low_stock_threshold": 10,
"enable_locations": False,
},
"orders": {
"require_payment": True,
"auto_archive_days": 90,
},
"marketplace": {
"sync_frequency_hours": 24,
"auto_import_products": False,
},
"customers": {
"enable_segmentation": True,
"marketing_consent_default": False,
},
"cms": {
"max_pages": 50,
"enable_seo": True,
},
"analytics": {
"data_retention_days": 365,
"enable_export": True,
},
"messaging": {
"enable_attachments": True,
"max_attachment_size_mb": 10,
},
"monitoring": {
"log_retention_days": 30,
"alert_email": "",
},
}
# Config option metadata for UI rendering
MODULE_CONFIG_SCHEMA: dict[str, list[dict[str, Any]]] = {
"billing": [
{
"key": "stripe_mode",
"label": "Stripe Mode",
"type": "select",
"options": ["test", "live"],
"description": "Use test or live Stripe API keys",
},
{
"key": "default_trial_days",
"label": "Default Trial Days",
"type": "number",
"min": 0,
"max": 90,
"description": "Number of trial days for new subscriptions",
},
{
"key": "allow_free_tier",
"label": "Allow Free Tier",
"type": "boolean",
"description": "Allow vendors to use free tier indefinitely",
},
],
"inventory": [
{
"key": "low_stock_threshold",
"label": "Low Stock Threshold",
"type": "number",
"min": 0,
"max": 1000,
"description": "Stock level below which low stock alerts trigger",
},
{
"key": "enable_locations",
"label": "Enable Locations",
"type": "boolean",
"description": "Enable multiple inventory locations",
},
],
"orders": [
{
"key": "require_payment",
"label": "Require Payment",
"type": "boolean",
"description": "Require payment before order confirmation",
},
{
"key": "auto_archive_days",
"label": "Auto Archive Days",
"type": "number",
"min": 30,
"max": 365,
"description": "Days after which completed orders are archived",
},
],
"marketplace": [
{
"key": "sync_frequency_hours",
"label": "Sync Frequency (hours)",
"type": "number",
"min": 1,
"max": 168,
"description": "How often to sync with external marketplaces",
},
{
"key": "auto_import_products",
"label": "Auto Import Products",
"type": "boolean",
"description": "Automatically import new products from marketplace",
},
],
"customers": [
{
"key": "enable_segmentation",
"label": "Enable Segmentation",
"type": "boolean",
"description": "Enable customer segmentation and tagging",
},
{
"key": "marketing_consent_default",
"label": "Marketing Consent Default",
"type": "boolean",
"description": "Default value for marketing consent checkbox",
},
],
"cms": [
{
"key": "max_pages",
"label": "Max Pages",
"type": "number",
"min": 1,
"max": 500,
"description": "Maximum number of content pages allowed",
},
{
"key": "enable_seo",
"label": "Enable SEO",
"type": "boolean",
"description": "Enable SEO fields for content pages",
},
],
"analytics": [
{
"key": "data_retention_days",
"label": "Data Retention (days)",
"type": "number",
"min": 30,
"max": 730,
"description": "How long to keep analytics data",
},
{
"key": "enable_export",
"label": "Enable Export",
"type": "boolean",
"description": "Allow exporting analytics data",
},
],
"messaging": [
{
"key": "enable_attachments",
"label": "Enable Attachments",
"type": "boolean",
"description": "Allow file attachments in messages",
},
{
"key": "max_attachment_size_mb",
"label": "Max Attachment Size (MB)",
"type": "number",
"min": 1,
"max": 50,
"description": "Maximum attachment file size in megabytes",
},
],
"monitoring": [
{
"key": "log_retention_days",
"label": "Log Retention (days)",
"type": "number",
"min": 7,
"max": 365,
"description": "How long to keep log files",
},
{
"key": "alert_email",
"label": "Alert Email",
"type": "string",
"description": "Email address for system alerts (blank to disable)",
},
],
}
# =============================================================================
# Pydantic Schemas
# =============================================================================
class ModuleConfigResponse(BaseModel):
"""Module configuration response."""
module_code: str
module_name: str
config: dict[str, Any]
schema_info: list[dict[str, Any]] = Field(default_factory=list)
defaults: dict[str, Any] = Field(default_factory=dict)
class UpdateConfigRequest(BaseModel):
"""Request to update module configuration."""
config: dict[str, Any] = Field(..., description="Configuration key-value pairs")
class ConfigDefaultsResponse(BaseModel):
"""Response for module config defaults."""
module_code: str
module_name: str
defaults: dict[str, Any]
schema_info: list[dict[str, Any]]
# =============================================================================
# API Endpoints
# =============================================================================
@router.get("/platforms/{platform_id}/modules/{module_code}/config", response_model=ModuleConfigResponse)
async def get_module_config(
platform_id: int = Path(..., description="Platform ID"),
module_code: str = Path(..., description="Module code"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Get configuration for a specific module on a platform.
Returns current config values merged with defaults.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Validate module code
if module_code not in MODULES:
raise ValidationException(f"Unknown module: {module_code}")
module = MODULES[module_code]
# Get current config
current_config = module_service.get_module_config(db, platform_id, module_code)
# Merge with defaults
defaults = MODULE_CONFIG_DEFAULTS.get(module_code, {})
merged_config = {**defaults, **current_config}
logger.info(
f"[MODULE_CONFIG] Super admin {current_user.email} fetched config "
f"for module '{module_code}' on platform {platform.code}"
)
return ModuleConfigResponse(
module_code=module_code,
module_name=module.name,
config=merged_config,
schema_info=MODULE_CONFIG_SCHEMA.get(module_code, []),
defaults=defaults,
)
@router.put("/platforms/{platform_id}/modules/{module_code}/config", response_model=ModuleConfigResponse)
async def update_module_config(
update_data: UpdateConfigRequest,
platform_id: int = Path(..., description="Platform ID"),
module_code: str = Path(..., description="Module code"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Update configuration for a specific module on a platform.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Validate module code
if module_code not in MODULES:
raise ValidationException(f"Unknown module: {module_code}")
module = MODULES[module_code]
# Update config
success = module_service.set_module_config(db, platform_id, module_code, update_data.config)
if success:
db.commit()
# Get updated config
current_config = module_service.get_module_config(db, platform_id, module_code)
defaults = MODULE_CONFIG_DEFAULTS.get(module_code, {})
merged_config = {**defaults, **current_config}
logger.info(
f"[MODULE_CONFIG] Super admin {current_user.email} updated config "
f"for module '{module_code}' on platform {platform.code}: {update_data.config}"
)
return ModuleConfigResponse(
module_code=module_code,
module_name=module.name,
config=merged_config,
schema_info=MODULE_CONFIG_SCHEMA.get(module_code, []),
defaults=defaults,
)
@router.get("/defaults/{module_code}", response_model=ConfigDefaultsResponse)
async def get_config_defaults(
module_code: str = Path(..., description="Module code"),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Get default configuration for a module.
Returns the default config values and schema for a module.
Super admin only.
"""
# Validate module code
if module_code not in MODULES:
raise ValidationException(f"Unknown module: {module_code}")
module = MODULES[module_code]
logger.info(
f"[MODULE_CONFIG] Super admin {current_user.email} fetched defaults "
f"for module '{module_code}'"
)
return ConfigDefaultsResponse(
module_code=module_code,
module_name=module.name,
defaults=MODULE_CONFIG_DEFAULTS.get(module_code, {}),
schema_info=MODULE_CONFIG_SCHEMA.get(module_code, []),
)
@router.post("/platforms/{platform_id}/modules/{module_code}/reset")
async def reset_module_config(
platform_id: int = Path(..., description="Platform ID"),
module_code: str = Path(..., description="Module code"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Reset module configuration to defaults.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Validate module code
if module_code not in MODULES:
raise ValidationException(f"Unknown module: {module_code}")
# Reset to defaults
defaults = MODULE_CONFIG_DEFAULTS.get(module_code, {})
success = module_service.set_module_config(db, platform_id, module_code, defaults)
if success:
db.commit()
logger.info(
f"[MODULE_CONFIG] Super admin {current_user.email} reset config "
f"for module '{module_code}' on platform {platform.code} to defaults"
)
return {
"success": success,
"message": f"Module '{module_code}' config reset to defaults",
"config": defaults,
}

View File

@@ -0,0 +1,399 @@
# app/modules/tenancy/routes/api/admin_modules.py
"""
Admin API endpoints for Platform Module Management.
Provides module enablement/disablement for platforms:
- GET /modules - List all available modules
- GET /modules/platforms/{platform_id} - Get modules for a platform
- PUT /modules/platforms/{platform_id} - Update enabled modules
- POST /modules/platforms/{platform_id}/enable - Enable a module
- POST /modules/platforms/{platform_id}/disable - Disable a module
All endpoints require super admin access.
"""
import logging
from fastapi import APIRouter, Depends, Path
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.api.deps import get_current_super_admin, get_db
from app.modules.registry import MODULES, get_core_module_codes
from app.modules.service import module_service
from app.modules.tenancy.services.platform_service import platform_service
from models.schema.auth import UserContext
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/modules")
# =============================================================================
# Pydantic Schemas
# =============================================================================
class ModuleResponse(BaseModel):
"""Module definition response."""
code: str
name: str
description: str
is_core: bool
is_enabled: bool
requires: list[str] = Field(default_factory=list)
features: list[str] = Field(default_factory=list)
menu_items_admin: list[str] = Field(default_factory=list)
menu_items_vendor: list[str] = Field(default_factory=list)
dependent_modules: list[str] = Field(default_factory=list)
class ModuleListResponse(BaseModel):
"""Response for module list."""
modules: list[ModuleResponse]
total: int
enabled: int
disabled: int
class PlatformModulesResponse(BaseModel):
"""Response for platform module configuration."""
platform_id: int
platform_code: str
platform_name: str
modules: list[ModuleResponse]
total: int
enabled: int
disabled: int
class EnableModulesRequest(BaseModel):
"""Request to set enabled modules."""
module_codes: list[str] = Field(..., description="List of module codes to enable")
class ToggleModuleRequest(BaseModel):
"""Request to enable/disable a single module."""
module_code: str = Field(..., description="Module code to toggle")
# =============================================================================
# Helper Functions
# =============================================================================
def _get_dependent_modules(module_code: str) -> list[str]:
"""Get modules that depend on a given module."""
dependents = []
for code, module in MODULES.items():
if module_code in module.requires:
dependents.append(code)
return dependents
def _build_module_response(
code: str,
is_enabled: bool,
) -> ModuleResponse:
"""Build ModuleResponse from module code."""
from app.modules.enums import FrontendType # noqa: API-007 - Enum for type safety
module = MODULES.get(code)
if not module:
raise ValueError(f"Unknown module: {code}")
return ModuleResponse(
code=module.code,
name=module.name,
description=module.description,
is_core=module.is_core,
is_enabled=is_enabled,
requires=module.requires,
features=module.features,
menu_items_admin=module.get_menu_items(FrontendType.ADMIN),
menu_items_vendor=module.get_menu_items(FrontendType.VENDOR),
dependent_modules=_get_dependent_modules(module.code),
)
# =============================================================================
# API Endpoints
# =============================================================================
@router.get("", response_model=ModuleListResponse)
async def list_all_modules(
current_user: UserContext = Depends(get_current_super_admin),
):
"""
List all available modules.
Returns all module definitions with their metadata.
Super admin only.
"""
modules = []
for code in MODULES.keys():
# All modules shown as enabled in the global list
modules.append(_build_module_response(code, is_enabled=True))
# Sort: core first, then alphabetically
modules.sort(key=lambda m: (not m.is_core, m.name))
logger.info(f"[MODULES] Super admin {current_user.email} listed all modules")
return ModuleListResponse(
modules=modules,
total=len(modules),
enabled=len(modules),
disabled=0,
)
@router.get("/platforms/{platform_id}", response_model=PlatformModulesResponse)
async def get_platform_modules(
platform_id: int = Path(..., description="Platform ID"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Get module configuration for a platform.
Returns all modules with their enablement status for the platform.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Get enabled module codes for this platform
enabled_codes = module_service.get_enabled_module_codes(db, platform_id)
modules = []
for code in MODULES.keys():
is_enabled = code in enabled_codes
modules.append(_build_module_response(code, is_enabled))
# Sort: core first, then alphabetically
modules.sort(key=lambda m: (not m.is_core, m.name))
enabled_count = sum(1 for m in modules if m.is_enabled)
logger.info(
f"[MODULES] Super admin {current_user.email} fetched modules "
f"for platform {platform.code} ({enabled_count}/{len(modules)} enabled)"
)
return PlatformModulesResponse(
platform_id=platform.id,
platform_code=platform.code,
platform_name=platform.name,
modules=modules,
total=len(modules),
enabled=enabled_count,
disabled=len(modules) - enabled_count,
)
@router.put("/platforms/{platform_id}", response_model=PlatformModulesResponse)
async def update_platform_modules(
update_data: EnableModulesRequest,
platform_id: int = Path(..., description="Platform ID"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Update enabled modules for a platform.
Sets the list of enabled modules. Core modules are automatically included.
Dependencies are automatically resolved.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Update enabled modules
module_service.set_enabled_modules(db, platform_id, update_data.module_codes)
db.commit()
# Get updated module list
enabled_codes = module_service.get_enabled_module_codes(db, platform_id)
modules = []
for code in MODULES.keys():
is_enabled = code in enabled_codes
modules.append(_build_module_response(code, is_enabled))
modules.sort(key=lambda m: (not m.is_core, m.name))
enabled_count = sum(1 for m in modules if m.is_enabled)
logger.info(
f"[MODULES] Super admin {current_user.email} updated modules "
f"for platform {platform.code}: {sorted(update_data.module_codes)}"
)
return PlatformModulesResponse(
platform_id=platform.id,
platform_code=platform.code,
platform_name=platform.name,
modules=modules,
total=len(modules),
enabled=enabled_count,
disabled=len(modules) - enabled_count,
)
@router.post("/platforms/{platform_id}/enable")
async def enable_module(
request: ToggleModuleRequest,
platform_id: int = Path(..., description="Platform ID"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Enable a single module for a platform.
Also enables required dependencies.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Validate module code
if request.module_code not in MODULES:
from app.modules.tenancy.exceptions import BadRequestException
raise BadRequestException(f"Unknown module: {request.module_code}")
# Enable module
success = module_service.enable_module(db, platform_id, request.module_code)
if success:
db.commit()
# Check what dependencies were also enabled
module = MODULES[request.module_code]
enabled_deps = module.requires if module.requires else []
logger.info(
f"[MODULES] Super admin {current_user.email} enabled module "
f"'{request.module_code}' for platform {platform.code}"
)
return {
"success": success,
"message": f"Module '{request.module_code}' enabled",
"also_enabled": enabled_deps,
}
@router.post("/platforms/{platform_id}/disable")
async def disable_module(
request: ToggleModuleRequest,
platform_id: int = Path(..., description="Platform ID"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Disable a single module for a platform.
Core modules cannot be disabled.
Also disables modules that depend on this one.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Validate module code
if request.module_code not in MODULES:
from app.modules.tenancy.exceptions import BadRequestException
raise BadRequestException(f"Unknown module: {request.module_code}")
# Check if core module
if request.module_code in get_core_module_codes():
from app.modules.tenancy.exceptions import BadRequestException
raise BadRequestException(f"Cannot disable core module: {request.module_code}")
# Get dependent modules before disabling
dependents = _get_dependent_modules(request.module_code)
# Disable module
success = module_service.disable_module(db, platform_id, request.module_code)
if success:
db.commit()
logger.info(
f"[MODULES] Super admin {current_user.email} disabled module "
f"'{request.module_code}' for platform {platform.code}"
)
return {
"success": success,
"message": f"Module '{request.module_code}' disabled",
"also_disabled": dependents if dependents else [],
}
@router.post("/platforms/{platform_id}/enable-all")
async def enable_all_modules(
platform_id: int = Path(..., description="Platform ID"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Enable all modules for a platform.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Enable all modules
all_codes = list(MODULES.keys())
module_service.set_enabled_modules(db, platform_id, all_codes)
db.commit()
logger.info(
f"[MODULES] Super admin {current_user.email} enabled all modules "
f"for platform {platform.code}"
)
return {
"success": True,
"message": "All modules enabled",
"enabled_count": len(all_codes),
}
@router.post("/platforms/{platform_id}/disable-optional")
async def disable_optional_modules(
platform_id: int = Path(..., description="Platform ID"),
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_super_admin),
):
"""
Disable all optional modules for a platform, keeping only core modules.
Super admin only.
"""
# Verify platform exists
platform = platform_service.get_platform_by_id(db, platform_id)
# Enable only core modules
core_codes = list(get_core_module_codes())
module_service.set_enabled_modules(db, platform_id, core_codes)
db.commit()
logger.info(
f"[MODULES] Super admin {current_user.email} disabled optional modules "
f"for platform {platform.code} (kept {len(core_codes)} core modules)"
)
return {
"success": True,
"message": "Optional modules disabled, core modules kept",
"core_modules": core_codes,
}

View File

@@ -23,7 +23,7 @@ from app.api.deps import get_current_super_admin, get_current_super_admin_api
from app.core.database import get_db
from app.exceptions import ValidationException
from app.modules.tenancy.services.admin_platform_service import admin_platform_service
from models.database.user import User # noqa: API-007 - Internal helper uses User model
from app.modules.tenancy.models import User # noqa: API-007 - Internal helper uses User model
from models.schema.auth import UserContext
admin_users_router = APIRouter(prefix="/admin-users")

View File

@@ -19,7 +19,7 @@ from app.core.database import get_db
from app.modules.tenancy.services.vendor_domain_service import vendor_domain_service
from app.modules.tenancy.services.vendor_service import vendor_service
from models.schema.auth import UserContext
from models.schema.vendor_domain import (
from app.modules.tenancy.schemas.vendor_domain import (
DomainDeletionResponse,
DomainVerificationInstructions,
DomainVerificationResponse,

View File

@@ -21,7 +21,7 @@ from app.modules.analytics.services.stats_service import stats_service
from app.modules.tenancy.services.vendor_service import vendor_service
from models.schema.auth import UserContext
from app.modules.analytics.schemas import VendorStatsResponse
from models.schema.vendor import (
from app.modules.tenancy.schemas.vendor import (
LetzshopExportRequest,
LetzshopExportResponse,
VendorCreate,

View File

@@ -18,7 +18,7 @@ from sqlalchemy.orm import Session
from app.core.database import get_db
from app.modules.tenancy.services.vendor_service import vendor_service # noqa: mod-004
from models.schema.vendor import VendorDetailResponse
from app.modules.tenancy.schemas.vendor import VendorDetailResponse
vendor_router = APIRouter()
logger = logging.getLogger(__name__)

View File

@@ -15,7 +15,7 @@ from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.modules.tenancy.services.vendor_service import vendor_service
from models.schema.auth import UserContext
from models.schema.vendor import VendorResponse, VendorUpdate
from app.modules.tenancy.schemas.vendor import VendorResponse, VendorUpdate
vendor_profile_router = APIRouter(prefix="/profile")
logger = logging.getLogger(__name__)

View File

@@ -25,7 +25,7 @@ from app.core.database import get_db
from app.core.permissions import VendorPermissions
from app.modules.tenancy.services.vendor_team_service import vendor_team_service
from models.schema.auth import UserContext
from models.schema.team import (
from app.modules.tenancy.schemas.team import (
BulkRemoveRequest,
BulkRemoveResponse,
InvitationAccept,