[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

193
.github/workflows/cd.yml vendored Normal file
View File

@@ -0,0 +1,193 @@
name: CD - Build & Deploy
on:
push:
branches: [ main ]
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-version: ${{ steps.meta.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
platforms: linux/amd64
- name: Image digest
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"
package-helm:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: 'latest'
- name: Package Helm chart
run: |
helm package helm/peikarband --destination .
helm repo index . --url https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}
- name: Upload Helm chart artifact
uses: actions/upload-artifact@v3
with:
name: helm-chart
path: |
*.tgz
index.yaml
deploy-staging:
runs-on: ubuntu-latest
needs: [build-and-push, package-helm]
if: github.ref == 'refs/heads/main'
environment:
name: staging
url: https://staging.peikarband.ir
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install kubectl
uses: azure/setup-kubectl@v3
- name: Install Helm
uses: azure/setup-helm@v3
- name: Configure kubectl
run: |
echo "${{ secrets.KUBECONFIG_STAGING }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to Staging
run: |
export KUBECONFIG=kubeconfig
helm upgrade --install peikarband-staging ./helm/peikarband \
--namespace staging \
--create-namespace \
--set image.tag=${{ needs.build-and-push.outputs.image-version }} \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--set ingress.hosts[0].host=staging.peikarband.ir \
--wait \
--timeout 5m
deploy-production:
runs-on: ubuntu-latest
needs: [build-and-push, package-helm]
if: startsWith(github.ref, 'refs/tags/v')
environment:
name: production
url: https://peikarband.ir
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install kubectl
uses: azure/setup-kubectl@v3
- name: Install Helm
uses: azure/setup-helm@v3
- name: Configure kubectl
run: |
echo "${{ secrets.KUBECONFIG_PRODUCTION }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to Production
run: |
export KUBECONFIG=kubeconfig
helm upgrade --install peikarband-prod ./helm/peikarband \
--namespace production \
--create-namespace \
--set image.tag=${{ needs.build-and-push.outputs.image-version }} \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--set replicaCount=3 \
--set autoscaling.enabled=true \
--values helm/peikarband/values-production.yaml \
--wait \
--timeout 10m
- name: Verify deployment
run: |
export KUBECONFIG=kubeconfig
kubectl rollout status deployment/peikarband-prod -n production
kubectl get pods -n production
release:
runs-on: ubuntu-latest
needs: [build-and-push, package-helm, deploy-production]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download Helm chart
uses: actions/download-artifact@v3
with:
name: helm-chart
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
*.tgz
index.yaml
generate_release_notes: true
draft: false
prerelease: false

121
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,121 @@
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.11', '3.12']
services:
postgres:
image: postgres:14
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: peikarband_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Lint with flake8
run: |
flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 src/ --count --max-complexity=10 --max-line-length=120 --statistics
- name: Type check with mypy
run: |
mypy src/
- name: Check formatting with black
run: |
black --check src/
- name: Check imports with isort
run: |
isort --check-only src/
- name: Run tests with pytest
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/peikarband_test
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: test-secret-key
JWT_SECRET_KEY: test-jwt-secret
CELERY_BROKER_URL: redis://localhost:6379/1
CELERY_RESULT_BACKEND: redis://localhost:6379/2
run: |
pytest tests/ -v --cov=src --cov-report=xml --cov-report=term-missing
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: false
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install bandit safety
- name: Run Bandit security scan
run: |
bandit -r src/ -f json -o bandit-report.json || true
- name: Run Safety check
run: |
safety check --json || true

66
.gitignore vendored Normal file
View File

@@ -0,0 +1,66 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
venv/
env/
ENV/
# Reflex
.web/
.reflex/
.states/
reflex.db
reflex.db-*
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/
# Logs
*.log
logs/
# Environment
.env
.env.local
.env.*.local
# Temporary
tmp/
temp/
*.tmp
# Docker
.dockerignore
# Kubernetes secrets
*secret*.yaml
*Secret*.yaml

43
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,43 @@
# Pre-commit hooks for Peikarband
# Install: pre-commit install
# Run manually: pre-commit run --all-files
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-json
- id: check-toml
- id: check-merge-conflict
- id: debug-statements
- id: mixed-line-ending
- repo: https://github.com/psf/black
rev: 23.12.0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
args: ["--max-line-length=120", "--extend-ignore=E203,W503"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [types-all]
args: ["--config-file=mypy.ini"]

71
Dockerfile Normal file
View File

@@ -0,0 +1,71 @@
# Peikarband Platform - Production Dockerfile
# Multi-stage build for optimized image size
# Stage 1: Builder
FROM python:3.11-slim as builder
WORKDIR /build
# Install build dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
curl \
gnupg \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js (required for Reflex)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Copy application code
COPY . .
# Initialize and build Reflex app
RUN python -m reflex init --template blank && \
python -m reflex export --frontend-only --no-zip
# Stage 2: Runtime
FROM python:3.11-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y \
postgresql-client \
curl \
nodejs \
&& rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local
# Copy application and built assets
COPY --from=builder /build /app
# Create non-root user
RUN useradd -m -u 1000 peikarband && \
chown -R peikarband:peikarband /app
# Set environment variables
ENV PATH=/root/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
REFLEX_ENV=production
USER peikarband
# Expose ports (backend: 8000, frontend: 3000)
EXPOSE 3000 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/ping || exit 1
# Run application
CMD ["python", "-m", "reflex", "run", "--env", "production", "--backend-only"]

126
Makefile Normal file
View File

@@ -0,0 +1,126 @@
# Peikarband Platform - Makefile
REGISTRY ?= registry.example.com
IMAGE_NAME ?= peikarband/landing
VERSION ?= $(shell git describe --tags --always --dirty)
HELM_RELEASE ?= peikarband
NAMESPACE ?= production
.PHONY: help install dev test lint format clean docker-up docker-down migrate
help:
@echo "Available commands:"
@echo ""
@echo "Development:"
@echo " make install - Install dependencies"
@echo " make dev - Run development server"
@echo " make test - Run tests"
@echo " make lint - Run linters"
@echo " make format - Format code"
@echo " make clean - Clean temporary files"
@echo ""
@echo "Docker:"
@echo " make docker-build - Build Docker image"
@echo " make docker-push - Push Docker image"
@echo " make docker-up - Start Docker Compose"
@echo " make docker-down - Stop Docker Compose"
@echo ""
@echo "Kubernetes/Helm:"
@echo " make helm-lint - Lint Helm chart"
@echo " make helm-package - Package Helm chart"
@echo " make helm-install - Install Helm chart"
@echo " make helm-upgrade - Upgrade Helm chart"
@echo " make helm-uninstall - Uninstall Helm chart"
@echo " make k8s-deploy - Deploy to Kubernetes"
@echo ""
@echo "Database:"
@echo " make migrate - Run database migrations"
install:
pip install -r requirements.txt
pip install -r requirements-dev.txt
pre-commit install
dev:
python -m reflex run
test:
pytest tests/ -v --cov=src --cov-report=html
lint:
flake8 src/
mypy src/
black --check src/
isort --check-only src/
format:
black src/
isort src/
clean:
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
find . -type f -name "*.pyo" -delete
find . -type d -name ".pytest_cache" -exec rm -rf {} +
find . -type d -name ".mypy_cache" -exec rm -rf {} +
find . -type d -name "*.egg-info" -exec rm -rf {} +
rm -rf .coverage htmlcov/
rm -rf dist/ build/
# Docker commands
docker-build:
docker build -t $(IMAGE_NAME):$(VERSION) .
docker tag $(IMAGE_NAME):$(VERSION) $(IMAGE_NAME):latest
docker-push:
docker tag $(IMAGE_NAME):$(VERSION) $(REGISTRY)/$(IMAGE_NAME):$(VERSION)
docker tag $(IMAGE_NAME):$(VERSION) $(REGISTRY)/$(IMAGE_NAME):latest
docker push $(REGISTRY)/$(IMAGE_NAME):$(VERSION)
docker push $(REGISTRY)/$(IMAGE_NAME):latest
docker-up:
docker-compose up -d
docker-down:
docker-compose down
# Helm commands
helm-lint:
helm lint helm/peikarband
helm-template:
helm template $(HELM_RELEASE) helm/peikarband --debug
helm-package:
helm package helm/peikarband --destination .
helm-install:
helm install $(HELM_RELEASE) helm/peikarband \
--namespace $(NAMESPACE) \
--create-namespace \
--set image.repository=$(REGISTRY)/$(IMAGE_NAME) \
--set image.tag=$(VERSION) \
--wait
helm-upgrade:
helm upgrade --install $(HELM_RELEASE) helm/peikarband \
--namespace $(NAMESPACE) \
--set image.repository=$(REGISTRY)/$(IMAGE_NAME) \
--set image.tag=$(VERSION) \
--wait
helm-uninstall:
helm uninstall $(HELM_RELEASE) --namespace $(NAMESPACE)
# Kubernetes deployment (full pipeline)
k8s-deploy: docker-build docker-push helm-upgrade
@echo "Deployment complete!"
@echo "Check status: kubectl get pods -n $(NAMESPACE)"
# Database
migrate:
alembic upgrade head
seed:
python scripts/seed_database.py

217
README.md Normal file
View File

@@ -0,0 +1,217 @@
# پیکربند - پلتفرم جامع مدیریت هاستینگ و زیرساخت ابری
## 📖 درباره پروژه
پیکربند یک پلتفرم حرفه‌ای برای مدیریت هاستینگ، سرورهای ابری، دامین و خدمات DevOps است. این پلتفرم با الهام از سرویس‌هایی مانند Cloudways، DigitalOcean و پارس پک طراحی شده است.
## 🏗️ معماری
این پروژه بر اساس **Clean Architecture** و اصول **SOLID** طراحی شده است:
- **Domain Layer**: منطق کسب‌وکار اصلی
- **Application Layer**: موارد استفاده (Use Cases)
- **Infrastructure Layer**: پیاده‌سازی‌های فنی
- **Presentation Layer**: رابط کاربری (Reflex)
## 🚀 تکنولوژی‌ها
- **Frontend/Backend**: Python Reflex
- **Database**: PostgreSQL + SQLAlchemy
- **Cache**: Redis
- **Task Queue**: Celery
- **Testing**: pytest
- **Code Quality**: black, flake8, mypy, isort
## 📋 پیش‌نیازها
- Python 3.11+
- PostgreSQL 14+
- Redis 7+
- Node.js 18+ (برای Reflex)
## 🛠️ نصب و راه‌اندازی
### 1. کلون کردن پروژه
```bash
git clone https://github.com/yourusername/peikarband.git
cd peikarband
```
### 2. ایجاد محیط مجازی
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
### 3. نصب وابستگی‌ها
```bash
pip install -r requirements.txt
pip install -r requirements-dev.txt # برای توسعه
```
### 4. تنظیم Environment Variables
```bash
cp .env.example .env
# ویرایش .env و تکمیل مقادیر
```
### 5. راه‌اندازی دیتابیس
```bash
# ایجاد دیتابیس
createdb peikarband
# اجرای migrations
alembic upgrade head
```
### 6. اجرای پروژه
```bash
# توسعه
python -m reflex run
# یا
make dev
```
## 🚢 Deployment
### با Docker
```bash
# Build
docker build -t peikarband:latest .
# Run
docker-compose up -d
```
### با Kubernetes/Helm
```bash
# Deploy
helm upgrade --install peikarband ./helm/peikarband \
--namespace production \
--set image.tag=0.1.0
# یا
make k8s-deploy
```
📖 [راهنمای کامل Deployment](docs/deployment/kubernetes.md)
## 📁 ساختار پروژه
```
peikarband/
├── docs/ # مستندات
├── src/
│ ├── config/ # تنظیمات
│ ├── core/ # هسته اصلی
│ │ ├── domain/ # Domain entities & logic
│ │ └── application/ # Use cases & DTOs
│ ├── infrastructure/ # پیاده‌سازی‌های فنی
│ ├── presentation/ # رابط کاربری
│ └── shared/ # کدهای مشترک
├── tests/ # تست‌ها
└── scripts/ # اسکریپت‌های کمکی
```
## 🧪 تست
```bash
# اجرای همه تست‌ها
pytest
# با coverage
pytest --cov=src tests/
# تست‌های خاص
pytest tests/unit/
pytest tests/integration/
```
## 📝 کدنویسی
### استانداردها
- **PEP 8**: استاندارد کدنویسی Python
- **PEP 20**: Zen of Python
- **Type Hints**: همه جا استفاده شود
- **Docstrings**: Google Style
### ابزارهای کیفیت کد
```bash
# Format
black src/
# Linting
flake8 src/
# Type checking
mypy src/
# Import sorting
isort src/
```
### Pre-commit Hooks
```bash
pre-commit install
pre-commit run --all-files
```
## 📚 مستندات
مستندات کامل در پوشه `docs/` موجود است:
- [Handbook](docs/handbook.md): راهنمای جامع پروژه
- [Architecture](docs/architecture/): معماری سیستم
- [Development](docs/development/): راهنمای توسعه
- [API Reference](docs/api/): مستندات API
## 🔐 امنیت
- همه پسوردها با bcrypt hash می‌شوند
- استفاده از JWT برای authentication
- پشتیبانی از 2FA
- اطلاعات حساس رمزنگاری می‌شوند
## 🤝 مشارکت
برای مشارکت در پروژه:
1. Fork کنید
2. Branch جدید بسازید (`git checkout -b feature/amazing-feature`)
3. Commit کنید (`git commit -m 'feat: add amazing feature'`)
4. Push کنید (`git push origin feature/amazing-feature`)
5. Pull Request بسازید
## 📄 لایسنس
این پروژه تحت لایسنس MIT منتشر شده است.
## 👥 تیم
- Lead Developer: [Your Name]
- Architecture: Clean Architecture
- Methodology: Agile/Scrum
## 📞 تماس
- Website: https://peikarband.ir
- Email: support@peikarband.ir
- Telegram: @peikarband
---
**نسخه**: 0.1.0
**آخرین بروزرسانی**: 2025-01-24

102
alembic.ini Normal file
View File

@@ -0,0 +1,102 @@
# Alembic configuration file
[alembic]
# path to migration scripts
script_location = src/infrastructure/database/migrations
# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to migrations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = postgresql://username:password@localhost/peikarband
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

154
argocd/README.md Normal file
View File

@@ -0,0 +1,154 @@
# ArgoCD Deployment
This directory contains ArgoCD Application manifests for deploying Peikarband to Kubernetes.
## Files
- `application.yaml`: Production deployment (main branch → peikarband namespace)
- `application-staging.yaml`: Staging deployment (develop branch → peikarband-staging namespace)
## Prerequisites
1. ArgoCD installed in your cluster
2. Git repository access configured in ArgoCD
3. Docker registry credentials (if using private registry)
## Deployment
### 1. Add Git Repository to ArgoCD
```bash
# For SSH
argocd repo add git@git.peikarband.ir:ehsan-minadd/peikarband.git \
--ssh-private-key-path ~/.ssh/id_rsa \
--insecure-skip-server-verification
# Or using argocd UI: Settings → Repositories → Connect Repo
```
### 2. Deploy Production
```bash
kubectl apply -f argocd/application.yaml
```
### 3. Deploy Staging
```bash
kubectl apply -f argocd/application-staging.yaml
```
## Sync Policy
Both applications use **automatic sync** with:
- **Auto-prune**: Remove resources deleted from Git
- **Self-heal**: Automatically sync when cluster state differs from Git
- **Retry logic**: 5 attempts with exponential backoff
## Monitoring
```bash
# Check application status
argocd app get peikarband
argocd app get peikarband-staging
# Watch sync progress
argocd app sync peikarband --watch
# View logs
argocd app logs peikarband
```
## Manual Sync
```bash
# Force sync
argocd app sync peikarband --force
# Sync with prune
argocd app sync peikarband --prune
```
## Rollback
```bash
# List history
argocd app history peikarband
# Rollback to specific revision
argocd app rollback peikarband <REVISION>
```
## Architecture
```
┌─────────────────────────────────────────────────┐
│ ArgoCD │
│ ┌───────────────────┐ ┌──────────────────┐ │
│ │ Production App │ │ Staging App │ │
│ │ (main branch) │ │ (develop branch) │ │
│ └─────────┬─────────┘ └────────┬─────────┘ │
└────────────┼─────────────────────┼──────────────┘
│ │
▼ ▼
┌────────────────┐ ┌─────────────────┐
│ namespace: │ │ namespace: │
│ peikarband │ │ peikarband-stg │
└────────────────┘ └─────────────────┘
```
## Environment Variables
Override via Helm values:
```yaml
# In values-production.yaml or values-staging.yaml
env:
- name: DATABASE_URL
value: "postgresql://..."
- name: REDIS_URL
value: "redis://..."
```
## Secrets Management
Secrets should be managed outside Git:
```bash
# Using kubectl
kubectl create secret generic peikarband-secrets \
--from-literal=database-password=xxx \
--namespace=peikarband
# Or using Sealed Secrets, External Secrets Operator, etc.
```
## Troubleshooting
### Application Out of Sync
```bash
argocd app sync peikarband --force
```
### Image Pull Errors
Check registry credentials:
```bash
kubectl get secret regcred -n peikarband -o yaml
```
### Health Check Failing
View pod logs:
```bash
kubectl logs -n peikarband -l app=peikarband --tail=100
```
### Helm Values Override Not Working
Verify values file path in Application manifest:
```bash
argocd app manifests peikarband | grep valueFiles
```

View File

@@ -0,0 +1,47 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: peikarband-staging
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: git@git.peikarband.ir:ehsan-minadd/peikarband.git
targetRevision: develop
path: helm/peikarband
helm:
releaseName: peikarband-staging
valueFiles:
- values-staging.yaml
destination:
server: https://kubernetes.default.svc
namespace: peikarband-staging
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 10
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas

47
argocd/application.yaml Normal file
View File

@@ -0,0 +1,47 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: peikarband
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: git@git.peikarband.ir:ehsan-minadd/peikarband.git
targetRevision: HEAD
path: helm/peikarband
helm:
releaseName: peikarband
valueFiles:
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: peikarband
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 10
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas

31
assets/custom.css Normal file
View File

@@ -0,0 +1,31 @@
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100..900&display=swap');
body {
font-family: 'Vazirmatn', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes glow {
0% { box-shadow: 0 12px 40px rgba(30, 64, 175, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2); }
50% { box-shadow: 0 18px 50px rgba(30, 64, 175, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.3); }
100% { box-shadow: 0 12px 40px rgba(30, 64, 175, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2); }
}
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}

92
docker-compose.yml Normal file
View File

@@ -0,0 +1,92 @@
version: '3.8'
services:
# PostgreSQL Database
postgres:
image: postgres:14-alpine
container_name: peikarband-db
environment:
POSTGRES_USER: ${DB_USER:-peikarband}
POSTGRES_PASSWORD: ${DB_PASSWORD:-peikarband}
POSTGRES_DB: ${DB_NAME:-peikarband}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U peikarband"]
interval: 10s
timeout: 5s
retries: 5
# Redis Cache
redis:
image: redis:7-alpine
container_name: peikarband-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Peikarband Application
app:
build: .
container_name: peikarband-app
depends_on:
- postgres
- redis
ports:
- "3000:3000"
- "8000:8000"
environment:
- DATABASE_URL=postgresql://peikarband:peikarband@postgres:5432/peikarband
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
- CELERY_RESULT_BACKEND=redis://redis:6379/2
- SECRET_KEY=${SECRET_KEY}
- JWT_SECRET_KEY=${JWT_SECRET_KEY}
- ENVIRONMENT=production
volumes:
- ./:/app
restart: unless-stopped
# Celery Worker
celery:
build: .
container_name: peikarband-celery
command: celery -A src.infrastructure.tasks.celery_app worker -l info
depends_on:
- postgres
- redis
environment:
- DATABASE_URL=postgresql://peikarband:peikarband@postgres:5432/peikarband
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
- CELERY_RESULT_BACKEND=redis://redis:6379/2
volumes:
- ./:/app
restart: unless-stopped
# Flower (Celery Monitoring)
flower:
build: .
container_name: peikarband-flower
command: celery -A src.infrastructure.tasks.celery_app flower
depends_on:
- redis
- celery
ports:
- "5555:5555"
environment:
- CELERY_BROKER_URL=redis://redis:6379/1
restart: unless-stopped
volumes:
postgres_data:
redis_data:

View File

@@ -0,0 +1,453 @@
# استراتژی دیتابیس - انعطاف و مقیاس‌پذیری
## مشکل: انعطاف vs ثبات
با توجه به اینکه پروژه قرار است:
- کلی feature جدید بگیره
- Options و configurations متنوع داشته باشه
- قابل توسعه سریع باشه
آیا PostgreSQL محدودیت ایجاد می‌کنه؟
## ✅ راه حل: Hybrid Database Architecture
### لایه‌بندی داده بر اساس نوع:
```
┌──────────────────────────────────────────────┐
│ Application Layer │
└────┬─────────────┬──────────────┬────────────┘
│ │ │
┌────▼─────┐ ┌───▼────┐ ┌───▼────┐
│PostgreSQL│ │MongoDB │ │ Redis │
│ │ │ │ │ │
│Critical │ │Flexible│ │ Cache │
│Structured│ │Dynamic │ │Session │
└──────────┘ └────────┘ └────────┘
```
### Tier 1: PostgreSQL - Critical Business Data
**چی بریزیم توی PostgreSQL:**
- User accounts
- Financial data (invoices, transactions, wallet)
- Service subscriptions
- Audit logs
- Anything که نیاز به ACID داره
```python
# ❌ این‌ها رو NEVER توی MongoDB نذاریم
class Invoice(BaseModel):
id: int
user_id: int
amount: Decimal # MUST be ACID
status: str
paid_at: datetime
```
**چرا PostgreSQL؟**
- ✅ ACID Transactions
- ✅ Data Integrity
- ✅ Complex Joins
- ✅ Proven for financial data
### Tier 2: PostgreSQL JSONB - Flexible Structured
**چی بریزیم توی JSONB:**
- Service configurations
- User preferences
- Feature flags
- Custom metadata
- Plugin settings
```python
from sqlalchemy.dialects.postgresql import JSONB
class Service(BaseModel):
# Structured
id = Column(Integer, primary_key=True)
user_id = Column(Integer)
type = Column(String) # 'vps', 'hosting', etc.
# Flexible - هر چیزی می‌تونه باشه!
config = Column(JSONB, default={})
metadata = Column(JSONB, default={})
# Example 1: VPS
vps = Service(
type="vps",
config={
"cpu": 4,
"ram": 8,
"disk": 100,
"os": "ubuntu-22.04"
}
)
# Example 2: WordPress Hosting
wp = Service(
type="wordpress",
config={
"domain": "example.com",
"php_version": "8.2",
"auto_update": True,
"cdn_enabled": True,
# Future features - بدون migration!
"new_feature_2025": {"enabled": True}
}
)
```
**Query کردن JSONB:**
```python
# پیدا کردن VPS های بالای 4 CPU
services = session.query(Service).filter(
Service.config['cpu'].astext.cast(Integer) >= 4
).all()
# پیدا کردن WordPress با CDN enabled
services = session.query(Service).filter(
Service.config['cdn_enabled'].astext == 'true'
).all()
# Index روی JSONB برای performance
from sqlalchemy import Index
Index(
'idx_service_config_cpu',
Service.config['cpu'].astext.cast(Integer)
)
```
### Tier 3: MongoDB - Completely Dynamic
**چی بریزیم توی MongoDB:**
- Logs و events
- Analytics data
- User activity tracking
- System metrics
- Unstructured data
```python
# MongoDB - No schema!
{
"_id": "service_log_123",
"service_id": 123,
"timestamp": "2025-01-24T...",
"events": [
{
"type": "cpu_spike",
"value": 95,
"custom_field_1": "...",
# هر چیزی که بخوایم!
}
],
"metrics": {
# Structure آزاد
"anything": "goes here"
}
}
```
## پیاده‌سازی در کد
### 1. Service با JSONB:
```python
# src/infrastructure/database/models/service_model.py
from sqlalchemy import Column, Integer, String, ForeignKey, Enum as SQLEnum
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
import enum
class ServiceType(enum.Enum):
VPS = "vps"
HOSTING = "hosting"
WORDPRESS = "wordpress"
DOMAIN = "domain"
class ServiceModel(BaseModel):
__tablename__ = "services"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
type = Column(SQLEnum(ServiceType), nullable=False)
status = Column(String(20), nullable=False)
# Flexible configuration
config = Column(JSONB, nullable=False, default={})
metadata = Column(JSONB, nullable=False, default={})
# Relationships
user = relationship("UserModel", back_populates="services")
```
### 2. Repository با JSONB support:
```python
# src/infrastructure/database/repositories/service_repository.py
from typing import Dict, Any, List
from sqlalchemy.orm import Session
class ServiceRepository:
def __init__(self, session: Session):
self._session = session
def find_by_config(
self,
json_path: str,
value: Any
) -> List[ServiceModel]:
"""Query by JSONB field.
Example:
repo.find_by_config('cpu', 4)
repo.find_by_config('cdn_enabled', True)
"""
return self._session.query(ServiceModel).filter(
ServiceModel.config[json_path].astext == str(value)
).all()
def update_config(
self,
service_id: int,
config_updates: Dict[str, Any]
) -> ServiceModel:
"""Update service config partially."""
service = self.get_by_id(service_id)
if service:
# Merge configs
new_config = {**service.config, **config_updates}
service.config = new_config
self._session.commit()
return service
```
### 3. Domain Entity که flexible هست:
```python
# src/core/domain/entities/service.py
from typing import Dict, Any, Optional
from src.core.domain.entities.base import BaseEntity
class Service(BaseEntity):
"""Service domain entity with flexible configuration."""
def __init__(
self,
id: Optional[int] = None,
user_id: int = None,
type: str = None,
status: str = None,
config: Dict[str, Any] = None,
metadata: Dict[str, Any] = None,
**kwargs
):
super().__init__(id=id, **kwargs)
self.user_id = user_id
self.type = type
self.status = status
self.config = config or {}
self.metadata = metadata or {}
def get_config(self, key: str, default: Any = None) -> Any:
"""Get config value safely."""
return self.config.get(key, default)
def set_config(self, key: str, value: Any) -> None:
"""Set config value."""
self.config[key] = value
def update_config(self, updates: Dict[str, Any]) -> None:
"""Update multiple config values."""
self.config.update(updates)
```
## مقایسه روش‌ها
### Migration-based (Traditional SQL)
```python
# ❌ هر feature جدید = migration جدید
# Migration 001
ALTER TABLE services ADD COLUMN cpu INTEGER;
# Migration 002
ALTER TABLE services ADD COLUMN ram INTEGER;
# Migration 003
ALTER TABLE services ADD COLUMN new_feature VARCHAR(255);
# بعد از 100 feature = 100 migration! 😱
```
### JSONB-based (Flexible SQL)
```python
# ✅ هیچ migration لازم نیست!
# روز اول
service.config = {"cpu": 4}
# یک ماه بعد
service.config = {"cpu": 4, "ram": 8}
# یک سال بعد
service.config = {
"cpu": 4,
"ram": 8,
"new_feature_2025": True,
"another_feature": {"nested": "data"}
}
# بدون هیچ migration! 🎉
```
## Best Practices
### 1. تصمیم‌گیری: PostgreSQL vs MongoDB vs JSONB
```python
# PostgreSQL (Structured)
User authentication
Financial transactions
Invoices
Core business entities
# PostgreSQL JSONB
Service configurations
User preferences
Feature flags
Plugin settings
Custom fields
# MongoDB
Logs & Events
Analytics
User activity
System metrics
Temporary data
```
### 2. JSONB Schema Validation (در Application)
```python
from pydantic import BaseModel
from typing import Optional
class VPSConfig(BaseModel):
"""Schema for VPS service config."""
cpu: int
ram: int
disk: int
os: str
backups: Optional[bool] = False
class WordPressConfig(BaseModel):
"""Schema for WordPress service config."""
domain: str
php_version: str
auto_update: bool
cdn_enabled: Optional[bool] = False
# Validation
def validate_service_config(service_type: str, config: dict):
"""Validate config based on service type."""
schemas = {
"vps": VPSConfig,
"wordpress": WordPressConfig,
}
schema = schemas.get(service_type)
if schema:
return schema(**config) # Validates
return config
```
### 3. Indexing برای Performance
```python
# در migration
from alembic import op
def upgrade():
# Create GIN index on JSONB
op.execute("""
CREATE INDEX idx_services_config_gin
ON services USING GIN (config);
""")
# Index specific keys
op.execute("""
CREATE INDEX idx_services_config_cpu
ON services ((config->>'cpu'));
""")
```
## مزایا و معایب
### PostgreSQL + JSONB (پیشنهاد من)
**مزایا:**
- ✅ انعطاف بالا (مثل MongoDB)
- ✅ ACID transactions (برای billing امن)
- ✅ Query قدرتمند
- ✅ یک database کمتر = ساده‌تر
- ✅ Performance خوب با indexing
- ✅ Data integrity
**معایب:**
- ❌ JSONB query ها کمی پیچیده‌تر از SQL معمولی
- ❌ نیاز به validation در application layer
### Hybrid (PostgreSQL + MongoDB)
**مزایا:**
- ✅ Best of both worlds
- ✅ Separation of concerns
- ✅ Optimal performance
**معایب:**
- ❌ پیچیدگی بیشتر
- ❌ دو database = maintenance بیشتر
- ❌ Consistency بین دو DB
## نتیجه‌گیری
**پیشنهاد برای پیکربند:**
```python
"""
Tier 1: PostgreSQL - Critical
- Users, Auth
- Invoices, Transactions
- Wallet, Payments
Tier 2: PostgreSQL JSONB - Flexible
- Service configs
- User preferences
- Custom settings
Tier 3: Redis - Cache
- Sessions
- Cache
- Rate limiting
(Optional) Tier 4: MongoDB - Logs
- Activity logs
- System metrics
- Analytics
"""
```
**در عمل:**
- شروع با PostgreSQL + JSONB
- اگر لازم شد، MongoDB اضافه می‌کنیم
- ساده، flexible، و قابل توسعه
**PostgreSQL دست و پای ما رو نمی‌بنده، اگر از JSONB استفاده کنیم!**

View File

@@ -0,0 +1,174 @@
# معماری کلی سیستم
## نمای کلی
پلتفرم پیکربند بر اساس **Clean Architecture** طراحی شده که قابلیت تست، نگهداری و توسعه را به حداکثر می‌رساند.
## لایه‌های معماری
### 1. Domain Layer (هسته مرکزی)
مستقل‌ترین لایه که شامل منطق کسب‌وکار خالص است:
**Components:**
- **Entities**: موجودیت‌های اصلی (User, Service, Invoice, Server)
- **Value Objects**: Email, Money, Phone, IPAddress
- **Domain Services**: منطق پیچیده‌ای که به چند entity مرتبط است
- **Domain Events**: رویدادهای کسب‌وکار
- **Exceptions**: خطاهای دامین
**قوانین:**
- هیچ وابستگی به لایه‌های دیگر ندارد
- فقط منطق کسب‌وکار
- بدون وابستگی به framework
- Pure Python
### 2. Application Layer (موارد استفاده)
**Components:**
- **Use Cases**: موارد استفاده سیستم (RegisterUser, CreateInvoice)
- **DTOs**: Data Transfer Objects
- **Interfaces**: تعریف رابط‌های سرویس‌ها
- **Validators**: اعتبارسنجی ورودی‌ها
**قوانین:**
- وابسته به Domain Layer
- مستقل از Infrastructure
- تعریف رابط‌های مورد نیاز
### 3. Infrastructure Layer (جزئیات فنی)
**Components:**
- **Database**: PostgreSQL + SQLAlchemy
- **Cache**: Redis
- **External APIs**: DigitalOcean, Hetzner, OVH
- **Tasks**: Celery background jobs
- **Security**: Authentication, Authorization
- **Logging**: Structured logging
**قوانین:**
- پیاده‌سازی interface های Application Layer
- وابسته به تکنولوژی‌های خاص
- قابل تعویض
### 4. Presentation Layer (رابط کاربری)
**Components:**
- **Web**: Reflex pages و components
- **API**: REST endpoints (optional)
- **State Management**: Reflex states
**قوانین:**
- فقط به Application Layer وابسته
- مستقل از Infrastructure details
## جریان داده
```
User Action
Presentation Layer (Reflex Component)
Application Layer (Use Case)
Domain Layer (Business Logic)
Application Layer (Interfaces)
Infrastructure Layer (Implementation)
External Systems (Database, APIs)
```
## Dependency Rule
وابستگی‌ها همیشه به سمت داخل (به سمت Domain) هستند:
```
Presentation → Application → Domain
Infrastructure → Application → Domain
```
**قانون طلایی**: لایه‌های داخلی هیچ چیز از لایه‌های خارجی نمی‌دانند.
## مزایای این معماری
1. **Testability**: هر لایه مستقلا قابل تست
2. **Maintainability**: تغییرات محلی و جداسازی شده
3. **Flexibility**: تعویض آسان تکنولوژی‌ها
4. **Scalability**: قابل مقیاس‌پذیری در هر لایه
5. **Business Logic First**: تمرکز روی منطق کسب‌وکار
## Domain-Driven Design (DDD)
پروژه از اصول DDD استفاده می‌کند:
- **Ubiquitous Language**: زبان مشترک با کسب‌وکار
- **Bounded Contexts**: محدوده‌های مشخص
- **Aggregates**: مجموعه‌های یکپارچه
- **Repositories**: دسترسی به داده
- **Domain Events**: رویدادهای کسب‌وکار
## Technology Stack
### Core
- **Language**: Python 3.11+
- **Framework**: Reflex 0.4.0
- **Database**: PostgreSQL 14+
- **Cache**: Redis 7+
- **ORM**: SQLAlchemy 2.0+
### Infrastructure
- **Task Queue**: Celery 5.3+
- **Testing**: pytest 7.4+
- **Logging**: structlog
- **API Client**: httpx
### External Services
- **Cloud Providers**: DigitalOcean, Hetzner, OVH
- **Payment**: Zarinpal, IDPay
- **Monitoring**: Sentry, Prometheus
## Security Architecture
- **Authentication**: JWT + 2FA
- **Authorization**: RBAC (Role-Based Access Control)
- **Encryption**: Data at rest & in transit
- **Secrets Management**: Environment variables
- **Audit Logging**: تمام اقدامات مهم
## Scalability Strategy
### Horizontal Scaling
- Load balancing برای Reflex
- Database replication
- Redis clustering
- Celery workers
### Vertical Scaling
- Connection pooling
- Query optimization
- Caching strategy
- Async operations
## Monitoring & Observability
- **Metrics**: Request rate, response time, error rate
- **Logs**: Structured logging با contextual info
- **Tracing**: Request tracing
- **Alerts**: Critical issues
- **Dashboards**: Grafana/Prometheus
## Future Considerations
- Microservices architecture (در صورت نیاز)
- Event-driven architecture
- CQRS pattern
- GraphQL API
- Multi-tenancy
---
**نسخه**: 1.0.0
**آخرین بروزرسانی**: 2025-01-24

View File

@@ -0,0 +1,42 @@
# Changelog
تمام تغییرات قابل توجه این پروژه در این فایل مستند می‌شود.
فرمت بر اساس [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) است،
و این پروژه از [Semantic Versioning](https://semver.org/spec/v2.0.0.html) پیروی می‌کند.
## [Unreleased]
### Added
- معماری اولیه پروژه بر اساس Clean Architecture
- ساختار پوشه‌های اصلی
- تنظیمات config با Pydantic
- Setup PostgreSQL و SQLAlchemy
- Setup Redis برای cache
- Structured logging با structlog
- pytest برای testing
- Pre-commit hooks
- مستندات جامع (Handbook)
### Changed
- انتقال landing page به ساختار جدید
## [0.1.0] - 2025-01-24
### Added
- شروع پروژه
- Landing page اولیه با Reflex
- رنگ‌بندی آبی مطابق لوگو
- بخش ویژه WordPress Cloud
- Badge "بزودی کنار شما خواهیم بود"
- فونت فارسی Vazirmatn
### Design
- Hero section با gradient animations
- Service cards
- Pricing section
- Server comparison table
- Testimonials
- Contact form
- Floating chat button

View File

@@ -0,0 +1,69 @@
# مشکلات شناخته شده (Known Issues)
این فایل لیست مشکلات شناخته شده و workaround های موقت را نگه می‌دارد.
## Format
هر issue باید شامل:
- عنوان
- توضیحات
- تاریخ کشف
- Priority (Critical/High/Medium/Low)
- Status (Open/In Progress/Resolved)
- Workaround (اگر وجود دارد)
- Related tickets
---
## فعلا مشکلی گزارش نشده
پروژه در مراحل اولیه است و هنوز مشکل خاصی گزارش نشده.
---
## Template برای Issue های آینده
```markdown
## [ISSUE-001] عنوان مشکل
**تاریخ کشف**: YYYY-MM-DD
**Priority**: Critical/High/Medium/Low
**Status**: Open/In Progress/Resolved
**نسخه**: X.Y.Z
### توضیحات
توضیحات کامل مشکل
### Steps to Reproduce
1. Step 1
2. Step 2
3. Step 3
### Expected Behavior
رفتار مورد انتظار
### Actual Behavior
رفتار واقعی
### Environment
- OS: Linux/Windows/Mac
- Python: 3.11
- Database: PostgreSQL 14
### Workaround
راه حل موقت (اگر وجود دارد)
### Related
- Ticket: #123
- PR: #456
### Notes
یادداشت‌های اضافی
```
---
## مشکلات حل شده
(لیست مشکلات حل شده به اینجا منتقل می‌شود)

View File

@@ -0,0 +1,65 @@
# Database Migrations History
این فایل تاریخچه تمام migration های دیتابیس را نگه می‌دارد.
## Format
هر migration باید شامل موارد زیر باشد:
- تاریخ
- نام فایل migration
- توضیحات کامل
- جداول/ستون‌های تغییر یافته
- وابستگی‌ها
- دستور rollback
---
## Migrations
### 2025-01-24: Initial Setup
**Migration**: `pending`
**Description**: آماده‌سازی اولیه - هنوز migration اجرا نشده
**Status**: Pending
**Tables**: None yet
**Dependencies**: None
**Rollback**: N/A
**Note**: اولین migration در فاز بعدی (phase0-database) ایجاد خواهد شد.
---
## Template برای Migration های آینده
```markdown
## YYYY-MM-DD: Title
**Migration**: `XXX_description.py`
**Description**: توضیحات کامل تغییرات
**Status**: Applied / Pending / Rolled Back
**Tables**:
- table_name_1
- table_name_2
**Changes**:
- Added column `column_name` to `table_name`
- Created table `new_table`
- Added index on `column_name`
**Dependencies**:
- Previous migration: XXX
**SQL Summary**:
```sql
ALTER TABLE users ADD COLUMN two_factor_enabled BOOLEAN DEFAULT FALSE;
```
**Rollback**:
```bash
alembic downgrade -1
```
**Tested**: Yes/No
**Production Deploy Date**: YYYY-MM-DD
```

View File

@@ -0,0 +1,449 @@
# راهنمای Deployment روی Kubernetes با Helm
این راهنما نحوه deploy کردن پلتفرم Peikarband روی Kubernetes با استفاده از Helm Chart را توضیح می‌دهد.
## پیش‌نیازها
### 1. ابزارهای مورد نیاز
```bash
# Kubectl (v1.24+)
kubectl version --client
# Helm (v3.10+)
helm version
# Docker (برای build local)
docker --version
```
### 2. دسترسی به Kubernetes Cluster
```bash
# تست دسترسی
kubectl cluster-info
kubectl get nodes
```
### 3. Namespace ها
```bash
# ساخت namespace ها
kubectl create namespace production
kubectl create namespace staging
```
## ساختار Helm Chart
```
helm/peikarband/
├── Chart.yaml # Metadata
├── values.yaml # Default values
├── values-production.yaml # Production overrides
├── templates/
│ ├── _helpers.tpl # Helper templates
│ ├── deployment.yaml # Deployment
│ ├── service.yaml # Service
│ ├── ingress.yaml # Ingress
│ ├── configmap.yaml # ConfigMap
│ ├── serviceaccount.yaml
│ ├── hpa.yaml # Horizontal Pod Autoscaler
│ ├── pdb.yaml # Pod Disruption Budget
│ ├── networkpolicy.yaml
│ └── NOTES.txt
└── .helmignore
```
## مراحل Deployment
### 1. آماده‌سازی Secrets
ابتدا باید secrets مورد نیاز را ایجاد کنید:
```bash
# Database credentials
kubectl create secret generic peikarband-secrets \
--from-literal=db-username=peikarband \
--from-literal=db-password=STRONG_PASSWORD_HERE \
--from-literal=redis-password=REDIS_PASSWORD_HERE \
-n production
# برای staging
kubectl create secret generic peikarband-secrets \
--from-literal=db-username=peikarband \
--from-literal=db-password=STAGING_PASSWORD \
--from-literal=redis-password=REDIS_PASSWORD \
-n staging
```
### 2. Build و Push Docker Image
#### روش اول: با GitHub Actions (توصیه می‌شود)
```bash
# فقط یک tag بزنید و GitHub Actions خودکار build و deploy می‌کند
git tag -a v0.1.0 -m "Release v0.1.0"
git push origin v0.1.0
```
#### روش دوم: Build دستی
```bash
# Build image
docker build -t peikarband/landing:0.1.0 .
# Tag for registry
docker tag peikarband/landing:0.1.0 registry.example.com/peikarband/landing:0.1.0
# Push
docker push registry.example.com/peikarband/landing:0.1.0
```
### 3. Validate Helm Chart
قبل از deploy، chart را validate کنید:
```bash
# Lint
helm lint helm/peikarband
# Dry-run
helm install peikarband-test ./helm/peikarband \
--dry-run \
--debug \
--namespace production
# Template rendering
helm template peikarband ./helm/peikarband > rendered.yaml
```
### 4. Deploy به Staging
```bash
helm upgrade --install peikarband-staging ./helm/peikarband \
--namespace staging \
--create-namespace \
--set image.repository=registry.example.com/peikarband/landing \
--set image.tag=0.1.0 \
--set ingress.hosts[0].host=staging.peikarband.ir \
--set replicaCount=2 \
--wait \
--timeout 5m
```
### 5. تست Staging
```bash
# چک کردن pods
kubectl get pods -n staging
# چک کردن logs
kubectl logs -f deployment/peikarband-staging -n staging
# Port forward برای تست local
kubectl port-forward svc/peikarband-staging 3000:3000 -n staging
# تست health check
curl http://localhost:8000/ping
```
### 6. Deploy به Production
```bash
helm upgrade --install peikarband-prod ./helm/peikarband \
--namespace production \
--create-namespace \
--set image.repository=registry.example.com/peikarband/landing \
--set image.tag=0.1.0 \
--values helm/peikarband/values-production.yaml \
--wait \
--timeout 10m
```
## پیکربندی‌های مهم
### 1. تغییر تعداد Replicas
```bash
# با Helm
helm upgrade peikarband-prod ./helm/peikarband \
--namespace production \
--reuse-values \
--set replicaCount=5
# یا با kubectl
kubectl scale deployment peikarband-prod --replicas=5 -n production
```
### 2. Update Image Version
```bash
helm upgrade peikarband-prod ./helm/peikarband \
--namespace production \
--reuse-values \
--set image.tag=0.2.0
```
### 3. تغییر Resources
```bash
helm upgrade peikarband-prod ./helm/peikarband \
--namespace production \
--reuse-values \
--set resources.limits.cpu=2000m \
--set resources.limits.memory=2Gi
```
### 4. فعال/غیرفعال کردن Autoscaling
```bash
# فعال کردن
helm upgrade peikarband-prod ./helm/peikarband \
--namespace production \
--reuse-values \
--set autoscaling.enabled=true \
--set autoscaling.minReplicas=3 \
--set autoscaling.maxReplicas=10
# غیرفعال کردن
helm upgrade peikarband-prod ./helm/peikarband \
--namespace production \
--reuse-values \
--set autoscaling.enabled=false \
--set replicaCount=3
```
## Ingress و SSL/TLS
### نصب cert-manager (برای Let's Encrypt)
```bash
# نصب cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# ساخت ClusterIssuer
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@peikarband.ir
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
```
### پیکربندی DNS
```bash
# A Record برای domain اصلی
peikarband.ir. A YOUR_CLUSTER_IP
# CNAME برای www
www.peikarband.ir. CNAME peikarband.ir.
```
## Monitoring و Logs
### 1. مشاهده Logs
```bash
# تمام pods
kubectl logs -f deployment/peikarband-prod -n production
# یک pod خاص
kubectl logs -f peikarband-prod-xxxxx-yyyyy -n production
# تمام logs (از همه pods)
kubectl logs -l app.kubernetes.io/name=peikarband -n production --tail=100
```
### 2. مشاهده Events
```bash
kubectl get events -n production --sort-by='.lastTimestamp'
```
### 3. چک کردن Resource Usage
```bash
# CPU و Memory
kubectl top pods -n production
# Metrics از deployment
kubectl top deployment peikarband-prod -n production
```
### 4. HPA Status
```bash
kubectl get hpa -n production
kubectl describe hpa peikarband-prod -n production
```
## Rollback
### 1. مشاهده History
```bash
# Helm releases
helm history peikarband-prod -n production
# Kubernetes rollout history
kubectl rollout history deployment/peikarband-prod -n production
```
### 2. Rollback با Helm
```bash
# به نسخه قبلی
helm rollback peikarband-prod -n production
# به نسخه خاص
helm rollback peikarband-prod 3 -n production
```
### 3. Rollback با Kubectl
```bash
# به نسخه قبلی
kubectl rollout undo deployment/peikarband-prod -n production
# به نسخه خاص
kubectl rollout undo deployment/peikarband-prod --to-revision=2 -n production
```
## Troubleshooting
### Pod در حالت Pending
```bash
# بررسی events
kubectl describe pod POD_NAME -n production
# چک کردن resources
kubectl describe nodes
```
### Pod در حالت CrashLoopBackOff
```bash
# مشاهده logs
kubectl logs POD_NAME -n production --previous
# مشاهده events
kubectl describe pod POD_NAME -n production
```
### Image Pull Error
```bash
# چک کردن imagePullSecrets
kubectl get secrets -n production
# بررسی pod
kubectl describe pod POD_NAME -n production
```
### Health Check Failing
```bash
# تست مستقیم health endpoint
kubectl exec -it POD_NAME -n production -- curl localhost:8000/ping
# بررسی liveness/readiness probes
kubectl describe pod POD_NAME -n production
```
## Clean Up
### حذف Release
```bash
# حذف کامل
helm uninstall peikarband-prod -n production
# با نگه داشتن history
helm uninstall peikarband-prod -n production --keep-history
```
### حذف Namespace
```bash
kubectl delete namespace production
```
## Best Practices
### 1. Always use specific image tags
```yaml
image:
tag: "v0.1.0" # ✅ Good
# tag: "latest" # ❌ Bad
```
### 2. Set resource limits
```yaml
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 250m
memory: 512Mi
```
### 3. Enable autoscaling برای production
```yaml
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
```
### 4. استفاده از Pod Disruption Budget
```yaml
podDisruptionBudget:
enabled: true
minAvailable: 1
```
### 5. NetworkPolicy برای امنیت
```yaml
networkPolicy:
enabled: true
```
## CI/CD Integration
### GitHub Actions
workflow ما به صورت خودکار:
1. ✅ Build Docker image
2. ✅ Push به registry
3. ✅ Package Helm chart
4. ✅ Deploy به staging (on push to main)
5. ✅ Deploy به production (on tag)
6. ✅ Create GitHub release
### Manual Trigger
```bash
# از طریق GitHub UI: Actions → CD → Run workflow
```
## Support
برای مشکلات و سوالات:
- 📧 Email: support@peikarband.ir
- 💬 Telegram: @peikarband_support
- 📚 Docs: https://docs.peikarband.ir

View File

@@ -0,0 +1,104 @@
# Quick Start - Deploy در 5 دقیقه
راهنمای سریع برای deploy کردن Peikarband روی Kubernetes.
## پیش‌نیاز
- Kubernetes cluster (v1.24+)
- Helm 3 نصب شده
- kubectl پیکربندی شده
- دسترسی به یک Container Registry
## مراحل
### 1. Clone Repository
```bash
git clone https://github.com/peikarband/landing.git
cd landing
```
### 2. Build Docker Image
```bash
# تغییر REGISTRY به registry خودتون
export REGISTRY=registry.example.com
export IMAGE_NAME=peikarband/landing
export VERSION=0.1.0
docker build -t ${REGISTRY}/${IMAGE_NAME}:${VERSION} .
docker push ${REGISTRY}/${IMAGE_NAME}:${VERSION}
```
### 3. ساخت Secrets
```bash
kubectl create namespace production
kubectl create secret generic peikarband-secrets \
--from-literal=db-username=peikarband \
--from-literal=db-password=YOUR_STRONG_PASSWORD \
--from-literal=redis-password=YOUR_REDIS_PASSWORD \
-n production
```
### 4. Deploy با Helm
```bash
helm upgrade --install peikarband ./helm/peikarband \
--namespace production \
--set image.repository=${REGISTRY}/${IMAGE_NAME} \
--set image.tag=${VERSION} \
--set ingress.hosts[0].host=yourdomain.com \
--wait
```
### 5. چک کردن وضعیت
```bash
# Pods
kubectl get pods -n production
# Service
kubectl get svc -n production
# Ingress
kubectl get ingress -n production
```
### 6. دسترسی به Application
```bash
# Port forward برای تست
kubectl port-forward svc/peikarband 3000:3000 -n production
# باز کردن در browser
open http://localhost:3000
```
## پیکربندی Production
برای production از `values-production.yaml` استفاده کنید:
```bash
helm upgrade --install peikarband ./helm/peikarband \
--namespace production \
--set image.repository=${REGISTRY}/${IMAGE_NAME} \
--set image.tag=${VERSION} \
--values helm/peikarband/values-production.yaml \
--wait
```
## Uninstall
```bash
helm uninstall peikarband -n production
kubectl delete namespace production
```
## بعدش چی؟
- [مستندات کامل Kubernetes](./kubernetes.md)
- [راهنمای CI/CD](../development/ci-cd.md)
- [Troubleshooting](./troubleshooting.md)

View File

@@ -0,0 +1,386 @@
# استانداردهای کدنویسی
## اصول کلی
### 1. کد تمیز (Clean Code)
کد باید:
- خوانا باشد
- ساده باشد
- قابل فهم باشد
- قابل نگهداری باشد
### 2. PEP 8 Compliance
**الزامی**: تمام کدها باید با PEP 8 مطابقت داشته باشند.
استفاده از ابزارها:
```bash
black src/ # Auto-formatting
flake8 src/ # Linting
isort src/ # Import sorting
```
## قوانین Naming
### Classes
```python
# ✅ GOOD: PascalCase
class UserService:
pass
class PaymentGateway:
pass
class DatabaseConnection:
pass
# ❌ BAD
class user_service: # Wrong case
class payment_gateway_service: # Too long
class UServ: # Unclear abbreviation
```
### Functions & Methods
```python
# ✅ GOOD: snake_case, descriptive
def calculate_total_price(items: List[Item]) -> Decimal:
pass
def send_welcome_email(user: User) -> bool:
pass
# ❌ BAD
def calcTotPrice(): # Mixed case
def cp(): # Not descriptive
def calculate(): # Too vague
```
### Variables
```python
# ✅ GOOD
user_email = "test@example.com"
total_amount = Decimal("100.00")
is_active = True
MAX_ATTEMPTS = 3 # Constant
# ❌ BAD
e = "test@example.com" # Too short
userEmail = "test@example.com" # camelCase
TOTAL = 100 # Constant style for variable
```
## Type Hints
**الزامی**: همه function signatures باید type hints داشته باشند.
```python
from typing import Optional, List, Dict, Any
from decimal import Decimal
# ✅ GOOD
def get_user(user_id: int) -> Optional[User]:
pass
def create_invoice(
user_id: int,
amount: Decimal,
items: List[InvoiceItem]
) -> Invoice:
pass
# ❌ BAD
def get_user(user_id): # No types
pass
def create_invoice(user_id, amount, items): # No types
pass
```
## Docstrings
**الزامی**: همه public functions, classes, و methods باید docstring داشته باشند.
### Google Style (استاندارد پروژه)
```python
def create_user(
email: str,
password: str,
full_name: str
) -> User:
"""Create a new user account.
Args:
email: User's email address
password: Plain text password
full_name: User's full name
Returns:
User: Created user object
Raises:
EmailAlreadyExistsException: If email exists
WeakPasswordException: If password is weak
Example:
>>> user = create_user(
... email="test@example.com",
... password="SecurePass123!",
... full_name="John Doe"
... )
"""
pass
```
## Error Handling
### Use Specific Exceptions
```python
# ✅ GOOD
try:
user = user_repository.get_by_id(user_id)
if not user:
raise UserNotFoundException(f"User {user_id} not found")
except DatabaseException as e:
logger.error("database_error", error=str(e))
raise
except Exception as e:
logger.critical("unexpected_error", error=str(e))
raise
# ❌ BAD
try:
user = get_user(user_id)
except: # Never use bare except!
pass
try:
user = get_user(user_id)
except Exception: # Too broad
pass
```
### Custom Exception Hierarchy
```python
class PeikarbandException(Exception):
"""Base exception."""
pass
class DomainException(PeikarbandException):
"""Domain layer exceptions."""
pass
class UserNotFoundException(DomainException):
"""User not found."""
pass
```
## Code Organization
### Imports
```python
# 1. Standard library
import os
import sys
from typing import Optional, List
from decimal import Decimal
# 2. Third-party
import redis
from sqlalchemy import Column, Integer
from pydantic import BaseModel
# 3. Local
from src.config.settings import settings
from src.core.domain.entities.user import User
```
### Function Length
**Max 50 lines per function** (توصیه: 20-30 lines)
```python
# ✅ GOOD: Short and focused
def calculate_discount(amount: Decimal, user: User) -> Decimal:
"""Calculate user discount."""
if user.is_premium:
return amount * Decimal("0.10")
return Decimal("0.00")
# ❌ BAD: Too long (100+ lines)
def process_order(order_data):
# 100+ lines of code
pass
```
### Class Length
**Max 300 lines per class** (توصیه: 100-200 lines)
اگر class بزرگ شد، به چند class کوچکتر تقسیم کنید.
## Comments
### When to Comment
```python
# ✅ GOOD: Explain WHY, not WHAT
def calculate_tax(amount: Decimal) -> Decimal:
# Iranian tax rate is 9% as of 2024
TAX_RATE = Decimal("0.09")
return amount * TAX_RATE
# ❌ BAD: States the obvious
def add(a, b):
# Add two numbers
return a + b
```
### TODO Comments
```python
# TODO(username): Description of what needs to be done
# TODO(john): Implement caching for this query
# FIXME(jane): This breaks when amount is negative
# HACK(bob): Temporary workaround until API is fixed
```
## Best Practices
### 1. Single Responsibility
```python
# ✅ GOOD
class UserRepository:
def save(self, user: User) -> User:
pass
class EmailService:
def send_email(self, to: str, subject: str) -> bool:
pass
# ❌ BAD
class UserManager:
def save_user(self, user):
pass
def send_email(self, user):
pass
def log_activity(self, user):
pass
```
### 2. DRY (Don't Repeat Yourself)
```python
# ❌ BAD
def get_user_name(user_id):
db = connect_db()
user = db.query(User).filter_by(id=user_id).first()
db.close()
return user.name
def get_user_email(user_id):
db = connect_db()
user = db.query(User).filter_by(id=user_id).first()
db.close()
return user.email
# ✅ GOOD
def get_user(user_id: int) -> Optional[User]:
with get_db_context() as db:
return db.query(User).filter_by(id=user_id).first()
def get_user_name(user_id: int) -> Optional[str]:
user = get_user(user_id)
return user.name if user else None
```
### 3. Early Return
```python
# ✅ GOOD: Early return
def process_payment(amount: Decimal) -> bool:
if amount <= 0:
return False
if not user.has_sufficient_balance(amount):
return False
# Process payment
return True
# ❌ BAD: Nested conditions
def process_payment(amount):
if amount > 0:
if user.has_sufficient_balance(amount):
# Process payment
return True
return False
```
### 4. Use Constants
```python
# ✅ GOOD
MAX_LOGIN_ATTEMPTS = 3
PASSWORD_MIN_LENGTH = 8
SESSION_TIMEOUT_MINUTES = 30
if attempts >= MAX_LOGIN_ATTEMPTS:
lock_account()
# ❌ BAD: Magic numbers
if attempts >= 3: # What's 3?
lock_account()
```
### 5. Avoid Deep Nesting
```python
# ✅ GOOD: Max 2-3 levels
def process_order(order: Order) -> bool:
if not order.is_valid():
return False
if not user.can_order():
return False
return save_order(order)
# ❌ BAD: Too nested
def process_order(order):
if order:
if order.is_valid():
if user:
if user.can_order():
if save_order(order):
return True
return False
```
## Code Review Checklist
قبل از ارسال PR، این موارد را بررسی کنید:
- [ ] همه تست‌ها pass می‌شوند
- [ ] Code coverage کافی است (>80%)
- [ ] تمام functions دارای type hints هستند
- [ ] تمام public functions دارای docstring هستند
- [ ] black, flake8, mypy بدون error
- [ ] هیچ TODO/FIXME جدید بدون توضیح نیست
- [ ] تغییرات در CHANGELOG.md ثبت شده
- [ ] مستندات به‌روز شده
---
**الزامات**: این استانداردها الزامی هستند و در code review بررسی می‌شوند.

View File

@@ -0,0 +1,433 @@
# Git Workflow
## Branch Strategy
### Main Branches
```
main (production)
└── develop (staging)
```
- **main**: Production-ready code
- **develop**: Integration branch for features
### Supporting Branches
```
develop
├── feature/user-authentication
├── feature/billing-system
├── bugfix/payment-timeout
└── hotfix/security-patch
```
## Branch Naming Convention
### Feature Branches
```
feature/short-description
feature/user-auth
feature/payment-gateway
```
### Bugfix Branches
```
bugfix/issue-description
bugfix/payment-timeout
bugfix/email-sending
```
### Hotfix Branches
```
hotfix/critical-issue
hotfix/security-vuln
hotfix/data-loss
```
## Commit Message Format
### Structure
```
<type>(<scope>): <subject>
<body>
<footer>
```
### Type
- `feat`: ویژگی جدید
- `fix`: رفع باگ
- `docs`: تغییرات مستندات
- `style`: فرمت‌بندی، فاصله‌گذاری
- `refactor`: بازنویسی کد
- `perf`: بهبود performance
- `test`: اضافه/اصلاح تست
- `chore`: کارهای نگهداری
- `build`: تغییرات build system
- `ci`: تغییرات CI/CD
### Scope
بخشی از کد که تغییر کرده:
- `auth`
- `billing`
- `api`
- `database`
- `ui`
### Examples
```bash
feat(auth): add two-factor authentication
- Implement TOTP-based 2FA
- Add QR code generation
- Add backup codes
Closes #123
```
```bash
fix(payment): resolve timeout issue with zarinpal
The payment gateway was timing out due to long response time.
Added retry logic with exponential backoff.
Fixes #456
```
```bash
docs(api): update authentication endpoints
Added examples for JWT token usage and refresh flow.
```
## Workflow Steps
### 1. شروع کار روی Feature جدید
```bash
# بروزرسانی develop
git checkout develop
git pull origin develop
# ایجاد branch جدید
git checkout -b feature/my-feature
# شروع کدنویسی...
```
### 2. Commit های منظم
```bash
# بررسی تغییرات
git status
git diff
# Stage کردن
git add src/specific/file.py
# یا
git add .
# Commit
git commit -m "feat(scope): description"
# Push (اولین بار)
git push -u origin feature/my-feature
# Push های بعدی
git push
```
### 3. نگهداری Branch به‌روز
```bash
# بروزرسانی از develop
git checkout develop
git pull origin develop
git checkout feature/my-feature
git merge develop
# حل conflict ها (در صورت وجود)
# ... edit files ...
git add .
git commit -m "merge: resolve conflicts with develop"
```
### 4. ایجاد Pull Request
1. Push کردن همه commits
2. رفتن به GitHub/GitLab
3. Create Pull Request
4. انتخاب base: `develop`, compare: `feature/my-feature`
5. عنوان و توضیحات
6. Request reviewers
7. Link کردن issues
### 5. Code Review Process
**برای نویسنده**:
- پاسخ به comments
- انجام تغییرات درخواستی
- Push کردن updates
- Request re-review
**برای Reviewer**:
- بررسی کد با دقت
- Check کردن tests
- بررسی code quality
- Approve یا Request Changes
### 6. Merge
پس از approval:
```bash
# Squash and Merge (توصیه می‌شود)
# یا
# Merge Commit
# یا
# Rebase and Merge
```
### 7. پاک کردن Branch
```bash
# Local
git branch -d feature/my-feature
# Remote (معمولا automatic)
git push origin --delete feature/my-feature
```
## Best Practices
### Commit های کوچک و مشخص
```bash
# ✅ GOOD: Small, focused commits
git commit -m "feat(auth): add email validation"
git commit -m "test(auth): add tests for email validation"
git commit -m "docs(auth): document email validation"
# ❌ BAD: Large, vague commit
git commit -m "add stuff"
```
### Commit منظم
```bash
# Commit هر چند ساعت یکبار
# نه خیلی کم (هر خط کد)
# نه خیلی زیاد (روزی یک commit)
```
### Push روزانه
```bash
# حداقل یکبار در روز push کنید
# از دست رفتن کد جلوگیری می‌کند
```
### استفاده از .gitignore
```bash
# اضافه نکردن فایل‌های غیرضروری
# - __pycache__
# - .env
# - venv/
# - *.pyc
```
## Git Commands Reference
### Basic
```bash
# Status
git status
# Log
git log
git log --oneline
git log --graph
# Diff
git diff
git diff --staged
```
### Branching
```bash
# لیست branches
git branch
git branch -a # با remote
# ایجاد branch
git branch feature/new-feature
git checkout -b feature/new-feature # ایجاد + checkout
# تغییر branch
git checkout develop
# حذف branch
git branch -d feature/old-feature
git branch -D feature/force-delete # Force
```
### Stashing
```bash
# ذخیره تغییرات موقت
git stash
# با پیام
git stash save "work in progress"
# لیست stash ها
git stash list
# برگرداندن stash
git stash apply
git stash pop # apply + delete
# حذف stash
git stash drop
git stash clear # همه
```
### Undoing Changes
```bash
# Unstage file
git reset HEAD file.py
# Discard changes
git checkout -- file.py
# Undo last commit (keep changes)
git reset --soft HEAD~1
# Undo last commit (discard changes)
git reset --hard HEAD~1
# Revert commit (safe)
git revert <commit-hash>
```
### Rebasing
```bash
# Rebase on develop
git checkout feature/my-feature
git rebase develop
# Interactive rebase
git rebase -i HEAD~3 # آخرین 3 commits
# Abort rebase
git rebase --abort
# Continue after resolving conflicts
git rebase --continue
```
### Tags
```bash
# ایجاد tag
git tag v1.0.0
# با پیام
git tag -a v1.0.0 -m "Release version 1.0.0"
# Push tags
git push origin v1.0.0
git push origin --tags # همه tags
# حذف tag
git tag -d v1.0.0
git push origin --delete v1.0.0
```
## Merge Conflicts
### حل Conflict
```bash
# 1. مشاهده conflicts
git status
# 2. باز کردن فایل و حل conflict
# <<<<<<< HEAD
# کد شما
# =======
# کد دیگران
# >>>>>>> branch-name
# 3. Stage کردن
git add resolved-file.py
# 4. Commit
git commit -m "merge: resolve conflicts"
```
### ابزارهای کمکی
```bash
# استفاده از merge tool
git mergetool
# استفاده از --ours یا --theirs
git checkout --ours file.py
git checkout --theirs file.py
```
## Tips & Tricks
### Aliases
```bash
# در ~/.gitconfig
[alias]
co = checkout
br = branch
ci = commit
st = status
lg = log --oneline --graph --decorate
```
### Commit Templates
```bash
# در ~/.gitmessage
<type>(<scope>): <subject>
<body>
<footer>
```
```bash
git config --global commit.template ~/.gitmessage
```
### Auto-completion
```bash
# Bash
source /usr/share/bash-completion/completions/git
# Zsh
autoload -Uz compinit && compinit
```
---
**مهم**: این workflow الزامی است و در code review بررسی می‌شود.

371
docs/development/setup.md Normal file
View File

@@ -0,0 +1,371 @@
# راهنمای راه‌اندازی محیط توسعه
## پیش‌نیازها
### نرم‌افزارهای مورد نیاز
| نرم‌افزار | نسخه مورد نیاز | لینک نصب |
|-----------|----------------|----------|
| Python | 3.11+ | [python.org](https://python.org) |
| PostgreSQL | 14+ | [postgresql.org](https://postgresql.org) |
| Redis | 7+ | [redis.io](https://redis.io) |
| Node.js | 18+ | [nodejs.org](https://nodejs.org) |
| Git | 2.x+ | [git-scm.com](https://git-scm.com) |
### بررسی نسخه‌ها
```bash
python --version # باید 3.11 یا بالاتر
psql --version # باید 14 یا بالاتر
redis-cli --version # باید 7 یا بالاتر
node --version # باید 18 یا بالاتر
git --version
```
## نصب
### 1. Clone Repository
```bash
git clone https://github.com/yourusername/peikarband.git
cd peikarband
```
### 2. Virtual Environment
```bash
# ایجاد virtual environment
python -m venv venv
# فعال‌سازی
# Linux/Mac:
source venv/bin/activate
# Windows:
venv\Scripts\activate
```
### 3. نصب Dependencies
```bash
# Core dependencies
pip install -r requirements.txt
# Development dependencies
pip install -r requirements-dev.txt
# تایید نصب
pip list
```
### 4. Pre-commit Hooks
```bash
# نصب pre-commit hooks
pre-commit install
# تست (اختیاری)
pre-commit run --all-files
```
## تنظیمات
### 1. Environment Variables
```bash
# کپی فایل example
cp .env.example .env
# ویرایش .env
nano .env
```
**متغیرهای ضروری**:
```env
# Database
DATABASE_URL=postgresql://username:password@localhost:5432/peikarband
# Redis
REDIS_URL=redis://localhost:6379/0
# Security
SECRET_KEY=generate-a-secure-random-key
JWT_SECRET_KEY=generate-another-secure-random-key
# Celery
CELERY_BROKER_URL=redis://localhost:6379/1
CELERY_RESULT_BACKEND=redis://localhost:6379/2
```
**تولید Secret Key**:
```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```
### 2. PostgreSQL Setup
```bash
# ایجاد دیتابیس
createdb peikarband
# یا با psql
psql -U postgres
CREATE DATABASE peikarband;
CREATE USER peikarband_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE peikarband TO peikarband_user;
\q
```
### 3. Redis Setup
```bash
# شروع Redis
redis-server
# تست
redis-cli ping
# باید "PONG" برگرداند
```
### 4. Database Migrations
```bash
# اجرای migrations
alembic upgrade head
# بررسی
alembic current
```
### 5. Seed Database (اختیاری)
```bash
python scripts/seed_database.py
```
## اجرای پروژه
### Development Mode
```bash
# روش 1: با Reflex
python -m reflex run
# روش 2: با auto-reload
python -m reflex run --reload
# روش 3: با loglevel
python -m reflex run --loglevel debug
```
### Background Services
**Terminal 1**: Celery Worker
```bash
celery -A src.infrastructure.tasks.celery_app worker -l info
```
**Terminal 2**: Celery Beat (برای scheduled tasks)
```bash
celery -A src.infrastructure.tasks.celery_app beat -l info
```
**Terminal 3**: Flower (Celery monitoring)
```bash
celery -A src.infrastructure.tasks.celery_app flower
# دسترسی: http://localhost:5555
```
### دسترسی به Application
- **Frontend**: http://localhost:3000
- **Backend API**: http://localhost:8000
- **Flower (Celery)**: http://localhost:5555
## Development Workflow
### 1. Branch جدید
```bash
git checkout develop
git pull origin develop
git checkout -b feature/your-feature-name
```
### 2. کدنویسی
```bash
# قبل از commit
black src/
isort src/
flake8 src/
mypy src/
# یا با pre-commit
pre-commit run --all-files
```
### 3. Testing
```bash
# همه تست‌ها
pytest
# با coverage
pytest --cov=src tests/
# تست‌های خاص
pytest tests/unit/
pytest tests/integration/
# تست یک فایل
pytest tests/unit/test_user.py
# تست یک function
pytest tests/unit/test_user.py::test_create_user
```
### 4. Commit
```bash
git add .
git commit -m "feat(scope): description"
git push origin feature/your-feature-name
```
### 5. Pull Request
1. Create PR از feature branch به develop
2. منتظر code review باشید
3. اصلاحات لازم را انجام دهید
4. Merge
## Troubleshooting
### مشکل: ModuleNotFoundError
```bash
# مطمئن شوید که در virtual environment هستید
which python
# باید به venv اشاره کند
# نصب مجدد
pip install -r requirements.txt
```
### مشکل: Database Connection
```bash
# بررسی PostgreSQL
sudo systemctl status postgresql
# شروع PostgreSQL
sudo systemctl start postgresql
# تست connection
psql -U username -d peikarband
```
### مشکل: Redis Connection
```bash
# بررسی Redis
redis-cli ping
# شروع Redis
redis-server
# بررسی port
netstat -an | grep 6379
```
### مشکل: Port Already in Use
```bash
# پیدا کردن process
lsof -i :3000
lsof -i :8000
# Kill process
kill -9 <PID>
```
## ابزارهای مفید
### IDE Setup
**VS Code Extensions**:
- Python
- Pylance
- Python Test Explorer
- GitLens
- Better Comments
**PyCharm**: تنظیمات پیشنهادی در `.idea/` موجود است.
### Database Tools
- **pgAdmin**: GUI برای PostgreSQL
- **DBeaver**: Multi-database tool
- **TablePlus**: Modern database GUI
### API Testing
- **httpie**: `pip install httpie`
- **Postman**: Desktop app
- **Insomnia**: Alternative to Postman
## بروزرسانی Dependencies
```bash
# بررسی dependencies قدیمی
pip list --outdated
# بروزرسانی یک package
pip install --upgrade package-name
# بروزرسانی همه (با دقت!)
pip install --upgrade -r requirements.txt
# ذخیره نسخه‌های جدید
pip freeze > requirements.txt
```
## مشکلات رایج
### Import Error
اطمینان حاصل کنید که:
- Virtual environment فعال است
- `PYTHONPATH` صحیح است
- همه `__init__.py` ها موجودند
### Alembic Migration Error
```bash
# Reset alembic
alembic downgrade base
alembic upgrade head
# ایجاد migration جدید
alembic revision --autogenerate -m "description"
```
### Test Failures
```bash
# پاک کردن cache
pytest --cache-clear
# اجرا با verbose
pytest -vv
# اجرا با output کامل
pytest -s
```
---
**نکته**: این راهنما همیشه به‌روز نگه داشته می‌شود. در صورت مشکل به team lead مراجعه کنید.

902
docs/handbook.md Normal file
View File

@@ -0,0 +1,902 @@
# پروژه پیکربند - Handbook جامع
**نسخه**: 1.0.0
**آخرین بروزرسانی**: 2025-01-24
**نویسنده**: تیم توسعه پیکربند
---
## فهرست مطالب
1. [معرفی پروژه](#1-معرفی-پروژه)
2. [معماری کلی](#2-معماری-کلی)
3. [قوانین و استانداردها](#3-قوانین-و-استانداردها)
4. [راه‌اندازی محیط توسعه](#4-راهاندازی-محیط-توسعه)
5. [ساختار پروژه](#5-ساختار-پروژه)
6. [Design Patterns](#6-design-patterns)
7. [Best Practices](#7-best-practices)
8. [Testing Strategy](#8-testing-strategy)
9. [Deployment Guide](#9-deployment-guide)
10. [Troubleshooting](#10-troubleshooting)
11. [مستندسازی تغییرات](#11-مستندسازی-تغییرات)
---
## 1. معرفی پروژه
### 1.1 هدف
پیکربند یک پلتفرم جامع مدیریت هاستینگ و زیرساخت ابری است که شامل:
- **هاستینگ وردپرس**: مدیریت حرفه‌ای سایت‌های WordPress
- **فروش دامین**: ثبت و مدیریت دامین‌ها
- **سرورهای اختصاصی**: VPS و Dedicated Servers
- **خدمات DevOps**: مشاوره و پیاده‌سازی
- **پنل مدیریت**: پنل کنترل سفارشی شبیه cPanel
### 1.2 تکنولوژی‌های استفاده شده
| تکنولوژی | نسخه | کاربرد |
|----------|------|--------|
| Python | 3.11+ | زبان اصلی |
| Reflex | 0.4.0 | Frontend/Backend Framework |
| PostgreSQL | 14+ | Database اصلی |
| Redis | 7+ | Cache & Sessions |
| SQLAlchemy | 2.0+ | ORM |
| Celery | 5.3+ | Task Queue |
| pytest | 7.4+ | Testing |
### 1.3 اصول طراحی
#### Clean Architecture
پروژه بر اساس معماری تمیز طراحی شده که شامل 4 لایه اصلی است:
1. **Domain Layer**: منطق کسب‌وکار خالص
2. **Application Layer**: موارد استفاده (Use Cases)
3. **Infrastructure Layer**: جزئیات فنی
4. **Presentation Layer**: رابط کاربری
#### SOLID Principles
- **S**ingle Responsibility Principle
- **O**pen/Closed Principle
- **L**iskov Substitution Principle
- **I**nterface Segregation Principle
- **D**ependency Inversion Principle
#### سایر اصول
- **DRY**: Don't Repeat Yourself
- **KISS**: Keep It Simple, Stupid
- **YAGNI**: You Aren't Gonna Need It
- **Convention over Configuration**
---
## 2. معماری کلی
### 2.1 نمای کلی لایه‌ها
```
┌─────────────────────────────────────────┐
│ Presentation Layer (Reflex UI) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Landing │ │Dashboard│ │ Admin │ │
│ └────────┘ └────────┘ └────────┘ │
└──────────────────┬──────────────────────┘
┌──────────────────┴──────────────────────┐
│ Application Layer (Use Cases) │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Auth │ │Billing│ │Server│ │Ticket│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└──────────────────┬──────────────────────┘
┌──────────────────┴──────────────────────┐
│ Domain Layer (Business Logic) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Entities│ │ Values │ │Services│ │
│ └────────┘ └────────┘ └────────┘ │
└──────────────────┬──────────────────────┘
┌──────────────────┴──────────────────────┐
│ Infrastructure Layer (Technical) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ DB │ │Cache│ │ API │ │Tasks│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
└─────────────────────────────────────────┘
```
### 2.2 جریان داده
```
User Request → Presentation → Application → Domain
Infrastructure ← Application ← Domain Logic
External APIs
```
### 2.3 Layer های جزئی
#### Domain Layer
- **Entities**: موجودیت‌های اصلی (User, Service, Invoice, etc.)
- **Value Objects**: اشیاء ارزشی (Email, Money, Phone)
- **Domain Services**: منطق پیچیده دامین
- **Enums**: مقادیر ثابت
- **Exceptions**: خطاهای دامین
#### Application Layer
- **Use Cases**: موارد استفاده سیستم
- **DTOs**: Data Transfer Objects
- **Interfaces**: تعریف رابط‌های سرویس‌ها
- **Validators**: اعتبارسنجی ورودی‌ها
#### Infrastructure Layer
- **Database**: Models و Repositories
- **Cache**: Redis implementation
- **External**: API های خارجی
- **Tasks**: Background jobs
- **Security**: Authentication & Authorization
- **Logging**: Structured logging
#### Presentation Layer
- **Web**: Reflex components و pages
- **API**: REST endpoints (optional)
- **State**: Reflex state management
---
## 3. قوانین و استانداردها
### 3.1 Python Code Style (PEP 8)
#### Naming Conventions
```python
# Classes: PascalCase
class UserService:
pass
class PaymentGateway:
pass
# Functions & Methods: snake_case
def create_user(email: str) -> User:
pass
def process_payment(amount: Decimal) -> PaymentResult:
pass
# Constants: UPPER_SNAKE_CASE
MAX_LOGIN_ATTEMPTS = 3
DEFAULT_TIMEOUT = 30
API_VERSION = "v1"
# Private methods: _leading_underscore
def _internal_helper():
pass
# Protected: __double_underscore (name mangling)
class BaseClass:
def __private_method(self):
pass
# Variables: snake_case
user_email = "test@example.com"
total_amount = Decimal("100.00")
```
### 3.2 Type Hints (الزامی)
```python
from typing import Optional, List, Dict, Any, Union
from decimal import Decimal
# Function signatures
def get_user_by_id(user_id: int) -> Optional[User]:
pass
def create_invoice(
user_id: int,
amount: Decimal,
items: List[InvoiceItem]
) -> Invoice:
pass
# Return types
def get_users(limit: int = 10) -> List[User]:
pass
def find_service(service_id: int) -> Optional[Service]:
pass
# Complex types
def process_config(config: Dict[str, Any]) -> bool:
pass
UserOrNone = Optional[User]
ServiceList = List[Service]
```
### 3.3 Docstrings (Google Style - الزامی)
```python
def create_user(
email: str,
password: str,
full_name: str,
phone: Optional[str] = None
) -> User:
"""Create a new user account.
This function creates a new user in the system with the provided
information. Password is automatically hashed before storage.
Args:
email: User's email address (must be unique)
password: Plain text password (will be hashed)
full_name: User's full name
phone: Optional phone number
Returns:
User: Created user object
Raises:
EmailAlreadyExistsException: If email is already registered
InvalidEmailException: If email format is invalid
WeakPasswordException: If password doesn't meet requirements
Example:
>>> user = create_user(
... email="test@example.com",
... password="SecurePass123!",
... full_name="John Doe",
... phone="+989123456789"
... )
>>> print(user.id)
1
"""
pass
```
### 3.4 Import Order (با isort)
```python
# 1. Standard library imports
import os
import sys
from typing import Optional, List
from decimal import Decimal
from datetime import datetime
# 2. Third-party imports
import redis
from sqlalchemy import Column, Integer, String
from pydantic import BaseModel
# 3. Local application imports
from src.config.settings import settings
from src.core.domain.entities.user import User
from src.infrastructure.database.repositories.user_repository import UserRepository
```
### 3.5 Error Handling
```python
# ✅ GOOD: Specific exceptions
try:
user = user_repository.get_by_id(user_id)
if not user:
raise UserNotFoundException(f"User {user_id} not found")
except DatabaseException as e:
logger.error("database_error", error=str(e), user_id=user_id)
raise
except Exception as e:
logger.critical("unexpected_error", error=str(e))
raise InfrastructureException("Internal server error") from e
# ❌ BAD: Generic exceptions
try:
# code
except: # Never do this!
pass
# ❌ BAD: Too broad
try:
# code
except Exception:
pass
```
### 3.6 Logging Best Practices
```python
import structlog
logger = structlog.get_logger(__name__)
# ✅ GOOD: Structured logging with context
logger.info(
"user_created",
user_id=user.id,
email=user.email,
registration_source="web",
ip_address=request.ip
)
logger.error(
"payment_failed",
user_id=user.id,
invoice_id=invoice.id,
amount=str(invoice.amount),
gateway="zarinpal",
error_code=response.code,
error_message=response.message
)
# ❌ BAD: String concatenation
logger.info("User " + str(user.id) + " created")
```
---
## 4. راه‌اندازی محیط توسعه
### 4.1 پیش‌نیازها
```bash
# Check Python version (3.11+)
python --version
# Check PostgreSQL (14+)
psql --version
# Check Redis (7+)
redis-cli --version
# Check Node.js (18+)
node --version
```
### 4.2 نصب وابستگی‌ها
```bash
# Clone repository
git clone https://github.com/yourusername/peikarband.git
cd peikarband
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Install pre-commit hooks
pre-commit install
```
### 4.3 تنظیم Environment Variables
```bash
# Copy example env file
cp .env.example .env
# Edit .env file
nano .env
# Required variables:
# - DATABASE_URL
# - REDIS_URL
# - SECRET_KEY
# - JWT_SECRET_KEY
```
### 4.4 راه‌اندازی دیتابیس
```bash
# Create database
createdb peikarband
# Or using psql
psql -U postgres
CREATE DATABASE peikarband;
\q
# Run migrations
alembic upgrade head
# (Optional) Seed database
python scripts/seed_database.py
```
### 4.5 اجرای پروژه
```bash
# Development mode
python -m reflex run
# With auto-reload
python -m reflex run --reload
# Production mode
python -m reflex run --env production
```
---
## 5. ساختار پروژه
### 5.1 نمای کلی
```
peikarband/
├── docs/ # Documentation
├── src/ # Source code
├── tests/ # Tests
├── scripts/ # Utility scripts
├── .github/workflows/ # CI/CD
└── [config files] # Configuration
```
### 5.2 src/ Structure
```
src/
├── config/ # Configuration
│ ├── settings.py # Main settings
│ ├── database.py # DB config
│ ├── cache.py # Redis config
│ └── logging.py # Log config
├── core/ # Core business logic
│ ├── domain/ # Domain layer
│ │ ├── entities/ # Business entities
│ │ ├── value_objects/ # Value objects
│ │ ├── enums/ # Enumerations
│ │ └── exceptions/ # Domain exceptions
│ │
│ ├── application/ # Application layer
│ │ ├── use_cases/ # Use cases
│ │ ├── dto/ # DTOs
│ │ ├── interfaces/ # Service interfaces
│ │ └── validators/ # Validators
│ │
│ └── utils/ # Core utilities
├── infrastructure/ # Infrastructure layer
│ ├── database/ # Database implementation
│ ├── cache/ # Cache implementation
│ ├── external/ # External APIs
│ ├── tasks/ # Background tasks
│ ├── security/ # Security utilities
│ └── logging/ # Logging setup
├── presentation/ # Presentation layer
│ ├── web/ # Reflex web app
│ │ ├── pages/ # Pages
│ │ ├── components/ # Components
│ │ ├── state/ # State management
│ │ └── styles/ # Styling
│ │
│ └── api/ # REST API (optional)
└── shared/ # Shared code
├── events/ # Domain events
└── messaging/ # Event bus
```
---
## 6. Design Patterns
### 6.1 Repository Pattern
```python
from abc import ABC, abstractmethod
from typing import Optional, List
# Interface (in core/application/interfaces/repositories/)
class IUserRepository(ABC):
@abstractmethod
def get_by_id(self, user_id: int) -> Optional[User]:
"""Get user by ID."""
pass
@abstractmethod
def get_by_email(self, email: str) -> Optional[User]:
"""Get user by email."""
pass
@abstractmethod
def save(self, user: User) -> User:
"""Save or update user."""
pass
@abstractmethod
def delete(self, user_id: int) -> bool:
"""Delete user."""
pass
# Implementation (in infrastructure/database/repositories/)
class UserRepository(IUserRepository):
def __init__(self, session: Session):
self._session = session
def get_by_id(self, user_id: int) -> Optional[User]:
model = self._session.query(UserModel).filter_by(id=user_id).first()
return self._to_entity(model) if model else None
def save(self, user: User) -> User:
model = self._to_model(user)
self._session.add(model)
self._session.commit()
self._session.refresh(model)
return self._to_entity(model)
```
### 6.2 Unit of Work Pattern
```python
from contextlib import contextmanager
class UnitOfWork:
"""Manages database transactions."""
def __init__(self, session_factory):
self.session_factory = session_factory
self._session = None
def __enter__(self):
self._session = self.session_factory()
self.users = UserRepository(self._session)
self.services = ServiceRepository(self._session)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
self._session.rollback()
else:
self._session.commit()
self._session.close()
# Usage
with UnitOfWork(SessionLocal) as uow:
user = uow.users.get_by_id(1)
user.email = "new@email.com"
uow.users.save(user)
# Automatically commits on success
```
### 6.3 Factory Pattern
```python
from abc import ABC, abstractmethod
class ICloudProvider(ABC):
@abstractmethod
def create_server(self, config: ServerConfig) -> Server:
pass
class DigitalOceanProvider(ICloudProvider):
def create_server(self, config: ServerConfig) -> Server:
# Implementation
pass
class HetznerProvider(ICloudProvider):
def create_server(self, config: ServerConfig) -> Server:
# Implementation
pass
class CloudProviderFactory:
@staticmethod
def create(provider_type: str) -> ICloudProvider:
providers = {
"digitalocean": DigitalOceanProvider,
"hetzner": HetznerProvider,
"ovh": OVHProvider,
}
provider_class = providers.get(provider_type)
if not provider_class:
raise ValueError(f"Unknown provider: {provider_type}")
return provider_class()
# Usage
provider = CloudProviderFactory.create("digitalocean")
server = provider.create_server(config)
```
### 6.4 Strategy Pattern
```python
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: Decimal) -> PaymentResult:
pass
class ZarinpalPayment(PaymentStrategy):
def pay(self, amount: Decimal) -> PaymentResult:
# Zarinpal implementation
pass
class IdPayPayment(PaymentStrategy):
def pay(self, amount: Decimal) -> PaymentResult:
# IdPay implementation
pass
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self._strategy = strategy
def process(self, amount: Decimal) -> PaymentResult:
return self._strategy.pay(amount)
# Usage
processor = PaymentProcessor(ZarinpalPayment())
result = processor.process(Decimal("100.00"))
```
---
## 7. Best Practices
### 7.1 کد تمیز
```python
# ✅ GOOD: Clear and simple
def calculate_total_price(items: List[Item]) -> Decimal:
"""Calculate total price of items."""
return sum(item.price * item.quantity for item in items)
# ❌ BAD: Too complex
def calc(i):
t = 0
for x in i:
t += x[0] * x[1]
return t
```
### 7.2 Don't Repeat Yourself (DRY)
```python
# ❌ BAD: Repetition
def get_user_name(user_id: int) -> str:
session = SessionLocal()
user = session.query(User).filter_by(id=user_id).first()
session.close()
return user.name
def get_user_email(user_id: int) -> str:
session = SessionLocal()
user = session.query(User).filter_by(id=user_id).first()
session.close()
return user.email
# ✅ GOOD: Reusable
def get_user(user_id: int) -> User:
with get_db_context() as session:
return session.query(User).filter_by(id=user_id).first()
def get_user_name(user_id: int) -> str:
user = get_user(user_id)
return user.name if user else None
```
### 7.3 Single Responsibility
```python
# ❌ BAD: Multiple responsibilities
class User:
def save(self):
# Saves to database
pass
def send_email(self):
# Sends email
pass
def calculate_discount(self):
# Business logic
pass
# ✅ GOOD: Separated
class User:
# Just entity
pass
class UserRepository:
def save(self, user: User):
pass
class EmailService:
def send_welcome_email(self, user: User):
pass
class DiscountService:
def calculate_user_discount(self, user: User):
pass
```
---
## 8. Testing Strategy
### 8.1 Test Pyramid
```
/\
/E2E\ 10% - End-to-End Tests
/------\
/Integr-\ 20% - Integration Tests
/----------\
/ Unit \ 70% - Unit Tests
```
### 8.2 Unit Test Example
```python
import pytest
from decimal import Decimal
class TestInvoiceCalculation:
def test_calculate_total_with_items(self):
# Arrange
items = [
InvoiceItem(name="Item 1", price=Decimal("10.00"), quantity=2),
InvoiceItem(name="Item 2", price=Decimal("5.00"), quantity=3),
]
invoice = Invoice(items=items)
# Act
total = invoice.calculate_total()
# Assert
assert total == Decimal("35.00")
def test_calculate_total_empty(self):
invoice = Invoice(items=[])
assert invoice.calculate_total() == Decimal("0.00")
```
### 8.3 Integration Test Example
```python
@pytest.mark.integration
class TestUserRepository:
def test_save_and_retrieve_user(self, db_session):
# Arrange
repository = UserRepository(db_session)
user = User(email="test@example.com", name="Test User")
# Act
saved_user = repository.save(user)
retrieved_user = repository.get_by_id(saved_user.id)
# Assert
assert retrieved_user is not None
assert retrieved_user.email == "test@example.com"
```
---
## 9. Deployment Guide
### 9.1 Production Checklist
- [ ] همه تست‌ها pass شدند
- [ ] Code coverage بالای 80%
- [ ] Security audit انجام شد
- [ ] Environment variables set شدند
- [ ] Database migrations اجرا شدند
- [ ] Monitoring setup شد
- [ ] Backup strategy تعریف شد
- [ ] SSL certificates نصب شدند
- [ ] Firewall rules تنظیم شدند
---
## 10. Troubleshooting
### 10.1 مشکلات رایج
#### Database Connection Error
**Problem**: `sqlalchemy.exc.OperationalError`
**Solution**:
1. Check DATABASE_URL in .env
2. Verify PostgreSQL is running
3. Check firewall rules
4. Test connection: `psql $DATABASE_URL`
#### Redis Connection Error
**Problem**: `redis.exceptions.ConnectionError`
**Solution**:
1. Check REDIS_URL in .env
2. Verify Redis is running: `redis-cli ping`
3. Check Redis password if set
---
## 11. مستندسازی تغییرات
### 11.1 Commit Message Format
```
<type>(<scope>): <subject>
<body>
<footer>
```
**Types**:
- feat: ویژگی جدید
- fix: رفع باگ
- docs: تغییرات مستندات
- style: فرمت‌بندی
- refactor: بازنویسی
- test: اضافه کردن تست
- chore: کارهای نگهداری
**Example**:
```
feat(auth): add two-factor authentication
- Implement TOTP-based 2FA
- Add QR code generation for authenticator apps
- Add backup codes feature
Closes #123
```
### 11.2 CHANGELOG.md
تمام تغییرات باید در CHANGELOG.md ثبت شود:
```markdown
# Changelog
## [Unreleased]
## [1.0.0] - 2025-01-24
### Added
- User authentication system
- Two-factor authentication
- Password reset functionality
### Changed
- Improved database schema
- Updated UI components
### Fixed
- Payment gateway timeout issue
- Email verification bug
```
---
## پیوست‌ها
### A. فایل‌های مهم
- `docs/architecture/`: معماری سیستم
- `docs/development/`: راهنمای توسعه
- `docs/changelog/`: تاریخچه تغییرات
- `docs/api/`: مستندات API
### B. لینک‌های مفید
- [Python PEP 8](https://pep8.org/)
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID)
---
**این handbook باید همیشه به‌روز نگه داشته شود.**

24
helm/.helmignore Normal file
View File

@@ -0,0 +1,24 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,24 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,18 @@
apiVersion: v2
name: peikarband
description: Peikarband Cloud Services Platform - Landing Page
type: application
version: 0.1.0
appVersion: "0.1.0"
keywords:
- cloud
- hosting
- devops
- wordpress
maintainers:
- name: Peikarband Team
email: support@peikarband.ir
sources:
- https://github.com/peikarband/landing
icon: https://peikarband.ir/logo.png

164
helm/peikarband/README.md Normal file
View File

@@ -0,0 +1,164 @@
# Peikarband Helm Chart
Official Helm chart برای deploy کردن Peikarband Cloud Platform.
## نصب
### اضافه کردن Repository
```bash
helm repo add peikarband https://peikarband.github.io/charts
helm repo update
```
### نصب Chart
```bash
helm install peikarband peikarband/peikarband \
--namespace production \
--create-namespace \
--set image.tag=0.1.0
```
## پیکربندی
### مهم‌ترین Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `replicaCount` | int | `2` | تعداد replicas |
| `image.repository` | string | `registry.example.com/peikarband/landing` | Docker image repository |
| `image.tag` | string | `latest` | Image tag |
| `image.pullPolicy` | string | `IfNotPresent` | Image pull policy |
| `resources.limits.cpu` | string | `1000m` | CPU limit |
| `resources.limits.memory` | string | `1Gi` | Memory limit |
| `autoscaling.enabled` | bool | `true` | فعال کردن HPA |
| `autoscaling.minReplicas` | int | `2` | حداقل replicas |
| `autoscaling.maxReplicas` | int | `10` | حداکثر replicas |
| `ingress.enabled` | bool | `true` | فعال کردن Ingress |
| `ingress.hosts[0].host` | string | `peikarband.ir` | Domain |
### مثال‌های استفاده
#### نصب با مقادیر سفارشی
```bash
helm install peikarband peikarband/peikarband \
--set image.tag=0.2.0 \
--set replicaCount=3 \
--set resources.limits.cpu=2000m \
--set ingress.hosts[0].host=example.com
```
#### استفاده از values file
```bash
helm install peikarband peikarband/peikarband \
-f my-values.yaml
```
#### Upgrade
```bash
helm upgrade peikarband peikarband/peikarband \
--set image.tag=0.3.0 \
--reuse-values
```
## Requirements
- Kubernetes 1.24+
- Helm 3.10+
- PostgreSQL (external یا in-cluster)
- Redis (external یا in-cluster)
## Values فایل‌ها
این chart شامل چند values file است:
- `values.yaml` - مقادیر پیش‌فرض
- `values-production.yaml` - تنظیمات production
## Components
این Chart شامل موارد زیر است:
- **Deployment**: اجرای application
- **Service**: ClusterIP service برای internal access
- **Ingress**: External access با TLS
- **ConfigMap**: تنظیمات application
- **ServiceAccount**: Kubernetes service account
- **HPA**: Horizontal Pod Autoscaler
- **PDB**: Pod Disruption Budget
- **NetworkPolicy**: محدودیت‌های network
## پیش‌نیازها
### ساخت Secrets
```bash
kubectl create secret generic peikarband-secrets \
--from-literal=db-username=USERNAME \
--from-literal=db-password=PASSWORD \
--from-literal=redis-password=REDIS_PASS \
-n production
```
### cert-manager (برای TLS)
```bash
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
```
## Troubleshooting
### مشاهده وضعیت
```bash
helm status peikarband -n production
kubectl get all -n production
kubectl get pods -l app.kubernetes.io/name=peikarband -n production
```
### مشاهده Logs
```bash
kubectl logs -f deployment/peikarband -n production
```
### Rollback
```bash
helm rollback peikarband -n production
```
## توسعه
### Lint
```bash
helm lint .
```
### Template
```bash
helm template peikarband . --debug
```
### Package
```bash
helm package .
```
## لایسنس
MIT
## پشتیبانی
- Email: support@peikarband.ir
- Website: https://peikarband.ir
- Docs: https://docs.peikarband.ir

View File

@@ -0,0 +1,43 @@
Thank you for installing {{ .Chart.Name }}.
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm status {{ .Release.Name }}
$ helm get all {{ .Release.Name }}
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "peikarband.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "peikarband.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "peikarband.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.frontend.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "peikarband.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
2. Check the deployment status:
kubectl get deployment {{ include "peikarband.fullname" . }} -n {{ .Release.Namespace }}
3. View logs:
kubectl logs -f deployment/{{ include "peikarband.fullname" . }} -n {{ .Release.Namespace }}
4. Scale the deployment:
kubectl scale deployment {{ include "peikarband.fullname" . }} --replicas=3 -n {{ .Release.Namespace }}
Peikarband - Cloud Services Platform

View File

@@ -0,0 +1,61 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "peikarband.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "peikarband.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "peikarband.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "peikarband.labels" -}}
helm.sh/chart: {{ include "peikarband.chart" . }}
{{ include "peikarband.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "peikarband.selectorLabels" -}}
app.kubernetes.io/name: {{ include "peikarband.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "peikarband.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "peikarband.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
data:
{{- toYaml .Values.configMap.data | nindent 2 }}

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "peikarband.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "peikarband.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "peikarband.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: backend
containerPort: {{ .Values.service.backend.targetPort }}
protocol: TCP
- name: frontend
containerPort: {{ .Values.service.frontend.targetPort }}
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- if .Values.postgresql.enabled }}
- name: DATABASE_HOST
value: {{ .Values.postgresql.external.host }}
- name: DATABASE_PORT
value: {{ .Values.postgresql.external.port | quote }}
- name: DATABASE_NAME
value: {{ .Values.postgresql.external.database }}
- name: DATABASE_USER
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.usernameSecret.name }}
key: {{ .Values.postgresql.external.usernameSecret.key }}
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.external.passwordSecret.name }}
key: {{ .Values.postgresql.external.passwordSecret.key }}
{{- end }}
{{- if .Values.redis.enabled }}
- name: REDIS_HOST
value: {{ .Values.redis.external.host }}
- name: REDIS_PORT
value: {{ .Values.redis.external.port | quote }}
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.redis.external.passwordSecret.name }}
key: {{ .Values.redis.external.passwordSecret.key }}
{{- end }}
envFrom:
- configMapRef:
name: {{ include "peikarband.fullname" . }}
{{- with .Values.envFrom }}
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,33 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "peikarband.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,42 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "peikarband.fullname" $ }}
port:
number: {{ $.Values.service.frontend.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,19 @@
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "peikarband.selectorLabels" . | nindent 6 }}
policyTypes:
{{- toYaml .Values.networkPolicy.policyTypes | nindent 4 }}
ingress:
{{- toYaml .Values.networkPolicy.ingress | nindent 4 }}
egress:
{{- toYaml .Values.networkPolicy.egress | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
spec:
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
selector:
matchLabels:
{{- include "peikarband.selectorLabels" . | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "peikarband.fullname" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.backend.port }}
targetPort: backend
protocol: TCP
name: backend
- port: {{ .Values.service.frontend.port }}
targetPort: frontend
protocol: TCP
name: frontend
selector:
{{- include "peikarband.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "peikarband.serviceAccountName" . }}
labels:
{{- include "peikarband.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,93 @@
# Production-specific values for peikarband
# This file overrides default values.yaml for production
replicaCount: 3
image:
pullPolicy: Always
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 60
targetMemoryUtilizationPercentage: 70
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
hosts:
- host: peikarband.ir
paths:
- path: /
pathType: Prefix
- host: www.peikarband.ir
paths:
- path: /
pathType: Prefix
tls:
- secretName: peikarband-tls
hosts:
- peikarband.ir
- www.peikarband.ir
postgresql:
enabled: true
external:
host: "postgres-prod.default.svc.cluster.local"
port: "5432"
database: "peikarband_prod"
usernameSecret:
name: "peikarband-prod-secrets"
key: "db-username"
passwordSecret:
name: "peikarband-prod-secrets"
key: "db-password"
redis:
enabled: true
external:
host: "redis-prod.default.svc.cluster.local"
port: "6379"
passwordSecret:
name: "peikarband-prod-secrets"
key: "redis-password"
configMap:
data:
APP_NAME: "peikarband"
LOG_LEVEL: "warning"
ENVIRONMENT: "production"
podDisruptionBudget:
enabled: true
minAvailable: 2
networkPolicy:
enabled: true
monitoring:
serviceMonitor:
enabled: true
interval: 30s
scrapeTimeout: 10s

View File

@@ -0,0 +1,63 @@
# Staging-specific values for peikarband
replicaCount: 1
image:
pullPolicy: Always
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
autoscaling:
enabled: false
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-staging"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: staging.peikarband.ir
paths:
- path: /
pathType: Prefix
tls:
- secretName: peikarband-staging-tls
hosts:
- staging.peikarband.ir
postgresql:
enabled: true
external:
host: "postgres-staging.default.svc.cluster.local"
port: "5432"
database: "peikarband_staging"
redis:
enabled: true
external:
host: "redis-staging.default.svc.cluster.local"
port: "6379"
configMap:
data:
APP_NAME: "peikarband-staging"
LOG_LEVEL: "debug"
ENVIRONMENT: "staging"
podDisruptionBudget:
enabled: false
networkPolicy:
enabled: false
monitoring:
serviceMonitor:
enabled: false

201
helm/peikarband/values.yaml Normal file
View File

@@ -0,0 +1,201 @@
# Default values for peikarband
# This is a YAML-formatted file.
replicaCount: 2
image:
repository: registry.example.com/peikarband/landing
pullPolicy: IfNotPresent
tag: "latest"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
service:
type: ClusterIP
backend:
port: 8000
targetPort: 8000
frontend:
port: 3000
targetPort: 3000
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
hosts:
- host: peikarband.ir
paths:
- path: /
pathType: Prefix
- host: www.peikarband.ir
paths:
- path: /
pathType: Prefix
tls:
- secretName: peikarband-tls
hosts:
- peikarband.ir
- www.peikarband.ir
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 250m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- peikarband
topologyKey: kubernetes.io/hostname
livenessProbe:
httpGet:
path: /ping
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ping
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
env:
- name: REFLEX_ENV
value: "production"
- name: PYTHONUNBUFFERED
value: "1"
envFrom: []
configMap:
data:
APP_NAME: "peikarband"
LOG_LEVEL: "info"
secretRef:
name: "peikarband-secrets"
postgresql:
enabled: false
external:
host: "postgres.default.svc.cluster.local"
port: "5432"
database: "peikarband"
usernameSecret:
name: "peikarband-secrets"
key: "db-username"
passwordSecret:
name: "peikarband-secrets"
key: "db-password"
redis:
enabled: false
external:
host: "redis.default.svc.cluster.local"
port: "6379"
passwordSecret:
name: "peikarband-secrets"
key: "redis-password"
persistence:
enabled: false
storageClass: ""
accessMode: ReadWriteOnce
size: 10Gi
podDisruptionBudget:
enabled: true
minAvailable: 1
networkPolicy:
enabled: true
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8000
- protocol: TCP
port: 3000
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 5432 # PostgreSQL
- protocol: TCP
port: 6379 # Redis
- protocol: TCP
port: 443 # HTTPS
- protocol: TCP
port: 80 # HTTP
- protocol: UDP
port: 53 # DNS
monitoring:
serviceMonitor:
enabled: false
interval: 30s
scrapeTimeout: 10s

30
mypy.ini Normal file
View File

@@ -0,0 +1,30 @@
[mypy]
# Mypy configuration for type checking
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
disallow_any_unimported = False
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = True
warn_unreachable = True
strict_equality = True
[mypy-tests.*]
disallow_untyped_defs = False
[mypy-reflex.*]
ignore_missing_imports = True
[mypy-sqlalchemy.*]
ignore_missing_imports = True
[mypy-redis.*]
ignore_missing_imports = True
[mypy-celery.*]
ignore_missing_imports = True

6
peikarband/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
"""Peikarband application package."""
from .peikarband import app
__all__ = ["app"]

10
peikarband/peikarband.py Normal file
View File

@@ -0,0 +1,10 @@
"""Peikarband main application entry point."""
import reflex as rx
# Import landing page
from src.presentation.web.pages.landing.index import app as landing_app
# Create main app
app = landing_app

43
pytest.ini Normal file
View File

@@ -0,0 +1,43 @@
[pytest]
# Pytest configuration for Peikarband
# Test discovery
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# Output
addopts =
-v
--strict-markers
--tb=short
--cov=src
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80
# Markers
markers =
unit: Unit tests
integration: Integration tests
e2e: End-to-end tests
slow: Slow tests
fast: Fast tests
database: Tests that require database
redis: Tests that require Redis
external: Tests that call external APIs
# Coverage
[coverage:run]
source = src
omit =
*/tests/*
*/migrations/*
*/__init__.py
[coverage:report]
precision = 2
show_missing = True
skip_covered = False

58
requirements-dev.txt Normal file
View File

@@ -0,0 +1,58 @@
# Peikarband Platform - Development Dependencies
# Include core requirements
-r requirements.txt
# ============================================
# Testing
# ============================================
pytest==7.4.3
pytest-cov==4.1.0
pytest-asyncio==0.21.1
pytest-mock==3.12.0
pytest-xdist==3.5.0
factory-boy==3.3.0
faker==20.1.0
# ============================================
# Code Quality
# ============================================
black==23.12.0
flake8==6.1.0
isort==5.13.2
mypy==1.7.1
pylint==3.0.3
autopep8==2.0.4
# ============================================
# Type Stubs
# ============================================
types-redis==4.6.0.11
types-requests==2.31.0.10
types-python-dateutil==2.8.19.14
# ============================================
# Pre-commit Hooks
# ============================================
pre-commit==3.5.0
# ============================================
# Documentation
# ============================================
mkdocs==1.5.3
mkdocs-material==9.5.2
mkdocstrings[python]==0.24.0
# ============================================
# Development Tools
# ============================================
ipython==8.18.1
ipdb==0.13.13
rich==13.7.0
# ============================================
# Security Scanning
# ============================================
bandit==1.7.5
safety==2.3.5

90
requirements.txt Normal file
View File

@@ -0,0 +1,90 @@
# Peikarband Platform - Core Dependencies
# ============================================
# Core Framework
# ============================================
reflex==0.4.0
# ============================================
# Database & ORM
# ============================================
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
alembic==1.13.0
# ============================================
# Data Validation
# ============================================
pydantic==2.5.2
pydantic-settings==2.1.0
email-validator==2.1.0
# ============================================
# Caching
# ============================================
redis==5.0.1
# ============================================
# Task Queue
# ============================================
celery==5.3.4
flower==2.0.1
# ============================================
# Security & Authentication
# ============================================
passlib[bcrypt]==1.7.4
pyjwt==2.8.0
pyotp==2.9.0
cryptography==41.0.7
# ============================================
# Cloud Provider APIs
# ============================================
python-digitalocean==1.17.0
hcloud==1.33.2
python-ovh==1.1.0
# ============================================
# Payment Gateways
# ============================================
zarinpal==1.0.0
idpay==1.0.0
# ============================================
# HTTP Client
# ============================================
httpx==0.25.2
requests==2.31.0
# ============================================
# Logging
# ============================================
structlog==23.2.0
python-json-logger==2.0.7
# ============================================
# Monitoring & Error Tracking
# ============================================
sentry-sdk==1.38.0
prometheus-client==0.19.0
# ============================================
# Utilities
# ============================================
python-decouple==3.8
python-dotenv==1.0.0
tenacity==8.2.3
python-multipart==0.0.6
# ============================================
# Server Management
# ============================================
paramiko==3.4.0
fabric==3.2.2
# ============================================
# Date & Time
# ============================================
python-dateutil==2.8.2
pytz==2023.3

19
rxconfig.py Normal file
View File

@@ -0,0 +1,19 @@
"""Reflex configuration file.
This file configures the Reflex application settings.
"""
import reflex as rx
config = rx.Config(
app_name="peikarband",
api_url="http://localhost:8000",
frontend_port=3000,
backend_port=8000,
db_url="sqlite:///reflex.db", # Temporary, will use PostgreSQL
disable_plugins=["reflex.plugins.sitemap.SitemapPlugin"],
stylesheets=[
"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap",
"https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css",
],
)

39
setup.py Normal file
View File

@@ -0,0 +1,39 @@
"""Setup configuration for Peikarband platform."""
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
with open("requirements.txt", "r", encoding="utf-8") as fh:
requirements = [line.strip() for line in fh if line.strip() and not line.startswith("#")]
setup(
name="peikarband",
version="0.1.0",
author="Peikarband Team",
author_email="support@peikarband.ir",
description="پلتفرم جامع مدیریت هاستینگ و زیرساخت ابری",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/yourusername/peikarband",
packages=find_packages(where="src"),
package_dir={"": "src"},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
python_requires=">=3.11",
install_requires=requirements,
entry_points={
"console_scripts": [
"peikarband=src.presentation.web.app:main",
],
},
)

0
src/__init__.py Normal file
View File

0
src/config/__init__.py Normal file
View File

106
src/config/cache.py Normal file
View File

@@ -0,0 +1,106 @@
"""Redis cache configuration and management.
This module handles Redis connections and caching operations.
"""
from typing import Optional, Any
from redis import Redis, ConnectionPool
import json
from src.config.settings import settings
# Redis Connection Pool
redis_pool = ConnectionPool.from_url(
settings.REDIS_URL,
max_connections=settings.REDIS_MAX_CONNECTIONS,
decode_responses=True,
)
# Redis Client
redis_client = Redis(connection_pool=redis_pool)
def get_redis() -> Redis:
"""Get Redis client instance.
Returns:
Redis: Redis client
"""
return redis_client
def cache_set(
key: str,
value: Any,
ttl: Optional[int] = None
) -> bool:
"""Set cache value.
Args:
key: Cache key
value: Value to cache
ttl: Time to live in seconds
Returns:
bool: True if successful
"""
try:
serialized_value = json.dumps(value)
if ttl:
return redis_client.setex(key, ttl, serialized_value)
return redis_client.set(key, serialized_value)
except Exception:
return False
def cache_get(key: str) -> Optional[Any]:
"""Get cache value.
Args:
key: Cache key
Returns:
Optional[Any]: Cached value or None
"""
try:
value = redis_client.get(key)
if value:
return json.loads(value)
return None
except Exception:
return None
def cache_delete(key: str) -> bool:
"""Delete cache value.
Args:
key: Cache key
Returns:
bool: True if successful
"""
try:
return bool(redis_client.delete(key))
except Exception:
return False
def cache_clear_pattern(pattern: str) -> int:
"""Clear cache by pattern.
Args:
pattern: Key pattern (e.g., "user:*")
Returns:
int: Number of keys deleted
"""
try:
keys = redis_client.keys(pattern)
if keys:
return redis_client.delete(*keys)
return 0
except Exception:
return 0

85
src/config/database.py Normal file
View File

@@ -0,0 +1,85 @@
"""Database configuration and connection management.
This module handles database connections, session management, and connection pooling.
"""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.ext.declarative import declarative_base
from contextlib import contextmanager
from typing import Generator
from src.config.settings import settings
# SQLAlchemy Base
Base = declarative_base()
# Database Engine
engine = create_engine(
settings.DATABASE_URL,
pool_size=settings.DATABASE_POOL_SIZE,
max_overflow=settings.DATABASE_MAX_OVERFLOW,
pool_pre_ping=True, # Enable connection health checks
echo=settings.DEBUG, # Log SQL queries in debug mode
)
# Session Factory
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
)
def get_session() -> Generator[Session, None, None]:
"""Get database session.
Yields:
Session: SQLAlchemy session
Example:
>>> with get_session() as session:
... user = session.query(User).first()
"""
session = SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
@contextmanager
def get_db_context() -> Generator[Session, None, None]:
"""Context manager for database session.
Yields:
Session: SQLAlchemy session
"""
session = SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
def init_db() -> None:
"""Initialize database (create all tables).
Note: In production, use Alembic migrations instead.
"""
Base.metadata.create_all(bind=engine)
def close_db() -> None:
"""Close database connections."""
engine.dispose()

View File

56
src/config/logging.py Normal file
View File

@@ -0,0 +1,56 @@
"""Logging configuration using structlog.
This module sets up structured logging for the application.
"""
import logging
import structlog
from typing import Any
from src.config.settings import settings
def setup_logging() -> None:
"""Configure structured logging with structlog."""
# Configure structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.dev.set_exc_info,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.JSONRenderer() if settings.LOG_FORMAT == "json"
else structlog.dev.ConsoleRenderer(),
],
wrapper_class=structlog.make_filtering_bound_logger(
logging.getLevelName(settings.LOG_LEVEL)
),
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=True,
)
# Configure standard logging
logging.basicConfig(
format="%(message)s",
level=logging.getLevelName(settings.LOG_LEVEL),
)
def get_logger(name: str) -> Any:
"""Get a logger instance.
Args:
name: Logger name (usually __name__)
Returns:
Logger instance
Example:
>>> logger = get_logger(__name__)
>>> logger.info("user_created", user_id=123, email="test@example.com")
"""
return structlog.get_logger(name)

74
src/config/settings.py Normal file
View File

@@ -0,0 +1,74 @@
"""Main configuration settings for Peikarband platform.
This module contains all configuration settings loaded from environment variables.
Follows PEP 8 and clean code principles.
"""
from typing import Optional
from pydantic import BaseSettings, Field, validator
import os
class Settings(BaseSettings):
"""Main settings class using Pydantic for validation."""
# Application
APP_NAME: str = "Peikarband"
APP_VERSION: str = "0.1.0"
DEBUG: bool = Field(default=False, env="DEBUG")
ENVIRONMENT: str = Field(default="development", env="ENVIRONMENT")
# Database
DATABASE_URL: str = Field(..., env="DATABASE_URL")
DATABASE_POOL_SIZE: int = Field(default=20, env="DATABASE_POOL_SIZE")
DATABASE_MAX_OVERFLOW: int = Field(default=10, env="DATABASE_MAX_OVERFLOW")
# Redis
REDIS_URL: str = Field(..., env="REDIS_URL")
REDIS_MAX_CONNECTIONS: int = Field(default=50, env="REDIS_MAX_CONNECTIONS")
# Security
SECRET_KEY: str = Field(..., env="SECRET_KEY")
JWT_SECRET_KEY: str = Field(..., env="JWT_SECRET_KEY")
JWT_ALGORITHM: str = Field(default="HS256", env="JWT_ALGORITHM")
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(default=30, env="JWT_ACCESS_TOKEN_EXPIRE_MINUTES")
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = Field(default=7, env="JWT_REFRESH_TOKEN_EXPIRE_DAYS")
# Celery
CELERY_BROKER_URL: str = Field(..., env="CELERY_BROKER_URL")
CELERY_RESULT_BACKEND: str = Field(..., env="CELERY_RESULT_BACKEND")
# External APIs
DIGITALOCEAN_API_KEY: Optional[str] = Field(default=None, env="DIGITALOCEAN_API_KEY")
HETZNER_API_KEY: Optional[str] = Field(default=None, env="HETZNER_API_KEY")
OVH_API_KEY: Optional[str] = Field(default=None, env="OVH_API_KEY")
# Payment Gateways
ZARINPAL_MERCHANT_ID: Optional[str] = Field(default=None, env="ZARINPAL_MERCHANT_ID")
IDPAY_API_KEY: Optional[str] = Field(default=None, env="IDPAY_API_KEY")
# Logging
LOG_LEVEL: str = Field(default="INFO", env="LOG_LEVEL")
LOG_FORMAT: str = Field(default="json", env="LOG_FORMAT")
# CORS
CORS_ORIGINS: list[str] = Field(default=["*"], env="CORS_ORIGINS")
class Config:
"""Pydantic configuration."""
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = True
@validator("ENVIRONMENT")
def validate_environment(cls, v: str) -> str:
"""Validate environment value."""
allowed = ["development", "staging", "production"]
if v not in allowed:
raise ValueError(f"ENVIRONMENT must be one of {allowed}")
return v
# Global settings instance
settings = Settings()

0
src/core/__init__.py Normal file
View File

View File

View File

View File

View File

View File

@@ -0,0 +1,67 @@
"""Base entity for domain layer.
This module defines the base entity class that all domain entities inherit from.
"""
from datetime import datetime
from typing import Optional
class BaseEntity:
"""Base domain entity.
All domain entities should inherit from this class.
This is NOT a database model, but a pure domain object.
Attributes:
id: Entity identifier
created_at: Creation timestamp
updated_at: Last update timestamp
"""
def __init__(
self,
id: Optional[int] = None,
created_at: Optional[datetime] = None,
updated_at: Optional[datetime] = None
):
"""Initialize base entity.
Args:
id: Entity identifier
created_at: Creation timestamp
updated_at: Last update timestamp
"""
self.id = id
self.created_at = created_at or datetime.utcnow()
self.updated_at = updated_at or datetime.utcnow()
def __eq__(self, other: object) -> bool:
"""Check equality based on ID.
Args:
other: Other object
Returns:
bool: True if same entity
"""
if not isinstance(other, self.__class__):
return False
return self.id == other.id
def __hash__(self) -> int:
"""Hash based on ID.
Returns:
int: Hash value
"""
return hash(self.id)
def __repr__(self) -> str:
"""String representation.
Returns:
str: String representation
"""
return f"<{self.__class__.__name__}(id={self.id})>"

View File

View File

View File

@@ -0,0 +1,59 @@
"""Base exceptions for domain layer.
This module defines the exception hierarchy for the domain layer.
"""
class PeikarbandException(Exception):
"""Base exception for all custom exceptions."""
def __init__(self, message: str, code: str = "INTERNAL_ERROR"):
"""Initialize exception.
Args:
message: Error message
code: Error code
"""
self.message = message
self.code = code
super().__init__(self.message)
class DomainException(PeikarbandException):
"""Base exception for domain layer."""
def __init__(self, message: str, code: str = "DOMAIN_ERROR"):
"""Initialize domain exception.
Args:
message: Error message
code: Error code
"""
super().__init__(message, code)
class ValidationException(DomainException):
"""Validation error in domain."""
def __init__(self, message: str, field: str = ""):
"""Initialize validation exception.
Args:
message: Error message
field: Field that failed validation
"""
self.field = field
super().__init__(message, "VALIDATION_ERROR")
class BusinessRuleException(DomainException):
"""Business rule violation."""
def __init__(self, message: str):
"""Initialize business rule exception.
Args:
message: Error message
"""
super().__init__(message, "BUSINESS_RULE_VIOLATION")

View File

@@ -0,0 +1,104 @@
"""User-related domain exceptions."""
from src.core.domain.exceptions.base import DomainException, ValidationException
class UserNotFoundException(DomainException):
"""User not found exception."""
def __init__(self, user_id: int = None, email: str = None):
"""Initialize exception.
Args:
user_id: User ID
email: User email
"""
if user_id:
message = f"User with ID {user_id} not found"
elif email:
message = f"User with email {email} not found"
else:
message = "User not found"
super().__init__(message, "USER_NOT_FOUND")
class EmailAlreadyExistsException(DomainException):
"""Email already exists exception."""
def __init__(self, email: str):
"""Initialize exception.
Args:
email: Email address
"""
super().__init__(
f"Email {email} is already registered",
"EMAIL_ALREADY_EXISTS"
)
class InvalidEmailException(ValidationException):
"""Invalid email format exception."""
def __init__(self, email: str):
"""Initialize exception.
Args:
email: Invalid email
"""
super().__init__(
f"Invalid email format: {email}",
field="email"
)
class WeakPasswordException(ValidationException):
"""Weak password exception."""
def __init__(self, reason: str = "Password does not meet requirements"):
"""Initialize exception.
Args:
reason: Reason why password is weak
"""
super().__init__(reason, field="password")
class InvalidCredentialsException(DomainException):
"""Invalid login credentials exception."""
def __init__(self):
"""Initialize exception."""
super().__init__(
"Invalid email or password",
"INVALID_CREDENTIALS"
)
class AccountLockedException(DomainException):
"""Account is locked exception."""
def __init__(self, reason: str = "Account is locked"):
"""Initialize exception.
Args:
reason: Reason for lock
"""
super().__init__(reason, "ACCOUNT_LOCKED")
class InsufficientBalanceException(DomainException):
"""Insufficient wallet balance exception."""
def __init__(self, required: str, available: str):
"""Initialize exception.
Args:
required: Required amount
available: Available amount
"""
super().__init__(
f"Insufficient balance. Required: {required}, Available: {available}",
"INSUFFICIENT_BALANCE"
)

View File

@@ -0,0 +1,94 @@
"""Email value object.
Email is an immutable value object that represents a validated email address.
"""
import re
from typing import Any
class Email:
"""Email value object.
Represents a validated email address.
Immutable - once created, cannot be changed.
Attributes:
value: The email address string
"""
EMAIL_REGEX = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
def __init__(self, value: str):
"""Initialize email.
Args:
value: Email address string
Raises:
ValueError: If email format is invalid
"""
if not self._is_valid(value):
raise ValueError(f"Invalid email format: {value}")
self._value = value.lower().strip()
@property
def value(self) -> str:
"""Get email value.
Returns:
str: Email address
"""
return self._value
@staticmethod
def _is_valid(email: str) -> bool:
"""Validate email format.
Args:
email: Email string to validate
Returns:
bool: True if valid
"""
if not email or not isinstance(email, str):
return False
return bool(Email.EMAIL_REGEX.match(email.strip()))
def __str__(self) -> str:
"""String representation.
Returns:
str: Email address
"""
return self._value
def __repr__(self) -> str:
"""Developer representation.
Returns:
str: Email representation
"""
return f"Email('{self._value}')"
def __eq__(self, other: Any) -> bool:
"""Check equality.
Args:
other: Other object
Returns:
bool: True if equal
"""
if not isinstance(other, Email):
return False
return self._value == other._value
def __hash__(self) -> int:
"""Hash value.
Returns:
int: Hash
"""
return hash(self._value)

View File

@@ -0,0 +1,202 @@
"""Money value object.
Money represents a monetary amount with currency.
"""
from decimal import Decimal
from typing import Any, Union
class Money:
"""Money value object.
Represents a monetary amount with currency.
Immutable and provides monetary operations.
Attributes:
amount: Decimal amount
currency: Currency code (e.g., 'IRR', 'USD')
"""
def __init__(self, amount: Union[Decimal, int, float, str], currency: str = "IRR"):
"""Initialize money.
Args:
amount: Monetary amount
currency: Currency code (default: IRR for Iranian Rial)
Raises:
ValueError: If amount is negative or currency is invalid
"""
self._amount = Decimal(str(amount))
if self._amount < 0:
raise ValueError("Amount cannot be negative")
if not currency or len(currency) != 3:
raise ValueError(f"Invalid currency code: {currency}")
self._currency = currency.upper()
@property
def amount(self) -> Decimal:
"""Get amount.
Returns:
Decimal: Amount
"""
return self._amount
@property
def currency(self) -> str:
"""Get currency.
Returns:
str: Currency code
"""
return self._currency
def add(self, other: "Money") -> "Money":
"""Add two money values.
Args:
other: Other money value
Returns:
Money: New money object with sum
Raises:
ValueError: If currencies don't match
"""
self._check_currency(other)
return Money(self._amount + other._amount, self._currency)
def subtract(self, other: "Money") -> "Money":
"""Subtract money value.
Args:
other: Other money value
Returns:
Money: New money object with difference
Raises:
ValueError: If currencies don't match or result is negative
"""
self._check_currency(other)
result = self._amount - other._amount
if result < 0:
raise ValueError("Result cannot be negative")
return Money(result, self._currency)
def multiply(self, multiplier: Union[int, float, Decimal]) -> "Money":
"""Multiply amount by a factor.
Args:
multiplier: Multiplication factor
Returns:
Money: New money object
"""
result = self._amount * Decimal(str(multiplier))
return Money(result, self._currency)
def _check_currency(self, other: "Money") -> None:
"""Check if currencies match.
Args:
other: Other money value
Raises:
ValueError: If currencies don't match
"""
if self._currency != other._currency:
raise ValueError(
f"Currency mismatch: {self._currency} vs {other._currency}"
)
def __str__(self) -> str:
"""String representation.
Returns:
str: Formatted money string
"""
return f"{self._amount:,.2f} {self._currency}"
def __repr__(self) -> str:
"""Developer representation.
Returns:
str: Money representation
"""
return f"Money({self._amount}, '{self._currency}')"
def __eq__(self, other: Any) -> bool:
"""Check equality.
Args:
other: Other object
Returns:
bool: True if equal
"""
if not isinstance(other, Money):
return False
return self._amount == other._amount and self._currency == other._currency
def __lt__(self, other: "Money") -> bool:
"""Less than comparison.
Args:
other: Other money value
Returns:
bool: True if less than
"""
self._check_currency(other)
return self._amount < other._amount
def __le__(self, other: "Money") -> bool:
"""Less than or equal comparison.
Args:
other: Other money value
Returns:
bool: True if less than or equal
"""
self._check_currency(other)
return self._amount <= other._amount
def __gt__(self, other: "Money") -> bool:
"""Greater than comparison.
Args:
other: Other money value
Returns:
bool: True if greater than
"""
self._check_currency(other)
return self._amount > other._amount
def __ge__(self, other: "Money") -> bool:
"""Greater than or equal comparison.
Args:
other: Other money value
Returns:
bool: True if greater than or equal
"""
self._check_currency(other)
return self._amount >= other._amount
def __hash__(self) -> int:
"""Hash value.
Returns:
int: Hash
"""
return hash((self._amount, self._currency))

View File

@@ -0,0 +1,140 @@
"""Phone number value object.
Phone represents a validated phone number.
"""
import re
from typing import Any
class Phone:
"""Phone number value object.
Represents a validated Iranian phone number.
Supports both mobile and landline numbers.
Attributes:
value: Phone number string (normalized format)
"""
# Iranian mobile: starts with 09, 10 digits
MOBILE_REGEX = re.compile(r'^09\d{9}$')
# International format: +989...
INTERNATIONAL_REGEX = re.compile(r'^\+989\d{9}$')
def __init__(self, value: str):
"""Initialize phone number.
Args:
value: Phone number string
Raises:
ValueError: If phone format is invalid
"""
normalized = self._normalize(value)
if not self._is_valid(normalized):
raise ValueError(f"Invalid phone number: {value}")
self._value = normalized
@property
def value(self) -> str:
"""Get phone number.
Returns:
str: Phone number
"""
return self._value
@property
def international_format(self) -> str:
"""Get international format.
Returns:
str: +989XXXXXXXXX format
"""
if self._value.startswith('+'):
return self._value
return f"+98{self._value[1:]}"
@staticmethod
def _normalize(phone: str) -> str:
"""Normalize phone number.
Removes spaces, dashes, parentheses.
Args:
phone: Phone string
Returns:
str: Normalized phone
"""
if not phone:
return ""
# Remove common separators
phone = re.sub(r'[\s\-\(\)]', '', phone.strip())
# Convert international format to local
if phone.startswith('+98'):
phone = '0' + phone[3:]
elif phone.startswith('0098'):
phone = '0' + phone[4:]
elif phone.startswith('98') and len(phone) == 12:
phone = '0' + phone[2:]
return phone
@staticmethod
def _is_valid(phone: str) -> bool:
"""Validate phone number.
Args:
phone: Normalized phone string
Returns:
bool: True if valid
"""
if not phone or not isinstance(phone, str):
return False
# Check if matches mobile pattern
return bool(Phone.MOBILE_REGEX.match(phone))
def __str__(self) -> str:
"""String representation.
Returns:
str: Phone number
"""
return self._value
def __repr__(self) -> str:
"""Developer representation.
Returns:
str: Phone representation
"""
return f"Phone('{self._value}')"
def __eq__(self, other: Any) -> bool:
"""Check equality.
Args:
other: Other object
Returns:
bool: True if equal
"""
if not isinstance(other, Phone):
return False
return self._value == other._value
def __hash__(self) -> int:
"""Hash value.
Returns:
int: Hash
"""
return hash(self._value)

View File

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

Some files were not shown because too many files have changed in this diff Show More