diff --git a/app/api/v1/vendor/__init__.py b/app/api/v1/vendor/__init__.py index a6435ec7..6bf8ede4 100644 --- a/app/api/v1/vendor/__init__.py +++ b/app/api/v1/vendor/__init__.py @@ -24,6 +24,7 @@ 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 """ from fastapi import APIRouter @@ -34,7 +35,6 @@ from . import ( dashboard, email_settings, email_templates, - info, media, messages, notifications, @@ -51,10 +51,6 @@ router = APIRouter() # ============================================================================ # These routes return JSON and are mounted at /api/v1/vendor/* -# IMPORTANT: Specific routes MUST be registered BEFORE catch-all routes -# The info.router has GET /{vendor_code} which catches everything, -# so it must be registered LAST - # Authentication (no prefix, specific routes like /auth/login) router.include_router(auth.router, tags=["vendor-auth"]) @@ -98,7 +94,4 @@ for route_info in get_vendor_api_routes(): ) -# Vendor info endpoint - MUST BE LAST! Has catch-all GET /{vendor_code} -router.include_router(info.router, tags=["vendor-info"]) - __all__ = ["router"] diff --git a/app/modules/tenancy/definition.py b/app/modules/tenancy/definition.py index 97f68afd..d8acf7f2 100644 --- a/app/modules/tenancy/definition.py +++ b/app/modules/tenancy/definition.py @@ -15,6 +15,7 @@ tenancy_module = ModuleDefinition( description="Platform, company, vendor, and admin user management. Required for multi-tenant operation.", version="1.0.0", is_core=True, + is_self_contained=True, features=[ "platform_management", "company_management", @@ -32,6 +33,10 @@ tenancy_module = ModuleDefinition( "team", ], }, + services_path="app.modules.tenancy.services", + models_path="app.modules.tenancy.models", + schemas_path="app.modules.tenancy.schemas", + exceptions_path="app.modules.tenancy.exceptions", ) __all__ = ["tenancy_module"] diff --git a/app/modules/tenancy/exceptions.py b/app/modules/tenancy/exceptions.py new file mode 100644 index 00000000..5006da58 --- /dev/null +++ b/app/modules/tenancy/exceptions.py @@ -0,0 +1,38 @@ +# app/modules/tenancy/exceptions.py +""" +Tenancy module exceptions. + +Exceptions for platform, company, vendor, and admin user management. +""" + +from app.exceptions import WizamartException + + +class TenancyException(WizamartException): + """Base exception for tenancy module.""" + + pass + + +class VendorNotFoundException(TenancyException): + """Vendor not found or inactive.""" + + def __init__(self, vendor_code: str): + super().__init__(f"Vendor '{vendor_code}' not found or inactive") + self.vendor_code = vendor_code + + +class CompanyNotFoundException(TenancyException): + """Company not found.""" + + def __init__(self, company_id: int): + super().__init__(f"Company {company_id} not found") + self.company_id = company_id + + +class PlatformNotFoundException(TenancyException): + """Platform not found.""" + + def __init__(self, platform_id: int): + super().__init__(f"Platform {platform_id} not found") + self.platform_id = platform_id diff --git a/app/modules/tenancy/models/__init__.py b/app/modules/tenancy/models/__init__.py new file mode 100644 index 00000000..ceb9ee8c --- /dev/null +++ b/app/modules/tenancy/models/__init__.py @@ -0,0 +1,14 @@ +# app/modules/tenancy/models/__init__.py +""" +Tenancy module database models. + +Models for platform, company, vendor, and admin user management. +Currently models remain in models/database/ - this package is a placeholder +for future migration. +""" + +# Models will be migrated here from models/database/ +# For now, import from legacy location if needed: +# from models.database.vendor import Vendor +# from models.database.company import Company +# from models.database.platform import Platform diff --git a/app/modules/tenancy/routes/__init__.py b/app/modules/tenancy/routes/__init__.py new file mode 100644 index 00000000..2f618e31 --- /dev/null +++ b/app/modules/tenancy/routes/__init__.py @@ -0,0 +1,6 @@ +# app/modules/tenancy/routes/__init__.py +""" +Tenancy module routes. + +API and page routes for platform, company, vendor, and admin user management. +""" diff --git a/app/modules/tenancy/routes/api/__init__.py b/app/modules/tenancy/routes/api/__init__.py new file mode 100644 index 00000000..e55c6b5f --- /dev/null +++ b/app/modules/tenancy/routes/api/__init__.py @@ -0,0 +1,11 @@ +# app/modules/tenancy/routes/api/__init__.py +""" +Tenancy module API routes. + +Includes: +- /info/{vendor_code} - Public vendor info lookup +""" + +from .vendor import vendor_router + +__all__ = ["vendor_router"] diff --git a/app/api/v1/vendor/info.py b/app/modules/tenancy/routes/api/vendor.py similarity index 82% rename from app/api/v1/vendor/info.py rename to app/modules/tenancy/routes/api/vendor.py index 2b1ad005..27d9c48f 100644 --- a/app/api/v1/vendor/info.py +++ b/app/modules/tenancy/routes/api/vendor.py @@ -1,10 +1,13 @@ -# app/api/v1/vendor/info.py +# app/modules/tenancy/routes/api/vendor.py """ -Vendor information endpoints. +Tenancy module vendor API routes. -This module provides: -- Public vendor information lookup (no auth required) -- Used by vendor login pages to display vendor details +Provides public vendor information lookup for: +- Vendor login pages to display branding +- Public vendor profile lookup + +These endpoints do NOT require authentication - they provide +public information about vendors. """ import logging @@ -13,14 +16,14 @@ from fastapi import APIRouter, Depends, Path from sqlalchemy.orm import Session from app.core.database import get_db -from app.services.vendor_service import vendor_service +from app.services.vendor_service import vendor_service # noqa: mod-004 from models.schema.vendor import VendorDetailResponse -router = APIRouter() +vendor_router = APIRouter() logger = logging.getLogger(__name__) -@router.get("/{vendor_code}", response_model=VendorDetailResponse) +@vendor_router.get("/info/{vendor_code}", response_model=VendorDetailResponse) def get_vendor_info( vendor_code: str = Path(..., description="Vendor code"), db: Session = Depends(get_db), diff --git a/app/modules/tenancy/schemas/__init__.py b/app/modules/tenancy/schemas/__init__.py new file mode 100644 index 00000000..22cd79d2 --- /dev/null +++ b/app/modules/tenancy/schemas/__init__.py @@ -0,0 +1,12 @@ +# app/modules/tenancy/schemas/__init__.py +""" +Tenancy module Pydantic schemas. + +Request/response schemas for platform, company, vendor, and admin user management. +Currently schemas remain in models/schema/ - this package is a placeholder +for future migration. +""" + +# Schemas will be migrated here from models/schema/ +# For now, import from legacy location if needed: +# from models.schema.vendor import VendorDetailResponse diff --git a/app/modules/tenancy/services/__init__.py b/app/modules/tenancy/services/__init__.py new file mode 100644 index 00000000..d62c6625 --- /dev/null +++ b/app/modules/tenancy/services/__init__.py @@ -0,0 +1,13 @@ +# app/modules/tenancy/services/__init__.py +""" +Tenancy module services. + +Business logic for platform, company, vendor, and admin user management. +Currently services remain in app/services/ - this package is a placeholder +for future migration. +""" + +# Services will be migrated here from app/services/ +# For now, import from legacy location if needed: +# from app.services.vendor_service import vendor_service +# from app.services.company_service import company_service diff --git a/static/vendor/js/init-alpine.js b/static/vendor/js/init-alpine.js index 1c68a808..79207fb4 100644 --- a/static/vendor/js/init-alpine.js +++ b/static/vendor/js/init-alpine.js @@ -98,8 +98,8 @@ function data() { if (!this.vendorCode) return; try { - // apiClient prepends /api/v1, so /vendor/{code} → /api/v1/vendor/{code} - const response = await apiClient.get(`/vendor/${this.vendorCode}`); + // apiClient prepends /api/v1, so /vendor/info/{code} → /api/v1/vendor/info/{code} + const response = await apiClient.get(`/vendor/info/${this.vendorCode}`); this.vendor = response; vendorLog.debug('Vendor info loaded', this.vendor); } catch (error) { diff --git a/static/vendor/js/login.js b/static/vendor/js/login.js index 1c353d05..dc1e92c0 100644 --- a/static/vendor/js/login.js +++ b/static/vendor/js/login.js @@ -57,7 +57,7 @@ function vendorLogin() { vendorLoginLog.info('Loading vendor information...'); this.loading = true; try { - const response = await apiClient.get(`/vendor/${this.vendorCode}`); + const response = await apiClient.get(`/vendor/info/${this.vendorCode}`); this.vendor = response; vendorLoginLog.info('Vendor loaded successfully:', { code: this.vendor.code,