diff --git "a/app.py" "b/app.py"
deleted file mode 100644--- "a/app.py"
+++ /dev/null
@@ -1,1830 +0,0 @@
-
-
-from flask import Flask, render_template_string, request, redirect, url_for, session, send_file
-import json
-import os
-import logging
-import threading
-import time
-from datetime import datetime
-from huggingface_hub import HfApi, hf_hub_download
-from huggingface_hub.utils import RepositoryNotFoundError
-from werkzeug.utils import secure_filename
-# Импортируем dotenv для загрузки переменных окружения из .env файла
-from dotenv import load_dotenv
-
-# Загружаем переменные окружения из файла .env (если он есть)
-load_dotenv()
-
-app = Flask(__name__)
-app.secret_key = 'your_unique_secret_key_soola_cosmetics_67890' # Новый уникальный секретный ключ
-DATA_FILE = 'data_soola.json'
-USERS_FILE = 'users_soola.json'
-
-# Список файлов для синхронизации
-SYNC_FILES = [DATA_FILE, USERS_FILE]
-
-# Настройки Hugging Face
-# Убедитесь, что REPO_ID соответствует вашему репозиторию на Hugging Face
-REPO_ID = "Kgshop/Soola" # Замените на ваш ID, если он другой
-HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
-HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Может быть тем же, что и HF_TOKEN
-
-# Адрес магазина
-STORE_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38" # Единый адрес
-
-# Валюта (только KGS)
-CURRENCY_CODE = 'KGS'
-CURRENCY_NAME = 'Кыргызский сом (с)'
-
-# Настройка логирования
-# Уровни: DEBUG, INFO, WARNING, ERROR, CRITICAL
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-
-# --- Функции работы с данными и пользователями ---
-
-def load_data():
- """Загрузка данных о товарах и категориях."""
- 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):
- logging.warning(f"{DATA_FILE} не является словарем. Инициализация пустой структурой.")
- return {'products': [], 'categories': []}
- if 'products' not in data:
- data['products'] = []
- if 'categories' not in data:
- data['categories'] = []
- return data
- except FileNotFoundError:
- logging.warning(f"Локальный файл {DATA_FILE} не найден. Попытка скачать с HF.")
- try:
- # download_db_from_hf() # Уже вызывали выше, избегаем повторного вызова при первой ошибке
- # Если скачивание не удалось выше, пытаемся просто создать пустые файлы
- if not os.path.exists(DATA_FILE):
- with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'products': [], 'categories': []}, f)
- logging.info(f"Создан пустой файл {DATA_FILE}")
- return {'products': [], 'categories': []}
- else: # Если файл появился после 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, RepositoryNotFoundError) as e: # Ловим ошибку репозитория при скачивании
- logging.warning(f"Файл {DATA_FILE} не найден локально и ошибка при доступе к репозиторию HF ({e}). Создание пустой структуры.")
- if not os.path.exists(DATA_FILE):
- with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'products': [], 'categories': []}, f)
- return {'products': [], 'categories': []}
- except json.JSONDecodeError:
- logging.error(f"Ошибка декодирования JSON в {DATA_FILE} после попытки скачивания.")
- return {'products': [], 'categories': []}
- except Exception as e:
- logging.error(f"Неизвестная ошибка при загрузке данных после попытки скачивания: {e}")
- return {'products': [], 'categories': []}
- except json.JSONDecodeError:
- logging.error(f"Ошибка декодирования JSON в локальном {DATA_FILE}. Файл может быть поврежден. Возврат пустой структуры.")
- # Можно добавить логику восстановления из бэкапа или HF, если нужно
- return {'products': [], 'categories': []}
- except Exception as e:
- logging.error(f"Неизвестная ошибка при загрузке данных ({DATA_FILE}): {e}", exc_info=True)
- 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 после сохранения (можно сделать опциональной)
- upload_db_to_hf(specific_file=DATA_FILE)
- except Exception as e:
- logging.error(f"Ошибка при сохранении данных в {DATA_FILE}: {e}", exc_info=True)
- # В реальном приложении можно добавить механизм повторной попытки или уведомления
- # raise # Перевыброс исключения может остановить приложение, если не обработан выше
-
-def load_users():
- """Загрузка данных пользователей."""
- try:
- # Опционально: скачать файл пользователей перед чтением
- # download_db_from_hf(specific_file=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.warning(f"Локальный файл {USERS_FILE} не найден. Попытка скачать с HF.")
- try:
- download_db_from_hf(specific_file=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, RepositoryNotFoundError):
- logging.warning(f"Файл {USERS_FILE} не найден локально и в репозитории HF. Создание пустого файла.")
- # Создаем пустой файл, если его нет
- with open(USERS_FILE, 'w', encoding='utf-8') as f: json.dump({}, f)
- return {}
- except json.JSONDecodeError:
- logging.error(f"Ошибка декодирования JSON в {USERS_FILE} после скачивания.")
- return {}
- except Exception as e:
- logging.error(f"Неизвестная ошибка при загрузке пользователей после скачивания: {e}", exc_info=True)
- return {}
- except json.JSONDecodeError:
- logging.error(f"Ошибка декодирования JSON в локальном {USERS_FILE}. Файл может быть поврежден. Возврат пустого словаря.")
- return {}
- except Exception as e:
- logging.error(f"Неизвестная ошибка при загрузке пользователей ({USERS_FILE}): {e}", exc_info=True)
- 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 после сохранения
- upload_db_to_hf(specific_file=USERS_FILE)
- except Exception as e:
- logging.error(f"Ошибка при сохранении данных пользователей в {USERS_FILE}: {e}", exc_info=True)
-
-# --- Функции синхронизации с Hugging Face ---
-
-def upload_db_to_hf(specific_file=None):
- """Загрузка файлов данных на Hugging Face.
- Если specific_file указан, загружает только его.
- """
- if not HF_TOKEN_WRITE:
- logging.warning("Переменная окружения HF_TOKEN (для записи) не установлена. Загрузка на Hugging Face пропущена.")
- return
- try:
- api = HfApi()
- files_to_upload = [specific_file] if specific_file else SYNC_FILES
- logging.info(f"Начало загрузки файлов {files_to_upload} на HF репозиторий {REPO_ID}...")
-
- for file_name in files_to_upload:
- 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",
- token=HF_TOKEN_WRITE,
- commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
- )
- logging.info(f"Файл {file_name} успешно загружен на Hugging Face.")
- except Exception as e:
- logging.error(f"Ошибка при загрузке файла {file_name} на Hugging Face: {e}")
- # Продолжаем пытаться загрузить другие файлы
- else:
- logging.warning(f"Файл {file_name} не найден локально, пропуск загрузки.")
- logging.info("Загрузка файлов на HF завершена.")
- except Exception as e:
- logging.error(f"Общая ошибка при инициализации или загрузке на Hugging Face: {e}", exc_info=True)
-
-def download_db_from_hf(specific_file=None):
- """Скачивание файлов данных с Hugging Face.
- Если specific_file указан, скачивает только его.
- """
- if not HF_TOKEN_READ:
- # Можно использовать и без токена для публичных репозиториев, но лучше предупредить
- logging.warning("Переменная окружения HF_TOKEN_READ не установлена. Попытка скачивания с Hugging Face без токена (может не сработать для приватных репо).")
- # Не выходим, пытаемся скачать анонимно
-
- files_to_download = [specific_file] if specific_file else SYNC_FILES
- logging.info(f"Начало скачивания файлов {files_to_download} с HF репозитория {REPO_ID}...")
- downloaded_files_count = 0
- try:
- # HfApi() не нужен для hf_hub_download, но можно использовать для проверки существования репо
- # api = HfApi()
- # api.dataset_info(repo_id=REPO_ID, token=HF_TOKEN_READ) # Проверка доступности репо
-
- for file_name in files_to_download:
- try:
- # Скачиваем в текущую директорию, перезаписывая существующие файлы
- local_path = 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 # Принудительно скачивать свежую версию
- )
- logging.info(f"Файл {file_name} успешно скачан из Hugging Face в {local_path}.")
- downloaded_files_count += 1
- except RepositoryNotFoundError:
- logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face. Скачивание прервано.")
- break # Прерываем цикл, если репозиторий не найден
- except Exception as e: # Ловим исключения для каждого файла отдельно
- # Проверяем, является ли ошибка 'Not Found' для конкретного файла
- # hf_hub_download часто возвращает HTTPError или FileNotFoundError внутри
- if "404" in str(e) or isinstance(e, FileNotFoundError):
- logging.warning(f"Файл {file_name} не найден в репозитории {REPO_ID} или ошибка доступа. Пропуск скачивания этого файла.")
- else:
- # Логируем другие, возможно, более серьезные ошибки
- logging.error(f"Ошибка при скачивании файла {file_name} с Hugging Face: {e}", exc_info=True)
- logging.info(f"Скачивание файлов с HF завершено. Скачано файлов: {downloaded_files_count}/{len(files_to_download)}.")
- except RepositoryNotFoundError:
- # Эта ошибка ловится и выше, но может возникнуть при первой проверке репо, если раскомментировать
- logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face. Скачивание не выполнено.")
- except Exception as e:
- # Общая ошибка, если не удалось даже инициализировать Api() или что-то глобальное
- logging.error(f"Общая ошибка при попытке скачивания с Hugging Face: {e}", exc_info=True)
- # Не прерываем работу приложения, будем использовать локальные файлы, если они есть
-
-
-def periodic_backup():
- """Периодическая загрузка данных на HF."""
- backup_interval = 1800 # 30 минут
- logging.info(f"Настройка периодического резервного копирования каждые {backup_interval} секунд.")
- while True:
- time.sleep(backup_interval)
- logging.info("Запуск периодического резервного копирования...")
- upload_db_to_hf() # Загружает все SYNC_FILES
- logging.info("Периодическое резервное копирование завершено.")
-
-
-# --- Маршруты Flask ---
-
-@app.route('/')
-def catalog():
- """Главная страница каталога товаров."""
- data = load_data()
- products = data.get('products', [])
- categories = data.get('categories', [])
- is_authenticated = 'user' in session
-
- # Убираем артефакты {/**/} из HTML шаблона
- catalog_html = '''
-
-
-
- {% endfor %}
- {# Сообщение, если нет товаров ПОСЛЕ фильтрации, будет добавлено через JS #}
- {% if not products %}
-
Товары пока не добавлены.
- {% endif %}
-
-
-
-
-
-
- ×
-
Загрузка...
-
-
-
-
-
-
- ×
-
Укажите количество и цвет
-
-
-
-
-
-
-
-
-
-
-
- ×
-
Ваша корзина
-
Ваша корзина пуста.
-
- Итого: 0.00 {{ currency_code }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- '''
- return render_template_string(
- catalog_html,
- products=products,
- categories=categories,
- repo_id=REPO_ID,
- is_authenticated=is_authenticated,
- store_address=STORE_ADDRESS,
- session=session, # session доступен в Jinja2 по умолчанию, но передать явно не помешает
- currency_code=CURRENCY_CODE
- )
-
-# --- Остальные маршруты (product_detail, login, auto_login, logout, admin, force_upload, force_download) ---
-# --- Код для этих маршрутов остается таким же, как в предыдущем ответе ---
-# --- ... (включая LOGIN_TEMPLATE и admin_html) ... ---
-
-@app.route('/product/')
-def product_detail(index):
- """Отдает HTML с деталями одного продукта для модального окна."""
- data = load_data()
- products = data.get('products', [])
- is_authenticated = 'user' in session
- try:
- product = products[index]
- except IndexError:
- logging.warning(f"Попытка доступа к несуществующему продукту с индексом {index}")
- return "Товар не найден", 404
-
- detail_html = '''
- {# Используем Jinja комментарий #}
-
-
{{ product['name'] }}
- {# Swiper Slider for Photos #}
-
-
- {% if product.get('photos') and product['photos']|length > 0 %}
- {% for photo in product['photos'] %}
-
-
{# Контейнер для зума #}
-
-
-
- {% endfor %}
- {% else %}
-
-
-
- {% endif %}
-
- {# Элементы управления Swiper (показываем только если фото больше 1) #}
- {% if product.get('photos') and product['photos']|length > 1 %}
-
-
-
- {% endif %}
-
- {% set colors = product.get('colors', []) %}
- {% if colors and colors|select('ne', '')|list|length > 0 %} {# Проверяем, что список не пуст и не содержит только пустые строки #}
-
-
- {# Сообщения об успехе/ошибке #}
- {% with messages = get_flashed_messages(with_categories=true) %}
- {% if messages %}
- {% for category, message in messages %}
-
{{ message }}
- {% endfor %}
- {% endif %}
- {% endwith %}
-
-
-
Синхронизация с Hugging Face
-
-
-
-
-
Резервное копирование на Hugging Face происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.