multidb support

This commit is contained in:
Mateusz Gruszczyński
2025-09-26 22:03:09 +02:00
parent eb9f11b2d6
commit b679700c6d
7 changed files with 207 additions and 24 deletions

View File

@@ -36,4 +36,32 @@ USE_ETAGS=True
PRAGMA_HEADER=
# Wartość nagłówka X-Robots-Tag, gdy BLOCK_BOTS=True
ROBOTS_TAG=noindex, nofollow, nosnippet, noarchive
ROBOTS_TAG="noindex, nofollow, nosnippet, noarchive"
# Rodzaj bazy: sqlite, pgsql, mysql
# Mozliwe wartosci: sqlite / pgsql / mysql
DB_ENGINE=sqlite
# --- Konfiguracja dla sqlite ---
# Plik bazy bedzie utworzony automatycznie w katalogu ./instance
# Pozostale zmienne sa ignorowane przy DB_ENGINE=sqlite
# --- Konfiguracja dla pgsql ---
# Ustaw DB_ENGINE=pgsql
# Domyslny port PostgreSQL to 5432
# Wymaga dzialajacego serwera PostgreSQL (np. kontener `postgres`)
# --- Konfiguracja dla mysql ---
# Ustaw DB_ENGINE=mysql
# Domyslny port MySQL to 3306
# Wymaga kontenera z MySQL i uzytkownika z dostepem do bazy
# Wspolne zmienne (dla pgsql, mysql)
# DB_HOST = pgsql lub mysql zgodnie z deployem (profil w docker-compose.yml)
DB_HOST=pgsql
DB_PORT=5432
DB_NAME=myapp
DB_USER=user
DB_PASSWORD=pass

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ venv/
version.txt
deploy/varnish/default.vcl
*.tar.gz
db/*

38
_tools/db/migrate.txt Normal file
View File

@@ -0,0 +1,38 @@
python3 -m venv venv_migrate
source venv_migrate/bin/activate
pip install sqlalchemy psycopg2-binary dotenv
docker compose --profile pgsql up -d --build
PYTHONPATH=. python3 _tools/db/migrate_sqlite_to_pgsql.py
rm -rf venv_migrate
# reset wszystkich sekwencji w pgsql
docker exec -it pgsql-db psql -U zbiorki -d zbiorki
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT
c.relname AS seq_name,
t.relname AS table_name,
a.attname AS column_name
FROM
pg_class c
JOIN
pg_depend d ON d.objid = c.oid
JOIN
pg_class t ON d.refobjid = t.oid
JOIN
pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
WHERE
c.relkind = 'S'
AND d.deptype = 'a'
LOOP
EXECUTE format(
'SELECT setval(%L, COALESCE((SELECT MAX(%I) FROM %I), 1), true)',
r.seq_name, r.column_name, r.table_name
);
END LOOP;
END$$;

View File

@@ -0,0 +1,61 @@
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import sessionmaker
from config import Config
from dotenv import load_dotenv
load_dotenv()
# Źródło: SQLite
sqlite_engine = create_engine("sqlite:///instance/baza.db")
sqlite_meta = MetaData()
sqlite_meta.reflect(bind=sqlite_engine)
# Cel: PostgreSQL
pg_engine = create_engine(Config.SQLALCHEMY_DATABASE_URI)
pg_meta = MetaData()
pg_meta.reflect(bind=pg_engine)
# Sesje
SQLiteSession = sessionmaker(bind=sqlite_engine)
PGSession = sessionmaker(bind=pg_engine)
sqlite_session = SQLiteSession()
pg_session = PGSession()
def migrate_table(table_name):
print("➡️ Używana baza docelowa:", Config.SQLALCHEMY_DATABASE_URI)
print(f"\n➡️ Migruję tabelę: {table_name}")
source_table = sqlite_meta.tables.get(table_name)
target_table = pg_meta.tables.get(table_name)
if source_table is None or target_table is None:
print(f"⚠️ Pominięto: {table_name} (brak w jednej z baz)")
return
rows = sqlite_session.execute(source_table.select()).fetchall()
if not rows:
print(" Brak danych do migracji.")
return
insert_data = [dict(row._mapping) for row in rows]
try:
with pg_engine.begin() as conn:
conn.execute(target_table.delete())
conn.execute(target_table.insert(), insert_data)
print(f"✅ Przeniesiono: {len(rows)} rekordów")
except Exception as e:
print(f"❌ Błąd przy migracji {table_name}: {e}")
def main():
tables = ["user", "shopping_list", "item", "expense", "receipt", "suggested_product"]
for table in tables:
migrate_table(table)
print("\n🎉 Migracja zakończona pomyślnie.")
if __name__ == "__main__":
main()

View File

@@ -1,5 +1,7 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))
def _get_bool(name: str, default: bool) -> bool:
val = os.environ.get(name)
if val is None:
@@ -24,8 +26,8 @@ class Config:
- ROBOTS_TAG
"""
# Baza danych
SQLALCHEMY_DATABASE_URI = _get_str("DATABASE_URL", "sqlite:///baza.db")
#SQLALCHEMY_DATABASE_URI = _get_str("DATABASE_URL", "sqlite:///baza.db")
# Flask
SECRET_KEY = _get_str("SECRET_KEY", "tajny_klucz")
@@ -49,3 +51,21 @@ class Config:
SQLALCHEMY_TRACK_MODIFICATIONS = False
HEALTHCHECK_TOKEN = _get_str("HEALTHCHECK_TOKEN", "healthcheck")
# Baza danych
DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite").lower()
if DB_ENGINE == "sqlite":
SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(basedir, 'db', 'database.db')}"
elif DB_ENGINE == "pgsql":
SQLALCHEMY_DATABASE_URI = (
f"postgresql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@"
f"{os.environ['DB_HOST']}:{os.environ.get('DB_PORT', 5432)}/{os.environ['DB_NAME']}"
)
elif DB_ENGINE == "mysql":
SQLALCHEMY_DATABASE_URI = (
f"mysql+pymysql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@"
f"{os.environ['DB_HOST']}:{os.environ.get('DB_PORT', 3306)}/{os.environ['DB_NAME']}"
)
else:
raise ValueError("Nieobsługiwany typ bazy danych.")

View File

@@ -10,23 +10,30 @@ fi
APP_PORT="${APP_PORT:-8080}"
# === Konfiguracja (możesz nadpisać zmiennymi środowiskowymi) ===
REPO_DIR="${REPO_DIR:-$(pwd)}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" # albo compose.yaml
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
GIT_REMOTE="${GIT_REMOTE:-origin}"
GIT_BRANCH="${GIT_BRANCH:-$(git -C "$REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)}"
# Jeśli chcesz uruchomić tylko wybrane serwisy, podaj je po komendzie,
# np.: ./deploy.sh web api
SERVICES=("$@")
# Domyślny profil i usługi
DB_PROFILE="sqlite" # domyślnie sqlite, czyli brak profilu pgsql/mysql
SERVICES=()
# Przetwarzanie argumentów
if [[ $# -gt 0 ]]; then
case "$1" in
pgsql|mysql|sqlite)
DB_PROFILE="$1"
shift
;;
esac
SERVICES=("$@")
fi
log() { printf "\n==> %s\n" "$*"; }
# --- Kontrole wstępne ---
command -v git >/dev/null || { echo "Brak 'git' w PATH"; exit 1; }
command -v docker >/dev/null || { echo "Brak 'docker' w PATH"; exit 1; }
if ! docker compose version >/dev/null 2>&1; then
echo "Wymagany jest 'docker compose' (plugin), nie stary 'docker-compose'."
exit 1
@@ -41,34 +48,37 @@ if [[ ! -f "$REPO_DIR/$COMPOSE_FILE" ]]; then
fi
fi
# --- Praca w katalogu repo ---
cd "$REPO_DIR"
# --- Aktualizacja kodu ---
log "Aktualizacja repo: git pull --ff-only ($GIT_REMOTE/$GIT_BRANCH)"
git fetch --prune "$GIT_REMOTE"
git checkout "$GIT_BRANCH" >/dev/null 2>&1 || true
git pull --ff-only "$GIT_REMOTE" "$GIT_BRANCH"
# --- Zapisanie wersji do pliku ---
log "Zapisywanie hasha commita do version.txt"
git rev-parse --short HEAD > version.txt
# --- Zatrzymanie i usunięcie bieżącego stacka ---
log "Docker Compose DOWN (usuwanie kontenerów i osieroconych usług)"
docker compose -f "$COMPOSE_FILE" down --remove-orphans
# --- Generowanie default.vcl z szablonu ---
log "Generowanie default.vcl z APP_PORT=$APP_PORT"
envsubst < deploy/varnish/default.vcl.template > deploy/varnish/default.vcl
# --- Budowanie i uruchamianie bez restartu zależności ---
log "Docker Compose UP (build bez deps) dla: ${SERVICES[*]:-(wszystkie)}"
if [[ ${#SERVICES[@]} -gt 0 ]]; then
docker compose -f "$COMPOSE_FILE" up -d --no-deps --build "${SERVICES[@]}"
# Budowanie i uruchamianie
log "Docker Compose UP (build bez deps) dla profilu: $DB_PROFILE i serwisów: ${SERVICES[*]:-(wszystkie)}"
if [[ "$DB_PROFILE" == "pgsql" ]]; then
PROFILE_OPT="--profile pgsql"
elif [[ "$DB_PROFILE" == "mysql" ]]; then
PROFILE_OPT="--profile mysql"
else
docker compose -f "$COMPOSE_FILE" up -d --no-deps --build
PROFILE_OPT=""
fi
if [[ ${#SERVICES[@]} -gt 0 ]]; then
docker compose -f "$COMPOSE_FILE" up -d --no-deps --build $PROFILE_OPT "${SERVICES[@]}"
else
docker compose -f "$COMPOSE_FILE" up -d --no-deps --build $PROFILE_OPT
fi
log "Gotowe ✅ (wersja: $(cat version.txt))"

View File

@@ -39,3 +39,28 @@ services:
env_file:
- .env
restart: unless-stopped
pgsql:
image: postgres:17
container_name: pgsql-db
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./db/pgsql:/var/lib/postgresql/data
restart: unless-stopped
profiles: ["pgsql"]
mysql:
image: mysql:8
container_name: mysql-db
environment:
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: 89o38kUX5T4C
volumes:
- ./db/mysql:/var/lib/mysql
restart: unless-stopped
profiles: ["mysql"]