diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,4 +1,6 @@ -from flask import Flask, render_template_string, request, redirect, url_for, session +# --- START OF FILE SoolaCosmetics.py --- + +from flask import Flask, render_template_string, request, redirect, url_for, session, send_file import json import os import logging @@ -6,502 +8,318 @@ import threading import time from datetime import datetime from huggingface_hub import HfApi, hf_hub_download -from huggingface_hub.utils import RepositoryNotFoundError +from huggingface_hub.utils import RepositoryNotFoundError, EntryNotFoundError from werkzeug.utils import secure_filename +# from werkzeug.security import generate_password_hash, check_password_hash # Import if implementing password hashing app = Flask(__name__) -app.secret_key = 'your_unique_secret_key_12345' # Уникальный секретный ключ -DATA_FILE = 'data_soola.json' -USERS_FILE = 'users_soola.json' -CONFIG_FILE = 'config.json' # Файл для хранения курса KGS к USD +# IMPORTANT: Change this secret key to something truly random and secret! +app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'your_very_secret_unique_key_56789_cosmetics') + +DATA_FILE = 'data_cosmetics.json' +USERS_FILE = 'users_cosmetics.json' -# Список файлов для синхронизации -SYNC_FILES = [DATA_FILE, USERS_FILE, CONFIG_FILE] +# Список файлов для синхронизации (config.json убран) +SYNC_FILES = [DATA_FILE, USERS_FILE] # Настройки Hugging Face -REPO_ID = "Kgshop/Soola" +REPO_ID = "Kgshop/SoolaCosmetics" # Changed Repo ID HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") -# Адреса магазина -WHOLESALE_ADDRESS = "Дордой, рынок Кербен, 9 ряд, 06 бутик" # Опт -RETAIL_ADDRESS = "Дордой Мир Обуви, номер 150" # Розница - -# Поддерживаемые валюты -CURRENCIES = { - 'USD': 'Доллар США ($)', - 'KGS': 'Кыргызский сом (с)' -} +# Адрес магазина (один) +SHOP_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38" # Настройка логирования -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -def load_config(): - """Загрузка конфигурации (курс KGS к USD).""" - try: - with open(CONFIG_FILE, 'r', encoding='utf-8') as file: - config = json.load(file) - return config.get('kgs_to_usd', 89.0) # Значение по умолчанию - except (FileNotFoundError, json.JSONDecodeError): - return 89.0 # Резервное значение - -def save_config(kgs_to_usd): - """Сохранение конфигурации.""" - with open(CONFIG_FILE, 'w', encoding='utf-8') as file: - json.dump({'kgs_to_usd': kgs_to_usd}, file, ensure_ascii=False, indent=4) - -def convert_price(price_usd, currency): - """Конвертация цены из USD в указанную валюту.""" - kgs_to_usd = load_config() - if currency == 'KGS': - return round(price_usd * kgs_to_usd, 2) - return round(price_usd, 2) +# --- Data Handling Functions --- + +def download_db_from_hf(): + """Скачивает файлы базы данных (JSON) из Hugging Face.""" + if not HF_TOKEN_READ: + logging.warning("HF_TOKEN_READ не установлен. Пропуск скачивания с Hugging Face.") + return False # Indicate download was skipped or failed + + api = HfApi() + downloaded_any = False + for file_name in SYNC_FILES: + try: + hf_hub_download( + repo_id=REPO_ID, + filename=file_name, + repo_type="dataset", + token=HF_TOKEN_READ, + local_dir=".", + local_dir_use_symlinks=False, + force_download=True, # Ensure we get the latest version + resume_download=False + ) + logging.info(f"Файл {file_name} успешно скачан из Hugging Face.") + downloaded_any = True + except RepositoryNotFoundError: + logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face. Проверьте REPO_ID и права доступа.") + # If repo not found, don't try other files + return False # Indicate failure + except EntryNotFoundError: + logging.warning(f"Файл {file_name} не найден в репозитории {REPO_ID}. Будет создан локально при первом сохранении, если не существует.") + # Continue trying to download other files + except Exception as e: + logging.error(f"Ошибка при скачивании файла {file_name} из Hugging Face: {e}") + # Continue trying to download other files, but log the error + return downloaded_any # Return true if at least one file was potentially downloaded or existed def load_data(): + """Загружает данные о товарах и категориях.""" + if not os.path.exists(DATA_FILE): + logging.warning(f"Локальный файл {DATA_FILE} не найден. Попытка скачивания с Hugging Face...") + if not download_db_from_hf(): + logging.warning(f"Не удалось скачать {DATA_FILE} с Hugging Face. Создается пустая структура данных.") + return {'products': [], 'categories': []} + # Check again if download succeeded + if not os.path.exists(DATA_FILE): + logging.warning(f"{DATA_FILE} все еще не найден после попытки скачивания. Создается пустая структура данных.") + return {'products': [], 'categories': []} + try: - download_db_from_hf() with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) - logging.info("Данные успешно загружены из JSON") - if not isinstance(data, dict) or 'products' not in data or 'categories' not in data: - return {'products': [], 'categories': [] if not isinstance(data, list) else data} + logging.info(f"Данные успешно загружены из {DATA_FILE}") + # Basic validation + if not isinstance(data, dict): + logging.warning(f"{DATA_FILE} не содержит словарь. Инициализация пустой структурой.") + return {'products': [], 'categories': []} + if 'products' not in data or not isinstance(data['products'], list): + logging.warning(f"Ключ 'products' отсутствует или не является списком в {DATA_FILE}. Инициализация.") + data['products'] = [] + if 'categories' not in data or not isinstance(data['categories'], list): + logging.warning(f"Ключ 'categories' отсутствует или не является списком в {DATA_FILE}. Инициализация.") + data['categories'] = [] return data - except FileNotFoundError: - logging.warning("Локальный файл базы данных не найден после скачивания.") - return {'products': [], 'categories': []} except json.JSONDecodeError: - logging.error("Ошибка: Невозможно декодировать JSON файл.") - return {'products': [], 'categories': []} - except RepositoryNotFoundError: - logging.error("Репозиторий не найден. Создание локальной базы данных.") + logging.error(f"Ошибка: Невозможно декодировать JSON файл {DATA_FILE}. Возвращается пустая структура.") return {'products': [], 'categories': []} except Exception as e: - logging.error(f"Произошла ошибка при загрузке данных: {e}") + logging.error(f"Произошла ошибка при загрузке данных из {DATA_FILE}: {e}") return {'products': [], 'categories': []} def save_data(data): + """Сохраняет данные о товарах и категориях в JSON и загружает на HF.""" try: with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) - logging.info("Данные успешно сохранены в JSON") - upload_db_to_hf() + logging.info(f"Данные успешно сохранены в {DATA_FILE}") + upload_file_to_hf(DATA_FILE) # Upload the specific file except Exception as e: - logging.error(f"Ошибка при сохранении данных: {e}") - raise + logging.error(f"Ошибка при сохранении данных в {DATA_FILE}: {e}") + # raise # Optionally re-raise if you want the caller to handle it def load_users(): + """Загружает данные пользователей.""" + if not os.path.exists(USERS_FILE): + logging.warning(f"Локальный файл {USERS_FILE} не найден. Попытка скачивания с Hugging Face...") + if not download_db_from_hf(): + logging.warning(f"Не удалось скачать {USERS_FILE} с Hugging Face. Создается пустой словарь пользователей.") + return {} + # Check again if download succeeded + if not os.path.exists(USERS_FILE): + logging.warning(f"{USERS_FILE} все еще не найден после попытки скачивания. Создается пустой словарь пользователей.") + return {} + try: with open(USERS_FILE, 'r', encoding='utf-8') as file: - return json.load(file) - except FileNotFoundError: - return {} + users = json.load(file) + if not isinstance(users, dict): + logging.warning(f"{USERS_FILE} не содержит словарь. Инициализация пустым словарем.") + return {} + logging.info(f"Данные пользователей успешно загружены из {USERS_FILE}") + return users except json.JSONDecodeError: + logging.error(f"Ошибка: Невозможно декодировать JSON файл {USERS_FILE}. Возвращается пустой словарь.") + return {} + except Exception as e: + logging.error(f"Произошла ошибка при загрузке пользователей из {USERS_FILE}: {e}") return {} def save_users(users): - with open(USERS_FILE, 'w', encoding='utf-8') as file: - json.dump(users, file, ensure_ascii=False, indent=4) - upload_db_to_hf() # Вызываем синхронизацию после сохранения - -def upload_db_to_hf(): + """Сохраняет данные пользователей в JSON и загружает на HF.""" try: - api = HfApi() - for file_name in SYNC_FILES: - if os.path.exists(file_name): - api.upload_file( - path_or_fileobj=file_name, - path_in_repo=file_name, - repo_id=REPO_ID, - repo_type="dataset", - token=HF_TOKEN_WRITE, - commit_message=f"Автоматическое резервное копирование файла {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" - ) - logging.info(f"Резервная копия {file_name} успешно загружена на Hugging Face.") - else: - logging.warning(f"Файл {file_name} не найден для загрузки.") + with open(USERS_FILE, 'w', encoding='utf-8') as file: + json.dump(users, file, ensure_ascii=False, indent=4) + logging.info(f"Данные пользователей успешно сохранены в {USERS_FILE}") + upload_file_to_hf(USERS_FILE) # Upload the specific file except Exception as e: - logging.error(f"Ошибка при загрузке резервной копии: {e}") + logging.error(f"Ошибка при сохранении пользователей в {USERS_FILE}: {e}") + # raise + +def upload_file_to_hf(file_name): + """Загружает указанный файл на Hugging Face.""" + if not HF_TOKEN_WRITE: + logging.warning("HF_TOKEN_WRITE не установлен. Пропуск загрузки на Hugging Face.") + return + + if not os.path.exists(file_name): + logging.warning(f"Файл {file_name} не найден локально. Пропуск загрузки на Hugging Face.") + return -def download_db_from_hf(): try: api = HfApi() - for file_name in SYNC_FILES: - hf_hub_download( - repo_id=REPO_ID, - filename=file_name, - repo_type="dataset", - token=HF_TOKEN_READ, - local_dir=".", - local_dir_use_symlinks=False - ) - logging.info(f"Файл {file_name} успешно скачан из Hugging Face.") - except RepositoryNotFoundError as e: - logging.error(f"Репозиторий не найден: {e}") - raise + api.upload_file( + path_or_fileobj=file_name, + path_in_repo=file_name, + repo_id=REPO_ID, + repo_type="dataset", + token=HF_TOKEN_WRITE, + commit_message=f"Обновлен файл {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + ) + logging.info(f"Файл {file_name} успешно загружен на Hugging Face ({REPO_ID}).") except Exception as e: - logging.error(f"Ошибка при скачивании файлов: {e}") - raise + logging.error(f"Ошибка при загрузке файла {file_name} на Hugging Face: {e}") def periodic_backup(): + """Периодически загружает все SYNC_FILES на Hugging Face.""" while True: - upload_db_to_hf() - time.sleep(800) + logging.info("Запуск периодического резервного копирования...") + if not HF_TOKEN_WRITE: + logging.warning("HF_TOKEN_WRITE не установлен. Пропуск периодического резервного копирования.") + time.sleep(1800) # Ждем 30 минут перед следующей проверкой + continue + + for file_name in SYNC_FILES: + upload_file_to_hf(file_name) + time.sleep(5) # Небольшая пауза между файлами + logging.info("Периодическое резервное копирование завершено.") + time.sleep(1800) # Интервал 30 минут (1800 секунд) + +# --- Flask Routes --- @app.route('/') def catalog(): + # Попытка загрузить данные при каждом заходе на главную, чтобы подтянуть изменения + # download_db_from_hf() # Consider if this is too slow for every request data = load_data() - products = data['products'] - categories = data['categories'] + products = data.get('products', []) + categories = data.get('categories', []) is_authenticated = 'user' in session - selected_currency = session.get('currency', 'USD') if is_authenticated else 'USD' - kgs_to_usd = load_config() + # HTML шаблон каталога catalog_html = ''' - Детская обувь оптом и в розницу + Soola Cosmetics - Каталог {/* Changed Title */}
-

Каталог

+

Soola Cosmetics

-
-
Опт: {{ wholesale_address }} | Розница: {{ retail_address }}
+ +
+ Наш адрес: {{ shop_address }} +
+
{% for category in categories %} @@ -509,41 +327,48 @@ def catalog(): {% endfor %}
- +
{% for product in products %} -
{% if product.get('photos') and product['photos']|length > 0 %}
- {{ product['name'] }}
+ {% else %} +
+ {/* Placeholder icon */} +
{% endif %}

{{ product['name'] }}

+

{{ product['description'] }}

{/* Show full desc, limited by CSS */} {% if is_authenticated %} -
{{ convert_price(product['price'], selected_currency) }} {{ selected_currency }}
+
{{ product['price'] }} с
{/* Directly show price in KGS */} {% else %} -
Цена доступна после входа
+
Цена по запросу
{% endif %} -

{{ product['description'][:50] }}{% if product['description']|length > 50 %}...{% endif %}

- + {% if is_authenticated %} - + {% endif %}
{% endfor %} + {% if not products %} +

Товары не найдены или каталог пуст.

+ {% endif %}
@@ -551,138 +376,180 @@ def catalog():