175 lines
5.9 KiB
Python
175 lines
5.9 KiB
Python
from dataclasses import dataclass, field
|
|
from typing import List, Optional, Dict, Any, Callable
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from typing import Union
|
|
|
|
|
|
# First, let's create our category models
|
|
class CategoryBase(BaseModel):
|
|
id: str
|
|
name: str
|
|
description: Optional[str] = None
|
|
|
|
|
|
class CategoryCreate(CategoryBase):
|
|
parent_id: Optional[str] = None
|
|
|
|
|
|
class CategoryResponse(CategoryBase):
|
|
children: List['CategoryResponse'] = []
|
|
parent_id: Optional[str] = None
|
|
|
|
|
|
# Category data structure for handling the hierarchy
|
|
@dataclass
|
|
class CategoryNode:
|
|
id: str
|
|
name: str
|
|
description: Optional[str]
|
|
parent_id: Optional[str] = None
|
|
children: List['CategoryNode'] = field(default_factory=list)
|
|
|
|
|
|
# Category Service for managing the hierarchy
|
|
class CategoryService:
|
|
def __init__(self):
|
|
self.categories: Dict[str, CategoryNode] = {}
|
|
|
|
def add_category(self, category: CategoryCreate) -> CategoryNode:
|
|
node = CategoryNode(
|
|
id=category.id,
|
|
name=category.name,
|
|
description=category.description,
|
|
parent_id=category.parent_id
|
|
)
|
|
|
|
self.categories[category.id] = node
|
|
|
|
if category.parent_id and category.parent_id in self.categories:
|
|
parent = self.categories[category.parent_id]
|
|
parent.children.append(node)
|
|
|
|
return node
|
|
|
|
def get_category_tree(self, category_id: str) -> Optional[CategoryNode]:
|
|
return self.categories.get(category_id)
|
|
|
|
def get_category_path(self, category_id: str) -> List[CategoryNode]:
|
|
path = []
|
|
current = self.categories.get(category_id)
|
|
|
|
while current:
|
|
path.append(current)
|
|
current = self.categories.get(current.parent_id) if current.parent_id else None
|
|
|
|
return list(reversed(path))
|
|
|
|
|
|
# Factory for creating category endpoints
|
|
class CategoryEndpointFactory:
|
|
def __init__(self, category_service: CategoryService):
|
|
self.category_service = category_service
|
|
|
|
def create_route_config(self, base_prefix: str) -> RouteFactoryConfig:
|
|
endpoints = [
|
|
# Create category endpoint
|
|
EndpointFactoryConfig(
|
|
url_prefix=base_prefix,
|
|
url_endpoint="/categories",
|
|
url_of_endpoint=f"{base_prefix}/categories",
|
|
endpoint="/categories",
|
|
method="POST",
|
|
summary="Create new category",
|
|
description="Create a new category with optional parent",
|
|
endpoint_function=self.create_category,
|
|
request_model=CategoryCreate,
|
|
response_model=CategoryResponse,
|
|
is_auth_required=True
|
|
),
|
|
|
|
# Get category tree endpoint
|
|
EndpointFactoryConfig(
|
|
url_prefix=base_prefix,
|
|
url_endpoint="/categories/{category_id}",
|
|
url_of_endpoint=f"{base_prefix}/categories/{{category_id}}",
|
|
endpoint="/categories/{category_id}",
|
|
method="GET",
|
|
summary="Get category tree",
|
|
description="Get category and its children",
|
|
endpoint_function=self.get_category_tree,
|
|
response_model=CategoryResponse,
|
|
is_auth_required=True
|
|
),
|
|
|
|
# Get category path endpoint
|
|
EndpointFactoryConfig(
|
|
url_prefix=base_prefix,
|
|
url_endpoint="/categories/{category_id}/path",
|
|
url_of_endpoint=f"{base_prefix}/categories/{{category_id}}/path",
|
|
endpoint="/categories/{category_id}/path",
|
|
method="GET",
|
|
summary="Get category path",
|
|
description="Get full path from root to this category",
|
|
endpoint_function=self.get_category_path,
|
|
response_model=List[CategoryResponse],
|
|
is_auth_required=True
|
|
)
|
|
]
|
|
|
|
return RouteFactoryConfig(
|
|
name="categories",
|
|
tags=["Categories"],
|
|
prefix=base_prefix,
|
|
endpoints=endpoints
|
|
)
|
|
|
|
async def create_category(self, category: CategoryCreate) -> CategoryResponse:
|
|
node = self.category_service.add_category(category)
|
|
return self._convert_to_response(node)
|
|
|
|
async def get_category_tree(self, category_id: str) -> CategoryResponse:
|
|
node = self.category_service.get_category_tree(category_id)
|
|
if not node:
|
|
raise HTTPException(status_code=404, detail="Category not found")
|
|
return self._convert_to_response(node)
|
|
|
|
async def get_category_path(self, category_id: str) -> List[CategoryResponse]:
|
|
path = self.category_service.get_category_path(category_id)
|
|
if not path:
|
|
raise HTTPException(status_code=404, detail="Category not found")
|
|
return [self._convert_to_response(node) for node in path]
|
|
|
|
def _convert_to_response(self, node: CategoryNode) -> CategoryResponse:
|
|
return CategoryResponse(
|
|
id=node.id,
|
|
name=node.name,
|
|
description=node.description,
|
|
parent_id=node.parent_id,
|
|
children=[self._convert_to_response(child) for child in node.children]
|
|
)
|
|
|
|
|
|
# Usage example
|
|
def create_category_router(base_prefix: str = "/api/v1") -> APIRouter:
|
|
category_service = CategoryService()
|
|
factory = CategoryEndpointFactory(category_service)
|
|
route_config = factory.create_route_config(base_prefix)
|
|
|
|
router = APIRouter(
|
|
prefix=route_config.prefix,
|
|
tags=route_config.tags
|
|
)
|
|
|
|
for endpoint in route_config.endpoints:
|
|
router.add_api_route(
|
|
path=endpoint.endpoint,
|
|
endpoint=endpoint.endpoint_function,
|
|
methods=[endpoint.method],
|
|
response_model=endpoint.response_model,
|
|
summary=endpoint.summary,
|
|
description=endpoint.description,
|
|
**endpoint.extra_options
|
|
)
|
|
|
|
return router |