diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -10,32 +10,32 @@ from werkzeug.utils import secure_filename import random app = Flask(__name__) -app.secret_key = 'supersecretkey' # Замените на безопасный ключ в продакшене -DATA_FILE = 'data_adusis.json' +app.secret_key = 'supersecretkey' # Replace with a secure key in production +DATA_FILE = 'datatest.json' REPO_ID = "Eluza133/w1f9" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен с правами записи +HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Write token HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE -# Настройка логирования +# Logging setup logging.basicConfig(level=logging.DEBUG) -# Функции работы с базой данных +# Database functions def load_data(): try: download_db_from_hf() with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): - logging.warning("Данные не в формате dict, инициализация пустой базы") + logging.warning("Data is not in dict format, initializing empty database") return {'posts': [], 'users': {}} if 'posts' not in data: data['posts'] = [] if 'users' not in data: data['users'] = {} - logging.info("Данные успешно загружены") + logging.info("Data loaded successfully") return data except Exception as e: - logging.error(f"Ошибка загрузки данных: {e}") + logging.error(f"Error loading data: {e}") return {'posts': [], 'users': {}} def save_data(data): @@ -43,9 +43,9 @@ def save_data(data): with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf() - logging.info("Данные сохранены и загружены на HF") + logging.info("Data saved and uploaded to HF") except Exception as e: - logging.error(f"Ошибка сохранения данных: {e}") + logging.error(f"Error saving data: {e}") raise def upload_db_to_hf(): @@ -59,9 +59,9 @@ def upload_db_to_hf(): token=HF_TOKEN_WRITE, commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) - logging.info("База данных загружена на Hugging Face") + logging.info("Database uploaded to Hugging Face") except Exception as e: - logging.error(f"Ошибка загрузки базы: {e}") + logging.error(f"Error uploading database: {e}") def download_db_from_hf(): try: @@ -73,9 +73,9 @@ def download_db_from_hf(): local_dir=".", local_dir_use_symlinks=False ) - logging.info("База данных скачана с Hugging Face") + logging.info("Database downloaded from Hugging Face") except Exception as e: - logging.error(f"Ошибка скачивания базы: {e}") + logging.error(f"Error downloading database: {e}") if not os.path.exists(DATA_FILE): with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump({'posts': [], 'users': {}}, f) @@ -85,27 +85,234 @@ def periodic_backup(): upload_db_to_hf() time.sleep(15) -# Базовый шаблон боковой навигации +# Updated base style with new color palette +BASE_STYLE = ''' + :root { + --primary: #ff6b6b; /* Bright coral */ + --secondary: #4ecdc4; /* Turquoise */ + --background-light: #fff5e6; /* Light peach */ + --background-dark: #2d1b3d; /* Deep purple */ + --card-bg-light: rgba(255, 245, 230, 0.95); + --card-bg-dark: rgba(60, 40, 80, 0.95); + --text-light: #2d1b3d; + --text-dark: #fff5e6; + --shadow: 0 12px 48px rgba(0, 0, 0, 0.2); + --glass-bg: rgba(255, 245, 230, 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 { + 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; + display: flex; + align-items: center; + gap: 15px; + } + .nav-brand { + font-size: 1.8em; + font-weight: 700; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + .logo { + width: 40px; + height: 40px; + border-radius: 50%; + } + .nav-links { + display: flex; + flex-direction: column; + gap: 20px; + } + .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 %s: #45b7b0; + } + .menu-btn { + 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: 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: #e65b5b; + } + 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, 245, 230, 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 { + 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; + } + } +''' + NAV_HTML = ''' ''' -# Регистрация пользователя +# Registration @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': @@ -114,57 +321,72 @@ def register(): data = load_data() if username in data['users']: - flash('Пользователь уже существует!') + flash('User already exists!') return redirect(url_for('register')) data['users'][username] = {'password': password, 'bio': '', 'link': '', 'avatar': None} save_data(data) - logging.info(f"Пользователь {username} зарегистрирован") - flash('Регистрация успешна! Войдите в систему.') + flash('Registration successful! Please log in.') return redirect(url_for('login')) is_authenticated = 'username' in session username = session.get('username', None) html = ''' - + - Регистрация - + + Register - Content Hub + ''' + NAV_HTML + ''' +
-

Регистрация

+

Register

{% with messages = get_flashed_messages() %} {% if messages %} {% for message in messages %} @@ -173,23 +395,30 @@ def register(): {% endif %} {% endwith %}
- - - + + +
-

Уже есть аккаунт? Войти

+
''' return render_template_string(html, is_authenticated=is_authenticated, username=username) -# Авторизация пользователя +# Login @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': @@ -200,53 +429,68 @@ def login(): if username in data['users'] and data['users'][username]['password'] == password: session['username'] = username session.permanent = True - logging.info(f"Успешный вход: {username}") return redirect(url_for('feed')) - flash('Неверный логин или пароль!') + flash('Invalid username or password!') return redirect(url_for('login')) is_authenticated = 'username' in session username = session.get('username', None) html = ''' - + - Вход - + + Login - Content Hub + ''' + NAV_HTML + ''' +
-

Вход

+

Login

{% with messages = get_flashed_messages() %} {% if messages %} {% for message in messages %} @@ -255,30 +499,36 @@ def login(): {% endif %} {% endwith %}
- - - + + +
-

Нет аккаунта? Зарегистрироваться

+
''' return render_template_string(html, is_authenticated=is_authenticated, username=username) -# Выход из системы +# Logout @app.route('/logout') def logout(): session.pop('username', None) - logging.info("Пользователь вышел из системы") return redirect(url_for('feed')) -# Главная страница - лента публикаций +# Feed @app.route('/', methods=['GET', 'POST']) def feed(): data = load_data() @@ -287,66 +537,120 @@ def feed(): username = session.get('username', None) search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower() - if search_query: posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()] html = ''' - + - Лента - + + Feed - Content Hub + ''' + NAV_HTML + ''' +
-

Лента публикаций

+

Content Feed

- - + +
@@ -361,8 +665,8 @@ def feed(): {% endif %}

{{ post['title'] }}

{{ post['description'] }}

-

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

-

Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}

+

Uploaded by: {{ post['uploader'] }} | {{ post['upload_date'] }}

+

Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}

{% endfor %}
@@ -374,92 +678,49 @@ def feed(): function toggleSidebar() { document.getElementById('sidebar').classList.toggle('active'); } - + function toggleTheme() { + document.body.classList.toggle('dark'); + localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); + } function openModal(src, event) { event.preventDefault(); const modal = document.getElementById('imageModal'); const modalImg = document.getElementById('modalImage'); modal.style.display = 'flex'; modalImg.src = src; - modalImg.style.transform = 'scale(1)'; } - function closeModal(event) { if (event.target.tagName !== 'IMG') { - const modal = document.getElementById('imageModal'); - modal.style.display = 'none'; + document.getElementById('imageModal').style.display = 'none'; } } - - document.addEventListener('DOMContentLoaded', function() { + window.onload = () => { + if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark'); const videos = document.querySelectorAll('.post-preview'); videos.forEach(video => { if (video.tagName === 'VIDEO') { - video.addEventListener('loadedmetadata', function() { - const duration = video.duration; - const randomTime = Math.random() * duration; - video.currentTime = randomTime; + video.addEventListener('loadedmetadata', () => { + video.currentTime = Math.random() * video.duration; }); } }); - - const modalImg = document.getElementById('modalImage'); - let scale = 1; - let startDistance = 0; - let lastTap = 0; - - modalImg.addEventListener('dblclick', function() { - scale = scale === 1 ? 2 : 1; - modalImg.style.transform = `scale(${scale})`; - }); - - modalImg.addEventListener('touchstart', function(e) { - if (e.touches.length === 2) { - startDistance = getDistance(e.touches[0], e.touches[1]); - } else if (e.touches.length === 1) { - const now = new Date().getTime(); - if (now - lastTap < 300) { - scale = scale === 1 ? 2 : 1; - modalImg.style.transform = `scale(${scale})`; - } - lastTap = now; - } - }); - - modalImg.addEventListener('touchmove', function(e) { - if (e.touches.length === 2) { - e.preventDefault(); - const currentDistance = getDistance(e.touches[0], e.touches[1]); - scale = Math.max(1, Math.min(3, scale * (currentDistance / startDistance))); - modalImg.style.transform = `scale(${scale})`; - startDistance = currentDistance; - } - }); - - function getDistance(touch1, touch2) { - const dx = touch1.pageX - touch2.pageX; - const dy = touch1.pageY - touch2.pageY; - return Math.sqrt(dx * dx + dy * dy); - } - }); + }; ''' return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query) -# Страница отдельной публикации +# Post page @app.route('/post/', methods=['GET', 'POST']) def post_page(post_id): data = load_data() post = next((p for p in data['posts'] if p['id'] == post_id), None) if not post: - return "Публикация не найдена", 404 + return "Post not found", 404 is_authenticated = 'username' in session username = session.get('username', None) - - # Увеличение количества просмотров post['views'] = post.get('views', 0) + 1 save_data(data) @@ -478,50 +739,76 @@ def post_page(post_id): html = ''' - + - {{ post['title'] }} - + + {{ post['title'] }} - Content Hub + ''' + NAV_HTML + ''' +

{{ post['title'] }}

{% if post['type'] == 'video' %} @@ -532,28 +819,28 @@ def post_page(post_id): {{ post['title'] }} {% endif %}

{{ post['description'] }}

-

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

-

Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}

+

Uploaded by: {{ post['uploader'] }} | {{ post['upload_date'] }}

+

Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}

{% if is_authenticated %}
-
- - + + +
{% endif %} -

Комментарии

+

Comments

{% for comment in post.get('comments', []) %}

{{ comment['user'] }} ({{ comment['date'] }}): {{ comment['text'] }}

{% endfor %} - Назад к ленте + Back to Feed {% if not is_authenticated %} -

Войдите, чтобы ставить лайки и комментировать.

+

Login to like and comment.

{% endif %}
@@ -1338,19 +1639,21 @@ def admin_panel(): function toggleSidebar() { document.getElementById('sidebar').classList.toggle('active'); } - - document.addEventListener('DOMContentLoaded', function() { + function toggleTheme() { + document.body.classList.toggle('dark'); + localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); + } + window.onload = () => { + if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark'); const videos = document.querySelectorAll('.post-preview'); videos.forEach(video => { if (video.tagName === 'VIDEO') { - video.addEventListener('loadedmetadata', function() { - const duration = video.duration; - const randomTime = Math.random() * duration; - video.currentTime = randomTime; + video.addEventListener('loadedmetadata', () => { + video.currentTime = Math.random() * video.duration; }); } }); - }); + };