multidb support
This commit is contained in:
30
.env.example
30
.env.example
@@ -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
1
.gitignore
vendored
@@ -6,3 +6,4 @@ venv/
|
||||
version.txt
|
||||
deploy/varnish/default.vcl
|
||||
*.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
|
||||
|
||||
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.")
|
46
deploy.sh
46
deploy.sh
@@ -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
|
||||
# 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))"
|
||||
|
@@ -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"]
|
Reference in New Issue
Block a user