wsparie dla mysql/pgsql/firebird/sqlite

This commit is contained in:
root
2025-07-23 10:57:13 +02:00
parent 57a3866ec8
commit 111a63d3af
9 changed files with 90 additions and 66 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ env
__pycache__
instance/
uploads/
.DS_Store
.DS_Store
db/*

View File

@@ -1,26 +1,38 @@
Scenariusz migracji
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
Zatrzymaj obecny kontener:
# reset wszystkich sekwencji w pgsql
docker exec -it pgsql-db psql -U lista -d lista
docker-compose down
Ustaw w .env:
.env
DB_ENGINE=pgsql
DB_NAME=myapp
DB_USER=user
DB_PASSWORD=pass
Uruchom bazę i aplikację:
docker-compose --profile pgsql up -d
Wykonaj migrację danych:
bash _tools/db/migrate_to_new_db.sh pgsql
Sprawdź działanie aplikacji.
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

@@ -1,17 +1,22 @@
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/shopping.db")
sqlite_meta = MetaData(bind=sqlite_engine)
sqlite_meta.reflect()
sqlite_meta = MetaData()
sqlite_meta.reflect(bind=sqlite_engine)
# Cel: PostgreSQL (czytany z .env przez Config)
# Cel: PostgreSQL
pg_engine = create_engine(Config.SQLALCHEMY_DATABASE_URI)
pg_meta = MetaData(bind=pg_engine)
pg_meta.reflect()
pg_meta = MetaData()
pg_meta.reflect(bind=pg_engine)
# Sesje
SQLiteSession = sessionmaker(bind=sqlite_engine)
@@ -21,29 +26,36 @@ sqlite_session = SQLiteSession()
pg_session = PGSession()
def migrate_table(table_name):
print(f"Migruję tabelę: {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 not source_table or not target_table:
print(f"Pominięto: {table_name} (brak w jednej z baz)")
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.")
print(" Brak danych do migracji.")
return
insert_data = [dict(row._mapping) for row in rows]
with pg_engine.begin() as conn:
conn.execute(target_table.insert(), insert_data)
print(f"✅ Przeniesiono: {len(rows)} rekordów")
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,14 +0,0 @@
#!/bin/bash
set -e
APP_CONTAINER=web
DB_ENGINE=${1:-pgsql} # domyślnie pgsql
echo "Migracja z SQLite do $DB_ENGINE..."
docker-compose run --rm $APP_CONTAINER bash -c "
export FLASK_APP=app.py
flask db upgrade
"
echo "Migracja zakończona."

12
app.py
View File

@@ -103,7 +103,7 @@ def utcnow():
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password_hash = db.Column(db.String(150), nullable=False)
password_hash = db.Column(db.String(512), nullable=False)
is_admin = db.Column(db.Boolean, default=False)
@@ -162,6 +162,7 @@ class Receipt(db.Model):
file_hash = db.Column(db.String(64), nullable=True, unique=True)
with app.app_context():
db.create_all()
@@ -1088,9 +1089,11 @@ def all_products():
top_products_query = top_products_query.filter(
SuggestedProduct.name.ilike(f"%{query}%")
)
top_products = (
top_products_query.order_by(
SuggestedProduct.usage_count.desc(), SuggestedProduct.name.asc()
SuggestedProduct.name.asc(), # musi być pierwsze
SuggestedProduct.usage_count.desc()
)
.distinct(SuggestedProduct.name)
.limit(20)
@@ -1125,8 +1128,9 @@ def all_products():
def upload_receipt(list_id):
l = db.session.get(ShoppingList, list_id)
if l is None or l.owner_id != current_user.id:
return _receipt_error("Nie masz uprawnień do tej listy.")
#if l is None or l.owner_id != current_user.id:
# return _receipt_error("Nie masz uprawnień do tej listy.")
if "receipt" not in request.files:
return _receipt_error("Brak pliku")

View File

@@ -1,12 +1,9 @@
import os
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY", "D8pceNZ8q%YR7^7F&9wAC2")
DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite").lower()
if DB_ENGINE == "sqlite":
SQLALCHEMY_DATABASE_URI = "sqlite:///instance/shopping.db"
elif DB_ENGINE == "pgsql":
@@ -18,9 +15,6 @@ class Config:
else:
raise ValueError("Nieobsługiwany typ bazy danych.")
SQLALCHEMY_TRACK_MODIFICATIONS = False
SYSTEM_PASSWORD = os.environ.get("SYSTEM_PASSWORD", "admin")
DEFAULT_ADMIN_USERNAME = os.environ.get("DEFAULT_ADMIN_USERNAME", "admin")

View File

@@ -1,15 +1,28 @@
#!/bin/bash
set -e
PROFILE=$1
if [[ -z "$PROFILE" ]]; then
echo "Użycie: $0 {pgsql|mysql|firebird|sqlite}"
exit 1
fi
echo "Zatrzymuję i usuwam stare kontenery..."
docker compose down --rmi all
if [[ "$PROFILE" == "sqlite" ]]; then
docker compose down --rmi all
else
docker compose --profile "$PROFILE" down --rmi all
fi
echo "Pobieram najnowszy kod z repozytorium..."
git pull
echo "Buduję obrazy i uruchamiam kontenery..."
#docker compose up -d --build
DB_ENGINE=pgsql docker compose --profile pgsql up -d --build
if [[ "$PROFILE" == "sqlite" ]]; then
docker compose up -d --build
else
DB_ENGINE="$PROFILE" docker compose --profile "$PROFILE" up -d --build
fi
echo "Gotowe!"

View File

@@ -17,8 +17,6 @@ services:
- ./uploads:/app/uploads
- ./instance:/app/instance
restart: unless-stopped
# depends_on:
# - ${DB_ENGINE:-sqlite}
pgsql:
image: postgres:17
@@ -32,6 +30,7 @@ services:
ports:
- "5432:5432"
restart: unless-stopped
hostname: db
profiles: ["pgsql"]
mysql:
@@ -47,6 +46,7 @@ services:
ports:
- "3306:3306"
restart: unless-stopped
hostname: db
profiles: ["mysql"]
firebird:
@@ -59,4 +59,5 @@ services:
ports:
- "3050:3050"
restart: unless-stopped
hostname: db
profiles: ["firebird"]

View File

@@ -11,3 +11,4 @@ pillow-heif
pytesseract
opencv-python-headless
psycopg2-binary