refactor: migrate vendor auth, profile, team, dashboard, settings to modules

Tenancy module (identity & organizational hierarchy):
- vendor_auth.py: login, logout, /me endpoints
- vendor_profile.py: vendor profile get/update
- vendor_team.py: team management, roles, permissions, invitations

Core module (foundational non-domain features):
- vendor_dashboard.py: dashboard statistics
- vendor_settings.py: localization, business info, letzshop settings

All routes auto-discovered via is_self_contained=True.
Deleted 5 legacy files from app/api/v1/vendor/.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 15:09:41 +01:00
parent d747f9ebaa
commit da3f28849e
12 changed files with 105 additions and 52 deletions

View File

@@ -24,23 +24,18 @@ Self-contained modules (auto-discovered from app/modules/{module}/routes/api/ven
- cms: Content pages management
- customers: Customer management
- payments: Payment configuration, Stripe connect, transactions
- tenancy: Public vendor info lookup
- tenancy: Vendor info, auth, profile, team management
"""
from fastapi import APIRouter
# Import all sub-routers (legacy routes that haven't been migrated to modules)
from . import (
auth,
dashboard,
email_settings,
email_templates,
media,
messages,
notifications,
profile,
settings,
team,
)
# Create vendor router
@@ -51,18 +46,14 @@ router = APIRouter()
# ============================================================================
# These routes return JSON and are mounted at /api/v1/vendor/*
# Authentication (no prefix, specific routes like /auth/login)
router.include_router(auth.router, tags=["vendor-auth"])
# Vendor management (with prefixes: /dashboard/*, /profile/*, /settings/*)
router.include_router(dashboard.router, tags=["vendor-dashboard"])
router.include_router(profile.router, tags=["vendor-profile"])
router.include_router(settings.router, tags=["vendor-settings"])
# Email configuration
router.include_router(email_templates.router, tags=["vendor-email-templates"])
router.include_router(email_settings.router, tags=["vendor-email-settings"])
# Business operations (with prefixes: /team/*)
router.include_router(team.router, tags=["vendor-team"])
# Services (with prefixes: /media/*, etc.)
router.include_router(media.router, tags=["vendor-media"])
router.include_router(notifications.router, tags=["vendor-notifications"])
router.include_router(messages.router, tags=["vendor-messages"])
# Services (with prefixes: /media/*, etc.)
router.include_router(media.router, tags=["vendor-media"])

View File

@@ -15,6 +15,7 @@ core_module = ModuleDefinition(
description="Dashboard, settings, and profile management. Required for basic operation.",
version="1.0.0",
is_core=True,
is_self_contained=True,
features=[
"dashboard",
"settings",

View File

@@ -0,0 +1,4 @@
# app/modules/core/routes/__init__.py
"""
Core module route registration.
"""

View File

@@ -0,0 +1,12 @@
# app/modules/core/routes/api/__init__.py
"""
Core module API routes.
Vendor routes:
- /dashboard/* - Dashboard statistics
- /settings/* - Vendor settings management
"""
from .vendor import vendor_router
__all__ = ["vendor_router"]

View File

@@ -0,0 +1,19 @@
# app/modules/core/routes/api/vendor.py
"""
Core module vendor API routes.
Aggregates:
- /dashboard/* - Dashboard statistics
- /settings/* - Vendor settings management
"""
from fastapi import APIRouter
from .vendor_dashboard import vendor_dashboard_router
from .vendor_settings import vendor_settings_router
vendor_router = APIRouter()
# Aggregate sub-routers
vendor_router.include_router(vendor_dashboard_router, tags=["vendor-dashboard"])
vendor_router.include_router(vendor_settings_router, tags=["vendor-settings"])

View File

@@ -1,4 +1,4 @@
# app/api/v1/vendor/dashboard.py
# app/modules/core/routes/api/vendor_dashboard.py
"""
Vendor dashboard and statistics endpoints.
@@ -26,11 +26,11 @@ from app.modules.analytics.schemas import (
VendorRevenueStats,
)
router = APIRouter(prefix="/dashboard")
vendor_dashboard_router = APIRouter(prefix="/dashboard")
logger = logging.getLogger(__name__)
@router.get("/stats", response_model=VendorDashboardStatsResponse)
@vendor_dashboard_router.get("/stats", response_model=VendorDashboardStatsResponse)
def get_vendor_dashboard_stats(
request: Request,
current_user: UserContext = Depends(get_current_vendor_api),

View File

@@ -1,4 +1,4 @@
# app/api/v1/vendor/settings.py
# app/modules/core/routes/api/vendor_settings.py
"""
Vendor settings and configuration endpoints.
@@ -18,7 +18,7 @@ from app.services.platform_settings_service import platform_settings_service
from app.services.vendor_service import vendor_service
from models.schema.auth import UserContext
router = APIRouter(prefix="/settings")
vendor_settings_router = APIRouter(prefix="/settings")
logger = logging.getLogger(__name__)
# Supported languages for dropdown
@@ -154,7 +154,7 @@ class LetzshopFeedSettingsUpdate(BaseModel):
return v
@router.get("")
@vendor_settings_router.get("")
def get_vendor_settings(
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -318,7 +318,7 @@ def get_vendor_settings(
}
@router.put("/business-info")
@vendor_settings_router.put("/business-info")
def update_business_info(
business_info: BusinessInfoUpdate,
current_user: UserContext = Depends(get_current_vendor_api),
@@ -360,7 +360,7 @@ def update_business_info(
}
@router.put("/letzshop")
@vendor_settings_router.put("/letzshop")
def update_letzshop_settings(
letzshop_config: LetzshopFeedSettingsUpdate,
current_user: UserContext = Depends(get_current_vendor_api),
@@ -394,7 +394,7 @@ def update_letzshop_settings(
}
@router.put("/localization")
@vendor_settings_router.put("/localization")
def update_localization_settings(
localization_config: LocalizationSettingsUpdate,
current_user: UserContext = Depends(get_current_vendor_api),

View File

@@ -2,10 +2,21 @@
"""
Tenancy module API routes.
Includes:
Vendor routes:
- /info/{vendor_code} - Public vendor info lookup
- /auth/* - Vendor authentication (login, logout, /me)
- /profile/* - Vendor profile management
- /team/* - Team member management, roles, permissions
"""
from .vendor import vendor_router
from .vendor_auth import vendor_auth_router
from .vendor_profile import vendor_profile_router
from .vendor_team import vendor_team_router
__all__ = ["vendor_router"]
__all__ = [
"vendor_router",
"vendor_auth_router",
"vendor_profile_router",
"vendor_team_router",
]

View File

@@ -2,12 +2,13 @@
"""
Tenancy module vendor API routes.
Provides public vendor information lookup for:
- Vendor login pages to display branding
- Public vendor profile lookup
Aggregates all vendor tenancy routes:
- /info/{vendor_code} - Public vendor info lookup
- /auth/* - Vendor authentication (login, logout, /me)
- /profile/* - Vendor profile management
- /team/* - Team member management, roles, permissions
These endpoints do NOT require authentication - they provide
public information about vendors.
The tenancy module owns identity and organizational hierarchy.
"""
import logging
@@ -80,3 +81,17 @@ def get_vendor_info(
owner_email=vendor.company.owner.email,
owner_username=vendor.company.owner.username,
)
# ============================================================================
# Aggregate Sub-Routers
# ============================================================================
# Include all tenancy vendor routes (auth, profile, team)
from .vendor_auth import vendor_auth_router
from .vendor_profile import vendor_profile_router
from .vendor_team import vendor_team_router
vendor_router.include_router(vendor_auth_router, tags=["vendor-auth"])
vendor_router.include_router(vendor_profile_router, tags=["vendor-profile"])
vendor_router.include_router(vendor_team_router, tags=["vendor-team"])

View File

@@ -1,4 +1,4 @@
# app/api/v1/vendor/auth.py
# app/modules/tenancy/routes/api/vendor_auth.py
"""
Vendor team authentication endpoints.
@@ -27,7 +27,7 @@ from middleware.vendor_context import get_current_vendor
from models.schema.auth import UserContext
from models.schema.auth import LogoutResponse, UserLogin, VendorUserResponse
router = APIRouter(prefix="/auth")
vendor_auth_router = APIRouter(prefix="/auth")
logger = logging.getLogger(__name__)
@@ -41,7 +41,7 @@ class VendorLoginResponse(BaseModel):
vendor_role: str
@router.post("/login", response_model=VendorLoginResponse)
@vendor_auth_router.post("/login", response_model=VendorLoginResponse)
def vendor_login(
user_credentials: UserLogin,
request: Request,
@@ -156,7 +156,7 @@ def vendor_login(
)
@router.post("/logout", response_model=LogoutResponse)
@vendor_auth_router.post("/logout", response_model=LogoutResponse)
def vendor_logout(response: Response):
"""
Vendor team member logout.
@@ -177,7 +177,7 @@ def vendor_logout(response: Response):
return LogoutResponse(message="Logged out successfully")
@router.get("/me", response_model=VendorUserResponse)
@vendor_auth_router.get("/me", response_model=VendorUserResponse)
def get_current_vendor_user(
user: UserContext = Depends(get_current_vendor_api), db: Session = Depends(get_db)
):

View File

@@ -1,4 +1,4 @@
# app/api/v1/vendor/profile.py
# app/modules/tenancy/routes/api/vendor_profile.py
"""
Vendor profile management endpoints.
@@ -17,11 +17,11 @@ from app.services.vendor_service import vendor_service
from models.schema.auth import UserContext
from models.schema.vendor import VendorResponse, VendorUpdate
router = APIRouter(prefix="/profile")
vendor_profile_router = APIRouter(prefix="/profile")
logger = logging.getLogger(__name__)
@router.get("", response_model=VendorResponse)
@vendor_profile_router.get("", response_model=VendorResponse)
def get_vendor_profile(
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -31,7 +31,7 @@ def get_vendor_profile(
return vendor
@router.put("", response_model=VendorResponse)
@vendor_profile_router.put("", response_model=VendorResponse)
def update_vendor_profile(
vendor_update: VendorUpdate,
current_user: UserContext = Depends(get_current_vendor_api),

View File

@@ -1,4 +1,4 @@
# app/api/v1/vendor/teams.py
# app/modules/tenancy/routes/api/vendor_team.py
"""
Vendor team member management endpoints.
@@ -40,7 +40,7 @@ from models.schema.team import (
UserPermissionsResponse,
)
router = APIRouter(prefix="/team")
vendor_team_router = APIRouter(prefix="/team")
logger = logging.getLogger(__name__)
@@ -49,7 +49,7 @@ logger = logging.getLogger(__name__)
# ============================================================================
@router.get("/members", response_model=TeamMemberListResponse)
@vendor_team_router.get("/members", response_model=TeamMemberListResponse)
def list_team_members(
request: Request,
include_inactive: bool = False,
@@ -91,7 +91,7 @@ def list_team_members(
)
@router.post("/invite", response_model=InvitationResponse)
@vendor_team_router.post("/invite", response_model=InvitationResponse)
def invite_team_member(
invitation: TeamMemberInvite,
request: Request,
@@ -165,7 +165,7 @@ def invite_team_member(
)
@router.post("/accept-invitation", response_model=InvitationAcceptResponse) # public
@vendor_team_router.post("/accept-invitation", response_model=InvitationAcceptResponse) # public
def accept_invitation(acceptance: InvitationAccept, db: Session = Depends(get_db)):
"""
Accept a team invitation and activate account.
@@ -215,7 +215,7 @@ def accept_invitation(acceptance: InvitationAccept, db: Session = Depends(get_db
)
@router.get("/members/{user_id}", response_model=TeamMemberResponse)
@vendor_team_router.get("/members/{user_id}", response_model=TeamMemberResponse)
def get_team_member(
user_id: int,
request: Request,
@@ -244,7 +244,7 @@ def get_team_member(
return TeamMemberResponse(**member)
@router.put("/members/{user_id}", response_model=TeamMemberResponse)
@vendor_team_router.put("/members/{user_id}", response_model=TeamMemberResponse)
def update_team_member(
user_id: int,
update_data: TeamMemberUpdate,
@@ -288,7 +288,7 @@ def update_team_member(
return TeamMemberResponse(**member)
@router.delete("/members/{user_id}")
@vendor_team_router.delete("/members/{user_id}")
def remove_team_member(
user_id: int,
request: Request,
@@ -320,7 +320,7 @@ def remove_team_member(
return {"message": "Team member removed successfully", "user_id": user_id}
@router.post("/members/bulk-remove", response_model=BulkRemoveResponse)
@vendor_team_router.post("/members/bulk-remove", response_model=BulkRemoveResponse)
def bulk_remove_team_members(
bulk_remove: BulkRemoveRequest,
request: Request,
@@ -365,7 +365,7 @@ def bulk_remove_team_members(
# ============================================================================
@router.get("/roles", response_model=RoleListResponse)
@vendor_team_router.get("/roles", response_model=RoleListResponse)
def list_roles(
request: Request,
db: Session = Depends(get_db),
@@ -395,7 +395,7 @@ def list_roles(
# ============================================================================
@router.get("/me/permissions", response_model=UserPermissionsResponse)
@vendor_team_router.get("/me/permissions", response_model=UserPermissionsResponse)
def get_my_permissions(
request: Request,
permissions: list[str] = Depends(get_user_permissions),
@@ -434,7 +434,7 @@ def get_my_permissions(
# ============================================================================
@router.get("/statistics", response_model=TeamStatistics)
@vendor_team_router.get("/statistics", response_model=TeamStatistics)
def get_team_statistics(
request: Request,
db: Session = Depends(get_db),