Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,13 +8,16 @@ from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder
|
|
| 8 |
from flask import Flask, request, jsonify, render_template_string
|
| 9 |
import logging
|
| 10 |
import threading
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# Настройка логирования
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
# Инициализация бота и Flask
|
| 17 |
-
BOT_TOKEN = '7734802681:AAGKHGG8O9uNk64JWTHH5yqXzvSxCcoLUdA'
|
| 18 |
bot = Bot(token=BOT_TOKEN)
|
| 19 |
dp = Dispatcher()
|
| 20 |
app = Flask(__name__)
|
|
@@ -22,24 +25,75 @@ app = Flask(__name__)
|
|
| 22 |
# Путь для хранения данных (товары и заказы)
|
| 23 |
DATA_FILE = 'data.json'
|
| 24 |
|
| 25 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def load_data():
|
| 27 |
try:
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
return {'products': [], 'orders': []}
|
| 32 |
except Exception as e:
|
| 33 |
logger.error(f"Ошибка при загрузке данных: {e}")
|
| 34 |
return {'products': [], 'orders': []}
|
| 35 |
|
|
|
|
| 36 |
def save_data(data):
|
| 37 |
try:
|
| 38 |
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
| 39 |
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
|
|
| 40 |
except Exception as e:
|
| 41 |
logger.error(f"Ошибка при сохранении данных: {e}")
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
data = load_data()
|
| 44 |
|
| 45 |
# Клавиатуры
|
|
@@ -67,8 +121,17 @@ async def show_products(message: types.Message):
|
|
| 67 |
await message.answer("Нет доступных товаров.")
|
| 68 |
return
|
| 69 |
for product in data['products']:
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
@dp.message(F.text == "🛒 Корзина")
|
| 74 |
async def show_cart(message: types.Message):
|
|
@@ -147,15 +210,17 @@ admin_html = """
|
|
| 147 |
input, textarea { width: 100%; margin: 5px 0; padding: 8px; }
|
| 148 |
button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; }
|
| 149 |
button:hover { background-color: #45a049; }
|
|
|
|
| 150 |
</style>
|
| 151 |
</head>
|
| 152 |
<body>
|
| 153 |
<div class="container">
|
| 154 |
<h1>Управление товарами</h1>
|
| 155 |
-
<form id="addProductForm">
|
| 156 |
<input type="text" name="name" placeholder="Название" required><br>
|
| 157 |
<input type="number" name="price" placeholder="Цена" step="0.01" required><br>
|
| 158 |
<textarea name="description" placeholder="Описание" required></textarea><br>
|
|
|
|
| 159 |
<button type="submit">Добавить товар</button>
|
| 160 |
</form>
|
| 161 |
<h2>Существующие товары</h2>
|
|
@@ -164,6 +229,9 @@ admin_html = """
|
|
| 164 |
<div class="product">
|
| 165 |
{{ product.name }} - {{ product.price }} руб.<br>
|
| 166 |
{{ product.description }}<br>
|
|
|
|
|
|
|
|
|
|
| 167 |
<button onclick="deleteProduct({{ product.id }})">Удалить</button>
|
| 168 |
</div>
|
| 169 |
{% endfor %}
|
|
@@ -191,12 +259,6 @@ admin_html = """
|
|
| 191 |
{% endif %}
|
| 192 |
</div>
|
| 193 |
<script>
|
| 194 |
-
document.getElementById('addProductForm').onsubmit = async (e) => {
|
| 195 |
-
e.preventDefault();
|
| 196 |
-
const formData = new FormData(e.target);
|
| 197 |
-
const response = await fetch('/add_product', { method: 'POST', body: formData });
|
| 198 |
-
if (response.ok) window.location.reload();
|
| 199 |
-
};
|
| 200 |
async function deleteProduct(productId) {
|
| 201 |
const response = await fetch(`/delete_product/${productId}`, { method: 'POST' });
|
| 202 |
if (response.ok) window.location.reload();
|
|
@@ -210,7 +272,7 @@ admin_html = """
|
|
| 210 |
def admin_panel():
|
| 211 |
try:
|
| 212 |
logger.info("Rendering admin panel with products and orders")
|
| 213 |
-
return render_template_string(admin_html, products=data['products'], orders=data['orders'])
|
| 214 |
except Exception as e:
|
| 215 |
logger.error(f"Ошибка в шаблоне: {e}")
|
| 216 |
return "Ошибка сервера. Проверь логи.", 500
|
|
@@ -223,8 +285,33 @@ def add_product():
|
|
| 223 |
name = request.form['name']
|
| 224 |
price = float(request.form['price'])
|
| 225 |
description = request.form['description']
|
|
|
|
| 226 |
product_id = max((p['id'] for p in data['products']), default=0) + 1
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
save_data(data)
|
| 229 |
return jsonify({'status': 'success'})
|
| 230 |
except Exception as e:
|
|
@@ -249,7 +336,7 @@ async def on_startup(_):
|
|
| 249 |
def run_flask():
|
| 250 |
try:
|
| 251 |
logger.info("Starting Flask server on port 7860")
|
| 252 |
-
app.run(host='0.0.0.0', port=7860)
|
| 253 |
except Exception as e:
|
| 254 |
logger.error(f"Ошибка в Flask: {e}")
|
| 255 |
|
|
@@ -265,4 +352,4 @@ if __name__ == '__main__':
|
|
| 265 |
except KeyboardInterrupt:
|
| 266 |
logger.info("Stopping bot and Flask")
|
| 267 |
finally:
|
| 268 |
-
flask_thread.join() # Ждём завершения потока Flask при завершении программы
|
|
|
|
| 8 |
from flask import Flask, request, jsonify, render_template_string
|
| 9 |
import logging
|
| 10 |
import threading
|
| 11 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 12 |
+
from huggingface_hub.utils import RepositoryNotFoundError
|
| 13 |
+
from werkzeug.utils import secure_filename
|
| 14 |
|
| 15 |
# Настройка логирования
|
| 16 |
logging.basicConfig(level=logging.INFO)
|
| 17 |
logger = logging.getLogger(__name__)
|
| 18 |
|
| 19 |
# Инициализация бота и Flask
|
| 20 |
+
BOT_TOKEN = '7734802681:AAGKHGG8O9uNk64JWTHH5yqXzvSxCcoLUdA' # Замените на токен вашего бота
|
| 21 |
bot = Bot(token=BOT_TOKEN)
|
| 22 |
dp = Dispatcher()
|
| 23 |
app = Flask(__name__)
|
|
|
|
| 25 |
# Путь для хранения данных (товары и заказы)
|
| 26 |
DATA_FILE = 'data.json'
|
| 27 |
|
| 28 |
+
# Настройки Hugging Face
|
| 29 |
+
REPO_ID = "flpolprojects/Clients" # Замените на ваш репозиторий
|
| 30 |
+
HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Установите переменную окружения HF_TOKEN
|
| 31 |
+
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Установите переменную окружения HF_TOKEN_READ
|
| 32 |
+
|
| 33 |
+
# Функция для загрузки данных из Hugging Face Hub
|
| 34 |
def load_data():
|
| 35 |
try:
|
| 36 |
+
download_db_from_hf()
|
| 37 |
+
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
| 38 |
+
return json.load(f)
|
| 39 |
+
except FileNotFoundError:
|
| 40 |
+
logger.warning("Локальный файл базы данных не найден после скачивания.")
|
| 41 |
+
return {'products': [], 'orders': []}
|
| 42 |
+
except json.JSONDecodeError:
|
| 43 |
+
logger.error("Ошибка при загрузке данных: Невозможно декодировать JSON файл.")
|
| 44 |
+
return {'products': [], 'orders': []}
|
| 45 |
+
except RepositoryNotFoundError:
|
| 46 |
+
logger.error("Репозиторий не найден. Создание локальной базы данных.")
|
| 47 |
return {'products': [], 'orders': []}
|
| 48 |
except Exception as e:
|
| 49 |
logger.error(f"Ошибка при загрузке данных: {e}")
|
| 50 |
return {'products': [], 'orders': []}
|
| 51 |
|
| 52 |
+
# Функция для сохранения данных в Hugging Face Hub
|
| 53 |
def save_data(data):
|
| 54 |
try:
|
| 55 |
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
| 56 |
json.dump(data, f, ensure_ascii=False, indent=4)
|
| 57 |
+
upload_db_to_hf()
|
| 58 |
except Exception as e:
|
| 59 |
logger.error(f"Ошибка при сохранении данных: {e}")
|
| 60 |
|
| 61 |
+
# Функция для загрузки базы данных из Hugging Face Hub
|
| 62 |
+
def upload_db_to_hf():
|
| 63 |
+
try:
|
| 64 |
+
api = HfApi()
|
| 65 |
+
api.upload_file(
|
| 66 |
+
path_or_fileobj=DATA_FILE,
|
| 67 |
+
path_in_repo=DATA_FILE,
|
| 68 |
+
repo_id=REPO_ID,
|
| 69 |
+
repo_type="dataset",
|
| 70 |
+
token=HF_TOKEN_WRITE,
|
| 71 |
+
commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
| 72 |
+
)
|
| 73 |
+
logger.info("Резервная копия JSON базы успешно загружена на Hugging Face.")
|
| 74 |
+
except Exception as e:
|
| 75 |
+
logger.error(f"Ошибка при загрузке резервной копии: {e}")
|
| 76 |
+
|
| 77 |
+
# Функция для скачивания базы данных из Hugging Face Hub
|
| 78 |
+
def download_db_from_hf():
|
| 79 |
+
try:
|
| 80 |
+
hf_hub_download(
|
| 81 |
+
repo_id=REPO_ID,
|
| 82 |
+
filename=DATA_FILE,
|
| 83 |
+
repo_type="dataset",
|
| 84 |
+
token=HF_TOKEN_READ,
|
| 85 |
+
local_dir=".",
|
| 86 |
+
local_dir_use_symlinks=False
|
| 87 |
+
)
|
| 88 |
+
logger.info("JSON база успешно скачана из Hugging Face.")
|
| 89 |
+
except RepositoryNotFoundError as e:
|
| 90 |
+
logger.error(f"Репозиторий не найден: {e}")
|
| 91 |
+
raise
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logger.error(f"Ошибка при скачивании JSON базы: {e}")
|
| 94 |
+
raise
|
| 95 |
+
|
| 96 |
+
# Загрузка данных
|
| 97 |
data = load_data()
|
| 98 |
|
| 99 |
# Клавиатуры
|
|
|
|
| 121 |
await message.answer("Нет доступных товаров.")
|
| 122 |
return
|
| 123 |
for product in data['products']:
|
| 124 |
+
photo_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{product['photo']}" if 'photo' in product else None
|
| 125 |
+
caption = f"🏷 {product['name']} - {product['price']} руб.\nОписание: {product['description']}\n/id: {product['id']}"
|
| 126 |
+
|
| 127 |
+
if photo_url:
|
| 128 |
+
try:
|
| 129 |
+
await bot.send_photo(chat_id=message.chat.id, photo=photo_url, caption=caption, reply_markup=get_product_keyboard(product['id']))
|
| 130 |
+
except Exception as e:
|
| 131 |
+
logger.error(f"Ошибка при отправке фото: {e}")
|
| 132 |
+
await message.answer(caption, reply_markup=get_product_keyboard(product['id']))
|
| 133 |
+
else:
|
| 134 |
+
await message.answer(caption, reply_markup=get_product_keyboard(product['id']))
|
| 135 |
|
| 136 |
@dp.message(F.text == "🛒 Корзина")
|
| 137 |
async def show_cart(message: types.Message):
|
|
|
|
| 210 |
input, textarea { width: 100%; margin: 5px 0; padding: 8px; }
|
| 211 |
button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; }
|
| 212 |
button:hover { background-color: #45a049; }
|
| 213 |
+
img { max-width: 100px; max-height: 100px; }
|
| 214 |
</style>
|
| 215 |
</head>
|
| 216 |
<body>
|
| 217 |
<div class="container">
|
| 218 |
<h1>Управление товарами</h1>
|
| 219 |
+
<form id="addProductForm" method="POST" enctype="multipart/form-data" action="/add_product">
|
| 220 |
<input type="text" name="name" placeholder="Название" required><br>
|
| 221 |
<input type="number" name="price" placeholder="Цена" step="0.01" required><br>
|
| 222 |
<textarea name="description" placeholder="Описание" required></textarea><br>
|
| 223 |
+
<input type="file" name="photo" accept="image/*"><br>
|
| 224 |
<button type="submit">Добавить товар</button>
|
| 225 |
</form>
|
| 226 |
<h2>Существующие товары</h2>
|
|
|
|
| 229 |
<div class="product">
|
| 230 |
{{ product.name }} - {{ product.price }} руб.<br>
|
| 231 |
{{ product.description }}<br>
|
| 232 |
+
{% if product.photo %}
|
| 233 |
+
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photo }}" alt="{{ product.name }}">
|
| 234 |
+
{% endif %}
|
| 235 |
<button onclick="deleteProduct({{ product.id }})">Удалить</button>
|
| 236 |
</div>
|
| 237 |
{% endfor %}
|
|
|
|
| 259 |
{% endif %}
|
| 260 |
</div>
|
| 261 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
async function deleteProduct(productId) {
|
| 263 |
const response = await fetch(`/delete_product/${productId}`, { method: 'POST' });
|
| 264 |
if (response.ok) window.location.reload();
|
|
|
|
| 272 |
def admin_panel():
|
| 273 |
try:
|
| 274 |
logger.info("Rendering admin panel with products and orders")
|
| 275 |
+
return render_template_string(admin_html, products=data['products'], orders=data['orders'], repo_id=REPO_ID)
|
| 276 |
except Exception as e:
|
| 277 |
logger.error(f"Ошибка в шаблоне: {e}")
|
| 278 |
return "Ошибка сервера. Проверь логи.", 500
|
|
|
|
| 285 |
name = request.form['name']
|
| 286 |
price = float(request.form['price'])
|
| 287 |
description = request.form['description']
|
| 288 |
+
photo = request.files['photo'] # Получаем файл фотографии
|
| 289 |
product_id = max((p['id'] for p in data['products']), default=0) + 1
|
| 290 |
+
|
| 291 |
+
photo_filename = None
|
| 292 |
+
if photo:
|
| 293 |
+
photo_filename = secure_filename(photo.filename)
|
| 294 |
+
temp_path = os.path.join(".", photo_filename) # Сохраняем временно локально
|
| 295 |
+
photo.save(temp_path)
|
| 296 |
+
|
| 297 |
+
try:
|
| 298 |
+
api = HfApi()
|
| 299 |
+
api.upload_file(
|
| 300 |
+
path_or_fileobj=temp_path,
|
| 301 |
+
path_in_repo=f"photos/{photo_filename}",
|
| 302 |
+
repo_id=REPO_ID,
|
| 303 |
+
repo_type="dataset",
|
| 304 |
+
token=HF_TOKEN_WRITE,
|
| 305 |
+
commit_message=f"Добавлено фото для товара {name}"
|
| 306 |
+
)
|
| 307 |
+
logger.info(f"Фото успешно загружено: {photo_filename}")
|
| 308 |
+
except Exception as e:
|
| 309 |
+
logger.error(f"Ошибка при загрузке фото: {e}")
|
| 310 |
+
return jsonify({'status': 'error', 'message': f'Ошибка при загрузке фото: {str(e)}'}), 500
|
| 311 |
+
finally:
|
| 312 |
+
os.remove(temp_path) # Удаляем временный файл
|
| 313 |
+
|
| 314 |
+
data['products'].append({'id': product_id, 'name': name, 'price': price, 'description': description, 'photo': photo_filename}) # Сохраняем имя файла фотографии
|
| 315 |
save_data(data)
|
| 316 |
return jsonify({'status': 'success'})
|
| 317 |
except Exception as e:
|
|
|
|
| 336 |
def run_flask():
|
| 337 |
try:
|
| 338 |
logger.info("Starting Flask server on port 7860")
|
| 339 |
+
app.run(host='0.0.0.0', port=7860, debug=True)
|
| 340 |
except Exception as e:
|
| 341 |
logger.error(f"Ошибка в Flask: {e}")
|
| 342 |
|
|
|
|
| 352 |
except KeyboardInterrupt:
|
| 353 |
logger.info("Stopping bot and Flask")
|
| 354 |
finally:
|
| 355 |
+
flask_thread.join() # Ждём завершения потока Flask при завершении программы
|