Files
TJWaterServerBinary/SECONDARY_DEVELOPMENT_GUIDE.md
T

28 KiB

TJWater ServerBinary - Secondary Development Documentation Guide

Executive Summary

TJWaterServerBinary is a FastAPI-based water distribution network management system with integrated EPANET simulation, SCADA data management, and comprehensive security architecture. This guide identifies key components and patterns essential for secondary development.


1. Project Structure & Key Directories

Overall Architecture

TJWaterServerBinary/
├── app/
│   ├── main.py                 # FastAPI application entry + lifecycle management
│   ├── api/v1/                 # API routes and endpoints
│   ├── algorithms/             # Core algorithms (simulation, leakage detection, etc.)
│   ├── auth/                   # Authentication logic and dependencies
│   ├── core/                   # Core configuration, security, encryption
│   ├── domain/                 # Domain models and Pydantic schemas
│   ├── infra/                  # Infrastructure layer (DB, cache, audit)
│   ├── services/               # Business logic services
│   ├── native/                 # Native module integration (binary/compiled code)
│   └── utils/                  # Utility functions
├── infra/docker/               # Docker and deployment configs
├── resources/sql/              # SQL initialization scripts
├── tests/                       # Unit and integration tests
└── requirements.txt            # Python dependencies

Key Directory Details

Directory Purpose Key Files
api/v1 REST API endpoints organized by domain router.py (centralized registration), endpoints/ (individual controllers)
domain Data models and schemas models/role.py, schemas/user.py, schemas/audit.py
infra Database access, caching, audit db/postgresql/, db/timescaledb/, cache/, audit/
auth JWT/OAuth2 validation, permissions dependencies.py, permissions.py, keycloak_dependencies.py
core Security, encryption, config config.py, security.py, encryption.py, audit.py
native Python-to-binary integration api/ module wrapping compiled code
services Business logic & orchestration tjnetwork.py (project management), simulation.py, etc.

2. API Endpoint Addition (Router Registration)

How to Add New API Endpoints

Step 1: Create Endpoint File

Create a new file in app/api/v1/endpoints/ with your endpoint handlers:

# app/api/v1/endpoints/my_feature.py
from fastapi import APIRouter, Depends, HTTPException, status
from app.auth.dependencies import get_current_active_user
from app.domain.schemas.user import UserInDB
from app.infra.repositories.my_repository import MyRepository
from app.auth.dependencies import get_user_repository

router = APIRouter()

@router.get("/my-feature/")
async def get_my_feature(user: UserInDB = Depends(get_current_active_user)):
    """Get feature data"""
    return {"feature": "data"}

@router.post("/my-feature/")
async def create_my_feature(
    data: dict,
    user: UserInDB = Depends(get_current_active_user),
):
    """Create new feature"""
    return {"status": "created"}

Step 2: Register Router in Central Router

Edit app/api/v1/router.py to include your new router:

# app/api/v1/router.py
from app.api.v1.endpoints import my_feature  # Import your endpoint module

api_router = APIRouter()

# Add your router
api_router.include_router(
    my_feature.router, 
    prefix="/my-feature",          # Optional: URL prefix
    tags=["My Feature"]             # For OpenAPI documentation
)

Step 3: Authentication & Authorization

  • No Auth Required: Endpoint is public
  • Authenticated Users: Use Depends(get_current_active_user)
  • Role-Based Access: Use Depends(require_admin) or Depends(require_role(UserRole.OPERATOR))
from app.auth.permissions import require_admin, require_role
from app.domain.models.role import UserRole

@router.post("/admin-only/")
async def admin_only(user: UserInDB = Depends(require_admin)):
    return {"admin": user.username}

Step 4: Response Models

Use Pydantic schemas for validation:

from pydantic import BaseModel
from typing import Optional

class MyFeatureResponse(BaseModel):
    id: int
    name: str
    status: Optional[str] = None

@router.get("/my-feature/{id}", response_model=MyFeatureResponse)
async def get_feature(id: int):
    return MyFeatureResponse(id=id, name="Feature", status="active")

Router Registration Pattern

The central router (app/api/v1/router.py) uses include_router() to compose all endpoints:

# Current structure (98 lines):
api_router.include_router(auth.router, prefix="/auth", tags=["Auth"])
api_router.include_router(project.router, tags=["Project"])
api_router.include_router(simulation.router, tags=["Simulation Control"])
# ... 30+ more routers

3. Database Interaction Patterns

ORM & Database Strategy

TJWater uses psycopg (PostgreSQL async driver) directly, NOT SQLAlchemy ORM

Multiple Database Types

  • PostgreSQL: Main relational data (users, metadata)
  • TimescaleDB: Time-series data (SCADA, sensor readings)
  • InfluxDB: Alternative time-series storage (optional)
  • Metadata DB: Separate instance (system_hub database)

Database Layer Architecture

1. Database Instances (app/infra/db/postgresql/database.py)

class Database:
    """Manages async connection pooling"""
    
    def __init__(self, db_name=None):
        self.pool = None
        self.db_name = db_name
    
    def init_pool(self, db_name=None):
        """Initialize connection pool"""
        self.pool = psycopg_pool.AsyncConnectionPool(...)
    
    async def get_connection(self) -> AsyncGenerator:
        """Yield connection from pool"""
        async with self.pool.connection() as conn:
            yield conn

# Global instances
db = Database()  # Default PostgreSQL
_database_instances = {}  # Per-project caches

2. Repository Pattern (app/infra/repositories/)

class UserRepository:
    """Data access layer for users"""
    
    def __init__(self, db: Database):
        self.db = db
    
    async def create_user(self, user: UserCreate) -> Optional[UserInDB]:
        query = """
            INSERT INTO users (username, email, hashed_password, role)
            VALUES (%(username)s, %(email)s, %(hashed_password)s, %(role)s)
            RETURNING id, username, email, role, created_at
        """
        async with self.db.get_connection() as conn:
            async with conn.cursor() as cur:
                await cur.execute(query, {
                    'username': user.username,
                    'email': user.email,
                    'hashed_password': get_password_hash(user.password),
                    'role': user.role.value
                })
                row = await cur.fetchone()
                return UserInDB(**row) if row else None

3. Query Execution Pattern

# Reading data
async with db.get_connection() as conn:
    async with conn.cursor() as cur:
        await cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        row = await cur.fetchone()  # Returns dict_row

# Writing data
async with db.get_connection() as conn:
    async with conn.cursor() as cur:
        await cur.execute(
            "UPDATE users SET email = %s WHERE id = %s",
            (new_email, user_id)
        )
        # conn auto-commits (if autocommit=True in pool config)

Database Configuration

Via app/core/config.py and .env:

# Main PostgreSQL (users, metadata)
DB_HOST = "localhost"
DB_PORT = "5432"
DB_NAME = "tjwater"
DB_USER = "postgres"
DB_PASSWORD = "password"

# TimescaleDB (time-series)
TIMESCALEDB_DB_HOST = "localhost"
TIMESCALEDB_DB_PORT = "5433"
TIMESCALEDB_DB_NAME = "szh"

# Metadata DB (system info)
METADATA_DB_NAME = "system_hub"

Dynamic Multi-Database Support

Project-specific databases are managed dynamically:

# app/infra/db/dynamic_manager.py
async def get_database_instance(db_name: Optional[str] = None) -> Database:
    """Get or create DB instance for project"""
    if not db_name:
        return db  # Default instance
    
    if db_name not in _database_instances:
        instance = Database(db_name=db_name)
        instance.init_pool()
        await instance.open()
        _database_instances[db_name] = instance
    
    return _database_instances[db_name]

Migrations & Schema Creation

  • Location: resources/sql/ contains initialization scripts
  • Naming: Numbered order (e.g., 002_create_audit_logs_table.sql)
  • Approach: SQL scripts are manually executed; no Alembic/Flyway framework
  • Example: resources/sql/002_create_audit_logs_table.sql

4. Native Module Integration

Architecture: Python ↔ Binary Code

TJWater integrates compiled C/Go binaries for water network simulation:

Python (FastAPI)
    ↓
app/native/wndb/
    ├── project.py              (list, create, delete, open projects)
    ├── s2_junctions.py         (CRUD for junctions)
    ├── s5_pipes.py             (CRUD for pipes)
    └── [40+ schema modules]    (network elements)
    ↓
app/native/api_encap/          (Low-level binary wrappers)
    └── api.so / api.dll        (Compiled binary library)

How Python Calls Native Code

1. Native API Initialization

# app/native/wndb/__init__.py
from app.native.api_encap.api import (
    ChangeSet,  # Change tracking
    API_ADD, API_UPDATE, API_DELETE,  # Operations
    list_project, create_project, open_project, close_project,
    # ... 100+ functions
)

2. Example: Project Management

# app/services/tjnetwork.py
from app.native.wndb import list_project, open_project, close_project

def list_project() -> list[str]:
    """List all project databases"""
    # Calls into compiled binary to enumerate DB names
    return api.list_project()

def open_project(name: str) -> None:
    """Open project and load into memory"""
    # Binary loads .inp file or DB data
    api.open_project(name)

3. ChangeSet Tracking

# For tracking network modifications
class ChangeSet:
    """Tracks ADD/UPDATE/DELETE operations on network elements"""
    def __init__(self):
        self.adds = []
        self.updates = []
        self.deletes = []

# Usage in services
def update_junction(name: str, demand: float):
    change = api.set_junction(name, {"demand": demand})
    # Returns ChangeSet for auditing/undo-redo

4. Network CRUD Operations

# All operations follow this pattern:
from app.native.wndb import get_all_junctions, add_junction, set_junction

# Read
junctions = get_all_junctions()  # Returns list of dicts

# Create
add_junction({"id": "J1", "x": 0.0, "y": 0.0, "demand": 100.0})

# Update
set_junction("J1", {"demand": 150.0})

# Delete
delete_junction("J1")

Data Flow Example: Simulation

FastAPI Request (POST /simulation/run)
    ↓
app/api/v1/endpoints/simulation.py
    ↓
app/services/simulation.py (business logic)
    ↓
app/native/wndb/ (calls binary functions)
    ├── open_project("szh")
    ├── set_option("QUALITY", "CHEMICAL")
    ├── run_simulation()
    └── get_result()
    ↓
TimescaleDB/InfluxDB (store results)
    ↓
Response to client

Python Module Constants

Native module exports constants for enumerations:

from app.native.wndb import (
    OPTION_UNITS_LPS,          # Flow units
    OPTION_PRESSURE_KPA,       # Pressure units
    OPTION_QUALITY_CHEMICAL,   # Quality type
    PIPE_STATUS_OPEN,          # Pipe status
    SCADA_DEVICE_TYPE_PRESSURE,
)

5. Authentication & Authorization Mechanisms

Architecture: JWT + OAuth2 + Role-Based Access Control (RBAC)

1. Authentication Flow

User Registration

# app/api/v1/endpoints/auth.py
@router.post("/register", response_model=UserResponse)
async def register(user_data: UserCreate):
    # 1. Hash password
    hashed = get_password_hash(user_data.password)
    
    # 2. Store in DB via repository
    user = await user_repo.create_user({
        'username': user_data.username,
        'email': user_data.email,
        'hashed_password': hashed,
        'role': 'USER'
    })
    return UserResponse.model_validate(user)

User Login & Token Generation

@router.post("/login", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # 1. Verify credentials
    user = await user_repo.get_user_by_username(form_data.username)
    if not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    # 2. Generate JWT tokens
    access_token = create_access_token(subject=user.username)
    refresh_token = create_refresh_token(subject=user.username)
    
    return Token(
        access_token=access_token,
        refresh_token=refresh_token,
        token_type="bearer",
        expires_in=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
    )

2. JWT Token Configuration

# app/core/config.py
SECRET_KEY: str = "your-secret-key"  # Change in production
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7

3. Token Validation & Current User

# app/auth/dependencies.py
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")

async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserInDB:
    """Validate JWT token and extract user"""
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        username: str = payload.get("sub")
        token_type: str = payload.get("type", "access")
        
        if username is None or token_type != "access":
            raise HTTPException(status_code=401, detail="Invalid token")
        
        # Fetch user from DB
        user = await user_repo.get_user_by_username(username)
        if user is None:
            raise HTTPException(status_code=401, detail="User not found")
        
        return user
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

4. Role-Based Access Control (RBAC)

User Roles Enum

# app/domain/models/role.py
class UserRole(str, Enum):
    ADMIN = "ADMIN"          # Full permissions
    OPERATOR = "OPERATOR"    # Modify data
    USER = "USER"            # Read/write
    VIEWER = "VIEWER"        # Read-only

# Hierarchy
get_hierarchy() = {
    VIEWER: 1,
    USER: 2,
    OPERATOR: 3,
    ADMIN: 4,
}

Permission Decorators

# app/auth/permissions.py

def require_admin(current_user: UserInDB = Depends(get_current_active_user)) -> UserInDB:
    """Restrict to admin users"""
    if current_user.role != UserRole.ADMIN:
        raise HTTPException(status_code=403, detail="Admin access required")
    return current_user

def require_role(role: UserRole):
    """Factory for role-based permission checks"""
    async def permission_checker(
        current_user: UserInDB = Depends(get_current_active_user)
    ) -> UserInDB:
        if not UserRole(current_user.role).has_permission(role):
            raise HTTPException(status_code=403, detail=f"Role {role} required")
        return current_user
    return permission_checker

def require_operator(current_user: UserInDB = Depends(get_current_active_user)) -> UserInDB:
    return current_user if require_role(UserRole.OPERATOR) else None

Using Permissions in Endpoints

# Require admin
@router.delete("/users/{user_id}")
async def delete_user(
    user_id: int,
    admin: UserInDB = Depends(require_admin)
):
    await user_repo.delete_user(user_id)
    return {"status": "deleted"}

# Require operator or higher
@router.post("/projects/")
async def create_project(
    data: dict,
    operator: UserInDB = Depends(require_operator)
):
    return {"project": "created"}

5. Optional Keycloak Integration

# app/auth/keycloak_dependencies.py
# Alternative: Validate tokens from external Keycloak server

KEYCLOAK_PUBLIC_KEY: str = ""  # From .env
KEYCLOAK_ALGORITHM: str = "RS256"

# Similar JWT validation but with Keycloak's public key

6. Token Schema

# app/domain/schemas/user.py
class Token(BaseModel):
    access_token: str
    refresh_token: Optional[str] = None
    token_type: str = "bearer"
    expires_in: int  # Seconds

class UserInDB(BaseModel):
    id: int
    username: str
    email: str
    role: str  # "ADMIN", "USER", etc.
    is_active: bool
    is_superuser: bool

6. Configuration Management

Configuration Hierarchy

Environment Variables (.env file)
    ↓
app/core/config.py (Pydantic Settings)
    ↓
Injected via Depends() in endpoints/services
    ↓
Runtime usage

Config File: app/core/config.py

Uses Pydantic v2 BaseSettings with .env support:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    # App
    PROJECT_NAME: str = "TJWater Server"
    API_V1_STR: str = "/api/v1"
    
    # Security
    SECRET_KEY: str = "your-secret"
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
    
    # Encryption (Fernet symmetric)
    ENCRYPTION_KEY: str = ""  # Required from .env
    DATABASE_ENCRYPTION_KEY: str = ""
    
    # PostgreSQL
    DB_NAME: str = "tjwater"
    DB_HOST: str = "localhost"
    DB_PORT: str = "5432"
    DB_USER: str = "postgres"
    DB_PASSWORD: str = "password"
    
    # TimescaleDB
    TIMESCALEDB_DB_NAME: str = "tjwater"
    TIMESCALEDB_DB_HOST: str = "localhost"
    TIMESCALEDB_DB_PORT: str = "5433"
    
    # Metadata DB
    METADATA_DB_NAME: str = "system_hub"
    METADATA_DB_HOST: str = "localhost"
    METADATA_DB_PORT: str = "5432"
    
    # Cache sizing
    PROJECT_PG_CACHE_SIZE: int = 50
    PROJECT_TS_CACHE_SIZE: int = 50
    
    # Connection pooling
    PROJECT_PG_POOL_SIZE: int = 5
    PROJECT_TS_POOL_MIN_SIZE: int = 1
    PROJECT_TS_POOL_MAX_SIZE: int = 10
    
    # InfluxDB (optional)
    INFLUXDB_URL: str = "http://localhost:8086"
    INFLUXDB_TOKEN: str = "token"
    
    @property
    def SQLALCHEMY_DATABASE_URI(self) -> str:
        return f"postgresql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
    
    model_config = SettingsConfigDict(
        env_file=".env",
        extra="ignore",
    )

settings = Settings()

Environment File: .env.example

# Security
SECRET_KEY=your-secret-key-change-in-production
ENCRYPTION_KEY=<generated via Fernet.generate_key()>
DATABASE_ENCRYPTION_KEY=

# PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_NAME=tjwater
DB_USER=postgres
DB_PASSWORD=password

# TimescaleDB
TIMESCALEDB_DB_NAME=szh
TIMESCALEDB_DB_HOST=localhost
TIMESCALEDB_DB_PORT=5433
TIMESCALEDB_DB_USER=tjwater
TIMESCALEDB_DB_PASSWORD=Tjwater@123456

# Metadata DB
METADATA_DB_NAME=system_hub
METADATA_DB_HOST=localhost

# Cache/Pool
PROJECT_PG_CACHE_SIZE=50
PROJECT_TS_CACHE_SIZE=50

Using Configuration in Code

from app.core.config import settings

# Direct access
db_url = settings.SQLALCHEMY_DATABASE_URI
secret = settings.SECRET_KEY

# In FastAPI lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Initialize based on settings
    db.init_pool(settings.DB_NAME)
    yield
    await db.close()

Encryption Configuration

# app/core/encryption.py
from app.core.config import settings

class Encryptor:
    def __init__(self, key: Optional[bytes] = None):
        if key is None:
            key_str = settings.ENCRYPTION_KEY
            if not key_str:
                raise ValueError("ENCRYPTION_KEY not configured")
            key = key_str.encode()
        self.fernet = Fernet(key)
    
    def encrypt(self, data: str) -> str:
        return self.fernet.encrypt(data.encode()).decode()
    
    def decrypt(self, data: str) -> str:
        return self.fernet.decrypt(data.encode()).decode()
    
    @staticmethod
    def generate_key() -> str:
        """Generate new encryption key"""
        return Fernet.generate_key().decode()

7. Testing Setup

Test Structure

tests/
├── conftest.py              # Pytest configuration & fixtures
├── api/                     # API integration tests
│   ├── test_api_integration.py
│   └── test_leakage_endpoints.py
├── unit/                    # Unit tests
│   ├── test_burst_location_service.py
│   ├── test_metadata_repository_dsn_decrypt.py
│   └── test_pipeline_health_analyzer.py
└── auth/                    # Auth tests
    └── test_encryption.py

Configuration: tests/conftest.py

import pytest
import sys
import os

# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

def run_this_test(test_file):
    """Run a single test file"""
    test_name = os.path.splitext(os.path.basename(test_file))[0]
    pytest.main([test_file, "-v"])

Testing Tools

# requirements.txt includes:
pytest==8.3.5                    # Test runner
# httpx (via FastAPI) for async HTTP client

Example: API Integration Test

# tests/api/test_api_integration.py
import pytest

@pytest.mark.parametrize(
    "module_name, desc",
    [
        ("app.core.encryption", "Encryption"),
        ("app.auth.permissions", "Permissions"),
        ("app.api.v1.endpoints.auth", "Auth endpoints"),
    ],
)
def test_module_imports(module_name, desc):
    """Verify critical modules can be imported"""
    try:
        __import__(module_name)
    except ImportError as e:
        pytest.fail(f"Cannot import {desc}: {e}")

def test_router_configuration():
    """Verify router is properly configured"""
    from app.api.v1 import router
    api_router = router.api_router
    routes = [r.path for r in api_router.routes if hasattr(r, "path")]
    assert len(routes) > 0

Example: Encryption Test

# tests/auth/test_encryption.py
from app.core.encryption import Encryptor

def test_encrypt_decrypt():
    """Test encryption roundtrip"""
    encryptor = Encryptor.generate_key()
    enc = Encryptor(encryptor)
    
    plaintext = "sensitive_data"
    encrypted = enc.encrypt(plaintext)
    decrypted = enc.decrypt(encrypted)
    
    assert decrypted == plaintext

Running Tests

# Run all tests
pytest tests/ -v

# Run specific test file
pytest tests/api/test_api_integration.py -v

# Run specific test
pytest tests/auth/test_encryption.py::test_encrypt_decrypt -v

8. Audit & Security Features

Audit Middleware

Automatically logs all critical operations:

# app/infra/audit/middleware.py
class AuditMiddleware(BaseHTTPMiddleware):
    AUDIT_METHODS = ["POST", "PUT", "DELETE", "PATCH"]
    AUDIT_TAGS = ["Audit", "Users", "Project", "Junctions", "Pipes", ...]
    
    async def dispatch(self, request: Request, call_next):
        # 1. Capture request body (for writes)
        # 2. Execute request
        # 3. Log to audit DB if matches criteria
        # 4. Return response

Audit Logging

# app/core/audit.py
async def log_audit_event(
    action: AuditAction,  # "CREATE", "UPDATE", "DELETE", "LOGIN"
    resource_type: str,   # "user", "project", "pipe"
    resource_id: str,
    actor_id: int,        # User ID
    details: dict = None
):
    """Log operation to audit table"""
    # Persists to audit_logs table via AuditRepository

Audit Schema

# app/domain/schemas/audit.py
class AuditLog(BaseModel):
    id: int
    timestamp: datetime
    action: str
    resource_type: str
    resource_id: str
    actor_id: int
    details: Optional[dict] = None

9. Key Dependencies & Tech Stack

Core Framework

FastAPI==0.128.0           # Web framework
uvicorn==0.34.0            # ASGI server
Pydantic==2.10.6           # Data validation
pydantic-settings==2.12.0  # Configuration management

Database

psycopg==3.2.5             # PostgreSQL async driver
psycopg-pool==3.3.0        # Connection pooling
GeoAlchemy2==0.17.1        # GIS support
SQLAlchemy==2.0.41         # ORM (optional)

Security & Auth

PyJWT==2.10.1              # JWT handling
python-jose==3.5.0         # Token validation
passlib==1.7.4             # Password hashing
cryptography==46.0.3       # Encryption (Fernet)
Authlib==1.6.6             # OAuth2 support

Data Science / Simulation

wntr==1.3.2                # Water network toolkit (EPANET)
numpy==1.26.2              # Numerical computing
pandas==2.2.3              # Data frames
scipy==1.15.2              # Scientific computing
scikit-learn==1.6.1        # ML algorithms

Time-Series & Data Storage

influxdb-client==1.48.0    # InfluxDB client
redis==5.2.1               # Caching & sessions

Utilities

python-dotenv==1.2.1       # .env file support
python-multipart==0.0.20   # File upload handling
email-validator==2.3.0     # Email validation

10. Documentation & Deployment

Key Documentation Files

  • SECURITY_README.md: Complete security implementation guide
  • DEPLOYMENT.md: Production deployment checklist
  • INTEGRATION_CHECKLIST.md: System integration steps
  • setup_server.md: Initial server setup
  • setup_influxdb.md: InfluxDB configuration

Docker Deployment

# infra/docker/docker-compose.yml
services:
  api:
    build: ...
    ports: ["8000:8000"]
    environment:
      - DB_HOST=postgis
      - TIMESCALEDB_HOST=timescaledb
      - REDIS_HOST=redis
  
  postgis:
    image: postgis/postgis:14-3.5
  
  timescaledb:
    image: timescale/timescaledb:latest-pg15
  
  redis:
    image: redis:latest
  
  influxdb:
    image: influxdb:2.7
  
  keycloak:
    image: keycloak/keycloak:latest
  
  grafana:
    image: grafana/grafana:latest

For Secondary Development Team:

  1. API Development Guide

    • How to add new endpoints (covered in Section 2)
    • Common endpoint patterns
    • Error handling standards
    • Response format conventions
  2. Database Development Guide

    • Repository pattern usage
    • Query building best practices
    • Migration procedures
    • Multi-database considerations
  3. Native Module Integration Guide

    • How to add new native functions
    • ChangeSet tracking
    • Binary library updates
    • Debugging native code calls
  4. Authentication & Authorization

    • Token generation and validation
    • Role-based access control implementation
    • Permission decorator usage
    • Keycloak integration
  5. Testing & CI/CD

    • Unit test patterns
    • Integration test setup
    • Test database configuration
    • GitHub Actions workflow
  6. Deployment & DevOps

    • Docker build & deployment
    • Kubernetes manifests
    • Environment variable management
    • Production security checklist
  7. Troubleshooting Guide

    • Common issues and solutions
    • Database connection problems
    • Authentication debugging
    • Native module errors

Summary Table: Key Components

Component Location Purpose Key Technology
API Routes app/api/v1/endpoints/ REST endpoints FastAPI, Pydantic
Router app/api/v1/router.py Central route registration APIRouter
Auth app/auth/ JWT/OAuth2 validation PyJWT, passlib
Database app/infra/db/ Data persistence psycopg, async pools
Repository app/infra/repositories/ Data access layer psycopg cursor
Domain app/domain/ Models & schemas Pydantic, Enum
Services app/services/ Business logic Python, native APIs
Native API app/native/wndb/ Binary integration ctypes/cffi, .so/.dll
Config app/core/config.py Settings management Pydantic Settings
Security app/core/security.py Encryption, hashing cryptography, passlib
Audit app/infra/audit/ Operation logging Middleware, Repository
Tests tests/ Test suite pytest

This guide provides the essential patterns and structures needed for secondary development on TJWaterServerBinary. Refer to individual source files for detailed implementation examples.