diff --git "a/app.py" "b/app.py"
--- "a/app.py"
+++ "b/app.py"
@@ -1,5 +1,5 @@
-from flask import Flask, render_template_string, request, redirect, url_for, session, send_file, flash
+from flask import Flask, render_template_string, request, redirect, url_for, session, send_file
import json
import os
import logging
@@ -9,14 +9,14 @@ 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 = os.getenv('FLASK_SECRET_KEY', 'fallback_very_secret_soola_key_123')
+app.secret_key = 'your_unique_secret_key_soola_cosmetics_67890' # Новый уникальный секретный ключ
DATA_FILE = 'data_soola.json'
USERS_FILE = 'users_soola.json'
@@ -24,46 +24,51 @@ USERS_FILE = 'users_soola.json'
SYNC_FILES = [DATA_FILE, USERS_FILE]
# Настройки Hugging Face
-REPO_ID = os.getenv('HF_REPO_ID', "Kgshop/Soola") # Получаем из .env или используем дефолтный
-HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен с правом записи
-HF_TOKEN_READ = os.getenv("HF_TOKEN_READ", HF_TOKEN_WRITE) # Токен чтения (может быть тот же)
+# Убедитесь, что 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"
+STORE_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38" # Единый адрес
# Валюта (только KGS)
CURRENCY_CODE = 'KGS'
CURRENCY_NAME = 'Кыргызский сом (с)'
-# Номер WhatsApp для заказов
-WHATSAPP_NUMBER = "996997703090" # <-- ИЗМЕНЕННЫЙ НОМЕР
-
# Настройка логирования
+# Уровни: 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'] = []
+ 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:
+ else: # Если файл появился после download_db_from_hf
with open(DATA_FILE, 'r', encoding='utf-8') as file:
data = json.load(file)
logging.info(f"Данные успешно загружены из {DATA_FILE} после попытки скачивания.")
@@ -71,7 +76,7 @@ def load_data():
if 'products' not in data: data['products'] = []
if 'categories' not in data: data['categories'] = []
return data
- except (FileNotFoundError, RepositoryNotFoundError) as e:
+ 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)
@@ -84,24 +89,31 @@ def load_data():
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}")
@@ -109,13 +121,15 @@ def load_users():
except FileNotFoundError:
logging.warning(f"Локальный файл {USERS_FILE} не найден. Попытка скачать с HF.")
try:
- download_db_from_hf(specific_file=USERS_FILE)
+ 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:
@@ -137,68 +151,97 @@ def save_users(users):
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."""
+ """Загрузка файлов данных на Hugging Face.
+ Если specific_file указан, загружает только его.
+ """
if not HF_TOKEN_WRITE:
- logging.warning("HF_TOKEN (для записи) не установлен. Загрузка на HF пропущена.")
- return
- if not REPO_ID:
- logging.error("HF_REPO_ID не установлен. Загрузка на HF невозможна.")
+ 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} успешно загружен на HF.")
+ 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} на HF: {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"Общая ошибка при инициализации или загрузке на HF: {e}", exc_info=True)
+ logging.error(f"Общая ошибка при инициализации или загрузке на Hugging Face: {e}", exc_info=True)
def download_db_from_hf(specific_file=None):
- """Скачивание файлов данных с Hugging Face."""
- if not REPO_ID:
- logging.error("HF_REPO_ID не установлен. Скачивание с HF невозможно.")
- return
+ """Скачивание файлов данных с 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} успешно скачан из HF в {local_path}.")
+ # Скачиваем в текущую директорию, перезаписывая существующие файлы
+ 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} не найден на HF. Скачивание прервано.")
- break
- except Exception as e:
+ 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} или ошибка доступа. Пропуск.")
+ logging.warning(f"Файл {file_name} не найден в репозитории {REPO_ID} или ошибка доступа. Пропуск скачивания этого файла.")
else:
- logging.error(f"Ошибка при скачивании файла {file_name} с HF: {e}", exc_info=True)
- logging.info(f"Скачивание файлов с HF завершено. Скачано: {downloaded_files_count}/{len(files_to_download)}.")
+ # Логируем другие, возможно, более серьезные ошибки
+ 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} не найден на HF. Скачивание не выполнено.")
+ # Эта ошибка ловится и выше, но может возникнуть при первой проверке репо, если раскомментировать
+ logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face. Скачивание не выполнено.")
except Exception as e:
- logging.error(f"Общая ошибка при попытке скачивания с HF: {e}", exc_info=True)
+ # Общая ошибка, если не удалось даже инициализировать Api() или что-то глобальное
+ logging.error(f"Общая ошибка при попытке скачивания с Hugging Face: {e}", exc_info=True)
+ # Не прерываем работу приложения, будем использовать локальные файлы, если они есть
+
def periodic_backup():
"""Периодическая загрузка данных на HF."""
@@ -207,9 +250,10 @@ def periodic_backup():
while True:
time.sleep(backup_interval)
logging.info("Запуск периодического резервного копирования...")
- upload_db_to_hf()
+ upload_db_to_hf() # Загружает все SYNC_FILES
logging.info("Периодическое резервное копирование завершено.")
+
# --- Маршруты Flask ---
@app.route('/')
@@ -220,7 +264,7 @@ def catalog():
categories = data.get('categories', [])
is_authenticated = 'user' in session
- # Обновленный HTML с темно-зеленой темой
+ # Убираем артефакты {/**/} из HTML шаблона
catalog_html = '''
@@ -232,113 +276,94 @@ def catalog():
@@ -811,53 +1024,108 @@ LOGIN_TEMPLATE = '''