From 57a3866ec8d4ea204c2ee2827ebaf7bf18bd97ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 23 Jul 2025 09:30:27 +0200 Subject: [PATCH 1/7] inne bazy z opcjach --- .env.example | 12 +++- add_products.py => _tools/add_products.py | 0 .../add_receipt_to_list.py | 0 _tools/db/migrate.txt | 26 ++++++++ _tools/db/migrate_sqlite_to_pgsql.py | 49 +++++++++++++++ _tools/db/migrate_to_new_db.sh | 14 +++++ .../migrate_to_webp.py | 0 .../update_missing_image_data.py | 0 config.py | 19 +++++- deploy_docker.sh | 4 +- docker-compose.yml | 61 +++++++++++++++---- requirements.txt | 2 +- 12 files changed, 170 insertions(+), 17 deletions(-) rename add_products.py => _tools/add_products.py (100%) rename add_receipt_to_list.py => _tools/add_receipt_to_list.py (100%) create mode 100644 _tools/db/migrate.txt create mode 100644 _tools/db/migrate_sqlite_to_pgsql.py create mode 100644 _tools/db/migrate_to_new_db.sh rename migrate_to_webp.py => _tools/migrate_to_webp.py (100%) rename update_missing_image_data.py => _tools/update_missing_image_data.py (100%) diff --git a/.env.example b/.env.example index ba00eff..5f5f384 100644 --- a/.env.example +++ b/.env.example @@ -23,4 +23,14 @@ AUTH_COOKIE_MAX_AGE=86400 HEALTHCHECK_TOKEN=alamapsaikota123 # sesja zalogowanego usera (domyślnie 7 dni) -SESSION_TIMEOUT_MINUTES=10080 \ No newline at end of file +SESSION_TIMEOUT_MINUTES=10080 + +# Rodzaj bazy: sqlite, pgsql, mysql, firebird +DB_ENGINE=sqlite + +# Wspólne zmienne (dla pgsql, mysql, firebird) +DB_HOST=db +DB_PORT=5432 +DB_NAME=myapp +DB_USER=user +DB_PASSWORD=pass diff --git a/add_products.py b/_tools/add_products.py similarity index 100% rename from add_products.py rename to _tools/add_products.py diff --git a/add_receipt_to_list.py b/_tools/add_receipt_to_list.py similarity index 100% rename from add_receipt_to_list.py rename to _tools/add_receipt_to_list.py diff --git a/_tools/db/migrate.txt b/_tools/db/migrate.txt new file mode 100644 index 0000000..1a93b27 --- /dev/null +++ b/_tools/db/migrate.txt @@ -0,0 +1,26 @@ +Scenariusz migracji + +Zatrzymaj obecny kontener: + + +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. + diff --git a/_tools/db/migrate_sqlite_to_pgsql.py b/_tools/db/migrate_sqlite_to_pgsql.py new file mode 100644 index 0000000..49f94ab --- /dev/null +++ b/_tools/db/migrate_sqlite_to_pgsql.py @@ -0,0 +1,49 @@ +import os +from sqlalchemy import create_engine, MetaData +from sqlalchemy.orm import sessionmaker +from config import Config + +# Źródło: SQLite +sqlite_engine = create_engine("sqlite:///instance/shopping.db") +sqlite_meta = MetaData(bind=sqlite_engine) +sqlite_meta.reflect() + +# Cel: PostgreSQL (czytany z .env przez Config) +pg_engine = create_engine(Config.SQLALCHEMY_DATABASE_URI) +pg_meta = MetaData(bind=pg_engine) +pg_meta.reflect() + +# Sesje +SQLiteSession = sessionmaker(bind=sqlite_engine) +PGSession = sessionmaker(bind=pg_engine) + +sqlite_session = SQLiteSession() +pg_session = PGSession() + +def migrate_table(table_name): + print(f"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)") + 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] + with pg_engine.begin() as conn: + conn.execute(target_table.insert(), insert_data) + + print(f"✅ Przeniesiono: {len(rows)} rekordów") + +def main(): + tables = ["user", "shopping_list", "item", "expense", "receipt", "suggested_product"] + for table in tables: + migrate_table(table) + +if __name__ == "__main__": + main() diff --git a/_tools/db/migrate_to_new_db.sh b/_tools/db/migrate_to_new_db.sh new file mode 100644 index 0000000..32c72fe --- /dev/null +++ b/_tools/db/migrate_to_new_db.sh @@ -0,0 +1,14 @@ +#!/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." diff --git a/migrate_to_webp.py b/_tools/migrate_to_webp.py similarity index 100% rename from migrate_to_webp.py rename to _tools/migrate_to_webp.py diff --git a/update_missing_image_data.py b/_tools/update_missing_image_data.py similarity index 100% rename from update_missing_image_data.py rename to _tools/update_missing_image_data.py diff --git a/config.py b/config.py index feb098e..1946569 100644 --- a/config.py +++ b/config.py @@ -3,7 +3,24 @@ import os class Config: SECRET_KEY = os.environ.get("SECRET_KEY", "D8pceNZ8q%YR7^7F&9wAC2") - SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///shopping.db") + + + DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite").lower() + + if DB_ENGINE == "sqlite": + SQLALCHEMY_DATABASE_URI = "sqlite:///instance/shopping.db" + elif DB_ENGINE == "pgsql": + SQLALCHEMY_DATABASE_URI = f"postgresql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@{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']}@{os.environ['DB_HOST']}:{os.environ.get('DB_PORT', 3306)}/{os.environ['DB_NAME']}" + elif DB_ENGINE == "firebird": + SQLALCHEMY_DATABASE_URI = f"firebird+fdb://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@{os.environ['DB_HOST']}/{os.environ['DB_NAME']}.fdb" + 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") diff --git a/deploy_docker.sh b/deploy_docker.sh index 98c64b8..7304eb1 100644 --- a/deploy_docker.sh +++ b/deploy_docker.sh @@ -8,6 +8,8 @@ echo "Pobieram najnowszy kod z repozytorium..." git pull echo "Buduję obrazy i uruchamiam kontenery..." -docker compose up -d --build +#docker compose up -d --build +DB_ENGINE=pgsql docker compose --profile pgsql up -d --build + echo "Gotowe!" diff --git a/docker-compose.yml b/docker-compose.yml index 68eeafa..070eb9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,18 +10,53 @@ services: timeout: 10s retries: 3 start_period: 10s - environment: - - FLASK_APP=app.py - - FLASK_ENV=production - - SECRET_KEY=${SECRET_KEY} - - SYSTEM_PASSWORD=${SYSTEM_PASSWORD} - - DEFAULT_ADMIN_USERNAME=${DEFAULT_ADMIN_USERNAME} - - DEFAULT_ADMIN_PASSWORD=${DEFAULT_ADMIN_PASSWORD} - - UPLOAD_FOLDER=${UPLOAD_FOLDER} - - AUTHORIZED_COOKIE_VALUE=${AUTHORIZED_COOKIE_VALUE} - - AUTH_COOKIE_MAX_AGE=${AUTH_COOKIE_MAX_AGE} - - HEALTHCHECK_TOKEN=${HEALTHCHECK_TOKEN} - - SESSION_TIMEOUT_MINUTES=${SESSION_TIMEOUT_MINUTES} + env_file: + - .env volumes: - .:/app - restart: unless-stopped \ No newline at end of file + - ./uploads:/app/uploads + - ./instance:/app/instance + restart: unless-stopped + # depends_on: + # - ${DB_ENGINE:-sqlite} + + 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 + ports: + - "5432:5432" + 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: root + volumes: + - ./db/mysql:/var/lib/mysql + ports: + - "3306:3306" + restart: unless-stopped + profiles: ["mysql"] + + firebird: + image: jacobalberty/firebird + container_name: firebird-db + environment: + ISC_PASSWORD: ${DB_PASSWORD} + volumes: + - ./db/firebird:/firebird/data + ports: + - "3050:3050" + restart: unless-stopped + profiles: ["firebird"] diff --git a/requirements.txt b/requirements.txt index 5957702..147773d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ psutil pillow-heif pytesseract -opencv-python-headless \ No newline at end of file +opencv-python-headless From 111a63d3af6e64d6f8cd4aa6b62f32da4cedec17 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 23 Jul 2025 10:57:13 +0200 Subject: [PATCH 2/7] wsparie dla mysql/pgsql/firebird/sqlite --- .gitignore | 3 +- _tools/db/migrate.txt | 58 +++++++++++++++++----------- _tools/db/migrate_sqlite_to_pgsql.py | 36 +++++++++++------ _tools/db/migrate_to_new_db.sh | 14 ------- app.py | 12 ++++-- config.py | 6 --- deploy_docker.sh | 21 ++++++++-- docker-compose.yml | 5 ++- requirements.txt | 1 + 9 files changed, 90 insertions(+), 66 deletions(-) delete mode 100644 _tools/db/migrate_to_new_db.sh diff --git a/.gitignore b/.gitignore index aa9d9a9..2e2fed2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ env __pycache__ instance/ uploads/ -.DS_Store \ No newline at end of file +.DS_Store +db/* diff --git a/_tools/db/migrate.txt b/_tools/db/migrate.txt index 1a93b27..eea8aa2 100644 --- a/_tools/db/migrate.txt +++ b/_tools/db/migrate.txt @@ -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$$; diff --git a/_tools/db/migrate_sqlite_to_pgsql.py b/_tools/db/migrate_sqlite_to_pgsql.py index 49f94ab..946a507 100644 --- a/_tools/db/migrate_sqlite_to_pgsql.py +++ b/_tools/db/migrate_sqlite_to_pgsql.py @@ -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() diff --git a/_tools/db/migrate_to_new_db.sh b/_tools/db/migrate_to_new_db.sh deleted file mode 100644 index 32c72fe..0000000 --- a/_tools/db/migrate_to_new_db.sh +++ /dev/null @@ -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." diff --git a/app.py b/app.py index ee97ac9..bf98b69 100644 --- a/app.py +++ b/app.py @@ -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") diff --git a/config.py b/config.py index 1946569..5c74d03 100644 --- a/config.py +++ b/config.py @@ -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") diff --git a/deploy_docker.sh b/deploy_docker.sh index 7304eb1..1e8ff59 100644 --- a/deploy_docker.sh +++ b/deploy_docker.sh @@ -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!" diff --git a/docker-compose.yml b/docker-compose.yml index 070eb9e..5c89a6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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"] diff --git a/requirements.txt b/requirements.txt index 147773d..43127ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ pillow-heif pytesseract opencv-python-headless +psycopg2-binary From 74ae7642e548bb270c8f5d566b095cf32a070a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 23 Jul 2025 13:46:57 +0200 Subject: [PATCH 3/7] usprawnienia w panelu --- app.py | 48 +++++++++++++++++++++++++++++++- config.py | 3 +- templates/admin/admin_panel.html | 5 +++- templates/admin/edit_list.html | 27 ++++++++++++++++-- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index bf98b69..dd8d573 100644 --- a/app.py +++ b/app.py @@ -45,7 +45,7 @@ from config import Config from PIL import Image, ExifTags, ImageFilter, ImageOps from werkzeug.utils import secure_filename from werkzeug.middleware.proxy_fix import ProxyFix -from sqlalchemy import func, extract +from sqlalchemy import func, extract, inspect from collections import defaultdict, deque from functools import wraps @@ -99,6 +99,7 @@ active_users = {} def utcnow(): return datetime.now(timezone.utc) +app_start_time = utcnow() class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) @@ -1386,6 +1387,30 @@ def admin_panel(): process = psutil.Process(os.getpid()) app_mem = process.memory_info().rss // (1024 * 1024) # MB + # Engine info + db_engine = db.engine + db_info = { + "engine": db_engine.name, + "version": getattr(db_engine.dialect, "server_version_info", None), + "url": str(db_engine.url).split("?")[0], + } + + # Tabele + inspector = inspect(db_engine) + table_count = len(inspector.get_table_names()) + + # Rekordy (szybkie zliczenie) + record_total = ( + db.session.query(func.count(User.id)).scalar() + + db.session.query(func.count(ShoppingList.id)).scalar() + + db.session.query(func.count(Item.id)).scalar() + + db.session.query(func.count(Receipt.id)).scalar() + + db.session.query(func.count(Expense.id)).scalar() + ) + + # Uptime + uptime_minutes = int((datetime.now(timezone.utc) - app_start_time).total_seconds() // 60) + return render_template( "admin/admin_panel.html", user_count=user_count, @@ -1401,6 +1426,11 @@ def admin_panel(): python_version=sys.version, system_info=platform.platform(), app_memory=f"{app_mem} MB", + + db_info=db_info, + table_count=table_count, + record_total=record_total, + uptime_minutes=uptime_minutes, ) @@ -1778,6 +1808,22 @@ def edit_list(list_id): flash("Nie znaleziono produktu", "danger") return redirect(url_for("edit_list", list_id=list_id)) + elif action == "edit_quantity": + item = db.session.get(Item, request.form.get("item_id")) + if item and item.list_id == list_id: + try: + new_quantity = int(request.form.get("quantity")) + if new_quantity > 0: + item.quantity = new_quantity + db.session.commit() + flash("Zmieniono ilość produktu", "success") + except ValueError: + flash("Nieprawidłowa ilość", "danger") + else: + flash("Nie znaleziono produktu", "danger") + return redirect(url_for("edit_list", list_id=list_id)) + + return render_template( "admin/edit_list.html", list=l, diff --git a/config.py b/config.py index 5c74d03..82f6a43 100644 --- a/config.py +++ b/config.py @@ -1,11 +1,12 @@ import os +basedir = os.path.abspath(os.path.dirname(__file__)) 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" + SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(basedir, 'instance', 'shopping.db')}" elif DB_ENGINE == "pgsql": SQLALCHEMY_DATABASE_URI = f"postgresql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@{os.environ['DB_HOST']}:{os.environ.get('DB_PORT', 5432)}/{os.environ['DB_NAME']}" elif DB_ENGINE == "mysql": diff --git a/templates/admin/admin_panel.html b/templates/admin/admin_panel.html index 6c3d3f6..18c1065 100644 --- a/templates/admin/admin_panel.html +++ b/templates/admin/admin_panel.html @@ -198,6 +198,9 @@ {% endblock %}
- Python: {{ python_version.split()[0] }} | {{ system_info }} | RAM app: {{ app_memory }} + Python: {{ python_version.split()[0] }} | {{ system_info }} | RAM app: {{ app_memory }} | + DB: {{ db_info.engine|upper }}{% if db_info.version %} v{{ db_info.version[0] }}{% endif %} | + Tabele: {{ table_count }} | Rekordy: {{ record_total }} | + Uptime: {{ uptime_minutes }} min
{% endblock %} \ No newline at end of file diff --git a/templates/admin/edit_list.html b/templates/admin/edit_list.html index c8c9b85..76743fc 100644 --- a/templates/admin/edit_list.html +++ b/templates/admin/edit_list.html @@ -65,8 +65,6 @@ - -
{{ item.name }} - (x{{ item.quantity }}) + (x{{ item.quantity }}) + + {% if item.note %} +
+ Notatka: {{ item.note }} +
+ {% endif %} + + {% if item.not_purchased_reason %} +
+ Powód: {{ item.not_purchased_reason }} +
+ {% endif %} + +
+ + +
+ + +
+
{% if item.purchased %} From 5a898c5b7ae2312ef1728afcf9dcbe7c3fbc22dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 23 Jul 2025 13:50:22 +0200 Subject: [PATCH 4/7] usprawnienia w panelu --- app.py | 15 ++++++++------- templates/admin/edit_list.html | 6 ------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app.py b/app.py index dd8d573..d3e57a6 100644 --- a/app.py +++ b/app.py @@ -99,8 +99,10 @@ active_users = {} def utcnow(): return datetime.now(timezone.utc) + app_start_time = utcnow() + class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(150), unique=True, nullable=False) @@ -163,7 +165,6 @@ class Receipt(db.Model): file_hash = db.Column(db.String(64), nullable=True, unique=True) - with app.app_context(): db.create_all() @@ -1094,7 +1095,7 @@ def all_products(): top_products = ( top_products_query.order_by( SuggestedProduct.name.asc(), # musi być pierwsze - SuggestedProduct.usage_count.desc() + SuggestedProduct.usage_count.desc(), ) .distinct(SuggestedProduct.name) .limit(20) @@ -1129,8 +1130,8 @@ 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: + + # 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: @@ -1409,7 +1410,9 @@ def admin_panel(): ) # Uptime - uptime_minutes = int((datetime.now(timezone.utc) - app_start_time).total_seconds() // 60) + uptime_minutes = int( + (datetime.now(timezone.utc) - app_start_time).total_seconds() // 60 + ) return render_template( "admin/admin_panel.html", @@ -1426,7 +1429,6 @@ def admin_panel(): python_version=sys.version, system_info=platform.platform(), app_memory=f"{app_mem} MB", - db_info=db_info, table_count=table_count, record_total=record_total, @@ -1823,7 +1825,6 @@ def edit_list(list_id): flash("Nie znaleziono produktu", "danger") return redirect(url_for("edit_list", list_id=list_id)) - return render_template( "admin/edit_list.html", list=l, diff --git a/templates/admin/edit_list.html b/templates/admin/edit_list.html index 76743fc..9666185 100644 --- a/templates/admin/edit_list.html +++ b/templates/admin/edit_list.html @@ -173,12 +173,6 @@ - - {% if item.not_purchased_reason %} -
- Powód: {{ item.not_purchased_reason }} -
- {% endif %} {% endif %} From 730330cba98cd714c2231259ef211ebbb9ccdc36 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 23 Jul 2025 23:50:06 +0200 Subject: [PATCH 5/7] remove firebird --- .app.py.swp | Bin 0 -> 1024 bytes .env.example | 21 +++++++++++++++++++-- config.py | 2 -- deploy_docker.sh | 10 +++++----- docker-compose.yml | 15 +-------------- requirements.txt | 4 +++- 6 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 .app.py.swp diff --git a/.app.py.swp b/.app.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..5d0c0516659ce3340b74f8c58667a24c5c8c5dcb GIT binary patch literal 1024 zcmYc?$V<%2S1{KzVn6{MZ2SyG`S~R%f;c$EMP)c-P=yl<3iJvpQN>1iqaiRF0s|KU E0D9vI3IG5A literal 0 HcmV?d00001 diff --git a/.env.example b/.env.example index 5f5f384..b6961b1 100644 --- a/.env.example +++ b/.env.example @@ -25,10 +25,27 @@ HEALTHCHECK_TOKEN=alamapsaikota123 # sesja zalogowanego usera (domyślnie 7 dni) SESSION_TIMEOUT_MINUTES=10080 -# Rodzaj bazy: sqlite, pgsql, mysql, firebird +# Rodzaj bazy: sqlite, pgsql, mysql +# Mozliwe wartosci: sqlite / pgsql / mysql DB_ENGINE=sqlite -# Wspólne zmienne (dla pgsql, mysql, firebird) +# --- 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`) +# Przyklad URI: postgresql://user:pass@db:5432/myapp + +# --- Konfiguracja dla mysql --- +# Ustaw DB_ENGINE=mysql +# Domyslny port MySQL to 3306 +# Wymaga kontenera z MySQL i uzytkownika z dostepem do bazy +# Przyklad URI: mysql+pymysql://user:pass@db:3306/myapp + +# Wspolne zmienne (dla pgsql, mysql) DB_HOST=db DB_PORT=5432 DB_NAME=myapp diff --git a/config.py b/config.py index 82f6a43..7fdf671 100644 --- a/config.py +++ b/config.py @@ -11,8 +11,6 @@ class Config: SQLALCHEMY_DATABASE_URI = f"postgresql://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@{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']}@{os.environ['DB_HOST']}:{os.environ.get('DB_PORT', 3306)}/{os.environ['DB_NAME']}" - elif DB_ENGINE == "firebird": - SQLALCHEMY_DATABASE_URI = f"firebird+fdb://{os.environ['DB_USER']}:{os.environ['DB_PASSWORD']}@{os.environ['DB_HOST']}/{os.environ['DB_NAME']}.fdb" else: raise ValueError("Nieobsługiwany typ bazy danych.") diff --git a/deploy_docker.sh b/deploy_docker.sh index 1e8ff59..38047d3 100644 --- a/deploy_docker.sh +++ b/deploy_docker.sh @@ -4,21 +4,21 @@ set -e PROFILE=$1 if [[ -z "$PROFILE" ]]; then - echo "Użycie: $0 {pgsql|mysql|firebird|sqlite}" + echo "Uzycie: $0 {pgsql|mysql|sqlite}" exit 1 fi -echo "Zatrzymuję i usuwam stare kontenery..." +echo "Zatrzymuje kontenery aplikacji i bazy..." if [[ "$PROFILE" == "sqlite" ]]; then - docker compose down --rmi all + docker compose stop else - docker compose --profile "$PROFILE" down --rmi all + docker compose --profile "$PROFILE" stop fi echo "Pobieram najnowszy kod z repozytorium..." git pull -echo "Buduję obrazy i uruchamiam kontenery..." +echo "Buduje i uruchamiam kontenery..." if [[ "$PROFILE" == "sqlite" ]]; then docker compose up -d --build else diff --git a/docker-compose.yml b/docker-compose.yml index 5c89a6a..32278c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,7 +40,7 @@ services: MYSQL_DATABASE: ${DB_NAME} MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} - MYSQL_ROOT_PASSWORD: root + MYSQL_ROOT_PASSWORD: 89o38kUX5T4C volumes: - ./db/mysql:/var/lib/mysql ports: @@ -48,16 +48,3 @@ services: restart: unless-stopped hostname: db profiles: ["mysql"] - - firebird: - image: jacobalberty/firebird - container_name: firebird-db - environment: - ISC_PASSWORD: ${DB_PASSWORD} - volumes: - - ./db/firebird:/firebird/data - ports: - - "3050:3050" - restart: unless-stopped - hostname: db - profiles: ["firebird"] diff --git a/requirements.txt b/requirements.txt index 43127ab..c27062c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,6 @@ pillow-heif pytesseract opencv-python-headless -psycopg2-binary +psycopg2-binary # pgsql +pymysql # mysql +cryptography From a68aa031bbc8a03b0f8a0c001fd3c64f4c8de72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Thu, 24 Jul 2025 09:54:18 +0200 Subject: [PATCH 6/7] poprawki w compose i kodzie --- .gitignore | 1 + docker-compose.yml | 4 ++-- static/js/functions.js | 2 +- templates/main.html | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 2e2fed2..62bde8f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ instance/ uploads/ .DS_Store db/* +*.swp \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 32278c1..079a122 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: volumes: - ./db/pgsql:/var/lib/postgresql/data ports: - - "5432:5432" + - "5432" restart: unless-stopped hostname: db profiles: ["pgsql"] @@ -44,7 +44,7 @@ services: volumes: - ./db/mysql:/var/lib/mysql ports: - - "3306:3306" + - "3306" restart: unless-stopped hostname: db profiles: ["mysql"] diff --git a/static/js/functions.js b/static/js/functions.js index 522222e..6df9da8 100644 --- a/static/js/functions.js +++ b/static/js/functions.js @@ -123,7 +123,7 @@ function copyLink(link) { if (navigator.share) { navigator.share({ title: 'Udostępnij link', - text: 'Link do listy::', + text: 'Udostępniam link do listy:', url: link }).then(() => { showToast('Link udostępniony!'); diff --git a/templates/main.html b/templates/main.html index 6a96553..1891bc7 100644 --- a/templates/main.html +++ b/templates/main.html @@ -79,7 +79,7 @@ {% else %}

Nie masz jeszcze żadnych list. Utwórz pierwszą, korzystając - z formularza powyżej!

+ z formularza powyżej

{% endif %} {% endif %} From 6f7d0069cc5be84b3551ca4b2dba3e24c8b1c77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Thu, 24 Jul 2025 09:56:30 +0200 Subject: [PATCH 7/7] poprawki w compose i kodzie --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 079a122..19ec0ed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,8 +27,8 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - ./db/pgsql:/var/lib/postgresql/data - ports: - - "5432" + #ports: + # - ":5432:5432" restart: unless-stopped hostname: db profiles: ["pgsql"] @@ -43,8 +43,8 @@ services: MYSQL_ROOT_PASSWORD: 89o38kUX5T4C volumes: - ./db/mysql:/var/lib/mysql - ports: - - "3306" + #ports: + # - "3306:3306" restart: unless-stopped hostname: db profiles: ["mysql"]