multidb support
This commit is contained in:
30
.env.example
30
.env.example
@@ -36,4 +36,32 @@ USE_ETAGS=True
|
|||||||
PRAGMA_HEADER=
|
PRAGMA_HEADER=
|
||||||
|
|
||||||
# Wartość nagłówka X-Robots-Tag, gdy BLOCK_BOTS=True
|
# 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
1
.gitignore
vendored
@@ -6,3 +6,4 @@ venv/
|
|||||||
version.txt
|
version.txt
|
||||||
deploy/varnish/default.vcl
|
deploy/varnish/default.vcl
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
db/*
|
38
_tools/db/migrate.txt
Normal file
38
_tools/db/migrate.txt
Normal 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$$;
|
61
_tools/db/migrate_sqlite_to_pgsql.py
Normal file
61
_tools/db/migrate_sqlite_to_pgsql.py
Normal 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()
|
24
config.py
24
config.py
@@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
def _get_bool(name: str, default: bool) -> bool:
|
def _get_bool(name: str, default: bool) -> bool:
|
||||||
val = os.environ.get(name)
|
val = os.environ.get(name)
|
||||||
if val is None:
|
if val is None:
|
||||||
@@ -24,8 +26,8 @@ class Config:
|
|||||||
- ROBOTS_TAG
|
- 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
|
# Flask
|
||||||
SECRET_KEY = _get_str("SECRET_KEY", "tajny_klucz")
|
SECRET_KEY = _get_str("SECRET_KEY", "tajny_klucz")
|
||||||
@@ -49,3 +51,21 @@ class Config:
|
|||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
HEALTHCHECK_TOKEN = _get_str("HEALTHCHECK_TOKEN", "healthcheck")
|
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.")
|
48
deploy.sh
48
deploy.sh
@@ -10,23 +10,30 @@ fi
|
|||||||
|
|
||||||
APP_PORT="${APP_PORT:-8080}"
|
APP_PORT="${APP_PORT:-8080}"
|
||||||
|
|
||||||
|
|
||||||
# === Konfiguracja (możesz nadpisać zmiennymi środowiskowymi) ===
|
|
||||||
REPO_DIR="${REPO_DIR:-$(pwd)}"
|
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_REMOTE="${GIT_REMOTE:-origin}"
|
||||||
GIT_BRANCH="${GIT_BRANCH:-$(git -C "$REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)}"
|
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,
|
# Domyślny profil i usługi
|
||||||
# np.: ./deploy.sh web api
|
DB_PROFILE="sqlite" # domyślnie sqlite, czyli brak profilu pgsql/mysql
|
||||||
SERVICES=("$@")
|
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" "$*"; }
|
log() { printf "\n==> %s\n" "$*"; }
|
||||||
|
|
||||||
# --- Kontrole wstępne ---
|
|
||||||
command -v git >/dev/null || { echo "Brak 'git' w PATH"; exit 1; }
|
command -v git >/dev/null || { echo "Brak 'git' w PATH"; exit 1; }
|
||||||
command -v docker >/dev/null || { echo "Brak 'docker' 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
|
if ! docker compose version >/dev/null 2>&1; then
|
||||||
echo "Wymagany jest 'docker compose' (plugin), nie stary 'docker-compose'."
|
echo "Wymagany jest 'docker compose' (plugin), nie stary 'docker-compose'."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -41,34 +48,37 @@ if [[ ! -f "$REPO_DIR/$COMPOSE_FILE" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Praca w katalogu repo ---
|
|
||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
|
|
||||||
# --- Aktualizacja kodu ---
|
|
||||||
log "Aktualizacja repo: git pull --ff-only ($GIT_REMOTE/$GIT_BRANCH)"
|
log "Aktualizacja repo: git pull --ff-only ($GIT_REMOTE/$GIT_BRANCH)"
|
||||||
git fetch --prune "$GIT_REMOTE"
|
git fetch --prune "$GIT_REMOTE"
|
||||||
git checkout "$GIT_BRANCH" >/dev/null 2>&1 || true
|
git checkout "$GIT_BRANCH" >/dev/null 2>&1 || true
|
||||||
git pull --ff-only "$GIT_REMOTE" "$GIT_BRANCH"
|
git pull --ff-only "$GIT_REMOTE" "$GIT_BRANCH"
|
||||||
|
|
||||||
# --- Zapisanie wersji do pliku ---
|
|
||||||
log "Zapisywanie hasha commita do version.txt"
|
log "Zapisywanie hasha commita do version.txt"
|
||||||
git rev-parse --short HEAD > 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)"
|
log "Docker Compose DOWN (usuwanie kontenerów i osieroconych usług)"
|
||||||
docker compose -f "$COMPOSE_FILE" down --remove-orphans
|
docker compose -f "$COMPOSE_FILE" down --remove-orphans
|
||||||
|
|
||||||
|
|
||||||
# --- Generowanie default.vcl z szablonu ---
|
|
||||||
log "Generowanie default.vcl z APP_PORT=$APP_PORT"
|
log "Generowanie default.vcl z APP_PORT=$APP_PORT"
|
||||||
envsubst < deploy/varnish/default.vcl.template > deploy/varnish/default.vcl
|
envsubst < deploy/varnish/default.vcl.template > deploy/varnish/default.vcl
|
||||||
|
|
||||||
# --- Budowanie i uruchamianie bez restartu zależności ---
|
# Budowanie i uruchamianie
|
||||||
log "Docker Compose UP (build bez deps) dla: ${SERVICES[*]:-(wszystkie)}"
|
log "Docker Compose UP (build bez deps) dla profilu: $DB_PROFILE i serwisów: ${SERVICES[*]:-(wszystkie)}"
|
||||||
if [[ ${#SERVICES[@]} -gt 0 ]]; then
|
|
||||||
docker compose -f "$COMPOSE_FILE" up -d --no-deps --build "${SERVICES[@]}"
|
if [[ "$DB_PROFILE" == "pgsql" ]]; then
|
||||||
|
PROFILE_OPT="--profile pgsql"
|
||||||
|
elif [[ "$DB_PROFILE" == "mysql" ]]; then
|
||||||
|
PROFILE_OPT="--profile mysql"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
log "Gotowe ✅ (wersja: $(cat version.txt))"
|
log "Gotowe ✅ (wersja: $(cat version.txt))"
|
||||||
|
@@ -39,3 +39,28 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
restart: unless-stopped
|
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"]
|
Reference in New Issue
Block a user