Files
orion/app/modules/tenancy/routes/api/merchant.py
Samir Boulahtit d9fc52d47a
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
feat: email verification, merchant/store password reset, seed gap fix
- Add EmailVerificationToken and UserPasswordResetToken models with migration
- Add email verification flow: verify-email page route, resend-verification API
- Block login for unverified users (EmailNotVerifiedException in auth_service)
- Add forgot-password/reset-password endpoints for merchant and store auth
- Add "Forgot Password?" links to merchant and store login pages
- Send welcome email with verification link on merchant creation
- Seed email_verification and merchant_password_reset email templates
- Fix db-reset Makefile to run all init-prod seed scripts
- Add UserAuthService to satisfy architecture validation rules
- Add 52 new tests (unit + integration) with full coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 23:22:46 +01:00

119 lines
3.7 KiB
Python

# app/modules/tenancy/routes/api/merchant.py
"""
Tenancy module merchant API routes.
Aggregates all merchant tenancy routes:
- /auth/* - Merchant authentication (login, logout, /me)
- /account/* - Merchant account management (stores, profile)
Auto-discovered by the route system (merchant.py in routes/api/).
"""
import logging
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy.orm import Session
from app.api.deps import get_current_merchant_api, get_merchant_for_current_user
from app.core.database import get_db
from app.modules.tenancy.schemas import (
MerchantPortalProfileResponse,
MerchantPortalProfileUpdate,
MerchantPortalStoreListResponse,
)
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 .merchant_auth import merchant_auth_router
logger = logging.getLogger(__name__)
router = APIRouter()
# Include auth routes (/auth/login, /auth/logout, /auth/me, /auth/forgot-password, /auth/reset-password)
router.include_router(merchant_auth_router, tags=["merchant-auth"])
# Include email verification routes (/resend-verification)
router.include_router(email_verification_api_router, tags=["email-verification"])
# Account routes are defined below with /account prefix
_account_router = APIRouter(prefix="/account")
# ============================================================================
# ACCOUNT ENDPOINTS
# ============================================================================
@_account_router.get("/stores", response_model=MerchantPortalStoreListResponse)
async def merchant_stores(
request: Request,
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=200, description="Max records to return"),
merchant=Depends(get_merchant_for_current_user),
db: Session = Depends(get_db),
):
"""
List all stores belonging to the merchant.
Returns a paginated list of store summaries for the authenticated merchant.
"""
stores, total = merchant_service.get_merchant_stores(
db, merchant.id, skip=skip, limit=limit
)
return MerchantPortalStoreListResponse(
stores=stores,
total=total,
skip=skip,
limit=limit,
)
@_account_router.get("/profile", response_model=MerchantPortalProfileResponse)
async def merchant_profile(
request: Request,
merchant=Depends(get_merchant_for_current_user),
):
"""
Get the authenticated merchant's profile information.
Returns merchant details including contact info, business details,
and verification status.
"""
return merchant
@_account_router.put("/profile", response_model=MerchantPortalProfileResponse)
async def update_merchant_profile(
request: Request,
profile_data: MerchantPortalProfileUpdate,
current_user: UserContext = Depends(get_current_merchant_api),
merchant=Depends(get_merchant_for_current_user),
db: Session = Depends(get_db),
):
"""
Update the authenticated merchant's profile information.
Accepts partial updates - only provided fields are changed.
"""
# Apply only the fields that were explicitly provided
update_data = profile_data.model_dump(exclude_unset=True)
for field_name, value in update_data.items():
setattr(merchant, field_name, value)
db.commit()
db.refresh(merchant)
logger.info(
f"Merchant profile updated: merchant_id={merchant.id}, "
f"user={current_user.username}, fields={list(update_data.keys())}"
)
return merchant
# Include account routes in main router
router.include_router(_account_router, tags=["merchant-account"])