From e9eb98119fcaf8cbd217514b95d42203f86b22b2 Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Mon, 2 Mar 2026 08:25:05 +0000 Subject: [PATCH] feat: migrate database driver from MariaDB to PostgreSQL Switch from pymysql to psycopg2-binary, update connection string to postgresql+psycopg2, replace MySQL-specific JSON imports with generic SQLAlchemy JSON, convert MySQL prefix index to PostgreSQL left() function, and remove FetchedValue() (unnecessary with PostgreSQL). Co-Authored-By: Claude Opus 4.6 --- backend/.env.example | 2 +- backend/app/config.py | 6 +++--- backend/app/models/audit.py | 2 +- backend/app/models/case.py | 8 +++----- backend/app/models/report.py | 2 +- backend/app/models/user.py | 2 -- backend/requirements.txt | 2 +- deploy/dak-backend.service | 2 +- 8 files changed, 11 insertions(+), 15 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 1b1d1b4..baafd14 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,6 +1,6 @@ # Database DB_HOST= -DB_PORT=3306 +DB_PORT=5432 DB_NAME=dak_c2s DB_USER=dak_c2s_admin DB_PASSWORD= diff --git a/backend/app/config.py b/backend/app/config.py index d2f39d9..4e9db2e 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -8,7 +8,7 @@ from functools import lru_cache class Settings(BaseSettings): # Database DB_HOST: str = "localhost" - DB_PORT: int = 3306 + DB_PORT: int = 5432 DB_NAME: str = "dak_c2s" DB_USER: str = "dak_c2s_admin" DB_PASSWORD: str = "" @@ -37,8 +37,8 @@ class Settings(BaseSettings): def database_url(self) -> str: password = quote_plus(self.DB_PASSWORD) return ( - f"mysql+pymysql://{self.DB_USER}:{password}" - f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}?charset=utf8mb4" + f"postgresql+psycopg2://{self.DB_USER}:{password}" + f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" ) class Config: diff --git a/backend/app/models/audit.py b/backend/app/models/audit.py index 3963567..4b7e90f 100644 --- a/backend/app/models/audit.py +++ b/backend/app/models/audit.py @@ -12,11 +12,11 @@ from sqlalchemy import ( ForeignKey, Index, Integer, + JSON, String, Text, func, ) -from sqlalchemy.dialects.mysql import JSON from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base diff --git a/backend/app/models/case.py b/backend/app/models/case.py index c007603..6e9f5d3 100644 --- a/backend/app/models/case.py +++ b/backend/app/models/case.py @@ -21,7 +21,6 @@ from sqlalchemy import ( text, ) from sqlalchemy.orm import Mapped, mapped_column, relationship -from sqlalchemy.schema import FetchedValue from app.database import Base @@ -115,7 +114,6 @@ class Case(Base): nullable=False, server_default=func.now(), onupdate=func.now(), - server_onupdate=FetchedValue(), ) updated_by: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("users.id"), nullable=True @@ -154,9 +152,9 @@ class Case(Base): Index("idx_fallgruppe", "fallgruppe"), Index("idx_datum", "datum"), Index("idx_nachname_vorname", "nachname", "vorname"), - # Note: idx_pending_icd uses a prefix length icd(20) which SQLAlchemy - # does not support natively. We use text() for the prefix column. - Index("idx_pending_icd", "jahr", "kw", "fallgruppe", text("icd(20)")), + # Note: idx_pending_icd uses a functional index on left(icd, 20). + # We use text() for the expression column. + Index("idx_pending_icd", "jahr", "kw", "fallgruppe", text("left(icd, 20)")), Index("idx_pending_coding", "gutachten", "gutachten_typ"), CheckConstraint( "fallgruppe IN ('onko','kardio','intensiv','galle','sd')", diff --git a/backend/app/models/report.py b/backend/app/models/report.py index 48f412d..facb270 100644 --- a/backend/app/models/report.py +++ b/backend/app/models/report.py @@ -11,12 +11,12 @@ from sqlalchemy import ( ForeignKey, Index, Integer, + JSON, SmallInteger, String, UniqueConstraint, func, ) -from sqlalchemy.dialects.mysql import JSON from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 5aa13fd..4ea0609 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -17,7 +17,6 @@ from sqlalchemy import ( func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship -from sqlalchemy.schema import FetchedValue from app.database import Base @@ -59,7 +58,6 @@ class User(Base): nullable=False, server_default=func.now(), onupdate=func.now(), - server_onupdate=FetchedValue(), ) # Relationships diff --git a/backend/requirements.txt b/backend/requirements.txt index cd57d2e..d36e458 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,7 +3,7 @@ uvicorn[standard]==0.34.0 gunicorn==23.0.0 sqlalchemy==2.0.36 alembic==1.14.1 -pymysql==1.1.1 +psycopg2-binary==2.9.10 cryptography==44.0.0 python-jose[cryptography]==3.3.0 passlib[bcrypt]==1.7.4 diff --git a/deploy/dak-backend.service b/deploy/dak-backend.service index 6cae648..d9bbf26 100644 --- a/deploy/dak-backend.service +++ b/deploy/dak-backend.service @@ -1,6 +1,6 @@ [Unit] Description=DAK Zweitmeinungs-Portal Backend (FastAPI/Uvicorn) -After=network.target mariadb.service +After=network.target postgresql.service [Service] Type=simple