first commit
This commit is contained in:
384
app/routers/wordpress.py
Normal file
384
app/routers/wordpress.py
Normal file
@@ -0,0 +1,384 @@
|
||||
"""
|
||||
WordPress Integration API Router.
|
||||
|
||||
Endpoints:
|
||||
- POST /wordpress/sync_users: Synchronize users from WordPress
|
||||
- POST /wordpress/verify_session: Verify WordPress session/token
|
||||
- GET /wordpress/website/{website_id}/users: Get all users for a website
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Header, status
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.models.website import Website
|
||||
from app.schemas.wordpress import (
|
||||
SyncUsersResponse,
|
||||
SyncStatsResponse,
|
||||
UserListResponse,
|
||||
VerifySessionRequest,
|
||||
VerifySessionResponse,
|
||||
WordPressUserResponse,
|
||||
)
|
||||
from app.services.wordpress_auth import (
|
||||
get_wordpress_user,
|
||||
sync_wordpress_users,
|
||||
verify_website_exists,
|
||||
verify_wordpress_token,
|
||||
get_or_create_user,
|
||||
WordPressAPIError,
|
||||
WordPressRateLimitError,
|
||||
WordPressTokenInvalidError,
|
||||
WebsiteNotFoundError,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/wordpress", tags=["wordpress"])
|
||||
|
||||
|
||||
def get_website_id_from_header(
|
||||
x_website_id: Optional[str] = Header(None, alias="X-Website-ID"),
|
||||
) -> int:
|
||||
"""
|
||||
Extract and validate website_id from request header.
|
||||
|
||||
Args:
|
||||
x_website_id: Website ID from header
|
||||
|
||||
Returns:
|
||||
Validated website ID as integer
|
||||
|
||||
Raises:
|
||||
HTTPException: If header is missing or invalid
|
||||
"""
|
||||
if x_website_id is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="X-Website-ID header is required",
|
||||
)
|
||||
try:
|
||||
return int(x_website_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="X-Website-ID must be a valid integer",
|
||||
)
|
||||
|
||||
|
||||
async def get_valid_website(
|
||||
website_id: int,
|
||||
db: AsyncSession,
|
||||
) -> Website:
|
||||
"""
|
||||
Validate website_id exists and return Website model.
|
||||
|
||||
Args:
|
||||
website_id: Website identifier
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Website model instance
|
||||
|
||||
Raises:
|
||||
HTTPException: If website not found
|
||||
"""
|
||||
try:
|
||||
return await verify_website_exists(website_id, db)
|
||||
except WebsiteNotFoundError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Website {website_id} not found",
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sync_users",
|
||||
response_model=SyncUsersResponse,
|
||||
summary="Synchronize users from WordPress",
|
||||
description="Fetch all users from WordPress API and sync to local database. Requires admin WordPress token.",
|
||||
)
|
||||
async def sync_users_endpoint(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
website_id: int = Depends(get_website_id_from_header),
|
||||
authorization: Optional[str] = Header(None, alias="Authorization"),
|
||||
) -> SyncUsersResponse:
|
||||
"""
|
||||
Synchronize users from WordPress to local database.
|
||||
|
||||
Process:
|
||||
1. Validate website_id exists
|
||||
2. Extract admin token from Authorization header
|
||||
3. Fetch all users from WordPress API
|
||||
4. Upsert: Update existing users, insert new users
|
||||
5. Return sync statistics
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
website_id: Website ID from header
|
||||
authorization: Authorization header with Bearer token
|
||||
|
||||
Returns:
|
||||
SyncUsersResponse with sync statistics
|
||||
|
||||
Raises:
|
||||
HTTPException: If website not found, token invalid, or API error
|
||||
"""
|
||||
# Validate website exists
|
||||
await get_valid_website(website_id, db)
|
||||
|
||||
# Extract token from Authorization header
|
||||
if authorization is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authorization header is required",
|
||||
)
|
||||
|
||||
# Parse Bearer token
|
||||
parts = authorization.split()
|
||||
if len(parts) != 2 or parts[0].lower() != "bearer":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid Authorization header format. Use: Bearer {token}",
|
||||
)
|
||||
|
||||
admin_token = parts[1]
|
||||
|
||||
try:
|
||||
sync_stats = await sync_wordpress_users(
|
||||
website_id=website_id,
|
||||
admin_token=admin_token,
|
||||
db=db,
|
||||
)
|
||||
|
||||
return SyncUsersResponse(
|
||||
synced=SyncStatsResponse(
|
||||
inserted=sync_stats.inserted,
|
||||
updated=sync_stats.updated,
|
||||
total=sync_stats.total,
|
||||
errors=sync_stats.errors,
|
||||
),
|
||||
website_id=website_id,
|
||||
message=f"Sync completed: {sync_stats.inserted} inserted, {sync_stats.updated} updated",
|
||||
)
|
||||
|
||||
except WordPressTokenInvalidError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=str(e),
|
||||
)
|
||||
except WordPressRateLimitError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
detail=str(e),
|
||||
)
|
||||
except WordPressAPIError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=str(e),
|
||||
)
|
||||
except WebsiteNotFoundError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/verify_session",
|
||||
response_model=VerifySessionResponse,
|
||||
summary="Verify WordPress session",
|
||||
description="Verify WordPress JWT token and user identity.",
|
||||
)
|
||||
async def verify_session_endpoint(
|
||||
request: VerifySessionRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> VerifySessionResponse:
|
||||
"""
|
||||
Verify WordPress session/token.
|
||||
|
||||
Process:
|
||||
1. Validate website_id exists
|
||||
2. Call WordPress API to verify token
|
||||
3. Verify wp_user_id matches token owner
|
||||
4. Get or create local user
|
||||
5. Return validation result
|
||||
|
||||
Args:
|
||||
request: VerifySessionRequest with wp_user_id, token, website_id
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
VerifySessionResponse with validation result
|
||||
|
||||
Raises:
|
||||
HTTPException: If website not found or API error
|
||||
"""
|
||||
# Validate website exists
|
||||
await get_valid_website(request.website_id, db)
|
||||
|
||||
try:
|
||||
# Verify token with WordPress
|
||||
wp_user_info = await verify_wordpress_token(
|
||||
token=request.token,
|
||||
website_id=request.website_id,
|
||||
wp_user_id=request.wp_user_id,
|
||||
db=db,
|
||||
)
|
||||
|
||||
if wp_user_info is None:
|
||||
return VerifySessionResponse(
|
||||
valid=False,
|
||||
error="User ID mismatch or invalid credentials",
|
||||
)
|
||||
|
||||
# Get or create local user
|
||||
user = await get_or_create_user(
|
||||
wp_user_id=request.wp_user_id,
|
||||
website_id=request.website_id,
|
||||
db=db,
|
||||
)
|
||||
|
||||
return VerifySessionResponse(
|
||||
valid=True,
|
||||
user=WordPressUserResponse.model_validate(user),
|
||||
wp_user_info={
|
||||
"username": wp_user_info.username,
|
||||
"email": wp_user_info.email,
|
||||
"display_name": wp_user_info.display_name,
|
||||
"roles": wp_user_info.roles,
|
||||
},
|
||||
)
|
||||
|
||||
except WordPressTokenInvalidError as e:
|
||||
return VerifySessionResponse(
|
||||
valid=False,
|
||||
error=f"Invalid credentials: {str(e)}",
|
||||
)
|
||||
except WordPressRateLimitError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
detail=str(e),
|
||||
)
|
||||
except WordPressAPIError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=str(e),
|
||||
)
|
||||
except WebsiteNotFoundError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/website/{website_id}/users",
|
||||
response_model=UserListResponse,
|
||||
summary="Get users for website",
|
||||
description="Retrieve all users for a specific website from local database with pagination.",
|
||||
)
|
||||
async def get_website_users(
|
||||
website_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> UserListResponse:
|
||||
"""
|
||||
Get all users for a website.
|
||||
|
||||
Args:
|
||||
website_id: Website identifier
|
||||
db: Database session
|
||||
page: Page number (default: 1)
|
||||
page_size: Number of users per page (default: 50, max: 100)
|
||||
|
||||
Returns:
|
||||
UserListResponse with paginated user list
|
||||
|
||||
Raises:
|
||||
HTTPException: If website not found
|
||||
"""
|
||||
# Validate website exists
|
||||
await get_valid_website(website_id, db)
|
||||
|
||||
# Clamp page_size
|
||||
page_size = min(max(1, page_size), 100)
|
||||
page = max(1, page)
|
||||
|
||||
# Get total count
|
||||
count_result = await db.execute(
|
||||
select(func.count()).select_from(User).where(User.website_id == website_id)
|
||||
)
|
||||
total = count_result.scalar() or 0
|
||||
|
||||
# Calculate pagination
|
||||
offset = (page - 1) * page_size
|
||||
total_pages = (total + page_size - 1) // page_size if total > 0 else 1
|
||||
|
||||
# Get users
|
||||
result = await db.execute(
|
||||
select(User)
|
||||
.where(User.website_id == website_id)
|
||||
.order_by(User.id)
|
||||
.offset(offset)
|
||||
.limit(page_size)
|
||||
)
|
||||
users = result.scalars().all()
|
||||
|
||||
return UserListResponse(
|
||||
users=[WordPressUserResponse.model_validate(user) for user in users],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
total_pages=total_pages,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/website/{website_id}/user/{wp_user_id}",
|
||||
response_model=WordPressUserResponse,
|
||||
summary="Get specific user",
|
||||
description="Retrieve a specific user by WordPress user ID.",
|
||||
)
|
||||
async def get_user_endpoint(
|
||||
website_id: int,
|
||||
wp_user_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> WordPressUserResponse:
|
||||
"""
|
||||
Get a specific user by WordPress user ID.
|
||||
|
||||
Args:
|
||||
website_id: Website identifier
|
||||
wp_user_id: WordPress user ID
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
WordPressUserResponse with user data
|
||||
|
||||
Raises:
|
||||
HTTPException: If website or user not found
|
||||
"""
|
||||
# Validate website exists
|
||||
await get_valid_website(website_id, db)
|
||||
|
||||
# Get user
|
||||
user = await get_wordpress_user(
|
||||
wp_user_id=wp_user_id,
|
||||
website_id=website_id,
|
||||
db=db,
|
||||
)
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"User {wp_user_id} not found for website {website_id}",
|
||||
)
|
||||
|
||||
return WordPressUserResponse.model_validate(user)
|
||||
Reference in New Issue
Block a user