diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,30 +1,25 @@ from flask import Flask, render_template_string, request, redirect, url_for, session, flash -from flask_caching import Cache # Добавляем кэширование import json import os import logging import threading import time -from datetime import datetime, timedelta +from datetime import datetime from huggingface_hub import HfApi, hf_hub_download from werkzeug.utils import secure_filename import random app = Flask(__name__) -app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey") # Безопасный ключ из переменной окружения -DATA_FILE = 'data_adusis.json' +app.secret_key = 'supersecretkey' # Замените на безопасный ключ в продакшене +DATA_FILE = 'data_ostenhost.json' REPO_ID = "Eluza133/w1f9" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен для записи +HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен с правами записи HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE -# Настройка кэширования -cache = Cache(app, config={'CACHE_TYPE': 'simple'}) - # Настройка логирования -logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) # Функции работы с базой данных -@cache.memoize(timeout=300) # Кэшируем данные на 5 минут def load_data(): try: download_db_from_hf() @@ -32,30 +27,27 @@ def load_data(): data = json.load(file) if not isinstance(data, dict): logging.warning("Данные не в формате dict, инициализация пустой базы") - return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} - data.setdefault('posts', []) - data.setdefault('users', {}) - data.setdefault('general_chat', []) - data.setdefault('private_chats', {}) - for user in data['users']: - data['users'][user].setdefault('last_chat_visit', '1970-01-01 00:00:00') - data['users'][user].setdefault('last_private_visit', '1970-01-01 00:00:00') - data['users'][user].setdefault('last_seen', '1970-01-01 00:00:00') + return {'posts': [], 'users': {}, 'pending_organizations': []} + if 'posts' not in data: + data['posts'] = [] + if 'users' not in data: + data['users'] = {} + if 'pending_organizations' not in data: + data['pending_organizations'] = [] logging.info("Данные успешно загружены") return data except Exception as e: - logging.error(f"Ошибка при загрузке данных: {e}") - return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} + logging.error(f"Ошибка загрузки данных: {e}") + return {'posts': [], 'users': {}, 'pending_organizations': []} def save_data(data): try: with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf() - cache.clear() # Очищаем кэш после сохранения logging.info("Данные сохранены и загружены на HF") except Exception as e: - logging.error(f"Ошибка при сохранении данных: {e}") + logging.error(f"Ошибка сохранения данных: {e}") raise def upload_db_to_hf(): @@ -67,11 +59,11 @@ def upload_db_to_hf(): repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) logging.info("База данных загружена на Hugging Face") except Exception as e: - logging.error(f"Ошибка при загрузке базы данных: {e}") + logging.error(f"Ошибка загрузки базы: {e}") def download_db_from_hf(): try: @@ -85,806 +77,706 @@ def download_db_from_hf(): ) logging.info("База данных скачана с Hugging Face") except Exception as e: - logging.error(f"Ошибка при скачивании базы данных: {e}") + logging.error(f"Ошибка скачивания базы: {e}") if not os.path.exists(DATA_FILE): with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump({'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}}, f) + json.dump({'posts': [], 'users': {}, 'pending_organizations': []}, f) def periodic_backup(): while True: upload_db_to_hf() - time.sleep(1800) # Увеличиваем интервал до 30 минут для снижения нагрузки - -def get_unread_count(data, username): - if username not in data['users']: - return 0 - last_visit = datetime.strptime(data['users'][username]['last_chat_visit'], '%Y-%m-%d %H:%M:%S') - return sum(1 for msg in data['general_chat'] if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > last_visit) - -def get_private_unread_count(data, username): - if username not in data['users']: - return 0 - last_visit = datetime.strptime(data['users'][username]['last_private_visit'], '%Y-%m-%d %H:%M:%S') - unread = 0 - for chat_key, messages in data['private_chats'].items(): - users = chat_key.split('_') - if username in users: - unread += sum(1 for msg in messages if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > last_visit and msg['sender'] != username) - return unread - -def is_user_online(data, username): - if username not in data['users']: - return False - last_seen = datetime.strptime(data['users'][username]['last_seen'], '%Y-%m-%d %H:%M:%S') - return (datetime.now() - last_seen).total_seconds() < 300 - -def update_last_seen(data, username): - if username in data['users']: - data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - save_data(data) + time.sleep(800) -# Обновленный стиль +# Обновленный базовый стиль BASE_STYLE = ''' -:root { - --primary: #ff4d6d; - --secondary: #00ddeb; - --accent: #8b5cf6; - --background-light: #fef9f1; - --background-dark: #1f1a44; - --card-bg: rgba(255, 255, 255, 0.9); - --card-bg-dark: rgba(50, 45, 80, 0.9); - --text-light: #2a1e5a; - --text-dark: #e8e1ff; - --shadow: 0 8px 24px rgba(0, 0, 0, 0.15); - --glass-bg: rgba(255, 255, 255, 0.1); - --transition: all 0.3s ease; - --online: #34c759; - --offline: #ff3b30; -} -* { margin: 0; padding: 0; box-sizing: border-box; } -body { - font-family: 'Inter', sans-serif; - background: var(--background-light); - color: var(--text-light); - line-height: 1.6; - transition: var(--transition); - overflow-x: hidden; -} -body.dark { - background: var(--background-dark); - color: var(--text-dark); -} -.sidebar { - position: fixed; - top: 0; - left: 0; - width: 280px; - height: 100%; - background: var(--glass-bg); - backdrop-filter: blur(15px); - padding: 20px; - box-shadow: var(--shadow); - z-index: 1000; - transition: transform var(--transition); -} -.sidebar.hidden { - transform: translateX(-100%); -} -.sidebar-header { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 30px; -} -.nav-brand { - font-size: 1.6em; - font-weight: 800; - background: linear-gradient(135deg, var(--primary), var(--accent)); - -webkit-background-clip: text; - color: transparent; -} -.logo { - width: 36px; - height: 36px; - border-radius: 12px; - box-shadow: var(--shadow); -} -.nav-links { - display: flex; - flex-direction: column; - gap: 12px; -} -.nav-link { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 20px; - background: var(--card-bg); - color: var(--text-light); - text-decoration: none; - border-radius: 12px; - font-size: 1em; - font-weight: 500; - transition: var(--transition); - position: relative; -} -body.dark .nav-link { - background: var(--card-bg-dark); - color: var(--text-dark); -} -.nav-link:hover { - transform: scale(1.02); - background: var(--primary); - color: white; - box-shadow: 0 4px 16px rgba(255, 77, 109, 0.3); -} -.nav-link .badge { - position: absolute; - right: 12px; - background: var(--secondary); - color: white; - padding: 3px 8px; - border-radius: 10px; - font-size: 0.75em; - font-weight: 700; -} -.logout-btn { - background: var(--secondary); - color: white; -} -.logout-btn:hover { - background: #00b8c5; -} -.menu-btn { - display: none; - font-size: 24px; - background: var(--glass-bg); - border: none; - color: var(--primary); - cursor: pointer; - position: fixed; - top: 15px; - left: 15px; - z-index: 1001; - padding: 10px; - border-radius: 50%; - box-shadow: var(--shadow); - transition: var(--transition); -} -.menu-btn:hover { - background: var(--primary); - color: white; -} -.container { - margin: 20px auto 20px 300px; - max-width: 1200px; - padding: 20px; - transition: var(--transition); -} -.btn { - padding: 12px 24px; - background: var(--primary); - color: white; - border: none; - border-radius: 12px; - cursor: pointer; - font-size: 1em; - font-weight: 600; - transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 8px; - box-shadow: var(--shadow); -} -.btn:hover { - transform: scale(1.03); - background: #e6415f; - box-shadow: 0 6px 20px rgba(255, 77, 109, 0.4); -} -input, textarea, select { - width: 100%; - padding: 12px; - margin: 10px 0; - border: none; - border-radius: 12px; - background: var(--glass-bg); - color: var(--text-light); - font-size: 1em; - transition: var(--transition); - box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.05); -} -body.dark input, body.dark textarea, body.dark select { - color: var(--text-dark); -} -input:focus, textarea:focus, select:focus { - outline: none; - background: rgba(255, 255, 255, 0.15); - box-shadow: 0 0 0 3px var(--primary); -} -.modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - z-index: 2000; - justify-content: center; - align-items: center; - transition: opacity var(--transition); -} -.modal img, .modal video { - max-width: 90%; - max-height: 90%; - object-fit: contain; - border-radius: 16px; - box-shadow: var(--shadow); - animation: zoomIn 0.3s ease; -} -.theme-toggle { - position: fixed; - top: 15px; - right: 15px; - background: var(--glass-bg); - border: none; - padding: 10px; - border-radius: 50%; - cursor: pointer; - font-size: 20px; - box-shadow: var(--shadow); - transition: var(--transition); -} -.theme-toggle:hover { - transform: rotate(90deg); - background: var(--accent); - color: white; -} -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - display: inline-block; - margin-left: 5px; -} -.online { background: var(--online); } -.offline { background: var(--offline); } -@keyframes zoomIn { - from { opacity: 0; transform: scale(0.95); } - to { opacity: 1; transform: scale(1); } -} -@media (max-width: 900px) { + :root { + --primary: #6b48ff; + --secondary: #ff4785; + --background-light: #f5f7ff; + --background-dark: #1a1a2e; + --card-bg-light: rgba(255, 255, 255, 0.95); + --card-bg-dark: rgba(40, 40, 60, 0.95); + --text-light: #1a1a2e; + --text-dark: #e0e0e0; + --shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + --glass-bg: rgba(255, 255, 255, 0.15); + --transition: all 0.4s ease; + } + * { margin: 0; padding: 0; box-sizing: border-box; } + body { + font-family: 'Inter', sans-serif; + background: var(--background-light); + color: var(--text-light); + line-height: 1.6; + transition: var(--transition); + overflow-x: hidden; + } + body.dark { + background: var(--background-dark); + color: var(--text-dark); + } .sidebar { - width: 100%; - max-width: 280px; - transform: translateX(-100%); + position: fixed; + top: 0; + left: 0; + width: 300px; + height: 100%; + background: var(--glass-bg); + backdrop-filter: blur(20px); + padding: 30px; + box-shadow: var(--shadow); + z-index: 1000; + transition: var(--transition); + } + .sidebar-header { + margin-bottom: 40px; + } + .nav-brand { + font-size: 2em; + font-weight: 700; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + .nav-links { + display: flex; + flex-direction: column; + gap: 20px; } - .sidebar.active { - transform: translateX(0); + .nav-link { + display: flex; + align-items: center; + gap: 15px; + padding: 18px; + background: var(--card-bg-light); + color: var(--text-light); + text-decoration: none; + border-radius: 15px; + font-size: 1.1em; + transition: var(--transition); + } + body.dark .nav-link { + background: var(--card-bg-dark); + color: var(--text-dark); + } + .nav-link:hover { + transform: translateX(15px); + background: var(--primary); + color: white; + } + .logout-btn { + background: var(--secondary); + color: white; + } + .logout-btn:hover { + background: #e63970; } .menu-btn { - display: block; + display: none; + font-size: 32px; + background: var(--glass-bg); + border: none; + color: var(--primary); + cursor: pointer; + position: fixed; + top: 20px; + left: 20px; + z-index: 1001; + padding: 12px; + border-radius: 50%; + box-shadow: var(--shadow); } .container { - margin: 70px 15px 20px 15px; - max-width: calc(100% - 30px); + margin: 30px auto 30px 330px; + max-width: 1200px; + padding: 30px; + transition: var(--transition); + } + .btn { + padding: 14px 28px; + background: var(--primary); + color: white; + border: none; + border-radius: 15px; + cursor: pointer; + font-size: 1.1em; + transition: var(--transition); + display: inline-block; + margin: 10px 0; + } + .btn:hover { + transform: scale(1.08); + background: #5439cc; + } + input, textarea { + width: 100%; + padding: 14px; + margin: 15px 0; + border: 2px solid rgba(0, 0, 0, 0.1); + border-radius: 15px; + background: var(--glass-bg); + color: var(--text-light); + font-size: 1.1em; + transition: var(--transition); + } + body.dark input, body.dark textarea { + border: 2px solid rgba(255, 255, 255, 0.1); + color: var(--text-dark); + } + input:focus, textarea:focus { + outline: none; + border-color: var(--primary); + background: rgba(255, 255, 255, 0.2); + } + .modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.85); + z-index: 2000; + justify-content: center; + align-items: center; + } + .modal img { + max-width: 95%; + max-height: 95%; + object-fit: contain; + border-radius: 15px; + box-shadow: var(--shadow); } .theme-toggle { - top: 70px; + position: fixed; + top: 20px; + right: 20px; + background: var(--glass-bg); + border: none; + padding: 12px; + border-radius: 50%; + cursor: pointer; + font-size: 24px; + box-shadow: var(--shadow); + transition: var(--transition); + } + @media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + width: 280px; + } + .sidebar.active { + transform: translateX(0); + } + .menu-btn { + display: block; + } + .container { + margin: 80px 15px 30px 15px; + max-width: calc(100% - 30px); + } + .theme-toggle { + top: 80px; + right: 15px; + } + .btn, input, textarea { + font-size: 1em; + padding: 12px; + } } -} -@media (max-width: 480px) { - .nav-brand { font-size: 1.4em; } - .nav-link { font-size: 0.9em; padding: 10px 16px; } - .btn { padding: 10px 18px; font-size: 0.9em; } -} ''' NAV_HTML = ''' - + ''' # Регистрация @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': + user_type = request.form.get('user_type') username = request.form.get('username') password = request.form.get('password') data = load_data() - + if username in data['users']: flash('Пользователь уже существует!') return redirect(url_for('register')) - - data['users'][username] = { - 'password': password, - 'bio': '', - 'link': '', - 'avatar': None, - 'last_chat_visit': '1970-01-01 00:00:00', - 'last_private_visit': '1970-01-01 00:00:00', - 'last_seen': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - save_data(data) - flash('Регистрация успешна! Пожалуйста, войдите.') - return redirect(url_for('login')) - + + if user_type == 'seller': + org_name = request.form.get('org_name') + org_phone = request.form.get('org_phone') + is_online = request.form.get('is_online') == 'on' + org_address = request.form.get('org_address') if not is_online else None + + if not org_name or not org_phone: + flash('Укажите название организации и рабочий номер!') + return redirect(url_for('register')) + + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'type': 'seller', + 'verified': False + } + data['pending_organizations'].append({ + 'username': username, + 'org_name': org_name, + 'org_phone': org_phone, + 'org_address': org_address, + 'is_online': is_online, + 'submitted_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + save_data(data) + flash('Регистрация успешна! Ваша организация на проверке, мы с вами свяжемся.') + return redirect(url_for('login')) + else: + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'type': 'buyer', + 'verified': True + } + save_data(data) + flash('Регистрация успешна! Войдите в систему.') + return redirect(url_for('login')) + is_authenticated = 'username' in session username = session.get('username', None) - data = load_data() - unread_count = get_unread_count(data, username) if is_authenticated else 0 - private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0 - user_count = len(data['users']) - is_online = is_user_online(data, username) if is_authenticated else False - html = ''' - - -
- - - - -{{ post['description']|truncate(100) }}
-By: {{ post['uploader'] }} | {{ post['upload_date'] }}
-Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}
- - {% endfor %} +