refactor(coordinator): standardize database path to follow blockchain-node pattern

- Change coordinator database from /opt/data/coordinator.db to ./data/coordinator.db
- Update config.py to use relative path consistent with blockchain-node
- Update deployment scripts to use /opt/coordinator-api/data/coordinator.db
- Add data directory creation in init_db() for consistency
- Update .env.example files to reflect new standard
- Maintain backward compatibility for production deployment
This commit is contained in:
oib
2026-02-27 17:32:00 +01:00
parent d023654e74
commit 27e836bf3f
9 changed files with 80 additions and 44 deletions

View File

@@ -8,7 +8,7 @@
# Coordinator API # Coordinator API
APP_ENV=dev APP_ENV=dev
DATABASE_URL=sqlite:///./coordinator.db DATABASE_URL=sqlite:///./data/coordinator.db
ADMIN_API_KEYS=["your-admin-key"] ADMIN_API_KEYS=["your-admin-key"]
CLIENT_API_KEYS=["your-client-key"] CLIENT_API_KEYS=["your-client-key"]
MINER_API_KEYS=["your-miner-key"] MINER_API_KEYS=["your-miner-key"]

View File

@@ -1,7 +1,7 @@
APP_ENV=dev APP_ENV=dev
APP_HOST=127.0.0.1 APP_HOST=127.0.0.1
APP_PORT=8011 APP_PORT=8011
DATABASE_URL=sqlite:///./coordinator.db DATABASE_URL=sqlite:///./data/coordinator.db
CLIENT_API_KEYS=${CLIENT_API_KEY},client_dev_key_2 CLIENT_API_KEYS=${CLIENT_API_KEY},client_dev_key_2
MINER_API_KEYS=${MINER_API_KEY},miner_dev_key_2 MINER_API_KEYS=${MINER_API_KEY},miner_dev_key_2
ADMIN_API_KEYS=${ADMIN_API_KEY} ADMIN_API_KEYS=${ADMIN_API_KEY}

View File

@@ -25,9 +25,9 @@ class DatabaseConfig(BaseSettings):
if self.url: if self.url:
return self.url return self.url
# Default SQLite path # Default SQLite path - consistent with blockchain-node pattern
if self.adapter == "sqlite": if self.adapter == "sqlite":
return "sqlite:////opt/data/coordinator.db" return "sqlite:///./data/coordinator.db"
# Default PostgreSQL connection string # Default PostgreSQL connection string
return f"{self.adapter}://localhost:5432/coordinator" return f"{self.adapter}://localhost:5432/coordinator"
@@ -117,8 +117,8 @@ class Settings(BaseSettings):
return self.test_database_url return self.test_database_url
if self.database.url: if self.database.url:
return self.database.url return self.database.url
# Default SQLite path for backward compatibility # Default SQLite path - consistent with blockchain-node pattern
return "sqlite:////opt/data/coordinator.db" return "sqlite:///./data/coordinator.db"
@database_url.setter @database_url.setter
def database_url(self, value: str): def database_url(self, value: str):

View File

@@ -1,35 +1,26 @@
""" """
Database storage module for AITBC Coordinator API Unified database configuration for AITBC Coordinator API
Provides unified database session management with connection pooling. Provides SQLite and PostgreSQL support with connection pooling.
""" """
from __future__ import annotations from __future__ import annotations
import os
from contextlib import contextmanager from contextlib import contextmanager
from typing import Annotated, Generator from contextlib import asynccontextmanager
from typing import Generator, AsyncGenerator
from fastapi import Depends from sqlalchemy import create_engine
from sqlalchemy.engine import Engine from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.pool import QueuePool from sqlalchemy.orm import Session, sessionmaker
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel import SQLModel
from ..config import settings from ..config import settings
from ..domain import (
Job,
Miner,
MarketplaceOffer,
MarketplaceBid,
JobPayment,
PaymentEscrow,
GPURegistry,
GPUBooking,
GPUReview,
)
from ..domain.gpu_marketplace import ConsumerGPUProfile, EdgeGPUMetrics
from .models_governance import GovernanceProposal, ProposalVote, TreasuryTransaction, GovernanceParameter
_engine: Engine | None = None _engine = None
_async_engine = None
def get_engine() -> Engine: def get_engine() -> Engine:
@@ -53,7 +44,6 @@ def get_engine() -> Engine:
_engine = create_engine( _engine = create_engine(
effective_url, effective_url,
echo=False, echo=False,
poolclass=QueuePool,
pool_size=db_config.pool_size, pool_size=db_config.pool_size,
max_overflow=db_config.max_overflow, max_overflow=db_config.max_overflow,
pool_pre_ping=db_config.pool_pre_ping, pool_pre_ping=db_config.pool_pre_ping,
@@ -62,8 +52,20 @@ def get_engine() -> Engine:
def init_db() -> Engine: def init_db() -> Engine:
"""Initialize database tables.""" """Initialize database tables and ensure data directory exists."""
engine = get_engine() engine = get_engine()
# Ensure data directory exists for SQLite (consistent with blockchain-node pattern)
if "sqlite" in str(engine.url):
db_path = engine.url.database
if db_path:
from pathlib import Path
# Extract directory path from database file path
if db_path.startswith("./"):
db_path = db_path[2:] # Remove ./
data_dir = Path(db_path).parent
data_dir.mkdir(parents=True, exist_ok=True)
SQLModel.metadata.create_all(engine) SQLModel.metadata.create_all(engine)
return engine return engine
@@ -72,21 +74,55 @@ def init_db() -> Engine:
def session_scope() -> Generator[Session, None, None]: def session_scope() -> Generator[Session, None, None]:
"""Context manager for database sessions.""" """Context manager for database sessions."""
engine = get_engine() engine = get_engine()
session = Session(engine) with Session(engine) as session:
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
def get_session() -> Generator[Session, None, None]:
"""Get a database session (for FastAPI dependency)."""
with session_scope() as session:
yield session yield session
SessionDep = Annotated[Session, Depends(get_session)] # Dependency for FastAPI
SessionDep = Session
def get_session() -> Session:
"""Get a database session."""
engine = get_engine()
return Session(engine)
# Async support for future use
async def get_async_engine() -> AsyncEngine:
"""Get or create async database engine."""
global _async_engine
if _async_engine is None:
db_config = settings.database
effective_url = db_config.effective_url
# Convert SQLite to async SQLite
if "sqlite" in effective_url:
async_url = effective_url.replace("sqlite:///", "sqlite+aiosqlite:///")
else:
# Convert PostgreSQL to async PostgreSQL
async_url = effective_url.replace("postgresql://", "postgresql+asyncpg://")
_async_engine = create_async_engine(
async_url,
echo=False,
pool_size=db_config.pool_size,
max_overflow=db_config.max_overflow,
pool_pre_ping=db_config.pool_pre_ping,
)
return _async_engine
@asynccontextmanager
async def async_session_scope() -> AsyncGenerator[AsyncSession, None]:
"""Async context manager for database sessions."""
engine = await get_async_engine()
async with AsyncSession(engine) as session:
yield session
async def get_async_session() -> AsyncSession:
"""Get an async database session."""
engine = await get_async_engine()
return async_sessionmaker(engine)()