from flask import Flask, render_template_string, request, redirect, url_for
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
app = Flask(__name__)
DATA_FILE = 'data.json'
REPO_ID = "Kgshop/teenager"
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
LOGO_URL = "https://huggingface.co/spaces/Teenagerkg/optom/resolve/main/PXL_20250419_062456154~2.jpg"
logging.basicConfig(level=logging.DEBUG)
def load_data():
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:
logging.warning("Структура JSON неверна, инициализация...")
if isinstance(data, list):
return {'products': [], 'categories': data}
else:
return {'products': [], 'categories': []}
if 'categories' not in data or not isinstance(data['categories'], list):
data['categories'] = []
if 'products' not in data or not isinstance(data['products'], list):
data['products'] = []
return data
except FileNotFoundError:
logging.warning("Локальный файл базы данных не найден.")
return {'products': [], 'categories': []}
except json.JSONDecodeError:
logging.error("Ошибка: Невозможно декодировать JSON файл.")
return {'products': [], 'categories': []}
except RepositoryNotFoundError:
logging.warning("Репозиторий HF не найден. Используется/создается локальная база.")
try:
with open(DATA_FILE, 'r', encoding='utf-8') as file:
data = json.load(file)
if not isinstance(data, dict) or 'products' not in data or 'categories' not in data:
if isinstance(data, list):
return {'products': [], 'categories': data}
else:
return {'products': [], 'categories': []}
if 'categories' not in data or not isinstance(data['categories'], list):
data['categories'] = []
if 'products' not in data or not isinstance(data['products'], list):
data['products'] = []
return data
except (FileNotFoundError, json.JSONDecodeError):
logging.warning("Локальный файл также не найден или поврежден.")
return {'products': [], 'categories': []}
except Exception as e:
logging.error(f"Произошла ошибка при загрузке данных: {e}")
return {'products': [], 'categories': []}
def save_data(data):
if not isinstance(data, dict):
logging.error("Попытка сохранить данные не в формате словаря. Сохранение отменено.")
return
if 'products' not in data or not isinstance(data['products'], list):
data['products'] = []
logging.warning("Ключ 'products' отсутствует или не является списком. Инициализирован как пустой список.")
if 'categories' not in data or not isinstance(data['categories'], list):
data['categories'] = []
logging.warning("Ключ 'categories' отсутствует или не является списком. Инициализирован как пустой список.")
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()
except Exception as e:
logging.error(f"Ошибка при сохранении данных: {e}")
def upload_db_to_hf():
if not HF_TOKEN_WRITE:
logging.warning("HF_TOKEN_WRITE не установлен. Загрузка на Hugging Face пропущена.")
return
if not os.path.exists(DATA_FILE):
logging.warning(f"Файл {DATA_FILE} не найден для загрузки на Hugging Face.")
return
try:
api = HfApi()
api.upload_file(
path_or_fileobj=DATA_FILE,
path_in_repo=DATA_FILE,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
logging.info("Резервная копия JSON базы успешно загружена на Hugging Face.")
except Exception as e:
logging.error(f"Ошибка при загрузке резервной копии: {e}")
def download_db_from_hf():
if not HF_TOKEN_READ:
logging.warning("HF_TOKEN_READ не установлен. Скачивание с Hugging Face пропущено.")
return
try:
hf_hub_download(
repo_id=REPO_ID,
filename=DATA_FILE,
repo_type="dataset",
token=HF_TOKEN_READ,
local_dir=".",
local_dir_use_symlinks=False,
force_download=True
)
logging.info("JSON база успешно скачана из Hugging Face.")
except RepositoryNotFoundError as e:
logging.error(f"Репозиторий не найден: {e}")
raise
except Exception as e:
logging.error(f"Ошибка при скачивании JSON базы: {e}")
def periodic_backup():
while True:
time.sleep(800)
logging.info("Запуск периодического резервного копирования...")
upload_db_to_hf()
@app.route('/')
def catalog():
data = load_data()
products = sorted(data.get('products', []), key=lambda x: x.get('added_at', ''), reverse=True)
categories = data.get('categories', [])
catalog_html = '''
TeenAger - детская одежда оптом
Каталог
{% for category in categories %}
{% endfor %}
{% for product in products %}
{% if product.get('photos') and product['photos']|length > 0 %}
{% endif %}
{{ product['name'] }}
{{ product['price'] }} с
{{ product['description'][:50] }}{% if product['description']|length > 50 %}...{% endif %}
{% endfor %}
×
×
Укажите количество и цвет
×
Корзина
'''
return render_template_string(catalog_html, products=products, categories=categories, repo_id=REPO_ID)
@app.route('/product/')
def product_detail(index):
data = load_data()
products = sorted(data.get('products', []), key=lambda x: x.get('added_at', ''), reverse=True)
try:
product = products[index]
except IndexError:
logging.error(f"Product index {index} out of range after sorting.")
return "Продукт не найден", 404
except Exception as e:
logging.error(f"Error retrieving product at index {index}: {e}")
return "Ошибка при загрузке продукта", 500
detail_html = '''
{{ product['name'] }}
{% set photos = product.get('photos', []) %}
{% if photos and photos|length > 0 %}
{% for photo in photos %}
{% if product.get('photos') and product['photos']|length > 0 %}
{% for photo in product['photos'] %}
{% endfor %}
{% else %}
Нет фотографий
{% endif %}
Редактировать
{% else %}
В базе пока нет товаров.
{% endfor %}
'''
return render_template_string(admin_html, products=products, categories=categories, repo_id=REPO_ID)
@app.route('/backup', methods=['POST'])
def backup():
try:
save_data(load_data())
return "Резервная копия успешно создана и загружена на Hugging Face.", 200
except Exception as e:
logging.error(f"Ошибка при ручном создании резервной копии: {e}")
return f"Ошибка при создании резервной копии: {e}", 500
@app.route('/download', methods=['GET'])
def download():
try:
download_db_from_hf()
load_data()
return "Актуальная база данных успешно скачана из Hugging Face и загружена в приложение.", 200
except RepositoryNotFoundError:
return "Ошибка: Репозиторий Hugging Face не найден.", 404
except Exception as e:
logging.error(f"Ошибка при ручном скачивании базы данных: {e}")
return f"Ошибка при скачивании базы данных: {e}", 500
if __name__ == '__main__':
os.makedirs('uploads', exist_ok=True)
if HF_TOKEN_WRITE:
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
backup_thread.start()
logging.info("Поток периодического резервного копирования запущен.")
else:
logging.warning("HF_TOKEN_WRITE не установлен, периодическое резервное копирование отключено.")
try:
load_data()
except Exception as e:
logging.warning(f"Не удалось первоначально загрузить/скачать базу данных: {e}. Приложение запустится с пустыми/старыми локальными данными.")
port = int(os.environ.get("PORT", 7860))
logging.info(f"Запуск Flask приложения на хосте 0.0.0.0 и порту {port}")
app.run(debug=False, host='0.0.0.0', port=port)