192.168.8.133 - - [17/Feb/2025 11:19:33] "POST /delete_comment/16 HTTP/1.1" 500 -
Error on request:
Traceback (most recent call last):
  File "/home/philip/.local/lib/python3.12/site-packages/flask/app.py", line 2529, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/flask/app.py", line 1825, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/flask_restful/__init__.py", line 298, in error_router
    return original_handler(e)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/flask/app.py", line 1823, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/run/media/philip/bd0812f2-6d94-4fdd-83d6-0b09ca3b4549/Downloads/asq123/main2.py", line 1764, in delete_comment
    comment = db_sess.query(Comment).get(comment_id)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 2, in get
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/util/deprecations.py", line 386, in warned
    return fn(*args, **kwargs)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 1127, in get
    return self._get_impl(ident, loading.load_on_pk_identity)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 1136, in _get_impl
    return self.session._get_impl(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 3781, in _get_impl
    instance = self._identity_lookup(
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2954, in _identity_lookup
    return_value = loading.get_from_identity(self, mapper, key, passive)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/loading.py", line 478, in get_from_identity
    state._load_expired(state, passive)
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/state.py", line 796, in _load_expired
    self.manager.expired_attribute_loader(self, toload, passive)
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/loading.py", line 1653, in load_scalar_attributes
    result = load_on_ident(
             ^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/loading.py", line 509, in load_on_ident
    return load_on_pk_identity(
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/loading.py", line 694, in load_on_pk_identity
    session.execute(
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2306, in execute
    return self._execute_internal(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2181, in _execute_internal
    conn = self._connection_for_bind(bind)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2050, in _connection_for_bind
    return trans._connection_for_bind(engine, execution_options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 2, in _connection_for_bind
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 103, in _go
    self._raise_for_prerequisite_state(fn.__name__, current_state)
  File "/home/philip/.local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 928, in _raise_for_prerequisite_state
    raise sa_exc.PendingRollbackError(
sqlalchemy.exc.PendingRollbackError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (sqlite3.IntegrityError) NOT NULL constraint failed: comment_likes.comment_id
[SQL: UPDATE comment_likes SET comment_id=? WHERE comment_likes.id = ?]
[parameters: (None, 12)]
(Background on this error at: https://sqlalche.me/e/20/gkpj) (Background on this error at: https://sqlalche.me/e/20/7s2a)

from flask_socketio import SocketIO
from flask import flash
import os
import json
from passlib.context import CryptContext
from PIL import Image
from flask import Flask, render_template, url_for, redirect, request, send_from_directory, flash, session
from flask_login import LoginManager, login_user, current_user, logout_user, UserMixin
from flask_restful import Api
from data.user_sid import UserSID
from sqlalchemy.exc import IntegrityError
from flask_sqlalchemy import SQLAlchemy
from data import db_session
from data.videos import Video
from data.videoedit_form import VideoEditForm
from data.users import User
from sqlalchemy.orm import joinedload
from data.bugs import Bug, BugComment, BugScreenshot
from data.login_form import LoginForm
from data.tracks import Track
from data.themes import Theme
from data.CommentLike import CommentLike
from data.likes import Like
from data.albums import Album
from data.games import Game
from data.register_form import RegisterForm
from data.video_form import VideoForm
from data.users_resource import UsersResource, UserListResource
from data.videos_resource import VideosResource, VideoListResource
from data.likes_resource import LikeResource, NotLikeResource
from data.subscriptions_resource import FollowResource, NotFollowResource
from werkzeug.utils import secure_filename
from data.avatar_form import AvatarForm
from data.db_session import SqlAlchemyBase
from data.comments import Comment
from data.comment_form import CommentForm
from data.music import Music
from werkzeug.utils import secure_filename
from sqlalchemy import ForeignKey
from wtforms_components import ColorField
from sqlalchemy.orm import relationship
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
from sqlalchemy import func
from flask_migrate import Migrate
import uuid
import time
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime, timedelta
# from datetime import datetime
import pytz
from flask.json import JSONEncoder
from datetime import datetime, timedelta
import math

class CustomJSONEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            # Adjust the datetime by +3 hours
            adjusted_time = obj + timedelta(hours=3)
            # Serialize to ISO 8601 format or any preferred format
            return adjusted_time.isoformat()

        # If you have other custom types, handle them here

        # For all other types, use the default serialization
        return super().default(obj)

moscow_tz = pytz.timezone('Europe/Moscow')
local_time = datetime.now(moscow_tz)
print(local_time)
app = Flask(__name__)
app.config['SECRET_KEY'] = 'yandexlyceum_secret_key'

api = Api(app)
socketio = SocketIO(app)
login_manager = LoginManager()
login_manager.init_app(app)

pwd_context = CryptContext(
    schemes=["pbkdf2_sha256"],
    default="pbkdf2_sha256",
    pbkdf2_sha256__default_rounds=30000
)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../instance/music.db'

db = SQLAlchemy(app)
migrate = Migrate(app, db)
# Set the folder to store avatars
AVATAR_UPLOAD_FOLDER = os.path.join(app.root_path, 'static', 'avatars')
# Allowed extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
BUG_SCREENSHOT_FOLDER = os.path.join(app.root_path, 'static', 'bug_screenshots')
ALLOWED_EXTENSIONS_BUG = {'png', 'jpg', 'jpeg', 'gif'}

os.makedirs(BUG_SCREENSHOT_FOLDER, exist_ok=True)
app.config['BANNER_UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads', 'banners')
os.makedirs(app.config['BANNER_UPLOAD_FOLDER'], exist_ok=True)  # Ensure the folder exists
app.config['BUG_SCREENSHOT_FOLDER'] = BUG_SCREENSHOT_FOLDER
app.config['AVATAR_UPLOAD_FOLDER'] = AVATAR_UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25 мегабайт
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.config['COVER_FOLDER'] = 'static/covers'
app.config['ALLOWED_AUDIO_EXTENSIONS'] = {'mp3', 'wav', 'flac', 'ogg'}
app.config['ALLOWED_IMAGE_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}

# Создаем папки, если их нет
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['COVER_FOLDER'], exist_ok=True)
import tkinter as tk
import psutil
import time
import threading

# Модель для треков
from functools import wraps
from flask import abort
import random
from flask import send_file
import magic
import subprocess
from werkzeug.utils import secure_filename

UPLOAD_FOLDERA = 'uploadsvids'
PROCESSED_FOLDERA = 'processedvids'
ALLOWED_EXTENSIONS = {'mp4', 'mkv', 'avi', 'mov', 'flv'}

app.config['UPLOAD_FOLDERA'] = UPLOAD_FOLDERA
app.config['PROCESSED_FOLDERA'] = PROCESSED_FOLDERA

# Ensure that directories exist
os.makedirs(UPLOAD_FOLDERA, exist_ok=True)
os.makedirs(PROCESSED_FOLDERA, exist_ok=True)

# Define allowed extensions
# Define allowed file extensions as a global variable
ALLOWED_EXTENSIONS = {'mp4', 'mkv', 'avi', 'mov', 'flv'}
from mutagen import File as MutagenFile
app.json_encoder = CustomJSONEncoder
CLIENTS_DIR = 'data/clients'
CLIENTS_FILE = os.path.join(CLIENTS_DIR, 'clients.txt')

BLOCKED_IPS_DIR = 'data/blocked'
BLOCKED_IPS_FILE = os.path.join(BLOCKED_IPS_DIR, 'blocked_ips.txt')

# Create directories if they don't exist
os.makedirs(os.path.dirname(CLIENTS_FILE), exist_ok=True)
os.makedirs(os.path.dirname(BLOCKED_IPS_FILE), exist_ok=True)

# Ensure files exist
for file in [CLIENTS_FILE, BLOCKED_IPS_FILE]:
    if not os.path.exists(file):
        with open(file, 'w') as f:
            pass  # Create empty file

# Load blocked IPs into a set for quick access
blocked_ips = set()
def load_blocked_ips():
    global blocked_ips
    blocked_ips = set()
    try:
        with open(BLOCKED_IPS_FILE, 'r') as f:
            for line in f:
                ip = line.strip()
                if ip:
                    blocked_ips.add(ip)
    except FileNotFoundError:
        pass

load_blocked_ips()
@app.context_processor
def inject_helpers():
    return dict(get_client_ip=get_client_ip)
def get_client_ip():
    if request.headers.get('X-Forwarded-For'):
        # If behind a proxy
        ip = request.headers.get('X-Forwarded-For').split(',')[0].strip()
    else:
        ip = request.remote_addr
    return ip
# Helper functions to manage IP blocking
def block_ip(ip):
    global blocked_ips
    if ip not in blocked_ips:
        blocked_ips.add(ip)
        with open(BLOCKED_IPS_FILE, 'a') as f:
            f.write(f"{ip}\n")

def unblock_ip(ip):
    global blocked_ips
    if ip in blocked_ips:
        blocked_ips.remove(ip)
        with open(BLOCKED_IPS_FILE, 'w') as f:
            for b_ip in blocked_ips:
                f.write(f"{b_ip}\n")

def is_blocked(ip):
    return ip in blocked_ips

def get_client_ip():
    if request.headers.get('X-Forwarded-For'):
        # If behind a proxy
        ip = request.headers.get('X-Forwarded-For').split(',')[0].strip()
    else:
        ip = request.remote_addr
    return ip
from flask import g

# 1. before_request to check birthday
# @app.before_request
# def check_birthday_modal():
#     g.show_birthday_modal = False  # Default flag
#
#     if current_user.is_authenticated and current_user.birthday:
#         # Adjust the current time to the desired timezone (e.g., Moscow)
#         now = datetime.utcnow() + timedelta(hours=3)
#         today = now.date()
#
#         # Check if today is the user's birthday
#         if (current_user.birthday.month, current_user.birthday.day) == (today.month, today.day):
#             # Check if the modal has already been shown today
#             if session.get('birthday_modal_shown_date') != today.isoformat():
#                 g.show_birthday_modal = True
#                 session['birthday_modal_shown_date'] = today.isoformat()

# 2. Context processor to inject the flag
@app.context_processor
def inject_birthday_modal():
    return {'show_birthday_modal': getattr(g, 'show_birthday_modal', False)}
# Logging and blocking logic before each request
@app.before_request
def log_and_block_ip():
    ip = get_client_ip()

    # Log the IP if not already logged
    with open(CLIENTS_FILE, 'a+') as f:
        f.seek(0)
        logged_ips = set(line.strip() for line in f)
        if ip not in logged_ips:
            f.write(f"{ip}\n")

    # Define admin routes to always allow access
    admin_routes = ['/admin/']

    # Define paths to exclude from blocking (e.g., '/blocked')
    exempt_paths = ['/blocked']

    # Allow access to admin routes and exempt paths
    if any(request.path.startswith(route) for route in admin_routes) or request.path in exempt_paths:
        return  # Allow admin and exempted routes access

    # Allow access to static files
    if request.path.startswith('/static/'):
        return

    # Check if the IP is blocked
    if is_blocked(ip):
        return redirect(url_for('blocked'))
# from apscheduler.schedulers.background import BackgroundScheduler
# from datetime import datetime, timedelta
#
# scheduler = BackgroundScheduler()
#
# def check_inactive_users():
#     """Mark users as offline if they haven't sent a heartbeat recently."""
#     threshold_time = datetime.utcnow() - timedelta(seconds=60)  # 60 seconds threshold
#     with db_sess.begin():
#         inactive_users = db_sess.query(User).filter(User.last_seen < threshold_time).all()
#         for user in inactive_users:
#             if user.is_online:  # Only change status if they are still marked as online
#                 user.is_online = False
#                 db_sess.commit()  # Commit the changes
#                 socketio.emit('user_status', {'user_id': user.id, 'status': 'offline'}, broadcast=True)
#                 print(f"User {user.id} marked as offline due to inactivity.")
#
# # Schedule the check to run every minute
# scheduler.add_job(check_inactive_users, 'interval', minutes=1)
# scheduler.start()
# import atexit
#
# atexit.register(lambda: scheduler.shutdown())
# import random
#
def get_random_videos(session, count=5):
    all_videos = session.query(Video).all()
    if len(all_videos) < count:
        return all_videos  # Return all if fewer than count available
    return random.sample(all_videos, count)  # Get random samples of the given count
# # Обработчик подключения
# @socketio.on('connect')
# def handle_connect():
#     """Handle user connection to set their online status."""
#     if current_user.is_authenticated:
#         with db_sess.begin():
#             user = db_sess.query(User).get(current_user.id)
#             if user:
#                 user.is_online = True  # Set online status to True
#                 db_sess.commit()  # Commit the changes
#                 emit('user_status', {'user_id': user.id, 'status': 'online'}, broadcast=True)
#                 print(f"User {user.id} is now online.")
#
#
# @socketio.on('disconnect')
# def handle_disconnect():
#     """Handle user disconnection to clean up user status."""
#     if current_user.is_authenticated:
#         with db_sess.begin():
#             user = db_sess.query(User).get(current_user.id)
#             if user:
#                 user.is_online = False  # Set online status to False
#                 db_sess.commit()  # Commit the changes
#                 emit('user_status', {'user_id': user.id, 'status': 'offline'}, broadcast=True)
#                 print(f"User {user.id} is now offline.")
#
#
# @socketio.on('heartbeat')
# def handle_heartbeat():
#     """Handle the heartbeat signal."""
#     if current_user.is_authenticated:
#         with db_sess.begin():
#             user = db_sess.query(User).get(current_user.id)
#             if user:
#                 # Update the last seen timestamp if needed
#                 user.last_seen = datetime.utcnow()  # Assuming you track the last seen time
#                 db_sess.commit()  # Commit the changes
#                 print(f"Heartbeat received for User {user.id}.")
# Route to display blocked page
@app.template_filter('add_hours')
def add_hours(dt, hours):
    return dt + timedelta(hours=hours)
@app.route('/blocked')
def blocked():
    return render_template('blocked.html')
@app.route('/paint')
def paint():
    return render_template('paint.html')
# Decorator to restrict access to admin users
def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not getattr(current_user, 'is_admin', False):
            abort(403)
        return f(*args, **kwargs)
    return decorated_function
@app.template_filter('truncate')
def truncate(string, length=30, suffix='...'):
    """Обрезает строку до указанной длины с добавлением суффикса."""
    if len(string) > length:
        return string[:length - len(suffix)] + suffix  # Убираем длину суффикса
    return string
# Admin interface to manage client IPs
@app.route('/admin/clients', methods=['GET', 'POST'])
@admin_required
def admin_clients():
    # Read all logged client IPs
    try:
        with open(CLIENTS_FILE, 'r') as f:
            clients = {line.strip() for line in f}
    except FileNotFoundError:
        clients = set()

    # Prepare the list with block status
    client_list = []
    admin_ip = get_client_ip()  # Get admin's IP
    for ip in sorted(clients):
        client_list.append({
            'ip': ip,
            'blocked': is_blocked(ip)
        })

    if request.method == 'POST':
        action = request.form.get('action')
        ip = request.form.get('ip')
        if ip:
            if action == 'block':
                if ip != admin_ip:  # Prevent admin from blocking themselves
                    block_ip(ip)
                    flash(f"IP {ip} has been blocked.", 'success')
                else:
                    flash("You cannot block your own IP.", 'danger')
            elif action == 'unblock':
                unblock_ip(ip)
                flash(f"IP {ip} has been unblocked.", 'success')
            return redirect(url_for('admin_clients'))

    return render_template('admin_clients.html', clients=client_list, admin_ip=admin_ip)
def get_audio_info(filepath):
    try:
        audio = MutagenFile(filepath, easy=True)
        if audio is None:
            raise Exception("Unsupported file type or corrupted file.")

        audio_info = {
            "duration": int(audio.info.length),  # Duration in seconds
            "bitrate": audio.info.bitrate,  # Bitrate in bps
            "filetype": audio.mime[0] if audio.mime else "Unknown",
            "size": os.path.getsize(filepath),  # File size in bytes
            "metadata": dict(audio)  # Metadata tags
        }

        return audio_info

    except Exception as e:
        print(f"Error getting audio info: {e}")
        return None
from data.private_messages import PrivateMessage
from flask import jsonify
from PIL import Image
import os
from shutil import copyfile

@app.route('/select_avatar', methods=['POST'])
def select_avatar():
    if not current_user.is_authenticated:
        return jsonify({'success': False, 'error': 'Unauthorized access.'}), 401

    avatar_url = request.form.get('avatar')
    user_id = current_user.id
    target_avatar = f'static/avatars/user_{user_id}.png'

    # Получаем имя файла и строим путь
    source_avatar = avatar_url.split('/')[-1]  # Извлекаем имя файла
    source_avatar_path = os.path.join('static', 'avatars', 'def', source_avatar)

    # Удаляем старый аватар, если он есть
    if os.path.exists(target_avatar):
        try:
            os.remove(target_avatar)
        except Exception as e:
            print(f"Error removing old avatar: {e}")
            return jsonify({'success': False, 'error': 'Could not remove old avatar.'}), 500

    try:
        if not os.path.exists(source_avatar_path):
            return jsonify({'success': False, 'error': 'Source avatar not found.'}), 404

        # Открываем изображение и изменяем его размер
        with Image.open(source_avatar_path) as img:
            img = img.resize((300, 300), Image.LANCZOS)  # Изменяем размер
            img.save(target_avatar)  # Сохраняем изображение

        flash('Аватар успешно установлен!', 'success')
        return jsonify({'success': True}), 200
    except FileNotFoundError:
        print(f"FileNotFoundError: {source_avatar_path} not found.")
        return jsonify({'success': False, 'error': 'Source avatar file not found.'}), 404
    except OSError as e:
        print(f"OSError: {str(e)}")
        return jsonify({'success': False, 'error': f"Error processing image: {str(e)}"}), 500
    except Exception as e:
        print(f"Unexpected error: {str(e)}")
        return jsonify({'success': False, 'error': f"Unexpected error: {str(e)}"}), 500

def should_redirect():
    try:
        with open('status.txt', 'r') as file:
            content = file.read().strip().lower()
            return content == 'yes'
    except FileNotFoundError:
        return False  # Handle the case where the file doesn't exist
import math
from datetime import datetime
def allowed_bug_screenshot(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS_BUG
def generate_shared_link(self):
        """Generates a unique shared link token."""
        self.shared_link = secrets.token_urlsafe(16)
def decline(number, one, two, five):
    """
    Возвращает правильную форму слова в зависимости от числа.

    Args:
        number (int): Число для склонения.
        one (str): Форма для числа 1 (например, "секунда").
        two (str): Форма для чисел 2-4 (например, "секунды").
        five (str): Форма для чисел 5 и более (например, "секунд").

    Returns:
        str: Правильная форма слова.
    """
    number = abs(number)
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return five
    if last == 1:
        return one
    if 2 <= last <= 4:
        return two
    return five

@app.route('/rules')
def rules():
    return render_template('rules.html')
@app.route('/test')
def test():
    return render_template('test.html')
from flask import jsonify  # Ensure this import is present
@app.route('/theme/publish', methods=['GET', 'POST'])
def publish_theme():
    if request.method == 'POST':
        name = request.form['name']
        description = request.form['description']
        primary_color = request.form['primary_color']
        secondary_color = request.form['secondary_color']
        background_color = request.form['background_color']

        # Create a new theme record
        new_theme = Theme(
            name=name,
            description=description,
            primary_color=primary_color,
            secondary_color=secondary_color,
            background_color=background_color,
            author_id=current_user.id
        )
        db_sess.add(new_theme)
        db_sess.commit()

        flash('Theme published successfully!', 'success')
        return redirect(url_for('theme_store'))

    return render_template('publish_theme.html')
@app.route('/theme/store', methods=['GET'])
def theme_store():
    themes = db_sess.query(Theme).all()  # Fetch all themes
    return render_template('theme_store.html', themes=themes)
@app.route('/select_theme/<int:theme_id>', methods=['POST'])
def select_theme(theme_id):
    theme = db_sess.query(Theme).get(theme_id)  # Get the theme by ID
    if theme is None:
        abort(404)  # If the theme does not exist, show a 404 error

    # Update the current user with the selected theme's information
    current_user.selected_theme_id = theme.id
    current_user.theme_primary_color = theme.primary_color
    current_user.theme_secondary_color = theme.secondary_color
    current_user.theme_background_color = theme.background_color
    db_sess.commit()  # Commit changes to the database

    flash('Theme selected successfully!', 'success')  # Notify the user of success
    return redirect(url_for('settings'))  # Redirect to settings or the appropriate page
@app.route('/games')
def games():
    db_sess = db_session.create_session()
    all_games = db_sess.query(Game).all()
    return render_template('games.html', games=all_games)

@app.route('/game_upload', methods=['GET', 'POST'])
def game_upload():
    if current_user.id != 1:
        return forbidden('')  # Ensure that only user with ID 1 can access

    if request.method == 'POST':
        name = request.form.get('name')
        iframe_code = request.form.get('iframe_code')
        image = request.files.get('image')
        slug = request.form.get('slug')

        if not name or not iframe_code or not image or not slug:
            flash('All fields are required!', 'danger')
            return redirect(request.url)

        # Save the image to the appropriate directory
        logo_folder = os.path.join('static', 'game_logos')
        os.makedirs(logo_folder, exist_ok=True)
        image_filename = secure_filename(image.filename)
        image_path = os.path.join(logo_folder, image_filename)
        image.save(image_path)

        new_game = Game(name=name, iframe_code=iframe_code, image=image_path, slug=slug, uploader_id=current_user.id)

        db_sess = db_session.create_session()
        db_sess.add(new_game)
        db_sess.commit()
        flash('Game uploaded successfully!', 'success')
        return redirect(url_for('games'))

    return render_template('game_upload.html')

@app.route('/game/<slug>')
def game(slug):
    db_sess = db_session.create_session()
    game = db_sess.query(Game).filter(Game.slug == slug).first()

    if not game:
        abort(404)  # This will trigger the 404 error handling in Flask

    uploader = db_sess.query(User).get(game.uploader_id)  # Get the uploader's information
    return render_template('game_detail.html', game=game, uploader=uploader)
@app.route('/delete_game/<int:game_id>', methods=['POST'])
def delete_game(game_id):
    if current_user.id != 1:
        return forbidden('')  # Ensure that only user with ID 1 can access

    db_sess = db_session.create_session()
    game = db_sess.query(Game).get(game_id)

    if game:
        db_sess.delete(game)
        db_sess.commit()
        flash('Game deleted successfully!', 'success')
    else:
        flash('Game not found.', 'danger')

    return redirect(url_for('games'))
@app.route('/admin_controls', methods=['GET', 'POST'])
def admin_controls():
    if not current_user.is_authenticated or current_user.id != 1:
        return forbidden('')  # Only allow user ID 1 (admin) to access this page

    if request.method == 'POST':
        # Adjust volume, bass, and treble based on form input
        volume = request.form.get('volume', type=float)
        bass = request.form.get('bass', type=float)
        treble = request.form.get('treble', type=float)

        # Emit an event to all connected clients
        socketio.emit('update_audio_settings', {
            'volume': volume,
            'bass': bass,
            'treble': treble
        }, namespace='/')  # Ensure the namespace matches your client-side setup

        # Return JSON response to the client
        return jsonify({'success': True, 'message': 'Audio settings updated successfully.'})

    return render_template('admin_controls.html')
@app.route('/bugs')
def bugs_list():
    bugs = db_sess.query(Bug).order_by(Bug.created_at.desc()).all()
    return render_template('bugs_list.html', bugs=bugs, current_user=current_user)
@app.route('/bugs/create', methods=['GET', 'POST'])
def create_bug():
    if request.method == 'POST':
        title = request.form.get('title')
        description = request.form.get('description')
        screenshots = request.files.getlist('screenshots')  # Получаем список файлов

        if not title or not description:
            flash('Название и описание обязательны.', 'danger')
            return redirect(request.url)

        new_bug = Bug(
            title=title,
            description=description,
            reporter_id=current_user.id
        )

        # Обработка нескольких скриншотов
        for screenshot in screenshots:
            if screenshot and allowed_bug_screenshot(screenshot.filename):
                filename = secure_filename(screenshot.filename)
                timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S')
                filename = f"{current_user.id}_{timestamp}_{filename}"
                screenshot.save(os.path.join(app.config['BUG_SCREENSHOT_FOLDER'], filename))
                bug_screenshot = BugScreenshot(filename=filename)
                new_bug.screenshots.append(bug_screenshot)

        db_sess.add(new_bug)
        db_sess.commit()
        flash('Баг успешно создан!', 'success')
        return redirect(url_for('bugs_list'))

    return render_template('create_bug.html', current_user=current_user)
@app.route('/bugs/<int:bug_id>', methods=['GET', 'POST'])
def bug_detail(bug_id):
    bug = db_sess.query(Bug).get(bug_id)
    if not bug:
        abort(404)

    if request.method == 'POST' and current_user.is_authenticated:
        content = request.form.get('comment')
        if content:
            new_comment = BugComment(
                bug_id=bug.id,
                user_id=current_user.id,
                content=content
            )
            db_sess.add(new_comment)
            db_sess.commit()
            flash('Комментарий добавлен.', 'success')
            return redirect(url_for('bug_detail', bug_id=bug.id))
        else:
            flash('Комментарий не может быть пустым.', 'danger')

    return render_template('bug_detail.html', bug=bug, current_user=current_user)
@app.route('/bugs/<int:bug_id>/close', methods=['POST'])
def close_bug(bug_id):
    bug = db_sess.query(Bug).get(bug_id)
    if not bug:
        abort(404)

    if current_user.id != 1 and not current_user.is_admin:
        abort(403)

    if bug.status == 'closed':
        flash('Баг уже закрыт.', 'info')
    else:
        bug.status = 'closed'
        db_sess.commit()
        flash('Баг закрыт.', 'success')

    return redirect(url_for('bug_detail', bug_id=bug.id))
@app.route('/bugs/<int:bug_id>/delete', methods=['POST'])
def delete_bug(bug_id):
    bug = db_sess.query(Bug).get(bug_id)
    if not bug:
        abort(404)

    if current_user.id != 1 and not current_user.is_admin:
        abort(403)

    # Удаление скриншота, если он существует
    if bug.screenshot:
        try:
            os.remove(os.path.join(app.config['BUG_SCREENSHOT_FOLDER'], bug.screenshot))
        except FileNotFoundError:
            pass

    db_sess.delete(bug)
    db_sess.commit()
    flash('Баг удален.', 'success')
    return redirect(url_for('bugs_list'))
def time_since(adjusted_upload_time):
    now = datetime.utcnow()
    diff = now - adjusted_upload_time
    seconds = diff.total_seconds()

    if seconds < 0:
        return "только что"  # If upload_time is in the future
    if seconds < 30:
        return "только что"
    elif seconds < 60:
        sec = math.floor(seconds)
        return f"{sec} {decline(sec, 'секунда', 'секунды', 'секунд')}"
    elif seconds < 3600:
        minutes = math.floor(seconds / 60)
        return f"{minutes} {decline(minutes, 'минута', 'минуты', 'минут')}"
    elif seconds < 86400:
        hours = math.floor(seconds / 3600)
        return f"{hours} {decline(hours, 'час', 'часа', 'часов')}"
    elif seconds < 604800:
        days = math.floor(seconds / 86400)
        return f"{days} {decline(days, 'день', 'дня', 'дней')}"
    elif seconds < 2592000:
        weeks = math.floor(seconds / 604800)
        return f"{weeks} {decline(weeks, 'неделя', 'недели', 'недель')}"
    elif seconds < 31536000:
        months = math.floor(seconds / 2592000)
        return f"{months} {decline(months, 'месяц', 'месяца', 'месяцев')}"
    elif seconds < 3153600000:
        years = math.floor(seconds / 31536000)
        return f"{years} {decline(years, 'год', 'года', 'лет')}"
    elif seconds < 3153600000000:
        centuries = math.floor(seconds / 3153600000)
        return f"{centuries} {decline(centuries, 'век', 'века', 'веков')}"
    else:
        millennia = math.floor(seconds / 3153600000000)
        return f"{millennia} {decline(millennia, 'тысячелетие', 'тысячелетия', 'тысячелетий')}"

# Jinja2 template filter
@app.template_filter('timesince')
def timesince_filter(upload_time):
    if not isinstance(upload_time, datetime):
        raise ValueError("upload_time должен быть объектом datetime")

    # The JSONEncoder already adjusts the time by +3 hours,
    # but if you need to use adjusted time elsewhere (like in templates),
    # you might need to adjust it here as well.
    adjusted_upload_time = upload_time + timedelta(hours=3)
    return time_since(adjusted_upload_time)

# This function will be executed before every request
@app.before_request
def check_file_and_redirect():
    # Exclude requests for static files and already redirected new_page
    if should_redirect() and not request.path.startswith('/static') and request.endpoint != 'pishu':
        return redirect(url_for('pishu'))
@app.route('/indev')

def pishu():
    params = {
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('underconstruct.html', **params)
@app.route('/video/<int:video_id>/share', methods=['POST'])
def share_video(video_id):
    db_sess = db_session.create_session()
    video = db_sess.query(Video).get(video_id)

    if video is None:
        flash('Видео не найдено.', 'danger')
        return redirect(url_for('index'))

    if video.author_id != current_user.id:
        flash('У вас нет прав для этого действия.', 'danger')
        return redirect(url_for('video', video_id=video_id))

    # Check if a shared link already exists
    if not video.shared_link:
        video.generate_shared_link()
        video.privacy = 2  # Set privacy to 'Shared' if not already
        try:
            db_sess.commit()
            flash('Ссылка для общего доступа создана.', 'success')
        except IntegrityError:
            db_sess.rollback()
            flash('Ошибка при создании ссылки. Попробуйте еще раз.', 'danger')
            return redirect(url_for('video', video_id=video_id))
    else:
        flash('Ссылка для общего доступа уже существует.', 'info')

    shared_url = url_for('video', video_id=video.id, shared=video.shared_link, _external=True)
    return jsonify({'shared_url': shared_url})  # Returning JSON for AJAX handling
@app.route('/faq')

def faq():
    params = {
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('underconstruct.html', **params)
@socketio.on('private_message')
def handle_private_message(data):
    """
    Handles sending of private messages.
    Expects data to contain:
    - 'receiver_id': ID of the user to send the message to
    - 'message': The message content
    """
    sender = current_user
    receiver_id = data.get('receiver_id')
    message_content = data.get('message')

    if not sender.is_authenticated:
        emit('error', {'message': 'Authentication required.'})
        return

    if not receiver_id or not message_content:
        emit('error', {'message': 'Invalid data.'})
        return

    receiver = db_sess.query(User).get(receiver_id)
    if not receiver:
        emit('error', {'message': 'Receiver not found.'})
        return

    # Save the message to the database
    private_message = PrivateMessage(
        sender_id=sender.id,
        receiver_id=receiver.id,
        content=message_content
    )
    db_sess.add(private_message)
    db_sess.commit()

    # Emit the message to the receiver's SIDs
    receiver_sids = [sid.sid for sid in receiver.sids]
    emit('receive_private_message', {
        'sender_id': sender.id,
        'sender_name': f"{sender.name} {sender.surname}",
        'message': message_content,
        'timestamp': private_message.timestamp.strftime('%Y-%m-%d %H:%M:%S')
    }, room=receiver_sids, namespace='/')

    # Optionally, emit the message back to the sender's other clients
    emit('receive_private_message', {
        'sender_id': sender.id,
        'sender_name': f"{sender.name} {sender.surname}",
        'message': message_content,
        'timestamp': private_message.timestamp.strftime('%Y-%m-%d %H:%M:%S')
    }, room=request.sid)

def convert_to_mp4(input_path, output_path):
    """
    Convert the input video file to MP4 format using FFmpeg.
    """
    try:
        subprocess.check_output([
            'ffmpeg', '-i', input_path, '-c:v', 'libx264',
            '-preset', 'fast', '-crf', '22',
            '-c:a', 'aac', '-b:a', '192k', output_path
        ])
    except subprocess.CalledProcessError as e:
        print(f"Failed to convert video. Error: {e.output}")
        raise
def allowed_file(filename):
    """
    Check if the file extension is in the allowed list.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# main2.py (continued)
# main2.py (continued)

@app.route('/private_chat')

def private_chat():
    users = db_sess.query(User).filter(User.id != current_user.id).all()
    params = {
        'title': 'Private Chat',
        'users': users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('private_chat.html', **params)

@app.route('/api/chat_history/<int:receiver_id>')

def chat_history(receiver_id):
    sender_id = current_user.id
    receiver = db_sess.query(User).get(receiver_id)
    if not receiver:
        return jsonify({'success': False, 'message': 'Receiver not found.'}), 404

    messages = db_sess.query(PrivateMessage).filter(
        or_(
            (PrivateMessage.sender_id == sender_id) & (PrivateMessage.receiver_id == receiver_id),
            (PrivateMessage.sender_id == receiver_id) & (PrivateMessage.receiver_id == sender_id)
        )
    ).order_by(PrivateMessage.timestamp.asc()).all()

    messages_data = []
    for msg in messages:
        messages_data.append({
            'sender_id': msg.sender_id,
            'sender_name': f"{msg.sender.name} {msg.sender.surname}",
            'message': msg.content,
            'timestamp': msg.timestamp.strftime('%Y-%m-%d %H:%M:%S')
        })

    return jsonify({'success': True, 'messages': messages_data})
@app.route('/mp4', methods=['GET', 'POST'])
def upload_mp4():
    if request.method == 'POST':
        if 'file' not in request.files:
            return "No file part"

        file = request.files['file']

        if file.filename == '':
            return "No selected file"

        # Call the allowed_file function with the filename only
        if file:
            filename = secure_filename(file.filename)
            file_path = os.path.join(app.config['UPLOAD_FOLDERA'], filename)
            file.save(file_path)

            mime = magic.Magic(mime=True)
            mime_type = mime.from_file(file_path)

            output_path = os.path.join(app.config['PROCESSED_FOLDERA'], f'{os.path.splitext(filename)[0]}.mp4')
            if mime_type != 'video/mp4':
                convert_to_mp4(file_path, output_path)
            else:
                convert_to_mp4(file_path, output_path)

            return redirect(url_for('uploaded_mp4', filename=os.path.basename(output_path)))

    return render_template('mp4.html')
@app.route('/tools', methods=['GET', 'POST'])
def tools():
    params = {
        'db_sess': db_sess,
        'User': User,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('tools.html', **params)

@app.route('/mp4s/<filename>')
def uploaded_mp4(filename):
    return send_from_directory(app.config['PROCESSED_FOLDERA'], filename)


UPLOAD_FOLDERI = 'static/uploadsimg'
CONVERTED_FOLDER = 'static/convertedimg'
os.makedirs(UPLOAD_FOLDERI, exist_ok=True)
os.makedirs(CONVERTED_FOLDER, exist_ok=True)

app.config['UPLOAD_FOLDERI'] = UPLOAD_FOLDERI


ALLOWED_IMAGE_EXTENSIONS_CONVERT = {'png', 'bmp', 'gif', 'jpeg', 'jpg'}

@app.route('/img2jpg', methods=['GET', 'POST'])
def convert_image():
    converted_file_url = None
    error_message = None  # Variable for error message

    if request.method == 'POST':
        if 'image' not in request.files:
            error_message = 'No file part'
            return render_template('convert.html', converted_file_url=converted_file_url, error_message=error_message)

        file = request.files['image']

        if file.filename == '':
            error_message = 'No selected file'
            return render_template('convert.html', converted_file_url=converted_file_url, error_message=error_message)

        # Check if the file extension is allowed
        if not allowed_file(file.filename, ALLOWED_IMAGE_EXTENSIONS_CONVERT):
            error_message = 'Unsupported image format. Please upload a PNG or similar image format.'
            return render_template('convert.html', converted_file_url=converted_file_url, error_message=error_message)

        # Save the original file
        filename = secure_filename(file.filename)
        upload_path = os.path.join(app.config['UPLOAD_FOLDERI'], filename)
        file.save(upload_path)

        # Convert to JPG
        base_filename = os.path.splitext(filename)[0]
        jpg_filename = f"{base_filename}.jpg"
        converted_path = os.path.join(CONVERTED_FOLDER, jpg_filename)

        try:
            with Image.open(upload_path) as img:
                rgb_im = img.convert('RGB')  # Convert to RGB
                rgb_im.save(converted_path, 'JPEG')

            # Remove the original uploaded file
            os.remove(upload_path)

            # Get URL for downloading
            converted_file_url = url_for('static', filename=f'convertedimg/{jpg_filename}')

        except Exception as e:
            error_message = f'Error during conversion: {str(e)}'

    return render_template('convert.html', converted_file_url=converted_file_url, error_message=error_message)
def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            abort(403)  # Запрещено
        return f(*args, **kwargs)
    return decorated_function

def owner_or_admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        track_id = kwargs.get('track_id')
        track = Track.query.get_or_404(track_id)
        if not current_user.is_authenticated:
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

@app.route('/delete_track/<int:track_id>', methods=['POST'])
def delete_track(track_id):
    track = db.session.query(Track).get(track_id)
    if not track:
        abort(404)  # Handle case where the track is not found

    try:
        # Delete audio file if it exists
        audio_path = os.path.join(app.config['UPLOAD_FOLDER'], track.audio_filename)
        if os.path.exists(audio_path):
            os.remove(audio_path)

        # List of default cover filenames
        default_covers = ['default.png']

        # Delete track cover if it's not a default cover
        if track.cover_filename and track.cover_filename not in default_covers:
            cover_path = os.path.join(app.config['COVER_FOLDER'], track.cover_filename)
            if os.path.exists(cover_path):
                os.remove(cover_path)

        # Delete the track record from the database
        db.session.delete(track)
        db.session.commit()

        flash('Трек успешно удалён.', 'success')
    except Exception as e:
        flash(f'Ошибка при удалении трека: {e}', 'danger')
        db.session.rollback()  # Rollback the session in case of error

    return redirect(url_for('tracks'))






# Проверка разрешенных расширений файлов
def allowed_file(filename, allowed_extensions):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in allowed_extensions

from sqlalchemy import func

@app.route('/audiosearch')
def audiosearch():
    query = request.args.get('query', '').strip()

    results = []
    if query:
        search_pattern = f"%{query.lower()}%"

        # Query to find matches in Track titles
        results = db.session.query(Track).filter(
            func.lower(Track.title).like(search_pattern)
        ).all()

    return render_template('search_results2.html', query=query, tracks=results)
# Генерация уникального идентификатора сессии
@app.before_request
def make_session_permanent():
    if 'session_id' not in session:
        session['session_id'] = str(uuid.uuid4())

# Страница загрузки трека
@app.route('/upload_audio', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        # Get form data
        title = request.form.get('title')
        audio_file = request.files.get('audio_file')
        cover_file = request.files.get('cover_file')
        album_id = request.form.get('album_id')
        new_album_title = request.form.get('new_album_title')
        new_album_artist = request.form.get('new_album_artist')
        new_album_cover = request.files.get('new_album_cover')

        # Check if form data contains album selection
        if album_id:
            album = db.session.query(Album).get(album_id)
            if not album:
                flash('Выбранный альбом не существует.', 'danger')
                return redirect(url_for('upload'))
        else:
            album_id = None

        # Validate required fields
        if not title or not audio_file:
            flash('Название и аудио файл обязательны для заполнения.', 'danger')
            return redirect(url_for('upload'))

        # Validate audio file type
        if not allowed_file(audio_file.filename, app.config['ALLOWED_AUDIO_EXTENSIONS']):
            flash('Неверный тип аудио файла.', 'danger')
            return redirect(url_for('upload'))

        # Handle new album creation
        if new_album_title and new_album_artist:
            # Handle new album cover
            new_album_cover_filename = 'default_album.png'  # Default if no cover is provided
            if new_album_cover and allowed_file(new_album_cover.filename, app.config['ALLOWED_IMAGE_EXTENSIONS']):
                new_album_cover_filename = f"{uuid.uuid4()}_{secure_filename(new_album_cover.filename)}"
                new_album_cover_path = os.path.join(app.config['COVER_FOLDER'], new_album_cover_filename)
                new_album_cover.save(new_album_cover_path)

            # Create new album record
            new_album = Album(title=new_album_title, artist=new_album_artist, cover_filename=new_album_cover_filename)
            db.session.add(new_album)
            db.session.commit()
            album_id = new_album.id
            flash('Новый альбом успешно создан!', 'success')

        # Save audio file
        audio_filename = f"{uuid.uuid4()}_{secure_filename(audio_file.filename)}"
        audio_path = os.path.join(app.config['UPLOAD_FOLDER'], audio_filename)
        audio_file.save(audio_path)

        # Save or default track cover
        track_cover_filename = 'default.png'
        if cover_file and allowed_file(cover_file.filename, app.config['ALLOWED_IMAGE_EXTENSIONS']):
            track_cover_filename = f"{uuid.uuid4()}_{secure_filename(cover_file.filename)}"
            cover_path = os.path.join(app.config['COVER_FOLDER'], track_cover_filename)
            cover_file.save(cover_path)

        # Create new track record
        new_track = Track(
            title=title,
            album_id=album_id,
            cover_filename=track_cover_filename,
            audio_filename=audio_filename,
            owner_id=current_user.id
        )

        db.session.add(new_track)
        db.session.commit()
        flash('Трек успешно загружен!', 'success')
        return redirect(url_for('track', track_id=new_track.id))

    # Retrieve all albums for the dropdown
    albums = db.session.query(Album).all()
    return render_template('upload.html', albums=albums)


# Маршрут для отображения всех альбомов
@app.route('/albums')
def albums():
    # Querying all albums ordered by upload_date in descending order
    all_albums = db.session.query(Album).order_by(Album.upload_date.desc()).all()
    all_tracks = db.session.query(Track).all()
    # Pass the queried album list to the template
    return render_template('albums.html', albums=all_albums, tracks=all_tracks)

# Маршрут для создания нового альбома
@app.route('/create_album', methods=['GET', 'POST'])
def create_album():
    # Только авторизованные пользователи могут создавать альбомы

    if request.method == 'POST':
        title = request.form.get('title')
        artist = request.form.get('artist')
        cover_file = request.files.get('cover_file')

        if not title or not artist:
            flash('Название и исполнитель обязательны для заполнения.', 'danger')
            return redirect(url_for('create_album'))

        # Обработка обложки альбома
        if cover_file and allowed_file(cover_file.filename, app.config['ALLOWED_IMAGE_EXTENSIONS']):
            cover_filename = str(uuid.uuid4()) + '_' + secure_filename(cover_file.filename)
            cover_path = os.path.join(app.config['COVER_FOLDER'], cover_filename)
            cover_file.save(cover_path)
        else:
            cover_filename = 'default_album.png'  # Используйте стандартную обложку

        # Создание нового альбома
        new_album = Album(title=title, artist=artist, cover_filename=cover_filename)
        db.session.add(new_album)
        db.session.commit()
        flash('Альбом успешно создан!', 'success')
        return redirect(url_for('albums'))

    return render_template('create_album.html')

# Маршрут для просмотра отдельного альбома с его треками
@app.route('/album/<int:album_id>')
def view_album(album_id):
    # Get the album by ID, or return 404 if not found
    album = db.session.query(Album).get_or_404(album_id)

    # Get all tracks associated with the album
    tracks = db.session.query(Track).filter_by(album_id=album_id).all()

    # Prepare parameters for rendering the template
    params = {
        'title': f"Album: {album.title} by {album.artist}",
        'album': album,
        'tracks': tracks,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    # Render the album view with the album and its tracks
    return render_template('view_album.html', **params)
# Страница со списком всех треков
@app.route('/record')
def record_audio():
    params = {
        'title': f"Записать аудио",
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('record.html', **params)
@app.route('/tracks')
def tracks():
    all_albums = db.session.query(Album).all()  # Запрос всех альбомов
    all_tracks = db.session.query(Track).all()  # Запрос всех треков
    random_albums = random.sample(all_albums, 3) if all_albums else []  # Выбор случайных альбомов, если они есть

    # Подготовка параметров для шаблона
    params = {
        'authenticated': current_user.is_authenticated,  # Проверка, авторизован ли пользователь
        'current_user': current_user,  # Информация о текущем пользователе
        'all_albums': all_albums,  # Все альбомы
        'all_tracks': all_tracks,  # Все треки
        'color_map': [
            "#FFB6C1", "#FFD700", "#ADFF2F",
            "#87CEFA", "#FF69B4", "#98FB98",
            "#FF6347", "#8A2BE2"
        ],
        'random_albums': random_albums  # Случайные альбомы для отображения
    }

    return render_template('tracks.html', **params)  # Передача параметров в шаблон
@app.route('/audiobox')
def audiobox():
    all_albums = db.session.query(Album).all()
    all_tracks = db.session.query(Track).filter(Track.album_id == None).all()
    random_albums = random.sample(all_albums, 3)
    params = {
        'user': user,
        'all_albums': all_albums
    }
    # Define a list of colors for default.png covers
    color_map = ["#FFB6C1", "#FFD700", "#ADFF2F", "#87CEFA", "#FF69B4", "#98FB98", "#FF6347", "#8A2BE2"]

    return render_template('audiobox.html', tracks=all_tracks, color_map=color_map, random_albums=random_albums, albums=all_albums)
# Page for individual track
@app.route('/snake')
def snake():
    return render_template('snake.html')
import openai
@app.route('/chatgpt', methods=['GET', 'POST'])
def chatgpt():
    if request.method == 'POST':
        api_key = request.form.get('api_key')
        if api_key:
            session['api_key'] = api_key
            return redirect(url_for('chatwithgpt'))
        else:
            flash('Please enter a valid OpenAI API Key.', 'danger')
    return render_template('chatgpt.html')

# Route for the chat interface
@app.route('/chatwithgpt', methods=['GET', 'POST'])
def chatwithgpt():
    if 'api_key' not in session:
        flash('Please enter your OpenAI API Key first.', 'warning')
        return redirect(url_for('chatgpt'))

    response = None
    if request.method == 'POST':
        user_input = request.form.get('user_input')
        if user_input:
            # Set the OpenAI API Key from the session
            openai.api_key = session['api_key']
            try:
                # Make a request to OpenAI's API (using ChatGPT model)
                completion = openai.ChatCompletion.create(
                    model="gpt-3.5-turbo",
                    messages=[
                        {"role": "system", "content": "You are a helpful assistant."},
                        {"role": "user", "content": user_input}
                    ]
                )
                response = completion.choices[0].message['content']
            except openai.error.AuthenticationError:
                flash('Authentication failed. Please check your API Key.', 'danger')
                return redirect(url_for('chatgpt'))
            except Exception as e:
                flash(f'An error occurred: {str(e)}', 'danger')
        else:
            flash('Please enter a message to send.', 'warning')

    return render_template('chat.html', response=response)
@app.route('/track/<int:track_id>')
def track(track_id):
    track = db.session.query(Track).get(track_id)
    if track is None:
        return "Track not found", 404

    # Path to the audio file
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], track.audio_filename)

    # Get audio information
    audio_info = get_audio_info(filepath)

    # Check if the user has liked this track
    session_id = session.get('session_id')
    liked = db.session.query(Like).filter_by(session_id=session_id, track_id=track_id).first() is not None

    return render_template('track.html', track=track, liked=liked, audio_info=audio_info)

# Обработка лайков
@app.route('/like_music/<int:track_id>', methods=['POST'])
def like(track_id):
    session_id = session.get('session_id')
    if session_id is None:
        return redirect(url_for('tracks'))

    # Use db.session.query to retrieve the track
    track = db.session.query(Track).get(track_id)
    if not track:
        return "Track not found", 404

    # Use db.session.query for checking Likes
    existing_like = db.session.query(Like).filter_by(session_id=session_id, track_id=track_id).first()

    # Create a new Like if none exists
    if not existing_like:
        new_like = Like(session_id=session_id, track_id=track_id)
        track.likes += 1
        db.session.add(new_like)
        db.session.commit()

    return redirect(url_for('track', track_id=track_id))

# Page with recommendations
@app.route('/recommendations')
def recommendations():
    # Query top liked tracks using the session-based query
    top_liked = db.session.query(Track).order_by(Track.likes.desc()).limit(5).all()

    # Query newest tracks ordered by upload date
    newest = db.session.query(Track).order_by(Track.upload_date.desc()).limit(5).all()

    return render_template('recommendations.html', top_liked=top_liked, newest=newest)

# Отдача обложек и аудио файлов
#@app.route('/covers/<filename>')
#def cover(filename):
 #   return send_from_directory(app.config['COVER_FOLDER'], filename)

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

@app.route('/favicon.ico')
def favicon():
    """Возвращает кастомную иконку страницы в браузере"""
    return send_from_directory(os.path.join(app.root_path, 'static/img'),
                               'vhs.ico', mimetype='image/vnd.microsoft.icon')


@socketio.on('comment')
def handle_comment_event(username, video_title):
    message = f"{username} commented on your video '{video_title}'"
    socketio.emit('notify', {'message': message})


def like_video(video_id):
    # Assuming this function is called during a like action
    video = db_sess.query(Video).get(video_id)
    if video:
        message = f"Someone liked your video '{video.title}'"
        socketio.emit('notify', {'message': message}, room=video.author_id)  # send to video author
def subscribe_to_user(user_id):
    # Assuming this function is called during a subscribe action
    user = db.session.query(User).first()
    if user:
        message = "Someone subscribed to you!"
        socketio.emit('notify', {'message': message}, room=user.id)


def load_user(user_id):
    return db_sess.get(User, user_id)

@app.route('/upload_avatar', methods=['GET', 'POST'])
def upload_avatar():
    params = {
        'title': 'Загрузка аватара',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    form = AvatarForm()
    if request.method == 'POST':
        # Handling form submission and image saving
        if 'avatar' in request.files:
            avatar = request.files['avatar']
            filename = secure_filename(avatar.filename)
            file_ext = os.path.splitext(filename)[1].lower()
            avatar_filename = f"user_{current_user.id}{file_ext}"
            avatar_path = os.path.join(app.config['AVATAR_UPLOAD_FOLDER'], avatar_filename)

            # Save and process the avatar
            try:
                with Image.open(avatar) as img:
                    img = img.convert('RGB')
                    img.thumbnail((300, 300))
                    img.save(avatar_path, optimize=True, quality=85)

                # Update user's avatar in the database
                current_user.avatar = avatar_filename
                db_sess.commit()
                flash("Аватар успешно обновлен!", "success")
                return redirect(url_for('user', user_id=current_user.id))

            except Exception as e:
                flash('Загрузка не удалась. Пожалуйста, попробуйте снова.', 'danger')
                return redirect(url_for('upload_avatar'))

    return render_template('upload_avatar.html', form=form, **params)

import signal
from flask import current_app, abort
import psutil

# Function to get system load
def get_system_load():
    return {
        'cpu_percent': psutil.cpu_percent(interval=1),
        'memory_percent': psutil.virtual_memory().percent,
        'disk_usage_percent': psutil.disk_usage('/').percent,
    }

@app.route('/admin/control_panel')
def admin_control_panel():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    system_load = get_system_load()

    params = {
        'title': 'Server Control Panel',
        'system_load': system_load,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    return render_template('admin_control_panel.html', **params)

@app.route('/admin/stop_server')
def stop_server():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    os.kill(os.getpid(), signal.SIGTERM)  # Gracefully terminate the server
    return "Server is stopping."

@app.route('/admin/stop_server_delayed')
def stop_server_delayed():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    def delayed_shutdown():
        time.sleep(300)  # Delay for 5 minutes
        os.kill(os.getpid(), signal.SIGTERM)  # Then terminate the server

    threading.Thread(target=delayed_shutdown).start()  # Run in a separate thread
    return "Server will stop in 5 minutes."

@app.route('/admin/reload')
def reload_server():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    os.kill(os.getpid(), signal.SIGHUP)  # Reload the server
    return "Server is reloading."

@app.errorhandler(403)
def forbidden(e):
    """Возвращает кастомную страницу ошибки 403"""
    params = {
        'title': 'Доступ запрещен',
        'message': 'Похоже, у вас нет доступа к этой странице ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error1.html', **params), 403

def to_int(value):
    try:
        return int(value)
    except ValueError:
        return 0  # or handle as needed

app.jinja_env.filters['to_int'] = to_int

@app.errorhandler(404)
def page_not_found(e):
    """Возвращает кастомную страницу ошибки 404"""
    params = {
        'title': 'Страница не найдена',
        'message': 'Похоже, мы не можем найти нужную вам страницу ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error2.html', **params), 404


@app.errorhandler(405)
def method_not_allowed(e):
    """Возвращает кастомную страницу ошибки 405"""
    params = {
        'title': 'Метод не разрешен',
        'message': 'Похоже, этот метод не разрешен ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error3.html', **params), 405


@app.errorhandler(500)
def internal_server_error(e):
    """Возвращает кастомную страницу ошибки 500"""
    params = {
        'title': 'Ошибка сервера',
        'message': 'Похоже, на сервере произошла непредвиденная ошибка ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error4.html', **params), 500


# In your main application file (e.g., main2.py)

from sqlalchemy.orm import joinedload

@app.route('/')
@app.route('/index')
def index():
    """Returns the home page"""
    # Eagerly load the 'author' relationship to avoid additional queries in the template
    all_videos = db_sess.query(Video).options(joinedload(Video.author)).all()

    if len(all_videos) > 10:
        last_videos = all_videos[-10:][::-1]  # Get the last 10 videos
    else:
        last_videos = all_videos[::-1]  # Reverse the list

    best_videos = sorted(all_videos, key=lambda x: -x.likes)[:3]
    underrated_videos = [video for video in all_videos if video.likes == 0]

    if len(underrated_videos) > 9:
        underrated_videos = underrated_videos[-9:][::-1]
    else:
        underrated_videos = underrated_videos[::-1]

    params = {
        'title': 'YouBox',
        'last_videos': last_videos,
        'best_videos': best_videos,
        'underrated_videos': underrated_videos,
        'user': user,
        'db_sess': db_sess,
        'User': User,
        'Video': Video,
        'authenticated': current_user.is_authenticated,
        'all_videos': all_videos,
        'current_user': current_user
    }
    return render_template('index.html', **params)


@login_manager.user_loader
def load_user(user_id):
    """Возвращает пользователя"""
    return db_sess.query(User).get(user_id)


@app.route('/login', methods=['GET', 'POST'])
def login():
    """Возвращает страницу авторизации и авторизует пользователя"""
    if current_user.is_authenticated:
        return forbidden('')
    form = LoginForm()
    params = {
        'title': 'Вход в аккаунт - YouBox',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        user = db_sess.query(User).filter(User.login == form.login.data).first()
        if user and pwd_context.verify(form.password.data, user.password):
            login_user(user, remember=form.remember_me.data)
            return redirect('/')
        return render_template('login.html',
                               message='Неправильный логин или пароль',
                               form=form, **params)
    return render_template('login.html', form=form, **params)

from flask import render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user

# @app.route('/edit_comment/<int:comment_id>', methods=['GET', 'POST'])
#
# def edit_comment(comment_id):
#     # Fetch the comment from the database using comment_id
#     comment = db_sess.query(Comment).filter(Comment.id == comment_id).first()
#
#     # Ensure the comment exists and the current_user is the author
#     if comment is None or comment.user_id != current_user.id:
#         flash('Comment not found or you do not have permission to edit it.', 'danger')
#         return redirect(url_for('video', video_id=comment.video_id))  # Redirect to the video's page
#
#     if request.method == 'POST':
#         new_content = request.form.get('content')
#         if new_content:
#             comment.content = new_content
#             db_sess.commit()
#             flash('Коментарий успешно изменен!', 'success')
#         else:
#             flash('Нет изменений в комментарие.', 'danger')
#         return redirect(url_for('video', video_id=comment.video_id))
#
#     return render_template('edit_comment.html', comment=comment)

@app.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return forbidden('')

    form = RegisterForm()
    params = {
        'title': 'Регистрация',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    if form.validate_on_submit():
        # Загружаем запрещенные слова из файла
        with open('banned_words.txt', 'r', encoding='utf-8') as file:
            banned_words = {line.strip().lower() for line in file}

        # Проверяем, содержат ли имя пользователя или имя запрещенные слова
        username = form.username.data.strip().lower()
        name = form.name.data.strip().lower()

        if any(banned_word in username for banned_word in banned_words) or \
           any(banned_word in name for banned_word in banned_words):
            flash('Имя пользователя или имя содержат запрещенные слова.', 'danger')
            return redirect(url_for('register'))

        # Проверяем, существует ли уже пользователь
        user = db_sess.query(User).filter(User.login == username).first()
        if user:
            flash('Пользователь с таким именем уже существует.', 'danger')
            return redirect(url_for('register'))

        # Создаем нового пользователя и устанавливаем фамилию на пустую строку
        new_user = User()
        new_user.login = username
        new_user.password = pwd_context.hash(form.password.data)
        new_user.name = form.name.data
        new_user.surname = ""  # Устанавливаем фамилию на пустую строку
        db_sess.add(new_user)
        db_sess.commit()
        login_user(new_user, remember=form.remember_me.data)
        flash('Регистрация успешна!', 'success')
        return redirect('/')

    return render_template('register.html', form=form, **params)
from flask import jsonify, request, current_app

# @app.route('/api/like_comment/<int:comment_id>', methods=['POST']) # Ensure the user is logged in
# def like_comment(comment_id):
#     # Logic to handle liking a comment
#     comment = db_sess.query(Comment).filter_by(id=comment_id).first()
#     if not comment:
#         return jsonify({'success': False, 'message': 'Comment not found.'}), 404
#
#     # Example logic: increment the like count
#     comment.likes += 1
#     db_sess.commit()
#
#     return jsonify({'success': True, 'new_like_count': comment.likes})

@app.route('/logout')
def logout():
    if current_user.is_authenticated:
        # Create a new session for this operation
        new_session = db_session.create_session()
        user = new_session.query(User).get(current_user.id)
        if user:
            user.is_online = False
            new_session.commit()
        logout_user()
    return redirect('/')
@app.route('/api/pin_comment/<int:comment_id>', methods=['POST'])
def pin_comment(comment_id):
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        return {'error': 'Comment not found'}, 404
    video = db_sess.query(Video).get(comment.video_id)

    # Сбрасываем все закрепленные комментарии для данного видео
    db_sess.query(Comment).filter(Comment.video_id == video.id).update({'pinned': False})
    # Закрепляем выбранный комментарий
    comment.pinned = True
    db_sess.commit()
    return {'success': True}
@app.route('/delete_comment/<int:comment_id>', methods=['POST'])
def delete_comment(comment_id):
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        flash('Комментарий не найден.', 'danger')
        return redirect(request.referrer)
    if not current_user.is_authenticated or current_user.id != comment.user_id:
        flash('Это не твой Комментарий, пошел нафиг.', 'danger')
        return redirect(request.referrer)
    db_sess.delete(comment)
    db_sess.commit()
    flash('Комментарий удален.', 'success')
    return redirect(request.referrer)
from flask import abort
from data.video_views import VideoView
import re
@app.route('/video/<int:video_id>', methods=['GET', 'POST'])
def video(video_id):
    db_sess = db_session.create_session()
    current_video = db_sess.query(Video).get(video_id)
    if current_video is None:
        return page_not_found('')
    random_videos = get_random_videos(db_sess, count=12)

    # Access Control Logic
    shared = request.args.get('shared', type=str)
    # By default, access is denied
    access_granted = True

    if current_video.privacy == 1:  # Public
        access_granted = True
    elif current_video.privacy == 2:  # Shared
        if shared and shared == current_video.shared_link:
            access_granted = True
    elif current_video.privacy == 3:  # Private
        if current_user.is_authenticated and current_user.id == current_video.author_id:
            access_granted = True

    if not access_granted:
        flash('У вас нет доступа к этому видео.', 'danger')
        return redirect(url_for('index'))

    # Proceed with existing logic
    comments_enabled = current_video.comments_enabled
    author = current_video.author

    # Efficient retrieval of last 10 videos
    last_videos = db_sess.query(Video)\
        .order_by(Video.datetime.desc())\
        .limit(10).all()

    like = False
    subscription = False

    if current_user.is_authenticated:
        # Handle view counting
        try:
            new_view = VideoView(user_id=current_user.id, video_id=video_id)
            db_sess.add(new_view)
            current_video.views += 1
            db_sess.commit()
        except IntegrityError:
            db_sess.rollback()  # View already exists

        # Handle likes and subscriptions using association tables
        likes = json.loads(current_user.likes) if current_user.likes else []
        subscriptions = json.loads(current_user.subscriptions) if current_user.subscriptions else []
        like = video_id in likes
        subscription = current_video.author_id in subscriptions

    # Handle comment form
    form = CommentForm()
    if comments_enabled and form.validate_on_submit():
        content = form.content.data.strip()

        # Validate length
        if len(content) > 50:
            flash('Максимальная длина комментария - 50 символов.', 'danger')
            return redirect(url_for('video', video_id=video_id))

        # Check for prohibited characters
        if any(char in content for char in "<>/"):
            flash('Комментарий содержит недопустимые символы.', 'danger')
            return redirect(url_for('video', video_id=video_id))

        # Replace @username with a link to the user's profile
        mention_pattern = r'@(\w+)'  # Регулярное выражение для поиска @username
        users = db_sess.query(User).all()  # Получите всех пользователей

        # Создание словаря соответствий для быстрого доступа к логинам
        user_dict = {user.login: user for user in users}

        # Заменить упоминания на ссылки
        for login, user in user_dict.items():
            content = re.sub(r'@' + re.escape(login) + r'\b', f'<a href="/user/{user.id}">@{login}</a>', content)

        if current_user.is_authenticated and not current_user.is_blocked:
            comment = Comment(
                content=content,
                user_id=current_user.id,
                video_id=video_id,
                parent_id=form.parent_id.data or None
            )
            db_sess.add(comment)
            db_sess.commit()
            flash('Комментарий добавлен!', 'success')
            return redirect(url_for('video', video_id=video_id))
        else:
            flash('Вы не можете оставить комментарий, вы заблокированы.', 'danger')
            return redirect(url_for('video', video_id=video_id))

    # Retrieve comments with eager loading
    comments = db_sess.query(Comment).filter(Comment.video_id == video_id).options(
        joinedload(Comment.user),
        joinedload(Comment.parent).joinedload(Comment.user),
        joinedload(Comment.replies).joinedload(Comment.user)
    ).order_by(Comment.datetime.desc()).all()
    # For each comment, determine if the current user has liked it
    if current_user.is_authenticated:  # Check if the user is logged in
        liked_comments = set(
            like.comment_id
            for like in db_sess.query(CommentLike).filter_by(user_id=current_user.id).all()
        )
    else:
        liked_comments = set()

    # Build root comments
    comments_by_id = {comment.id: comment for comment in comments}
    root_comments = []
    for comment in comments:
        comment.liked = comment.id in liked_comments
        if comment.parent_id is None:
            root_comments.append(comment)
        else:
            parent = comments_by_id.get(comment.parent_id)
            if parent:
                if not hasattr(parent, 'replies_sorted'):
                    parent.replies_sorted = []
                parent.replies_sorted.append(comment)

    params = {
        'title': f"{current_video.title} - {author.name} {author.surname}",
        'current_video': current_video,
        'video_src': url_for("static", filename=f'vid/{video_id}.mp4'),
        'author': author,
        'like': like,
        'random_videos': random_videos,  # Pass random videos to the template
        'subscription': subscription,
        'comments': root_comments,  # Root comments only
        'last_videos': last_videos,
        'form': form,
        'avatar_url': url_for('static', filename=f'avatars/{author.avatar}'),
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
    }
    return render_template('video.html', **params)



from PIL import Image

@app.route('/add_video', methods=['GET', 'POST'])
def add_video():
    if not current_user.is_authenticated:
        return forbidden('')  # Ensure you have a `forbidden` handler defined

    form = VideoForm()
    params = {
        'title': 'Добавление видео',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    if current_user.is_blocked:
        flash('Ваш аккаунт заблокирован и вы не можете загружать видео.', 'danger')
        return render_template('add_video.html', form=form, **params)

    if form.validate_on_submit():
        # Validate title
        title = form.title.data.strip()
        if len(title) > 45:
            flash('Максимальная длина названия видео - 45 символов.', 'danger')
            return render_template('add_video.html', form=form, **params)

        if any(char in title for char in "<>/"):
            flash('Название видео содержит недопустимые символы.', 'danger')
            return render_template('add_video.html', form=form, **params)

        # Validate description
        description = form.description.data.strip()
        if len(description) > 100:
            flash('Максимальная длина описания видео - 100 символов.', 'danger')
            return render_template('add_video.html', form=form, **params)

        if any(char in description for char in "<>/"):
            flash('Описание видео содержит недопустимые символы.', 'danger')
            return render_template('add_video.html', form=form, **params)

        # Check if user is allowed to choose a date
        if current_user.id != 1 and form.datetime.data:
            flash('У вас нет прав выбора даты!', 'danger')
            return render_template('add_video.html', form=form, **params)

        try:
            # Create a new Video object
            video = Video(
                title=title,
                description='<br>'.join(description.split('\n')),
                author_id=current_user.id,
                comments_enabled=form.comments_enabled.data,
                datetime=form.datetime.data or datetime.utcnow()  # Use provided date or current UTC time
            )
            db_sess.add(video)
            db_sess.commit()

            # Save uploaded files
            video_path = os.path.join('static/vid', f'{video.id}.mp4')
            preview_path = os.path.join('static/pre', f'{video.id}.jpg')
            form.video.data.save(video_path)
            form.preview.data.save(preview_path)

            # Resize the preview image to 406x230
            with Image.open(preview_path) as img:
                img = img.resize((406, 230), Image.Resampling.LANCZOS)
                img.save(preview_path, optimize=True, quality=85)  # Save resized image

            flash('Видео успешно загружено!', 'success')
            return redirect('/')

        except Exception as e:
            db_sess.rollback()
            flash(f'Произошла ошибка при загрузке видео: {e}', 'danger')
            return render_template('add_video.html', form=form, **params)

    return render_template('add_video.html', form=form, **params)


@app.route('/edit_video/<int:video_id>', methods=['GET', 'POST'])
def edit_video(video_id):
    """
    Возвращает страницу редактирования видео, если пользователь авторизован и является автором,
    в противном случае доступ запрещен.
    """
    form = VideoEditForm()  # Используем новую форму
    video = db_sess.query(Video).get(video_id)

    if not current_user.is_authenticated or video.author_id != current_user.id:
        return forbidden('')

    params = {
        'title': 'Редактирование видео',
        'video': video,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    if form.validate_on_submit():
        title = form.title.data.strip()
        description = form.description.data.strip()

        # Проверка заголовка
        if len(title) > 45 or any(char in title for char in "<>/"):
            flash('Длина заголовка должна быть не более 45 символов и не может содержать <, >, /', 'danger')
            return render_template('edit_video.html', form=form, **params)

        # Проверка описания
        if len(description) > 100 or any(char in description for char in "<>/"):
            flash('Длина описания должна быть не более 100 символов и не может содержать <, >, /', 'danger')
            return render_template('edit_video.html', form=form, **params)

        # Обновление видео
        video.title = title
        video.description = '<br>'.join(description.split('\n'))
        try:
            db_sess.commit()
            flash('Видео успешно обновлено!', 'success')
            return redirect('/')
        except Exception as e:
            db_sess.rollback()
            flash(f'Ошибка при обновлении видео: {e}', 'danger')
            return render_template('edit_video.html', form=form, **params)

    if request.method == 'GET':
        form.title.data = video.title
        form.description.data = '\n'.join(video.description.split('<br>'))

    return render_template('edit_video.html', form=form, **params)


@app.route('/delete_video/<int:video_id>')
def delete_video(video_id):
    video = db_sess.query(Video).get(video_id)
    if not video:
        return page_not_found('Video not found.')


    # No need for explicit comment deletion; should cascade
    db_sess.delete(video)
    db_sess.commit()

    # Remove video file and preview
    try:
        os.remove(os.path.join('static/vid', f'{video.id}.mp4'))
        os.remove(os.path.join('static/pre', f'{video.id}.jpg'))
    except FileNotFoundError:
        pass  # Handle if the files do not exist

    return redirect(f'/')


@app.route('/user/<identifier>')
def user(identifier):
    """Returns the user page by either user ID or user login."""
    user = None

    # Query for user using identifier (as ID or login)
    if identifier.isdigit():  # If the identifier is numeric, try both scenarios
        user = db_sess.query(User).filter(
            (User.id == int(identifier)) | (User.login == identifier)
        ).first()
    else:  # Non-numeric; query by login
        user = db_sess.query(User).filter(User.login == identifier).first()

    if not user:
        return page_not_found('User not found')  # Return 404 if user is not found
    deleted = user.is_deleted
    # Retrieve user's videos
    videos = db_sess.query(Video).filter(Video.author_id == user.id).all()[::-1]

    # Logic for birthday (if applicable)
    is_birthday = False
    if user.birthday:
        today = datetime.utcnow()
        # Add timezone adjustment (if needed)
        today_with_offset = today + timedelta(hours=3)
        if (user.birthday.month, user.birthday.day) == (today_with_offset.month, today_with_offset.day):
            is_birthday = True

    # Pagination for videos (3 per page)
    paginated_videos = [videos[i * 3:i * 3 + 3] for i in range(len(videos) // 3)] + [videos[(len(videos) // 3) * 3:]]

    video_count = len(videos)

    subscription = False
    if current_user.is_authenticated:
        subscriptions = json.loads(current_user.subscriptions)
        subscription = user.id in subscriptions

    params = {
        'title': f"{user.name if not deleted else 'Deleted User'} - YouBox",
        'user': user,
        'videos': paginated_videos,
        'subscription': subscription,
        'video_count': video_count,
        'empty': video_count == 0,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
        'is_birthday': is_birthday,
        'is_deleted': deleted
    }

    return render_template('user.html', **params)
@app.route('/delete_channel', methods=['GET', 'POST'])
def delete_user():
    """Handles user deletion by marking them as 'Deleted User'."""
    if not current_user.is_authenticated:
        return forbidden('')

    if request.method == 'POST':
        if 'confirm_delete' in request.form:
            # Mark the user as deleted
            current_user.name = "Deleted User"
            current_user.surname = ""
            current_user.login = f"deleted_{uuid.uuid4().hex}"  # Create unique placeholder login
            current_user.avatar = "deleted_user.png"
            current_user.password = "DELETED"  # Set a placeholder password
            current_user.is_deleted = True

            # Commit the changes and log the user out
            db_sess.commit()
            logout_user()

            flash('Ваш аккаунт успешно удален. Ваши видео остаются доступными.', 'success')
            return redirect(url_for('index'))

        flash('Удаление аккаунта отменено.', 'info')
        return redirect(url_for('settings'))

    return render_template('delete_user.html', title='Удалить аккаунт')
@app.route('/settings')
def settings():


    if not current_user.is_authenticated:
        return forbidden('')
    params = {
        'title': f'Настройки',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('settings.html', **params)
def decline_subscription(number):
    """
    Возвращает правильную форму слова "подписчик" в зависимости от числа.
    Например:
        1 -> подписчик
        2 -> подписчика
        5 -> подписчиков
    """
    number = abs(number)  # Убираем знак, если число отрицательное
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return "подписчиков"
    if last == 1:
        return "подписчик"
    if 2 <= last <= 4:
        return "подписчика"
    return "подписчиков"
def decline_views(number):
    """
    Возвращает правильную форму слова "подписчик" в зависимости от числа.
    Например:
        1 -> подписчик
        2 -> подписчика
        5 -> подписчиков
    """
    number = abs(number)  # Убираем знак, если число отрицательное
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return "просмотров"
    if last == 1:
        return "просмотр"
    if 2 <= last <= 4:
        return "просмотра"
    return "просмотров"
# Регистрация фильтра в Jinja2
app.jinja_env.filters['decline_subscription'] = decline_subscription
app.jinja_env.filters['decline_views'] = decline_views
from data.users import User
from sqlalchemy import update

@app.route('/admin/users', methods=['GET', 'POST'])
def admin_users():
    """Административная страница для управления флажками, блокировками и статусом админа пользователей"""
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')  # Только администратор имеет доступ

    users = db_sess.query(User).all()

    if request.method == 'POST':
        # Обработка изменений флажков, блокировок и статуса админа
        for user in users:
            if user.id == 1:
                continue  # Не изменять статус администратора

            # Обработка флажков is_checked
            is_checked = request.form.get(f'checked_{user.id}', 'off') == 'on'
            if user.is_checked != is_checked:
                user.is_checked = is_checked
                db_sess.add(user)

            # Обработка блокировок is_blocked
            is_blocked = request.form.get(f'blocked_{user.id}', 'off') == 'on'
            if user.is_blocked != is_blocked:
                user.is_blocked = is_blocked
                db_sess.add(user)

            is_tester = request.form.get(f'is_tester_{user.id}', 'off') == 'on'
            if user.is_tester != is_tester:
                user.is_tester = is_tester
                db_sess.add(user)

            # Обработка статуса администратора is_admin
            is_admin = request.form.get(f'is_admin_{user.id}', 'off') == 'on'
            if user.is_admin != is_admin:
                user.is_admin = is_admin
                db_sess.add(user)
        db_sess.commit()
        flash('Изменения успешно сохранены.', 'success')
        return redirect(url_for('admin_users'))

    params = {
        'title': 'Администрирование пользователей',
        'users': users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('admin_users.html', **params)
@app.route('/feed')
def feed():
    """
    Returns a page with videos from users that the authenticated user is subscribed to.
    If the user is not authenticated, access is forbidden.
    """
    if not current_user.is_authenticated:
        return forbidden('')

    subscriptions = json.loads(current_user.subscriptions)

    # Assuming subscriptions contain user IDs
    videos = []
    for author_id in subscriptions:
        # Ensure that author_id is indeed an integer
        videos += db_sess.query(Video).filter(Video.author_id == author_id).all()  # Use author_id to filter the videos

    new_videos = sorted(videos, key=lambda x: x.datetime, reverse=True)  # Sort by DateTime descending
    new_videos = [
        {
            'video': video,
            'author': db_sess.query(User).get(video.author_id)  # Fetch author information
        }
        for video in new_videos
    ]

    params = {
        'title': 'Feed',
        'new_videos': [new_videos[i * 3:i * 3 + 3] for i in range(len(new_videos) // 3)] +
                       [new_videos[(len(new_videos) // 3) * 3:]],  # Pagination logic
        'empty': len(new_videos) == 0,
        'subscripted': len(subscriptions) != 0,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('feed.html', **params)


@app.route('/favorite')
def favorite():
    """
    Возвращает страницу с любимыми видео пользователя.
    Если пользователь не авторизован, доступ запрещен.
    """
    if not current_user.is_authenticated:
        return forbidden('')

    # Get the list of liked video IDs
    liked_video_ids = current_user.get_likes()  # This calls the method to get liked video IDs
    liked_videos = []

    for video_id in liked_video_ids:
        video = db_sess.query(Video).get(video_id)  # Fetch video using its ID
        if video:  # Only proceed if the video exists
            author = db_sess.query(User).get(video.author_id)  # Fetch the author of the video
            if author:  # If the author exists
                liked_videos.append({
                    'video': video,
                    'author': author
                })

    empty = len(liked_videos) == 0  # Check if the liked videos list is empty

    params = {
        'title': 'Любимые видео',
        'liked_videos': [liked_videos[i * 3:i * 3 + 3] for i in range((len(liked_videos) + 2) // 3)],  # Group by 3 for rendering
        'empty': empty,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    return render_template('favorite.html', **params)


@app.route('/people')
def people():
    """
    Возвращает страницу с пользователями, на которых подписан пользователь.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    subscriptions = json.loads(current_user.subscriptions)
    subscripted_users = [
        {
            'user': db_sess.query(User).get(i)
        }
        for i in subscriptions
    ]
    subscripted_users.sort(key=lambda x: x['user'].name + x['user'].surname + x['user'].login)
    params = {
        'title': f'Люди',
        'subscripted_users': subscripted_users,
        'empty': True if len(subscripted_users) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('people.html', **params)
from data.edit_profile_form import EditProfileForm  # Импортируем форму
from data.banner_form import BannerForm  # Импортируем форму
# In your main2.py (or appropriate routes file)

@app.route('/edit_profile', methods=['GET', 'POST'])
def edit_profile():
    if not current_user.is_authenticated:
        return forbidden('')
    banner_form = BannerForm()
    form = EditProfileForm(obj=current_user)
    params = {
        'title': 'Редактирование профиля',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
        'banner_form': banner_form
    }

    if form.validate_on_submit():
        if form.login.data and form.login.data != current_user.login:
            existing_user = db_sess.query(User).filter(User.login == form.login.data).first()
            if existing_user:
                flash('Пользователь с таким логином уже существует.', 'danger')
                return render_template('edit_profile.html', form=form, **params)

        current_user.name = form.name.data
        current_user.surname = form.surname.data
        if form.password.data:
            current_user.password = pwd_context.hash(form.password.data)

        current_user.channel_description = form.channel_description.data

        # Update the birthday field
        current_user.birthday = form.birthday.data if form.birthday.data else None

        # Update theme selection
        current_user.theme_name = form.theme_name.data
        if banner_form.validate_on_submit():
            if banner_form.banner.data:
                banner_file = banner_form.banner.data
                filename = secure_filename(banner_file.filename)
                banner_path = os.path.join(app.config['UPLOAD_FOLDER'], 'banners', filename)

                # Create the banners directory if it doesn't exist
                os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'banners'), exist_ok=True)

                # Save the banner image
                banner_file.save(banner_path)

                # Update user's banner filename
                current_user.banner = filename
                db_sess.commit()
                flash("Баннер обновлен успешно!", "success")

            return redirect(url_for('user', identifier=current_user.id))

        if form.theme_name.data == 'custom':
            current_user.theme_primary_color = str(form.theme_primary_color.data)
            current_user.theme_secondary_color = str(form.theme_secondary_color.data)
            current_user.theme_background_color = str(form.theme_background_color.data)
        else:
            # If not custom, set theme colors to defaults based on theme name
            if form.theme_name.data == 'dark':
                current_user.theme_primary_color = '#343a40'        # Dark gray
                current_user.theme_secondary_color = '#ffffff'      # White
                current_user.theme_background_color = '#212529'     # Darker gray
            elif form.theme_name.data == 'light':
                current_user.theme_primary_color = '#ffffff'        # White
                current_user.theme_secondary_color = '#000000'      # Black
                current_user.theme_background_color = '#f8f9fa'     # Light gray
            else:  # default
                current_user.theme_primary_color = '#007bff'        # Bootstrap primary color
                current_user.theme_secondary_color = '#6c757d'      # Bootstrap secondary color
                current_user.theme_background_color = '#ffffff'     # White

        db_sess.commit()
        flash('Профиль обновлён успешно!', 'success')
        return redirect(url_for('user', identifier=current_user.id))

    return render_template('edit_profile.html', form=form, **params)
@app.route('/ping')
def ping_page():
    return render_template('ping.html')

@app.route('/pong', methods=['GET'])
def ping():
    # Return a simple Pong response with the current timestamp
    return jsonify({'message': 'Pong', 'timestamp': time.time()})
@app.route('/api/comments/<int:comment_id>/like', methods=['POST'])
def like_comment(comment_id):
    if not current_user.is_authenticated:
        return jsonify({'success': False, 'error': 'You must be logged in to like a comment.'}), 401

    # Fetch the comment from the database
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        return jsonify({'success': False, 'error': 'Comment not found.'}), 404

    # Check whether the user has already liked the comment
    likes_query = db_sess.query(CommentLike).filter_by(user_id=current_user.id, comment_id=comment.id).first()
    if likes_query:
        return jsonify({'success': False, 'error': 'You have already liked this comment.'}), 400

    # Add a like to the database
    new_like = CommentLike(user_id=current_user.id, comment_id=comment.id)
    comment.likes += 1  # Increment the comment's like counter
    db_sess.add(new_like)
    db_sess.commit()

    return jsonify({'success': True, 'likes': comment.likes, 'message': 'Comment liked successfully.'}), 200

@app.route('/api/comments/<int:comment_id>/unlike', methods=['POST'])
def unlike_comment(comment_id):
    if not current_user.is_authenticated:
        return jsonify({'success': False, 'error': 'You must be logged in to unlike a comment.'}), 401

    # Fetch the comment from the database
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        return jsonify({'success': False, 'error': 'Comment not found.'}), 404

    # Check whether the user has liked the comment
    existing_like = db_sess.query(CommentLike).filter_by(user_id=current_user.id, comment_id=comment.id).first()
    if not existing_like:
        return jsonify({'success': False, 'error': 'You haven\'t liked this comment yet.'}), 400

    # Remove the existing like and update the database
    db_sess.delete(existing_like)
    comment.likes -= 1  # Decrement the comment's like counter
    db_sess.commit()

    return jsonify({'success': True, 'likes': comment.likes, 'message': 'Comment unliked successfully.'}), 200
@app.route('/about')
def about():
    params = {
        'title': f'Про сайт',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user

    }
    return render_template('about.html', **params)
from flask import request, render_template
from sqlalchemy import or_
@app.route('/reset_password/<int:user_id>', methods=['POST'])
@admin_required  # Ensure only admins can access this route
def reset_password(user_id):
    db_sess = db_session.create_session()
    user = db_sess.query(User).get(user_id)

    if user is None:
        flash('User not found.', 'danger')
        return redirect(url_for('admin_users'))  # Redirect to the user management page

    # Set the password to "HELLO"
    user.password = pwd_context.hash('HELLO')  # Use hashed password
    db_sess.commit()  # Commit the changes to the database

    flash(f'Password for user {user.login} has been reset to "HELLO".', 'success')
    return redirect(url_for('admin_users'))  # Redirect back to the user management page

@app.route('/найти')
@app.route('/search')
def search():
    query = request.args.get('search', '').strip().lower()
    results = []
    if query:
        search_pattern = f"%{query}%"
        # Perform case-insensitive search on title, description, and author's full name
        results = db_sess.query(Video).join(User).filter(
            db.or_(
                func.lower(Video.title).like(search_pattern),
                func.lower(Video.description).like(search_pattern),
                func.lower(func.concat(User.name, ' ', User.surname)).like(search_pattern)
            )
        ).all()

        # Добавляем информацию об авторах к каждому видео
        for video in results:
            # Проверяем, является ли video.author объектом User или просто ID
            if isinstance(video.author, int):  # Если это ID
                author = db_sess.query(User).filter(User.id == video.author).first()
            elif isinstance(video.author, User):  # Если это объект User
                author = video.author
            else:
                author = None

            if author:
                video.author_name = f"{author.name} {author.surname}"
                video.author_avatar = author.avatar
            else:
                video.author_name = "Неизвестный автор"
                video.author_avatar = "default_avatar.jpg"

    return render_template('search_results.html', videos=results, query=query)
@app.route('/users')
def users():
    # Query all users from the database
    all_users = db_sess.query(User).all()

    # Set up template parameters
    params = {
        'title': 'Все пользователи',
        'users': all_users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    # Render the template with the list of users
    return render_template('users.html', **params)



db_session.global_init("db/hosting.sql")

db_sess = db_session.create_session()
SqlAlchemyBase.metadata.create_all(bind=db_sess.bind)
db_sess.rollback

SqlAlchemyBase.metadata.create_all(bind=db_sess.bind)
api.add_resource(UsersResource, '/api/users/<int:user_id>')
api.add_resource(UserListResource, '/api/users')
api.add_resource(VideosResource, '/api/videos/<int:video_id>')
api.add_resource(VideoListResource, '/api/videos')
api.add_resource(LikeResource, '/api/like/<int:video_id>')
api.add_resource(NotLikeResource, '/api/not_like/<int:video_id>')
api.add_resource(FollowResource, '/api/follow/<int:user_id>')
api.add_resource(NotFollowResource, '/api/not_follow/<int:user_id>')


if __name__ == '__main__':
    from waitress import serve
    app.run(port=8080, host='0.0.0.0')