from flask import Flask, render_template_string, request, redirect, url_for, session, send_file, flash
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
from dotenv import load_dotenv
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]
REPO_ID = "Kgshop/Soola"
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
STORE_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38"
CURRENCY_CODE = 'KGS'
CURRENCY_NAME = 'Кыргызский сом (с)'
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:
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:
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}. Файл может быть поврежден. Возврат пустой структуры.")
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}")
upload_db_to_hf(specific_file=DATA_FILE)
except Exception as e:
logging.error(f"Ошибка при сохранении данных в {DATA_FILE}: {e}", exc_info=True)
def load_users():
try:
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}")
upload_db_to_hf(specific_file=USERS_FILE)
except Exception as e:
logging.error(f"Ошибка при сохранении данных пользователей в {USERS_FILE}: {e}", exc_info=True)
def upload_db_to_hf(specific_file=None):
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):
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:
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:
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:
logging.error(f"Общая ошибка при попытке скачивания с Hugging Face: {e}", exc_info=True)
def periodic_backup():
backup_interval = 1800
logging.info(f"Настройка периодического резервного копирования каждые {backup_interval} секунд.")
while True:
time.sleep(backup_interval)
logging.info("Запуск периодического резервного копирования...")
upload_db_to_hf()
logging.info("Периодическое резервное копирование завершено.")
@app.route('/')
def catalog():
data = load_data()
products = data.get('products', [])
categories = data.get('categories', [])
is_authenticated = 'user' in session
catalog_html = '''
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
Синхронизация с Датацентром
Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.