872 lines
29 KiB
Markdown
872 lines
29 KiB
Markdown
# Architecture Redesign Proposal
|
|
|
|
## Overview
|
|
|
|
This document outlines a comprehensive architectural redesign for the ProfytAI Compliance Management Platform, addressing critical issues identified in the current implementation.
|
|
|
|
## Design Principles
|
|
|
|
1. **Separation of Concerns**: Clear boundaries between layers
|
|
2. **Dependency Injection**: Loose coupling, easy testing
|
|
3. **Domain-Driven Design**: Business logic in domain layer
|
|
4. **Security First**: Authentication, authorization, input validation
|
|
5. **Testability**: All components should be easily testable
|
|
6. **Scalability**: Support for horizontal scaling
|
|
7. **Maintainability**: Clear structure, minimal complexity
|
|
|
|
---
|
|
|
|
## Proposed Architecture: Layered Architecture with Clean Architecture Principles
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Presentation Layer │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ API Routes │ │ Middleware │ │ WebSocket │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Application Layer │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Services │ │ Use Cases │ │ DTOs │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Domain Layer │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Entities │ │ Interfaces │ │ Value Obj. │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Infrastructure Layer │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Repositories │ │ External │ │ Config │ │
|
|
│ │ │ │ Services │ │ │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## New Directory Structure
|
|
|
|
```
|
|
be0/
|
|
├── src/
|
|
│ ├── api/ # API Layer
|
|
│ │ ├── __init__.py
|
|
│ │ ├── dependencies.py # Dependency injection
|
|
│ │ ├── middleware/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── auth.py # Authentication middleware
|
|
│ │ │ ├── cors.py # CORS configuration
|
|
│ │ │ ├── rate_limit.py # Rate limiting
|
|
│ │ │ └── error_handler.py # Global error handling
|
|
│ │ ├── routes/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── workflows.py # Workflow endpoints
|
|
│ │ │ ├── documents.py # Document endpoints
|
|
│ │ │ ├── compliance.py # Compliance endpoints
|
|
│ │ │ ├── health.py # Health check
|
|
│ │ │ └── auth.py # Authentication endpoints
|
|
│ │ └── schemas/ # Request/Response schemas
|
|
│ │ ├── __init__.py
|
|
│ │ ├── workflow.py
|
|
│ │ ├── document.py
|
|
│ │ └── compliance.py
|
|
│ │
|
|
│ ├── application/ # Application Layer
|
|
│ │ ├── __init__.py
|
|
│ │ ├── services/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── workflow_service.py
|
|
│ │ │ ├── document_service.py
|
|
│ │ │ ├── compliance_service.py
|
|
│ │ │ └── ai_service.py
|
|
│ │ ├── use_cases/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── create_workflow.py
|
|
│ │ │ ├── update_workflow_item.py
|
|
│ │ │ ├── analyze_compliance.py
|
|
│ │ │ └── process_document.py
|
|
│ │ └── dto/ # Data Transfer Objects
|
|
│ │ ├── __init__.py
|
|
│ │ ├── workflow_dto.py
|
|
│ │ └── compliance_dto.py
|
|
│ │
|
|
│ ├── domain/ # Domain Layer
|
|
│ │ ├── __init__.py
|
|
│ │ ├── entities/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── workflow.py
|
|
│ │ │ ├── workflow_item.py
|
|
│ │ │ ├── document.py
|
|
│ │ │ └── compliance_rule.py
|
|
│ │ ├── value_objects/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── task_status.py
|
|
│ │ │ └── workflow_phase.py
|
|
│ │ ├── interfaces/ # Repository interfaces
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── workflow_repository.py
|
|
│ │ │ ├── document_repository.py
|
|
│ │ │ └── compliance_repository.py
|
|
│ │ └── exceptions/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── domain_exceptions.py
|
|
│ │ └── service_exceptions.py
|
|
│ │
|
|
│ ├── infrastructure/ # Infrastructure Layer
|
|
│ │ ├── __init__.py
|
|
│ │ ├── database/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── connection.py # DB connection pool
|
|
│ │ │ ├── repositories/
|
|
│ │ │ │ ├── __init__.py
|
|
│ │ │ │ ├── workflow_repository_impl.py
|
|
│ │ │ │ ├── document_repository_impl.py
|
|
│ │ │ │ └── neo4j_repository.py
|
|
│ │ │ └── migrations/
|
|
│ │ ├── external/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── ollama_client.py # Ollama service client
|
|
│ │ │ └── storage/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ └── file_storage.py # File storage abstraction
|
|
│ │ ├── config/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── settings.py # Pydantic settings
|
|
│ │ │ └── logging_config.py
|
|
│ │ └── security/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── auth.py # JWT, password hashing
|
|
│ │ └── permissions.py
|
|
│ │
|
|
│ ├── core/ # Core utilities
|
|
│ │ ├── __init__.py
|
|
│ │ ├── logging.py
|
|
│ │ ├── exceptions.py
|
|
│ │ └── constants.py
|
|
│ │
|
|
│ └── main.py # Application entry point
|
|
│
|
|
├── tests/ # Test suite
|
|
│ ├── __init__.py
|
|
│ ├── unit/
|
|
│ │ ├── domain/
|
|
│ │ ├── application/
|
|
│ │ └── infrastructure/
|
|
│ ├── integration/
|
|
│ │ ├── api/
|
|
│ │ └── database/
|
|
│ ├── fixtures/
|
|
│ └── conftest.py
|
|
│
|
|
├── alembic/ # Database migrations
|
|
│ ├── versions/
|
|
│ └── env.py
|
|
│
|
|
├── requirements.txt
|
|
├── requirements-dev.txt
|
|
├── .env.example
|
|
└── Dockerfile
|
|
```
|
|
|
|
---
|
|
|
|
## Key Architectural Components
|
|
|
|
### 1. API Layer (Presentation)
|
|
|
|
**Purpose**: Handle HTTP requests, validate input, return responses
|
|
|
|
**Responsibilities**:
|
|
- Route definitions
|
|
- Request/Response serialization
|
|
- Input validation
|
|
- Authentication/Authorization checks
|
|
- Error handling
|
|
|
|
### 2. Application Layer
|
|
|
|
**Purpose**: Orchestrate business logic, coordinate between domain and infrastructure
|
|
|
|
**Responsibilities**:
|
|
- Use case implementation
|
|
- Service orchestration
|
|
- DTO transformation
|
|
- Transaction management
|
|
|
|
### 3. Domain Layer
|
|
|
|
**Purpose**: Core business logic, entities, and business rules
|
|
|
|
**Responsibilities**:
|
|
- Domain entities
|
|
- Business rules
|
|
- Value objects
|
|
- Domain events
|
|
- Repository interfaces (abstractions)
|
|
|
|
### 4. Infrastructure Layer
|
|
|
|
**Purpose**: External concerns - database, file system, external APIs
|
|
|
|
**Responsibilities**:
|
|
- Database access
|
|
- External API clients
|
|
- File storage
|
|
- Configuration
|
|
- Security implementation
|
|
|
|
---
|
|
|
|
## Implementation Examples
|
|
|
|
### Example 1: Configuration Management
|
|
|
|
```python
|
|
# infrastructure/config/settings.py
|
|
from pydantic_settings import BaseSettings
|
|
from typing import List
|
|
|
|
class Settings(BaseSettings):
|
|
# Application
|
|
app_name: str = "ProfytAI Compliance Platform"
|
|
app_version: str = "1.0.0"
|
|
debug: bool = False
|
|
|
|
# Server
|
|
host: str = "0.0.0.0"
|
|
port: int = 4402
|
|
|
|
# Database
|
|
neo4j_uri: str
|
|
neo4j_user: str
|
|
neo4j_password: str
|
|
|
|
# Security
|
|
secret_key: str
|
|
algorithm: str = "HS256"
|
|
access_token_expire_minutes: int = 30
|
|
cors_origins: List[str] = []
|
|
|
|
# AI/ML
|
|
ollama_base_url: str = "http://localhost:11434"
|
|
ollama_model: str = "gemma3:27b"
|
|
embedding_model: str = "embeddinggemma:300m"
|
|
|
|
# Storage
|
|
upload_dir: str = "./assets/data/uploads"
|
|
max_upload_size: int = 10 * 1024 * 1024 # 10MB
|
|
|
|
# Rate Limiting
|
|
rate_limit_per_minute: int = 60
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
case_sensitive = False
|
|
|
|
settings = Settings()
|
|
```
|
|
|
|
### Example 2: Domain Entity
|
|
|
|
```python
|
|
# domain/entities/workflow.py
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
from uuid import UUID, uuid4
|
|
from domain.value_objects.task_status import TaskStatus
|
|
from domain.value_objects.workflow_phase import WorkflowPhase
|
|
|
|
@dataclass
|
|
class WorkflowItem:
|
|
id: int
|
|
task: str
|
|
status: TaskStatus
|
|
requires_approval: bool
|
|
approver: Optional[str] = None
|
|
comment: Optional[str] = None
|
|
updated_by: Optional[str] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
@dataclass
|
|
class Workflow:
|
|
id: UUID
|
|
project_name: str
|
|
project_description: Optional[str]
|
|
records_officer_email: Optional[str]
|
|
current_phase: WorkflowPhase
|
|
checklist_items: List[WorkflowItem] = field(default_factory=list)
|
|
completed_items: List[int] = field(default_factory=list)
|
|
pending_approvals: List[str] = field(default_factory=list)
|
|
comments: dict = field(default_factory=dict)
|
|
validation_results: dict = field(default_factory=dict)
|
|
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
updated_at: datetime = field(default_factory=datetime.utcnow)
|
|
|
|
def add_item(self, item: WorkflowItem) -> None:
|
|
"""Add a checklist item to the workflow."""
|
|
self.checklist_items.append(item)
|
|
self.updated_at = datetime.utcnow()
|
|
|
|
def update_item_status(
|
|
self,
|
|
item_id: int,
|
|
status: TaskStatus,
|
|
updated_by: str,
|
|
comment: Optional[str] = None
|
|
) -> None:
|
|
"""Update the status of a workflow item."""
|
|
item = next((i for i in self.checklist_items if i.id == item_id), None)
|
|
if not item:
|
|
raise ValueError(f"Item {item_id} not found")
|
|
|
|
item.status = status
|
|
item.updated_by = updated_by
|
|
item.updated_at = datetime.utcnow()
|
|
if comment:
|
|
item.comment = comment
|
|
|
|
if status == TaskStatus.COMPLETED and item_id not in self.completed_items:
|
|
self.completed_items.append(item_id)
|
|
|
|
self.updated_at = datetime.utcnow()
|
|
|
|
def can_advance_phase(self) -> bool:
|
|
"""Check if workflow can advance to next phase."""
|
|
all_completed = all(
|
|
item.status == TaskStatus.COMPLETED
|
|
for item in self.checklist_items
|
|
)
|
|
no_pending_approvals = len(self.pending_approvals) == 0
|
|
return all_completed and no_pending_approvals
|
|
|
|
@property
|
|
def completion_percentage(self) -> float:
|
|
"""Calculate completion percentage."""
|
|
if not self.checklist_items:
|
|
return 0.0
|
|
completed = len(self.completed_items)
|
|
total = len(self.checklist_items)
|
|
return (completed / total) * 100
|
|
```
|
|
|
|
### Example 3: Repository Interface (Domain)
|
|
|
|
```python
|
|
# domain/interfaces/workflow_repository.py
|
|
from abc import ABC, abstractmethod
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
from domain.entities.workflow import Workflow
|
|
|
|
class IWorkflowRepository(ABC):
|
|
"""Repository interface for workflow persistence."""
|
|
|
|
@abstractmethod
|
|
async def create(self, workflow: Workflow) -> Workflow:
|
|
"""Create a new workflow."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def get_by_id(self, workflow_id: UUID) -> Optional[Workflow]:
|
|
"""Get workflow by ID."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def get_all(self, skip: int = 0, limit: int = 100) -> List[Workflow]:
|
|
"""Get all workflows with pagination."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def update(self, workflow: Workflow) -> Workflow:
|
|
"""Update an existing workflow."""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def delete(self, workflow_id: UUID) -> bool:
|
|
"""Delete a workflow."""
|
|
pass
|
|
```
|
|
|
|
### Example 4: Repository Implementation (Infrastructure)
|
|
|
|
```python
|
|
# infrastructure/database/repositories/workflow_repository_impl.py
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
from domain.entities.workflow import Workflow
|
|
from domain.interfaces.workflow_repository import IWorkflowRepository
|
|
from infrastructure.database.connection import get_db_session
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
|
|
class WorkflowRepository(IWorkflowRepository):
|
|
"""Neo4j implementation of workflow repository."""
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
self.session = session
|
|
|
|
async def create(self, workflow: Workflow) -> Workflow:
|
|
"""Create workflow in Neo4j."""
|
|
query = """
|
|
CREATE (w:Workflow {
|
|
id: $id,
|
|
project_name: $project_name,
|
|
project_description: $project_description,
|
|
records_officer_email: $records_officer_email,
|
|
current_phase: $current_phase,
|
|
created_at: $created_at,
|
|
updated_at: $updated_at
|
|
})
|
|
RETURN w
|
|
"""
|
|
# Implementation details...
|
|
return workflow
|
|
|
|
async def get_by_id(self, workflow_id: UUID) -> Optional[Workflow]:
|
|
"""Get workflow by ID from Neo4j."""
|
|
query = """
|
|
MATCH (w:Workflow {id: $workflow_id})
|
|
OPTIONAL MATCH (w)-[:HAS_ITEM]->(i:WorkflowItem)
|
|
RETURN w, collect(i) as items
|
|
"""
|
|
# Implementation details...
|
|
pass
|
|
|
|
# ... other methods
|
|
```
|
|
|
|
### Example 5: Service Layer
|
|
|
|
```python
|
|
# application/services/workflow_service.py
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
from domain.entities.workflow import Workflow, WorkflowItem
|
|
from domain.interfaces.workflow_repository import IWorkflowRepository
|
|
from domain.value_objects.workflow_phase import WorkflowPhase
|
|
from domain.value_objects.task_status import TaskStatus
|
|
from domain.exceptions.domain_exceptions import WorkflowNotFoundError
|
|
|
|
class WorkflowService:
|
|
"""Service for workflow business logic."""
|
|
|
|
def __init__(self, workflow_repository: IWorkflowRepository):
|
|
self.workflow_repository = workflow_repository
|
|
|
|
async def create_workflow(
|
|
self,
|
|
project_name: str,
|
|
project_description: Optional[str],
|
|
records_officer_email: Optional[str]
|
|
) -> Workflow:
|
|
"""Create a new workflow with initial phase."""
|
|
workflow = Workflow(
|
|
id=UUID(),
|
|
project_name=project_name,
|
|
project_description=project_description,
|
|
records_officer_email=records_officer_email,
|
|
current_phase=WorkflowPhase.CONCEPT_DEVELOPMENT
|
|
)
|
|
|
|
# Initialize Phase 1 items
|
|
phase1_items = self._get_phase1_items()
|
|
for item in phase1_items:
|
|
workflow.add_item(item)
|
|
|
|
return await self.workflow_repository.create(workflow)
|
|
|
|
async def get_workflow(self, workflow_id: UUID) -> Workflow:
|
|
"""Get workflow by ID."""
|
|
workflow = await self.workflow_repository.get_by_id(workflow_id)
|
|
if not workflow:
|
|
raise WorkflowNotFoundError(f"Workflow {workflow_id} not found")
|
|
return workflow
|
|
|
|
async def update_workflow_item(
|
|
self,
|
|
workflow_id: UUID,
|
|
item_id: int,
|
|
status: TaskStatus,
|
|
updated_by: str,
|
|
comment: Optional[str] = None
|
|
) -> Workflow:
|
|
"""Update a workflow item."""
|
|
workflow = await self.get_workflow(workflow_id)
|
|
workflow.update_item_status(item_id, status, updated_by, comment)
|
|
return await self.workflow_repository.update(workflow)
|
|
|
|
async def advance_workflow(self, workflow_id: UUID) -> Workflow:
|
|
"""Advance workflow to next phase."""
|
|
workflow = await self.get_workflow(workflow_id)
|
|
|
|
if not workflow.can_advance_phase():
|
|
raise ValueError("Cannot advance: Phase requirements not met")
|
|
|
|
# Advance to next phase logic...
|
|
return await self.workflow_repository.update(workflow)
|
|
|
|
def _get_phase1_items(self) -> List[WorkflowItem]:
|
|
"""Get Phase 1 checklist items."""
|
|
return [
|
|
WorkflowItem(
|
|
id=1,
|
|
task="Include Records Officer in system design process",
|
|
status=TaskStatus.PENDING,
|
|
requires_approval=True,
|
|
approver="Records Officer"
|
|
),
|
|
# ... more items
|
|
]
|
|
```
|
|
|
|
### Example 6: API Route with Dependency Injection
|
|
|
|
```python
|
|
# api/routes/workflows.py
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from uuid import UUID
|
|
from typing import List
|
|
from api.schemas.workflow import (
|
|
WorkflowCreateRequest,
|
|
WorkflowResponse,
|
|
WorkflowItemUpdateRequest
|
|
)
|
|
from application.services.workflow_service import WorkflowService
|
|
from api.dependencies import get_workflow_service, get_current_user
|
|
from domain.value_objects.task_status import TaskStatus
|
|
|
|
router = APIRouter(prefix="/workflows", tags=["workflows"])
|
|
|
|
@router.post("", response_model=WorkflowResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_workflow(
|
|
request: WorkflowCreateRequest,
|
|
workflow_service: WorkflowService = Depends(get_workflow_service),
|
|
current_user = Depends(get_current_user)
|
|
):
|
|
"""Create a new workflow."""
|
|
try:
|
|
workflow = await workflow_service.create_workflow(
|
|
project_name=request.project_name,
|
|
project_description=request.project_description,
|
|
records_officer_email=request.records_officer_email
|
|
)
|
|
return WorkflowResponse.from_entity(workflow)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=str(e)
|
|
)
|
|
|
|
@router.get("/{workflow_id}", response_model=WorkflowResponse)
|
|
async def get_workflow(
|
|
workflow_id: UUID,
|
|
workflow_service: WorkflowService = Depends(get_workflow_service),
|
|
current_user = Depends(get_current_user)
|
|
):
|
|
"""Get workflow by ID."""
|
|
try:
|
|
workflow = await workflow_service.get_workflow(workflow_id)
|
|
return WorkflowResponse.from_entity(workflow)
|
|
except WorkflowNotFoundError:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Workflow not found"
|
|
)
|
|
|
|
@router.put("/{workflow_id}/items", response_model=WorkflowResponse)
|
|
async def update_workflow_item(
|
|
workflow_id: UUID,
|
|
request: WorkflowItemUpdateRequest,
|
|
workflow_service: WorkflowService = Depends(get_workflow_service),
|
|
current_user = Depends(get_current_user)
|
|
):
|
|
"""Update a workflow item."""
|
|
try:
|
|
workflow = await workflow_service.update_workflow_item(
|
|
workflow_id=workflow_id,
|
|
item_id=request.item_id,
|
|
status=TaskStatus(request.status),
|
|
updated_by=current_user.email,
|
|
comment=request.comment
|
|
)
|
|
return WorkflowResponse.from_entity(workflow)
|
|
except WorkflowNotFoundError:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Workflow not found"
|
|
)
|
|
```
|
|
|
|
### Example 7: Dependency Injection Setup
|
|
|
|
```python
|
|
# api/dependencies.py
|
|
from functools import lru_cache
|
|
from infrastructure.database.connection import get_db_session
|
|
from infrastructure.database.repositories.workflow_repository_impl import WorkflowRepository
|
|
from application.services.workflow_service import WorkflowService
|
|
from infrastructure.external.ollama_client import OllamaClient
|
|
from application.services.compliance_service import ComplianceService
|
|
from infrastructure.config.settings import settings
|
|
|
|
# Repository dependencies
|
|
async def get_workflow_repository():
|
|
async for session in get_db_session():
|
|
yield WorkflowRepository(session)
|
|
|
|
# Service dependencies
|
|
def get_workflow_service(
|
|
workflow_repo: WorkflowRepository = Depends(get_workflow_repository)
|
|
) -> WorkflowService:
|
|
return WorkflowService(workflow_repo)
|
|
|
|
def get_compliance_service() -> ComplianceService:
|
|
ollama_client = OllamaClient(
|
|
base_url=settings.ollama_base_url,
|
|
model=settings.ollama_model
|
|
)
|
|
return ComplianceService(ollama_client)
|
|
|
|
# Auth dependencies
|
|
async def get_current_user(
|
|
token: str = Depends(oauth2_scheme)
|
|
):
|
|
# JWT validation logic
|
|
pass
|
|
```
|
|
|
|
### Example 8: Main Application Setup
|
|
|
|
```python
|
|
# main.py
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from infrastructure.config.settings import settings
|
|
from infrastructure.config.logging_config import setup_logging
|
|
from api.middleware.error_handler import setup_exception_handlers
|
|
from api.middleware.cors import setup_cors
|
|
from api.routes import workflows, documents, compliance, health, auth
|
|
|
|
# Setup logging
|
|
setup_logging()
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(
|
|
title=settings.app_name,
|
|
version=settings.app_version,
|
|
debug=settings.debug
|
|
)
|
|
|
|
# Setup middleware
|
|
setup_cors(app, settings.cors_origins)
|
|
setup_exception_handlers(app)
|
|
|
|
# Include routers
|
|
app.include_router(auth.router)
|
|
app.include_router(workflows.router)
|
|
app.include_router(documents.router)
|
|
app.include_router(compliance.router)
|
|
app.include_router(health.router)
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
"""Initialize services on startup."""
|
|
# Initialize database connections
|
|
# Initialize external services
|
|
pass
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown_event():
|
|
"""Cleanup on shutdown."""
|
|
# Close database connections
|
|
# Cleanup resources
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Security Improvements
|
|
|
|
### 1. Authentication & Authorization
|
|
|
|
```python
|
|
# infrastructure/security/auth.py
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
from jose import JWTError, jwt
|
|
from passlib.context import CryptContext
|
|
from infrastructure.config.settings import settings
|
|
|
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Verify a password against a hash."""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
def get_password_hash(password: str) -> str:
|
|
"""Hash a password."""
|
|
return pwd_context.hash(password)
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
"""Create JWT access token."""
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.utcnow() + expires_delta
|
|
else:
|
|
expire = datetime.utcnow() + timedelta(
|
|
minutes=settings.access_token_expire_minutes
|
|
)
|
|
to_encode.update({"exp": expire})
|
|
encoded_jwt = jwt.encode(
|
|
to_encode,
|
|
settings.secret_key,
|
|
algorithm=settings.algorithm
|
|
)
|
|
return encoded_jwt
|
|
```
|
|
|
|
### 2. CORS Configuration
|
|
|
|
```python
|
|
# api/middleware/cors.py
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from typing import List
|
|
|
|
def setup_cors(app: FastAPI, allowed_origins: List[str]):
|
|
"""Configure CORS middleware."""
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=allowed_origins, # Specific origins, not "*"
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
allow_headers=["Content-Type", "Authorization"],
|
|
)
|
|
```
|
|
|
|
### 3. Rate Limiting
|
|
|
|
```python
|
|
# api/middleware/rate_limit.py
|
|
from fastapi import Request, HTTPException, status
|
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
from slowapi.util import get_remote_address
|
|
from slowapi.errors import RateLimitExceeded
|
|
|
|
limiter = Limiter(key_func=get_remote_address)
|
|
|
|
@router.post("")
|
|
@limiter.limit("10/minute") # 10 requests per minute
|
|
async def create_workflow(request: Request, ...):
|
|
# Implementation
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Structure
|
|
|
|
```python
|
|
# tests/conftest.py
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from main import app
|
|
from infrastructure.database.connection import get_test_db
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
return TestClient(app)
|
|
|
|
@pytest.fixture
|
|
def test_db():
|
|
# Setup test database
|
|
yield
|
|
# Teardown
|
|
|
|
# tests/unit/application/services/test_workflow_service.py
|
|
import pytest
|
|
from uuid import UUID
|
|
from application.services.workflow_service import WorkflowService
|
|
from domain.entities.workflow import Workflow
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_workflow():
|
|
# Mock repository
|
|
mock_repo = MockWorkflowRepository()
|
|
service = WorkflowService(mock_repo)
|
|
|
|
workflow = await service.create_workflow(
|
|
project_name="Test Project",
|
|
project_description="Test Description",
|
|
records_officer_email="test@example.com"
|
|
)
|
|
|
|
assert workflow.project_name == "Test Project"
|
|
assert workflow.current_phase == WorkflowPhase.CONCEPT_DEVELOPMENT
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Strategy
|
|
|
|
### Phase 1: Foundation (Week 1-2)
|
|
1. Create new directory structure
|
|
2. Set up configuration management
|
|
3. Implement dependency injection
|
|
4. Set up database connection
|
|
|
|
### Phase 2: Domain Layer (Week 3)
|
|
1. Create domain entities
|
|
2. Define repository interfaces
|
|
3. Implement value objects
|
|
|
|
### Phase 3: Infrastructure (Week 4)
|
|
1. Implement repository classes
|
|
2. Set up external service clients
|
|
3. Configure security
|
|
|
|
### Phase 4: Application Layer (Week 5)
|
|
1. Create service classes
|
|
2. Implement use cases
|
|
3. Create DTOs
|
|
|
|
### Phase 5: API Layer (Week 6)
|
|
1. Create route modules
|
|
2. Implement middleware
|
|
3. Set up error handling
|
|
|
|
### Phase 6: Testing & Migration (Week 7-8)
|
|
1. Write unit tests
|
|
2. Write integration tests
|
|
3. Migrate existing endpoints
|
|
4. Deploy and monitor
|
|
|
|
---
|
|
|
|
## Benefits of This Architecture
|
|
|
|
1. **Testability**: Each layer can be tested independently
|
|
2. **Maintainability**: Clear separation of concerns
|
|
3. **Scalability**: Easy to add new features
|
|
4. **Security**: Built-in security at every layer
|
|
5. **Flexibility**: Easy to swap implementations (e.g., different databases)
|
|
6. **Team Collaboration**: Different teams can work on different layers
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. Review and approve this architecture
|
|
2. Create detailed implementation plan
|
|
3. Set up project structure
|
|
4. Begin Phase 1 implementation
|
|
5. Establish coding standards and review process
|