Stage 04.1 - storage foundation (PostgreSQL) and system dashboard UI
This commit is contained in:
33
app/src/storage/models.py
Normal file
33
app/src/storage/models.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class BalanceSnapshotRecord:
|
||||
id: int | None
|
||||
created_at: str
|
||||
source: str
|
||||
payload_json: str
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class JournalEventRecord:
|
||||
id: int | None
|
||||
created_at: str
|
||||
level: str
|
||||
event_type: str
|
||||
message: str
|
||||
payload_json: str | None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class OrderDraftRecord:
|
||||
id: int | None
|
||||
created_at: str
|
||||
symbol: str
|
||||
side: str
|
||||
order_type: str
|
||||
quantity: str
|
||||
status: str
|
||||
payload_json: str | None
|
||||
44
app/src/storage/schema.py
Normal file
44
app/src/storage/schema.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from src.storage.session import get_connection
|
||||
|
||||
|
||||
DDL = [
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS balance_snapshots (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
source TEXT NOT NULL,
|
||||
payload_json JSONB NOT NULL
|
||||
)
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS journal_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
level TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
payload_json JSONB
|
||||
)
|
||||
''',
|
||||
'''
|
||||
CREATE TABLE IF NOT EXISTS order_drafts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
symbol TEXT NOT NULL,
|
||||
side TEXT NOT NULL,
|
||||
order_type TEXT NOT NULL,
|
||||
quantity NUMERIC(36, 18) NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
payload_json JSONB
|
||||
)
|
||||
'''
|
||||
]
|
||||
|
||||
|
||||
def init_schema() -> None:
|
||||
with get_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
for statement in DDL:
|
||||
cursor.execute(statement)
|
||||
42
app/src/storage/session.py
Normal file
42
app/src/storage/session.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import psycopg
|
||||
|
||||
from src.core.config import load_settings
|
||||
|
||||
|
||||
def build_dsn() -> str:
|
||||
settings = load_settings()
|
||||
password_part = settings.db_password.replace("@", "%40")
|
||||
return (
|
||||
f"postgresql://{settings.db_user}:{password_part}"
|
||||
f"@{settings.db_host}:{settings.db_port}/{settings.db_name}"
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_connection():
|
||||
connection = psycopg.connect(build_dsn(), autocommit=False)
|
||||
try:
|
||||
yield connection
|
||||
connection.commit()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def check_database_health() -> tuple[bool, str]:
|
||||
try:
|
||||
with get_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT version()")
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
return False, "PostgreSQL ping returned no rows."
|
||||
|
||||
version = str(row[0]).strip()
|
||||
except Exception as exc:
|
||||
return False, f"PostgreSQL error: {exc}"
|
||||
|
||||
return True, version
|
||||
Reference in New Issue
Block a user