diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,5 +1,4 @@ -# --- START OF FILE Soola_Cosmetics.py --- from flask import Flask, render_template_string, request, redirect, url_for, session, send_from_directory import json @@ -11,428 +10,394 @@ from datetime import datetime from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError from werkzeug.utils import secure_filename +import shutil # Import shutil for file operations app = Flask(__name__) -# Важно: Замените 'your_very_secure_secret_key_98765' на действительно случайный и секретный ключ -app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'your_very_secure_secret_key_98765') +app.secret_key = 'your_unique_secret_key_soola_cosmetics_7890' # Новый уникальный секретный ключ +DATA_FILE = 'data_soola.json' +USERS_FILE = 'users_soola.json' +# CONFIG_FILE убран, так как курс фиксирован на KGS -# --- Настройки Магазина --- -SHOP_NAME = "Soola Cosmetics" -SHOP_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38" -PRIMARY_CURRENCY = 'KGS' # Основная и единственная валюта - сом -PRIMARY_CURRENCY_SYMBOL = 'с' # Символ сома +# Список файлов для синхронизации (убрали CONFIG_FILE) +SYNC_FILES = [DATA_FILE, USERS_FILE] -# --- Файлы Данных и Конфигурации --- -DATA_FILE = 'data_soola_cosmetics.json' -USERS_FILE = 'users_soola_cosmetics.json' -# CONFIG_FILE убран, так как курс больше не нужен +# Настройки Hugging Face +REPO_ID = "Kgshop/Soola" # Оставляем старый? Или создать новый репозиторий? Уточнить. +HF_TOKEN_WRITE = os.getenv("HF_TOKEN") +HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Может быть тот же, что и WRITE -# Список файлов для синхронизации с Hugging Face -SYNC_FILES = [DATA_FILE, USERS_FILE] # Убран CONFIG_FILE +# Адрес магазина (только один) +STORE_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38" -# --- Настройки Hugging Face --- -# Убедитесь, что репозиторий соответствует вашему проекту -REPO_ID = "Kgshop/SoolaCosmetics" # Можно обновить имя репозитория, если хотите -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен с правом записи -HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Токен с правом чтения (может быть тот же) +# Поддерживаемые валюты - теперь только KGS +CURRENCY_CODE = 'KGS' +CURRENCY_NAME = 'Кыргызский сом (с)' -# --- Настройка Логирования --- -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +# Настройка логирования +logging.basicConfig(level=logging.INFO) # Можно изменить на DEBUG для подробного лога -# --- Функции Загрузки/Сохранения Данных --- +# --- Функции работы с данными и Hugging Face (без изменений в логике, кроме списка файлов) --- def load_data(): - """Загрузка данных о товарах и категориях.""" + """Загружает данные товаров и категорий.""" try: - # Попытка скачать актуальные данные перед чтением локального файла + # Попробуем скачать актуальные файлы перед чтением download_db_from_hf() + except RepositoryNotFoundError: + logging.warning(f"Репозиторий {REPO_ID} не найден на Hugging Face. Используется локальная версия, если есть.") + except Exception as e: + logging.warning(f"Ошибка при скачивании файлов с Hugging Face: {e}. Используется локальная версия, если есть.") + + try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) logging.info(f"Данные успешно загружены из {DATA_FILE}") # Проверка базовой структуры if not isinstance(data, dict): - logging.warning(f"{DATA_FILE} имеет неверный формат (не словарь). Инициализация пустой структурой.") + logging.warning(f"{DATA_FILE} не является словарем. Сброс к формату по умолчанию.") return {'products': [], 'categories': []} if 'products' not in data: data['products'] = [] + logging.warning(f"Ключ 'products' отсутствовал в {DATA_FILE}. Добавлен пустой список.") if 'categories' not in data: data['categories'] = [] + logging.warning(f"Ключ 'categories' отсутствовал в {DATA_FILE}. Добавлен пустой список.") return data except FileNotFoundError: - logging.warning(f"Локальный файл {DATA_FILE} не найден. Попытка скачивания...") - try: - download_db_from_hf() - # Повторная попытка чтения после скачивания - with open(DATA_FILE, 'r', encoding='utf-8') as file: - data = json.load(file) - logging.info(f"Данные успешно загружены из {DATA_FILE} после скачивания.") - if not isinstance(data, dict): return {'products': [], 'categories': []} - if 'products' not in data: data['products'] = [] - if 'categories' not in data: data['categories'] = [] - return data - except FileNotFoundError: - logging.error(f"Файл {DATA_FILE} не найден даже после попытки скачивания. Создание пустой базы данных.") - return {'products': [], 'categories': []} - except RepositoryNotFoundError: - logging.error("Репозиторий Hugging Face не найден. Создание локальной пустой базы данных.") - return {'products': [], 'categories': []} - except Exception as e: - logging.error(f"Ошибка при загрузке данных после скачивания: {e}") - return {'products': [], 'categories': []} + logging.warning(f"Локальный файл {DATA_FILE} не найден. Создание структуры по умолчанию.") + return {'products': [], 'categories': []} except json.JSONDecodeError: - logging.error(f"Ошибка: Невозможно декодировать JSON из файла {DATA_FILE}. Возвращение пустых данных.") - # Попытка бэкапа испорченного файла + logging.error(f"Ошибка: Невозможно декодировать JSON из {DATA_FILE}. Возврат структуры по умолчанию.") + # Попытка создать резервную копию поврежденного файла try: - corrupted_filename = f"{DATA_FILE}.corrupted_{datetime.now().strftime('%Y%m%d%H%M%S')}" - os.rename(DATA_FILE, corrupted_filename) - logging.info(f"Испорченный файл переименован в {corrupted_filename}") - except Exception as rename_e: - logging.error(f"Не удалось переименовать испорченный файл: {rename_e}") + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + shutil.copyfile(DATA_FILE, f"{DATA_FILE}.corrupted_{timestamp}") + logging.info(f"Создана резервная копия поврежденного файла: {DATA_FILE}.corrupted_{timestamp}") + except Exception as copy_e: + logging.error(f"Не удалось создать резервную копию поврежденного файла {DATA_FILE}: {copy_e}") return {'products': [], 'categories': []} except Exception as e: - logging.error(f"Непредвиденная ошибка при загрузке данных: {e}") + logging.error(f"Непредвиденная ошибка при загрузке данных из {DATA_FILE}: {e}") return {'products': [], 'categories': []} def save_data(data): - """Сохранение данных о товарах и категориях.""" + """Сохраняет данные товаров и категорий.""" try: with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) logging.info(f"Данные успешно сохранены в {DATA_FILE}") - # Запускаем загрузку на HF в отдельном потоке, чтобы не блокировать основной процесс - threading.Thread(target=upload_db_to_hf, args=([DATA_FILE],)).start() + # Загружаем на HF после локального сохранения + upload_db_to_hf() except Exception as e: logging.error(f"Ошибка при сохранении данных в {DATA_FILE}: {e}") - # Не пробрасываем исключение дальше, чтобы приложение продолжало работать, - # но ошибка залогирована. + # Не пробрасываем исключение дальше, чтобы приложение могло продолжить работу, + # но логируем ошибку. Важно отслеживать такие ошибки. def load_users(): - """Загрузка данных пользователей.""" + """Загружает данные пользователей.""" + try: + # Попробуем скачать актуальные файлы перед чтением + download_db_from_hf() # Убедимся, что работаем с последней версией + except RepositoryNotFoundError: + logging.warning(f"Репозиторий {REPO_ID} не найден на Hugging Face. Используется локальная версия users_soola.json, если есть.") + except Exception as e: + logging.warning(f"Ошибка при скачивании users_soola.json с Hugging Face: {e}. Используется локальная версия, если есть.") + try: - # Попытка скачать актуальные данные перед чтением - download_db_from_hf([USERS_FILE]) # Скачиваем только файл пользователей with open(USERS_FILE, 'r', encoding='utf-8') as file: users = json.load(file) logging.info(f"Данные пользователей успешно загружены из {USERS_FILE}") - return users if isinstance(users, dict) else {} + if not isinstance(users, dict): + logging.warning(f"{USERS_FILE} не является словарем. Возвращается пустой словарь.") + return {} + return users except FileNotFoundError: - logging.warning(f"Локальный файл {USERS_FILE} не найден. Попытка скачивания...") - try: - download_db_from_hf([USERS_FILE]) - with open(USERS_FILE, 'r', encoding='utf-8') as file: - users = json.load(file) - logging.info(f"Данные пользователей успешно загружены из {USERS_FILE} после скачивания.") - return users if isinstance(users, dict) else {} - except FileNotFoundError: - logging.error(f"Файл {USERS_FILE} не найден даже после скачивания. Создание пустого списка пользователей.") - return {} - except RepositoryNotFoundError: - logging.error("Репозиторий Hugging Face не найден при скачивании пользователей. Возвращение пустого списка.") - return {} - except Exception as e: - logging.error(f"Ошибка при загрузке пользователей после скачивания: {e}") - return {} + logging.warning(f"Локальный файл {USERS_FILE} не найден. Возвращается пустой словарь.") + return {} except json.JSONDecodeError: - logging.error(f"Ошибка декодирования JSON из {USERS_FILE}. Возвращение пустого списка.") - # Попытка бэкапа + logging.error(f"Ошибка: Невозможно декодировать JSON из {USERS_FILE}. Возвращается пустой словарь.") + # Попытка создать резервную копию поврежденного файла try: - corrupted_filename = f"{USERS_FILE}.corrupted_{datetime.now().strftime('%Y%m%d%H%M%S')}" - os.rename(USERS_FILE, corrupted_filename) - logging.info(f"Испорченный файл пользователей переименован в {corrupted_filename}") - except Exception as rename_e: - logging.error(f"Не удалось переименовать испорченный файл пользователей: {rename_e}") + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + shutil.copyfile(USERS_FILE, f"{USERS_FILE}.corrupted_{timestamp}") + logging.info(f"Создана резервная копия поврежденного файла: {USERS_FILE}.corrupted_{timestamp}") + except Exception as copy_e: + logging.error(f"Не удалось создать резервную копию поврежденного файла {USERS_FILE}: {copy_e}") return {} except Exception as e: - logging.error(f"Непредвиденная ошибка при загрузке пользователей: {e}") + logging.error(f"Непредвиденная ошибка при загрузке данных из {USERS_FILE}: {e}") return {} - def save_users(users): - """Сохранение данных пользователей.""" + """Сохраняет данные пользователей.""" try: with open(USERS_FILE, 'w', encoding='utf-8') as file: json.dump(users, file, ensure_ascii=False, indent=4) logging.info(f"Данные пользователей успешно сохранены в {USERS_FILE}") - # Запускаем загрузку на HF - threading.Thread(target=upload_db_to_hf, args=([USERS_FILE],)).start() + # Загружаем на HF после локального сохранения + upload_db_to_hf() except Exception as e: - logging.error(f"Ошибка при сохранении пользователей в {USERS_FILE}: {e}") + logging.error(f"Ошибка при сохранении данных пользователей в {USERS_FILE}: {e}") -# --- Функции Синхронизации с Hugging Face --- -def upload_db_to_hf(files_to_sync=None): - """Загрузка указанных файлов (или всех SYNC_FILES) на Hugging Face.""" +def upload_db_to_hf(): + """Загружает файлы данных на Hugging Face.""" if not HF_TOKEN_WRITE: - logging.warning("HF_TOKEN (write) не установлен. Загрузка на Hugging Face отключена.") + logging.warning("HF_TOKEN (токен для записи) не установлен. Загрузка на Hugging Face отключена.") return - if files_to_sync is None: - files_to_sync = SYNC_FILES - try: api = HfApi() - logging.info(f"Попытка загрузки файлов: {files_to_sync} в репозиторий {REPO_ID}") - for file_name in files_to_sync: + logging.info(f"Попытка загрузки файлов {SYNC_FILES} в репозиторий {REPO_ID}...") + for file_name in SYNC_FILES: if os.path.exists(file_name): - try: - api.upload_file( - path_or_fileobj=file_name, - path_in_repo=file_name, # Путь в репозитории совпадает с именем файла - repo_id=REPO_ID, - repo_type="dataset", # Репозитории для данных обычно типа dataset - token=HF_TOKEN_WRITE, - commit_message=f"Auto-sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" - ) - logging.info(f"Файл {file_name} успешно загружен на Hugging Face.") - except Exception as upload_exc: - logging.error(f"Ошибка при загрузке файла {file_name} на Hugging Face: {upload_exc}") + 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} не найден локально, пропуск загрузки.") except Exception as e: - logging.error(f"Общая ошибка при инициализации или процессе загрузки на Hugging Face: {e}") + logging.error(f"Ошибка при загрузке файлов на Hugging Face: {e}") - -def download_db_from_hf(files_to_sync=None): - """Скачивание указанных файлов (или всех SYNC_FILES) с Hugging Face.""" +def download_db_from_hf(): + """Скачивает файлы данных с Hugging Face.""" if not HF_TOKEN_READ: - logging.warning("HF_TOKEN_READ не установлен. Скачивание с Hugging Face может быть недоступно для приватных репозиториев.") - # Можно продолжить без токена для публичных репозиториев - # return # Раскомментируйте, если чтение без токена не нужно - - if files_to_sync is None: - files_to_sync = SYNC_FILES - + logging.warning("HF_TOKEN_READ (токен для чтения) не установлен. Скачивание с Hugging Face отключено.") + # Не вызываем raise, чтобы приложение могло работать с локальными файлами, если они есть + return try: - api = HfApi() # Для скачивания токен не всегда обязателен, если репозиторий публичный - logging.info(f"Попытка скачивания файлов: {files_to_sync} и�� репозитория {REPO_ID}") - for file_name in files_to_sync: + api = HfApi() # Можно и без создания объекта api, hf_hub_download сам его использует + logging.info(f"Попытка скачивания файлов {SYNC_FILES} из репозитория {REPO_ID}...") + for file_name in SYNC_FILES: try: - # Скачиваем файл в текущую директорию (.) hf_hub_download( repo_id=REPO_ID, - filename=file_name, # Имя файла в репозитории + filename=file_name, repo_type="dataset", - token=HF_TOKEN_READ, # Передаем токен чтения, если он есть - local_dir=".", # Сохраняем в текущую папку - local_dir_use_symlinks=False # Важно для избежания проблем с символическими ссылками + token=HF_TOKEN_READ, + local_dir=".", # Скачиваем в текущую директорию + local_dir_use_symlinks=False, # Важно для большинства сред развертывания + force_download=True # Принудительно скачиваем, чтобы получить последнюю версию ) logging.info(f"Файл {file_name} успешно скачан из Hugging Face.") - except RepositoryNotFoundError: - logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face.") - raise # Пробрасываем ошибку, чтобы load_data мог ее обработать - except Exception as download_exc: - # Логируем ошибку для конкретного файла, но пытаемся скачать остальные - logging.error(f"Ошибка при скачивании файла {file_name} из Hugging Face: {download_exc}") - # Если файла нет в репо, hf_hub_download вызовет исключение (например, EntryNotFoundError) - # Не пробрасываем его дальше, чтобы не прерывать скачивание других файлов - # Но если файл критичен (как DATA_FILE), load_data обработает FileNotFoundError + except RepositoryNotFoundError as repo_e: + logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face: {repo_e}") + raise # Пробрасываем ошибку, если репозиторий не найден + except Exception as file_e: + # Логируем ошибку для конкретного файла, но продолжаем скачивать остальные + logging.error(f"Ошибка при скачивании файла {file_name}: {file_e}") + except RepositoryNotFoundError: + # Эта ошибка уже обработана выше, но на всякий случай ловим здесь тоже + raise except Exception as e: - logging.error(f"Общая ошибка при инициализации или процессе скачивания с Hugging Face: {e}") - # Не пробрасываем общую ошибку, чтобы приложение могло запуститься с локальными данными, если они есть + # Общая ошибка при попытке скачивания + logging.error(f"Общая ошибка при скачивании файлов с Hugging Face: {e}") + # Не пробрасываем ошибку дальше, чтобы приложение могло попытаться запуститься с локальными данными def periodic_backup(): - """Периодическая загрузка всех файлов на Hugging Face.""" - logging.info("Запуск потока периодического резервного копирования.") + """Периодически загружает данные на Hugging Face.""" while True: - time.sleep(800) # Пауза в 800 секунд (примерно 13 минут) - logging.info("Выполнение планового резервного копирования...") - upload_db_to_hf() # Загружаем все файлы из SYNC_FILES + logging.info("Запуск периодического резервного копирования...") + upload_db_to_hf() + logging.info("Периодическое резервное копирование завершено. Следующий запуск через 800 секу��д.") + time.sleep(800) # 13 минут 20 секунд # --- Маршруты Flask --- @app.route('/') def catalog(): - """Главная страница каталога товаров.""" + """Главная страница каталога.""" data = load_data() products = data.get('products', []) categories = data.get('categories', []) is_authenticated = 'user' in session - # Валюта теперь фиксирована - current_currency = PRIMARY_CURRENCY - current_currency_symbol = PRIMARY_CURRENCY_SYMBOL - catalog_html = f''' + catalog_html = '''
-{{ product.get('description', 'Нет описания') }}
{# Убрано ограничение длины, CSS теперь обрезает #} -{{ product.get('description', '') }}
- {{% if is_authenticated %}} - - {{% endif %}} + {% if is_authenticated %} + + {% endif %}