[INIT-001] Initial project setup with Clean Architecture (feat)
Some checks failed
CD - Build & Deploy / build-and-push (push) Has been cancelled
CD - Build & Deploy / package-helm (push) Has been cancelled
CD - Build & Deploy / deploy-staging (push) Has been cancelled
CD - Build & Deploy / deploy-production (push) Has been cancelled
CD - Build & Deploy / release (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / security (push) Has been cancelled

- Implemented Clean Architecture with Domain, Application, Infrastructure, Presentation layers
- Added comprehensive project structure following SOLID principles
- Created Kubernetes deployment with Helm charts (HPA, PDB, NetworkPolicy)
- Configured ArgoCD for automated deployment (production + staging)
- Implemented CI/CD pipeline with GitHub Actions
- Added comprehensive documentation (handbook, architecture, coding standards)
- Configured PostgreSQL, Redis, Celery for backend services
- Created modern landing page with Persian fonts (Vazirmatn)
- Added Docker multi-stage build for production
- Configured development tools (pytest, black, flake8, mypy, isort)
- Added pre-commit hooks for code quality
- Implemented Makefile for common operations
This commit is contained in:
Ehsan.Asadi
2025-12-26 15:52:50 +03:30
commit 8a924f6091
135 changed files with 8637 additions and 0 deletions

View File

0
src/infrastructure/cache/__init__.py vendored Normal file
View File

View File

View File

@@ -0,0 +1,87 @@
"""Alembic environment configuration.
This module is used by Alembic to manage database migrations.
"""
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context
import sys
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parents[4]
sys.path.insert(0, str(project_root))
from src.config.settings import settings
from src.config.database import Base
# Import all models to ensure they're registered with Base.metadata
from src.infrastructure.database.models import * # noqa
# Alembic Config object
config = context.config
# Set database URL from settings
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
# Interpret the config file for Python logging
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# Target metadata for autogenerate
target_metadata = Base.metadata
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,27 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
"""Upgrade database schema."""
${upgrades if upgrades else "pass"}
def downgrade() -> None:
"""Downgrade database schema."""
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,51 @@
"""Base model for all database models.
This module defines the base class that all SQLAlchemy models inherit from.
"""
from datetime import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.ext.declarative import declared_attr
from src.config.database import Base as DeclarativeBase
class BaseModel(DeclarativeBase):
"""Base model with common fields.
All models should inherit from this class to get:
- id (primary key)
- created_at
- updated_at
- Automatic table naming
"""
__abstract__ = True
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(
DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
nullable=False
)
@declared_attr
def __tablename__(cls) -> str:
"""Generate table name from class name.
Converts PascalCase to snake_case.
Example: UserModel -> user_model
"""
import re
name = cls.__name__
# Convert PascalCase to snake_case
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
return name
def __repr__(self) -> str:
"""String representation."""
return f"<{self.__class__.__name__}(id={self.id})>"

View File

@@ -0,0 +1,122 @@
"""Base repository implementation.
This module provides a base repository class with common CRUD operations.
"""
from typing import TypeVar, Generic, Optional, List, Type
from sqlalchemy.orm import Session
from src.infrastructure.database.models.base import BaseModel
ModelType = TypeVar("ModelType", bound=BaseModel)
class BaseRepository(Generic[ModelType]):
"""Base repository with common database operations.
Provides basic CRUD operations that can be inherited by specific repositories.
Attributes:
model: SQLAlchemy model class
session: Database session
"""
def __init__(self, model: Type[ModelType], session: Session):
"""Initialize repository.
Args:
model: SQLAlchemy model class
session: Database session
"""
self.model = model
self._session = session
def get_by_id(self, id: int) -> Optional[ModelType]:
"""Get entity by ID.
Args:
id: Entity ID
Returns:
Optional[ModelType]: Entity or None
"""
return self._session.query(self.model).filter(self.model.id == id).first()
def get_all(self, skip: int = 0, limit: int = 100) -> List[ModelType]:
"""Get all entities with pagination.
Args:
skip: Number of records to skip
limit: Maximum number of records to return
Returns:
List[ModelType]: List of entities
"""
return self._session.query(self.model).offset(skip).limit(limit).all()
def create(self, entity: ModelType) -> ModelType:
"""Create new entity.
Args:
entity: Entity to create
Returns:
ModelType: Created entity
"""
self._session.add(entity)
self._session.flush()
self._session.refresh(entity)
return entity
def update(self, entity: ModelType) -> ModelType:
"""Update existing entity.
Args:
entity: Entity to update
Returns:
ModelType: Updated entity
"""
self._session.merge(entity)
self._session.flush()
self._session.refresh(entity)
return entity
def delete(self, id: int) -> bool:
"""Delete entity by ID.
Args:
id: Entity ID
Returns:
bool: True if deleted, False if not found
"""
entity = self.get_by_id(id)
if entity:
self._session.delete(entity)
self._session.flush()
return True
return False
def count(self) -> int:
"""Count total number of entities.
Returns:
int: Total count
"""
return self._session.query(self.model).count()
def exists(self, id: int) -> bool:
"""Check if entity exists.
Args:
id: Entity ID
Returns:
bool: True if exists
"""
return self._session.query(
self._session.query(self.model).filter(self.model.id == id).exists()
).scalar()

View File

@@ -0,0 +1,81 @@
"""Unit of Work pattern implementation.
This module implements the Unit of Work pattern for managing database transactions.
"""
from typing import Optional
from sqlalchemy.orm import Session
from src.config.database import SessionLocal
class UnitOfWork:
"""Unit of Work for managing database transactions.
Usage:
with UnitOfWork() as uow:
user = uow.users.get_by_id(1)
user.email = "new@email.com"
uow.users.update(user)
# Automatically commits on success
"""
def __init__(self, session_factory=SessionLocal):
"""Initialize Unit of Work.
Args:
session_factory: Factory to create database sessions
"""
self.session_factory = session_factory
self._session: Optional[Session] = None
def __enter__(self) -> "UnitOfWork":
"""Enter context manager.
Returns:
UnitOfWork: Self
"""
self._session = self.session_factory()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit context manager.
Commits on success, rollbacks on error.
"""
if exc_type is not None:
self.rollback()
else:
self.commit()
self.close()
def commit(self) -> None:
"""Commit the current transaction."""
if self._session:
self._session.commit()
def rollback(self) -> None:
"""Rollback the current transaction."""
if self._session:
self._session.rollback()
def close(self) -> None:
"""Close the session."""
if self._session:
self._session.close()
self._session = None
@property
def session(self) -> Session:
"""Get the current session.
Returns:
Session: Current database session
Raises:
RuntimeError: If session not initialized
"""
if self._session is None:
raise RuntimeError("Session not initialized. Use within context manager.")
return self._session

View File

View File

View File

View File

View File

View File

View File

View File