Fixed docker config. Added services

This commit is contained in:
2025-09-17 22:45:33 +02:00
parent da63f1b75b
commit df86a3d998
12 changed files with 513 additions and 41 deletions

View File

@@ -8,10 +8,12 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY app/ .
COPY . .
ENV PYTHONPATH=/app
# Expose port
EXPOSE 8000
# Command will be overridden by docker-compose
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -11,7 +11,7 @@ from pymongo import MongoClient
from pymongo.database import Database
from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError
from config.settings import get_mongodb_url, get_mongodb_database_name
from app.config.settings import get_mongodb_url, get_mongodb_database_name
# Global variables for singleton pattern
_client: Optional[MongoClient] = None

View File

@@ -13,7 +13,7 @@ from pymongo.errors import DuplicateKeyError
from pymongo.collection import Collection
from app.models.user import UserCreate, UserInDB, UserUpdate
from utils.security import hash_password
from app.utils.security import hash_password
class UserRepository:

View File

@@ -4,19 +4,74 @@ FastAPI application for MyDocManager file processor service.
This service provides API endpoints for health checks and task dispatching.
"""
import logging
import os
from fastapi import FastAPI, HTTPException
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
import redis
from celery import Celery
from database.connection import test_database_connection
from app.database.connection import test_database_connection, get_database
from app.database.repositories.user_repository import UserRepository
from app.models.user import UserCreate
from app.services.init_service import InitializationService
from app.services.user_service import UserService
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Application lifespan manager for startup and shutdown tasks.
Handles initialization tasks that need to run when the application starts,
including admin user creation and other setup procedures.
"""
# Startup tasks
logger.info("Starting MyDocManager application...")
try:
# Initialize database connection
database = get_database()
# Initialize repositories and services
user_repository = UserRepository(database)
user_service = UserService(user_repository)
init_service = InitializationService(user_service)
# Run initialization tasks
initialization_result = init_service.initialize_application()
if initialization_result["initialization_success"]:
logger.info("Application startup completed successfully")
if initialization_result["admin_user_created"]:
logger.info("Default admin user was created during startup")
else:
logger.error("Application startup completed with errors:")
for error in initialization_result["errors"]:
logger.error(f" - {error}")
except Exception as e:
logger.error(f"Critical error during application startup: {str(e)}")
# You might want to decide if the app should continue or exit here
# For now, we log the error but continue
yield # Application is running
# Shutdown tasks (if needed)
logger.info("Shutting down MyDocManager application...")
# Initialize FastAPI app
app = FastAPI(
title="MyDocManager File Processor",
description="File processing and task dispatch service",
version="1.0.0"
version="1.0.0",
lifespan=lifespan
)
# Environment variables
@@ -44,6 +99,27 @@ class TestTaskRequest(BaseModel):
message: str
def get_user_service() -> UserService:
"""
Dependency to get user service instance.
This should be properly implemented with database connection management
in your actual application.
"""
database = get_database()
user_repository = UserRepository(database)
return UserService(user_repository)
# Your API routes would use the service like this:
@app.post("/api/users")
async def create_user(
user_data: UserCreate,
user_service: UserService = Depends(get_user_service)
):
return user_service.create_user(user_data)
@app.get("/health")
async def health_check():
"""
@@ -125,4 +201,4 @@ async def root():
"service": "MyDocManager File Processor",
"version": "1.0.0",
"status": "running"
}
}

View File

@@ -100,13 +100,18 @@ def validate_username_not_empty(username: str) -> str:
return username.strip()
class UserCreate(BaseModel):
class UserCreateNoValidation(BaseModel):
"""Model for creating a new user."""
username: str
email: EmailStr
email: str
password: str
role: UserRole = UserRole.USER
class UserCreate(UserCreateNoValidation):
"""Model for creating a new user."""
email: EmailStr
@field_validator('username')
@classmethod

View File

@@ -0,0 +1,58 @@
"""
Authentication service for password hashing and verification.
This module provides authentication-related functionality including
password hashing, verification, and JWT token management.
"""
from app.utils.security import hash_password, verify_password
class AuthService:
"""
Service class for authentication operations.
Handles password hashing, verification, and other authentication
related operations with proper security practices.
"""
@staticmethod
def hash_user_password(password: str) -> str:
"""
Hash a plaintext password for secure storage.
Args:
password (str): Plaintext password to hash
Returns:
str: Hashed password safe for database storage
Example:
>>> auth = AuthService()
>>> hashed = auth.hash_user_password("mypassword123")
>>> len(hashed) > 0
True
"""
return hash_password(password)
@staticmethod
def verify_user_password(password: str, hashed_password: str) -> bool:
"""
Verify a password against its hash.
Args:
password (str): Plaintext password to verify
hashed_password (str): Stored hashed password
Returns:
bool: True if password matches hash, False otherwise
Example:
>>> auth = AuthService()
>>> hashed = auth.hash_user_password("mypassword123")
>>> auth.verify_user_password("mypassword123", hashed)
True
>>> auth.verify_user_password("wrongpassword", hashed)
False
"""
return verify_password(password, hashed_password)

View File

@@ -0,0 +1,134 @@
"""
Initialization service for application startup tasks.
This module handles application initialization tasks including
creating default admin user if none exists.
"""
import logging
from typing import Optional
from app.models.user import UserCreate, UserInDB, UserCreateNoValidation
from app.models.auth import UserRole
from app.services.user_service import UserService
logger = logging.getLogger(__name__)
class InitializationService:
"""
Service for handling application initialization tasks.
This service manages startup operations like ensuring required
users exist and system is properly configured.
"""
def __init__(self, user_service: UserService):
"""
Initialize service with user service dependency.
Args:
user_service (UserService): Service for user operations
"""
self.user_service = user_service
def ensure_admin_user_exists(self) -> Optional[UserInDB]:
"""
Ensure default admin user exists in the system.
Creates a default admin user if no admin user exists in the system.
Uses default credentials that should be changed after first login.
Returns:
UserInDB or None: Created admin user if created, None if already exists
Raises:
Exception: If admin user creation fails
"""
logger.info("Checking if admin user exists...")
# Check if any admin user already exists
if self._admin_user_exists():
logger.info("Admin user already exists, skipping creation")
return None
logger.info("No admin user found, creating default admin user...")
try:
# Create default admin user
admin_data = UserCreateNoValidation(
username="admin",
email="admin@mydocmanager.local",
password="admin", # Should be changed after first login
role=UserRole.ADMIN
)
created_user = self.user_service.create_user(admin_data)
logger.info(f"Default admin user created successfully with ID: {created_user.id}")
logger.warning(
"Default admin user created with username 'admin' and password 'admin'. "
"Please change these credentials immediately for security!"
)
return created_user
except Exception as e:
logger.error(f"Failed to create default admin user: {str(e)}")
raise Exception(f"Admin user creation failed: {str(e)}")
def _admin_user_exists(self) -> bool:
"""
Check if any admin user exists in the system.
Returns:
bool: True if at least one admin user exists, False otherwise
"""
try:
# Get all users and check if any have admin role
users = self.user_service.list_users(limit=1000) # Reasonable limit for admin check
for user in users:
if user.role == UserRole.ADMIN and user.is_active:
return True
return False
except Exception as e:
logger.error(f"Error checking for admin users: {str(e)}")
# In case of error, assume admin exists to avoid creating duplicates
return True
def initialize_application(self) -> dict:
"""
Perform all application initialization tasks.
This method runs all necessary initialization procedures including
admin user creation and any other startup requirements.
Returns:
dict: Summary of initialization tasks performed
"""
logger.info("Starting application initialization...")
initialization_summary = {
"admin_user_created": False,
"initialization_success": False,
"errors": []
}
try:
# Ensure admin user exists
created_admin = self.ensure_admin_user_exists()
if created_admin:
initialization_summary["admin_user_created"] = True
initialization_summary["initialization_success"] = True
logger.info("Application initialization completed successfully")
except Exception as e:
error_msg = f"Application initialization failed: {str(e)}"
logger.error(error_msg)
initialization_summary["errors"].append(error_msg)
return initialization_summary

View File

@@ -0,0 +1,181 @@
"""
User service for business logic operations.
This module provides user-related business logic including user creation,
retrieval, updates, and authentication operations with proper error handling.
"""
from typing import Optional, List
from pymongo.errors import DuplicateKeyError
from app.models.user import UserCreate, UserInDB, UserUpdate, UserResponse, UserCreateNoValidation
from app.models.auth import UserRole
from app.database.repositories.user_repository import UserRepository
from app.services.auth_service import AuthService
class UserService:
"""
Service class for user business logic operations.
This class handles user-related operations including creation,
authentication, and data management with proper validation.
"""
def __init__(self, user_repository: UserRepository):
"""
Initialize user service with repository dependency.
Args:
user_repository (UserRepository): Repository for user data operations
"""
self.user_repository = user_repository
self.auth_service = AuthService()
def create_user(self, user_data: UserCreate | UserCreateNoValidation) -> UserInDB:
"""
Create a new user with business logic validation.
Args:
user_data (UserCreate): User creation data
Returns:
UserInDB: Created user with database information
Raises:
ValueError: If user already exists or validation fails
"""
# Check if user already exists
if self.user_repository.user_exists(user_data.username):
raise ValueError(f"User with username '{user_data.username}' already exists")
# Check if email already exists
existing_user = self.user_repository.find_user_by_email(user_data.email)
if existing_user:
raise ValueError(f"User with email '{user_data.email}' already exists")
try:
return self.user_repository.create_user(user_data)
except DuplicateKeyError:
raise ValueError(f"User with username '{user_data.username}' already exists")
def get_user_by_username(self, username: str) -> Optional[UserInDB]:
"""
Retrieve user by username.
Args:
username (str): Username to search for
Returns:
UserInDB or None: User if found, None otherwise
"""
return self.user_repository.find_user_by_username(username)
def get_user_by_id(self, user_id: str) -> Optional[UserInDB]:
"""
Retrieve user by ID.
Args:
user_id (str): User ID to search for
Returns:
UserInDB or None: User if found, None otherwise
"""
return self.user_repository.find_user_by_id(user_id)
def authenticate_user(self, username: str, password: str) -> Optional[UserInDB]:
"""
Authenticate user with username and password.
Args:
username (str): Username for authentication
password (str): Password for authentication
Returns:
UserInDB or None: Authenticated user if valid, None otherwise
"""
user = self.user_repository.find_user_by_username(username)
if not user:
return None
if not user.is_active:
return None
if not self.auth_service.verify_user_password(password, user.hashed_password):
return None
return user
def update_user(self, user_id: str, user_update: UserUpdate) -> Optional[UserInDB]:
"""
Update user information.
Args:
user_id (str): User ID to update
user_update (UserUpdate): Updated user data
Returns:
UserInDB or None: Updated user if successful, None otherwise
Raises:
ValueError: If username or email already exists for different user
"""
# Validate username uniqueness if being updated
if user_update.username is not None:
existing_user = self.user_repository.find_user_by_username(user_update.username)
if existing_user and str(existing_user.id) != user_id:
raise ValueError(f"Username '{user_update.username}' is already taken")
# Validate email uniqueness if being updated
if user_update.email is not None:
existing_user = self.user_repository.find_user_by_email(user_update.email)
if existing_user and str(existing_user.id) != user_id:
raise ValueError(f"Email '{user_update.email}' is already taken")
return self.user_repository.update_user(user_id, user_update)
def delete_user(self, user_id: str) -> bool:
"""
Delete user from system.
Args:
user_id (str): User ID to delete
Returns:
bool: True if user was deleted, False otherwise
"""
return self.user_repository.delete_user(user_id)
def list_users(self, skip: int = 0, limit: int = 100) -> List[UserInDB]:
"""
List users with pagination.
Args:
skip (int): Number of users to skip (default: 0)
limit (int): Maximum number of users to return (default: 100)
Returns:
List[UserInDB]: List of users
"""
return self.user_repository.list_users(skip=skip, limit=limit)
def count_users(self) -> int:
"""
Count total number of users.
Returns:
int: Total number of users in system
"""
return self.user_repository.count_users()
def user_exists(self, username: str) -> bool:
"""
Check if user exists by username.
Args:
username (str): Username to check
Returns:
bool: True if user exists, False otherwise
"""
return self.user_repository.user_exists(username)

View File

@@ -1,6 +1,9 @@
fastapi==0.116.1
uvicorn==0.35.0
bcrypt==4.3.0
celery==5.5.3
redis==6.4.0
email-validator==2.3.0
fastapi==0.116.1
httptools==0.6.4
pymongo==4.15.0
pydantic==2.11.9
pydantic==2.11.9
redis==6.4.0
uvicorn==0.35.0