Files
TimeTracker/app/utils/api_responses.py
Dries Peeters 9d1ece5263 feat: Implement comprehensive architectural improvements and new features
This commit implements a complete architectural transformation of the TimeTracker
application, introducing modern design patterns and comprehensive feature set.

## Architecture Improvements

### Service Layer (18 Services)
- TimeTrackingService: Time entry management with timer functionality
- ProjectService: Project operations and lifecycle management
- InvoiceService: Invoice creation, management, and status tracking
- TaskService: Task management and workflow
- ExpenseService: Expense tracking and categorization
- ClientService: Client relationship management
- PaymentService: Payment processing and invoice reconciliation
- CommentService: Comment system for projects, tasks, and quotes
- UserService: User management and role operations
- NotificationService: Notification delivery system
- ReportingService: Report generation and analytics
- AnalyticsService: Event tracking and analytics
- ExportService: CSV export functionality
- ImportService: CSV import with validation
- EmailService: Email operations and invoice delivery
- PermissionService: Role-based permission management
- BackupService: Database backup operations
- HealthService: System health checks and monitoring

### Repository Layer (9 Repositories)
- BaseRepository: Generic CRUD operations
- TimeEntryRepository: Time entry data access
- ProjectRepository: Project data access with filtering
- InvoiceRepository: Invoice queries and status management
- TaskRepository: Task data access
- ExpenseRepository: Expense data access
- ClientRepository: Client data access
- UserRepository: User data access
- PaymentRepository: Payment data access
- CommentRepository: Comment data access

### Schema Layer (9 Schemas)
- Marshmallow schemas for validation and serialization
- Create, update, and full schemas for all entities
- Input validation and data transformation

### Utility Modules (15 Utilities)
- api_responses: Standardized API response helpers
- validation: Input validation utilities
- query_optimization: N+1 query prevention and eager loading
- error_handlers: Centralized error handling
- cache: Caching foundation (Redis-ready)
- transactions: Transaction management decorators
- event_bus: Domain event system
- performance: Performance monitoring decorators
- logger: Enhanced structured logging
- pagination: Pagination utilities
- file_upload: Secure file upload handling
- search: Full-text search utilities
- rate_limiting: Rate limiting helpers
- config_manager: Configuration management
- datetime_utils: Enhanced date/time utilities

## Database Improvements
- Performance indexes migration (15+ indexes)
- Query optimization utilities
- N+1 query prevention patterns

## Testing Infrastructure
- Comprehensive test fixtures (conftest.py)
- Service layer unit tests
- Repository layer unit tests
- Integration test examples

## CI/CD Pipeline
- GitHub Actions workflow
- Automated linting (Black, Flake8, Pylint)
- Security scanning (Bandit, Safety, Semgrep)
- Automated testing with coverage
- Docker image builds

## Documentation
- Architecture migration guide
- Quick start guide
- API enhancements documentation
- Implementation summaries
- Refactored route examples

## Key Benefits
- Separation of concerns: Business logic decoupled from routes
- Testability: Services and repositories can be tested in isolation
- Maintainability: Consistent patterns across codebase
- Performance: Database indexes and query optimization
- Security: Input validation and security scanning
- Scalability: Event-driven architecture and health checks

## Statistics
- 70+ new files created
- 8,000+ lines of code
- 18 services, 9 repositories, 9 schemas
- 15 utility modules
- 5 test files with examples

This transformation establishes a solid foundation for future development
and follows industry best practices for maintainable, scalable applications.
2025-11-23 20:00:10 +01:00

258 lines
5.5 KiB
Python

"""
Consistent API response helpers.
Provides standardized response formats for all API endpoints.
"""
from typing import Any, Dict, Optional, List
from flask import jsonify, Response
from marshmallow import ValidationError
def success_response(
data: Any = None,
message: Optional[str] = None,
status_code: int = 200,
meta: Optional[Dict[str, Any]] = None
) -> Response:
"""
Create a successful API response.
Args:
data: Response data
message: Optional success message
status_code: HTTP status code
meta: Optional metadata
Returns:
Flask JSON response
"""
response = {
'success': True,
}
if message:
response['message'] = message
if data is not None:
response['data'] = data
if meta:
response['meta'] = meta
return jsonify(response), status_code
def error_response(
message: str,
error_code: Optional[str] = None,
status_code: int = 400,
errors: Optional[Dict[str, List[str]]] = None,
details: Optional[Dict[str, Any]] = None
) -> Response:
"""
Create an error API response.
Args:
message: Error message
error_code: Optional error code
status_code: HTTP status code
errors: Optional field-specific errors
details: Optional additional error details
Returns:
Flask JSON response
"""
response = {
'success': False,
'error': error_code or 'error',
'message': message
}
if errors:
response['errors'] = errors
if details:
response['details'] = details
return jsonify(response), status_code
def validation_error_response(
errors: Dict[str, List[str]],
message: str = "Validation failed"
) -> Response:
"""
Create a validation error response.
Args:
errors: Field-specific validation errors
message: Error message
Returns:
Flask JSON response
"""
return error_response(
message=message,
error_code='validation_error',
status_code=400,
errors=errors
)
def not_found_response(
resource: str = "Resource",
resource_id: Optional[Any] = None
) -> Response:
"""
Create a not found error response.
Args:
resource: Resource type name
resource_id: Optional resource ID
Returns:
Flask JSON response
"""
message = f"{resource} not found"
if resource_id is not None:
message = f"{resource} with ID {resource_id} not found"
return error_response(
message=message,
error_code='not_found',
status_code=404
)
def unauthorized_response(message: str = "Authentication required") -> Response:
"""
Create an unauthorized error response.
Args:
message: Error message
Returns:
Flask JSON response
"""
return error_response(
message=message,
error_code='unauthorized',
status_code=401
)
def forbidden_response(message: str = "Insufficient permissions") -> Response:
"""
Create a forbidden error response.
Args:
message: Error message
Returns:
Flask JSON response
"""
return error_response(
message=message,
error_code='forbidden',
status_code=403
)
def paginated_response(
items: List[Any],
page: int,
per_page: int,
total: int,
message: Optional[str] = None
) -> Response:
"""
Create a paginated response.
Args:
items: List of items for current page
page: Current page number
per_page: Items per page
total: Total number of items
message: Optional message
Returns:
Flask JSON response
"""
pages = (total + per_page - 1) // per_page if total > 0 else 0
pagination = {
'page': page,
'per_page': per_page,
'total': total,
'pages': pages,
'has_next': page < pages,
'has_prev': page > 1,
'next_page': page + 1 if page < pages else None,
'prev_page': page - 1 if page > 1 else None
}
return success_response(
data=items,
message=message,
meta={'pagination': pagination}
)
def handle_validation_error(error: ValidationError) -> Response:
"""
Handle Marshmallow validation errors.
Args:
error: ValidationError instance
Returns:
Flask JSON response
"""
errors = {}
if isinstance(error.messages, dict):
errors = error.messages
elif isinstance(error.messages, list):
errors = {'_general': error.messages}
return validation_error_response(errors=errors)
def created_response(
data: Any,
message: Optional[str] = None,
location: Optional[str] = None
) -> Response:
"""
Create a 201 Created response.
Args:
data: Created resource data
message: Optional success message
location: Optional resource location URL
Returns:
Flask JSON response
"""
response_data = {'data': data}
if message:
response_data['message'] = message
response = jsonify(response_data)
response.status_code = 201
if location:
response.headers['Location'] = location
return response
def no_content_response() -> Response:
"""
Create a 204 No Content response.
Returns:
Flask response
"""
return '', 204