from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify import sqlite3 import os import re from werkzeug.security import generate_password_hash, check_password_hash from functools import wraps from datetime import datetime app = Flask(__name__) app.secret_key = 'your_secret_key_here_change_in_production' # --------------------- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ --------------------- def get_db_connection(): conn = sqlite3.connect('horse_farm.db') conn.row_factory = sqlite3.Row return conn def is_valid_email(email): if not email: return True pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.(ru|com)$' return re.match(pattern, email.lower()) is not None def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: flash('Пожалуйста, войдите в систему', 'warning') return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function def admin_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: flash('Пожалуйста, войдите в систему', 'warning') return redirect(url_for('login')) if session.get('role') != 'admin': flash('Доступ запрещен. Требуются права администратора.', 'danger') return redirect(url_for('index')) return f(*args, **kwargs) return decorated_function # ------------------------- API ------------------------- @app.route('/api/services') def api_services(): conn = get_db_connection() services = conn.execute('SELECT id, name, price, duration FROM services').fetchall() abonements = conn.execute('SELECT id, name, price, description FROM abonements').fetchall() certificates = conn.execute('SELECT id, name, price, description FROM certificates').fetchall() conn.close() return jsonify({ 'services': [dict(row) for row in services], 'abonements': [dict(row) for row in abonements], 'certificates': [dict(row) for row in certificates] }) @app.route('/api/unread_count') @login_required def api_unread_count(): conn = get_db_connection() count = conn.execute(''' SELECT COUNT(*) as count FROM messages m JOIN orders o ON m.order_id = o.id WHERE o.user_id = ? AND m.is_read = 0 AND m.sender_type != 'user' ''', (session['user_id'],)).fetchone() conn.close() return jsonify({'count': count['count'] if count else 0}) @app.route('/api/unread_by_order') @login_required def api_unread_by_order(): conn = get_db_connection() unread = conn.execute(''' SELECT o.id, COUNT(*) as count FROM messages m JOIN orders o ON m.order_id = o.id WHERE o.user_id = ? AND m.is_read = 0 AND m.sender_type != 'user' GROUP BY o.id ''', (session['user_id'],)).fetchall() conn.close() return jsonify([dict(row) for row in unread]) @app.route('/api/mark_read', methods=['POST']) @login_required def mark_read(): data = request.get_json() order_id = data.get('order_id') conn = get_db_connection() conn.execute(''' UPDATE messages SET is_read = 1 WHERE order_id = ? AND user_id = ? AND sender_type != 'user' ''', (order_id, session['user_id'])) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/set_timezone', methods=['POST']) def set_timezone(): data = request.get_json() if data and 'timezone_offset' in data: session['timezone_offset'] = data['timezone_offset'] return jsonify({'success': True}) return jsonify({'success': False}), 400 @app.route('/api/admin_unread_counts') @admin_required def api_admin_unread_counts(): conn = get_db_connection() counts = conn.execute(''' SELECT order_id, COUNT(*) as count FROM messages WHERE is_read = 0 AND sender_type = 'user' GROUP BY order_id ''').fetchall() conn.close() return jsonify({str(row['order_id']): row['count'] for row in counts}) @app.route('/api/mark_order_read', methods=['POST']) @admin_required def mark_order_read(): data = request.get_json() order_id = data.get('order_id') conn = get_db_connection() conn.execute('UPDATE messages SET is_read = 1 WHERE order_id = ? AND sender_type = "user"', (order_id,)) conn.commit() conn.close() return jsonify({'success': True}) @app.route('/api/toggle_payment', methods=['POST']) @admin_required def toggle_payment(): data = request.get_json() order_id = data.get('order_id') conn = get_db_connection() current = conn.execute('SELECT payment_status FROM orders WHERE id = ?', (order_id,)).fetchone() new_status = 'paid' if current['payment_status'] != 'paid' else 'not_paid' conn.execute('UPDATE orders SET payment_status = ? WHERE id = ?', (new_status, order_id)) conn.commit() conn.close() return jsonify({'status': new_status}) # ---------------------- СТРАНИЦЫ ---------------------- @app.route('/') def index(): return render_template('index.html') @app.route('/about') def about(): return render_template('about.html') @app.route('/services') def services(): conn = get_db_connection() services = conn.execute('SELECT * FROM services').fetchall() conn.close() return render_template('services.html', services=services) @app.route('/abonements') def abonements(): conn = get_db_connection() abonements = conn.execute('SELECT * FROM abonements').fetchall() conn.close() return render_template('abonements.html', abonements=abonements) @app.route('/certificates') def certificates(): conn = get_db_connection() certificates = conn.execute('SELECT * FROM certificates').fetchall() conn.close() return render_template('certificates.html', certificates=certificates) @app.route('/gallery') def gallery(): images_dir = os.path.join(app.static_folder, 'images/gallery') images = [] if os.path.exists(images_dir): images = [f for f in os.listdir(images_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp'))] return render_template('gallery.html', images=images) @app.route('/discounts') def discounts(): return render_template('discounts.html') # ---------------------- РЕГИСТРАЦИЯ / ВХОД ---------------------- @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] email = request.form.get('email', '') if email and not is_valid_email(email): flash('Введите корректный email адрес (должен заканчиваться на .ru или .com)', 'danger') return redirect(url_for('register')) conn = get_db_connection() user = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone() if user: flash('Пользователь с таким именем уже существует', 'danger') conn.close() return redirect(url_for('register')) hashed_password = generate_password_hash(password) conn.execute('INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?)', (username, hashed_password, email if email else None, 'user')) conn.commit() conn.close() flash('Регистрация успешна! Теперь вы можете войти', 'success') return redirect(url_for('login')) return render_template('register.html') @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] conn = get_db_connection() user = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone() conn.close() if user and check_password_hash(user['password'], password): session.permanent = True session['user_id'] = user['id'] session['username'] = user['username'] session['role'] = user['role'] flash(f'Добро пожаловать, {username}!', 'success') return redirect(url_for('index')) else: flash('Неверное имя пользователя или пароль', 'danger') return render_template('login.html') @app.route('/logout') def logout(): session.clear() flash('Вы вышли из системы', 'info') return redirect(url_for('index')) # ---------------------- ЗАКАЗЫ ---------------------- @app.route('/order', methods=['GET', 'POST']) @login_required def order(): if request.method == 'POST': service_type = request.form['service_type'] service_id = request.form.get('service_id') service_name = request.form['service_name'] date = request.form.get('date') time = request.form.get('time') persons = request.form.get('persons', 1) price = request.form.get('price', 0) comment = request.form.get('comment', '') try: persons = int(float(persons)) if service_type != 'certificate' else 1 if persons < 1: persons = 1 if persons > 20: persons = 20 except: persons = 1 conn = get_db_connection() final_price = int(price) discount_applied = False discount_amount = 0 original_price = int(price) if service_type != 'certificate': duration_minutes = 0 duration_str = '' if service_type == 'service': service = conn.execute('SELECT duration FROM services WHERE id = ?', (service_id,)).fetchone() if service: duration_str = service['duration'] if 'минут' in duration_str: duration_minutes = int(duration_str.split()[0]) elif 'час' in duration_str: hours = int(duration_str.split()[0]) duration_minutes = hours * 60 try: hour = int(time.split(':')[0]) minute = int(time.split(':')[1]) start_minutes = hour * 60 + minute end_minutes = start_minutes + duration_minutes if start_minutes < 540: flash('Заказы принимаются с 9:00', 'danger') conn.close() return redirect(url_for('order')) if end_minutes > 1200: max_hour = (1200 - duration_minutes) // 60 max_minute = (1200 - duration_minutes) % 60 flash(f'Услуга длится {duration_str}. Необходимо закончить до 20:00. Максимальное время начала: {max_hour}:{max_minute:02d}', 'danger') conn.close() return redirect(url_for('order')) except: flash('Неверный формат времени', 'danger') conn.close() return redirect(url_for('order')) today = datetime.now().strftime('%Y-%m-%d') if date < today: flash('Нельзя выбрать прошедшую дату', 'danger') conn.close() return redirect(url_for('order')) if persons >= 4 and service_type == 'service': discount_amount = int(int(price) * 0.15) final_price = int(int(price) * 0.85) discount_applied = True conn.execute(''' INSERT INTO orders (user_id, service_type, service_name, service_id, date, time, persons, price, comment, discount_applied, discount_amount, original_price, payment_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'not_paid') ''', (session['user_id'], service_type, service_name, service_id, date, time, persons, final_price, comment, discount_applied, discount_amount, original_price)) conn.commit() conn.close() if discount_applied: flash(f'Заказ успешно создан! Применена семейная скидка 15%! Итоговая цена: {final_price} руб.', 'success') else: flash('Заказ успешно создан!', 'success') return redirect(url_for('my_orders')) return render_template('order_form.html') @app.route('/my-orders') @login_required def my_orders(): sort = request.args.get('sort', 'newest') status = request.args.get('status', 'all') conn = get_db_connection() query = 'SELECT * FROM orders WHERE user_id = ?' params = [session['user_id']] if status != 'all': query += ' AND status = ?' params.append(status) if sort == 'newest': query += ' ORDER BY created_at DESC' elif sort == 'oldest': query += ' ORDER BY created_at ASC' elif sort == 'price_high': query += ' ORDER BY price DESC' elif sort == 'price_low': query += ' ORDER BY price ASC' orders = conn.execute(query, params).fetchall() orders_list = [] for order in orders: order_dict = dict(order) unread = conn.execute(''' SELECT COUNT(*) as count FROM messages WHERE order_id = ? AND is_read = 0 AND sender_type != 'user' ''', (order['id'],)).fetchone() order_dict['unread_count'] = unread['count'] if unread else 0 orders_list.append(order_dict) conn.close() return render_template('my_orders.html', orders=orders_list, sort=sort, status=status) # ---------------------- ОТЗЫВЫ ---------------------- @app.route('/reviews', methods=['GET', 'POST']) def reviews(): if request.method == 'POST': if 'user_id' not in session: flash('Пожалуйста, войдите в систему, чтобы оставить отзыв', 'warning') return redirect(url_for('login')) order_id = request.form.get('order_id') rating = int(request.form['rating']) comment = request.form['comment'] conn = get_db_connection() existing_review = conn.execute('SELECT * FROM reviews WHERE user_id = ? AND order_id = ?', (session['user_id'], order_id)).fetchone() if existing_review: flash('Вы уже оставляли отзыв на этот заказ', 'danger') conn.close() return redirect(url_for('reviews')) order = conn.execute('SELECT * FROM orders WHERE id = ? AND user_id = ? AND status = "completed"', (order_id, session['user_id'])).fetchone() if not order: flash('Вы можете оставить отзыв только на выполненные заказы', 'danger') conn.close() return redirect(url_for('reviews')) conn.execute(''' INSERT INTO reviews (user_id, order_id, username, rating, comment, status) VALUES (?, ?, ?, ?, ?, 'pending') ''', (session['user_id'], order_id, session['username'], rating, comment)) conn.commit() conn.close() flash('Ваш отзыв отправлен на модерацию!', 'success') return redirect(url_for('reviews')) sort = request.args.get('sort', 'newest') rating_filter = request.args.get('rating', 'all') conn = get_db_connection() completed_orders = [] if 'user_id' in session: completed_orders = conn.execute(''' SELECT o.* FROM orders o LEFT JOIN reviews r ON o.id = r.order_id AND r.user_id = ? WHERE o.user_id = ? AND o.status = 'completed' AND r.id IS NULL ORDER BY o.created_at DESC ''', (session['user_id'], session['user_id'])).fetchall() query = 'SELECT * FROM reviews WHERE status = "approved"' params = [] if rating_filter != 'all': query += ' AND rating = ?' params.append(rating_filter) if sort == 'newest': query += ' ORDER BY created_at DESC' elif sort == 'oldest': query += ' ORDER BY created_at ASC' elif sort == 'rating_high': query += ' ORDER BY rating DESC' elif sort == 'rating_low': query += ' ORDER BY rating ASC' approved_reviews = conn.execute(query, params).fetchall() conn.close() return render_template('reviews.html', reviews=approved_reviews, completed_orders=completed_orders, sort=sort, rating_filter=rating_filter) # ---------------------- ЧАТ ---------------------- @app.route('/chat/') @login_required def chat(order_id): conn = get_db_connection() order = conn.execute('SELECT * FROM orders WHERE id = ?', (order_id,)).fetchone() if not order: flash('Заказ не найден', 'danger') return redirect(url_for('my_orders')) if session['user_id'] != order['user_id'] and session.get('role') != 'admin': flash('Доступ запрещен', 'danger') return redirect(url_for('index')) if session.get('role') == 'admin': conn.execute('UPDATE messages SET is_read = 1 WHERE order_id = ? AND sender_type = "user"', (order_id,)) messages = conn.execute(''' SELECT m.*, CASE WHEN m.sender_type = 'user' THEN u.username WHEN m.sender_type = 'admin' THEN a.username ELSE 'Система' END as sender_name FROM messages m LEFT JOIN users u ON m.user_id = u.id LEFT JOIN users a ON m.admin_id = a.id WHERE m.order_id = ? ORDER BY m.created_at ASC ''', (order_id,)).fetchall() conn.close() return render_template('chat.html', order=order, messages=messages) @app.route('/send_message', methods=['POST']) @login_required def send_message(): order_id = request.form['order_id'] message = request.form['message'] if not message or not message.strip(): return jsonify({'error': 'Сообщение не может быть пустым'}), 400 conn = get_db_connection() order = conn.execute('SELECT * FROM orders WHERE id = ?', (order_id,)).fetchone() if not order: conn.close() return jsonify({'error': 'Заказ не найден'}), 404 if session['user_id'] != order['user_id'] and session.get('role') != 'admin': conn.close() return jsonify({'error': 'Доступ запрещен'}), 403 if session.get('role') == 'admin': sender_type = 'admin' admin_id = session['user_id'] user_id = order['user_id'] else: sender_type = 'user' user_id = session['user_id'] admin_id = None conn.execute(''' INSERT INTO messages (order_id, user_id, admin_id, message, sender_type) VALUES (?, ?, ?, ?, ?) ''', (order_id, user_id, admin_id, message.strip(), sender_type)) conn.commit() conn.close() return jsonify({'success': True, 'message': 'Сообщение отправлено'}) # ---------------------- АДМИНКА ---------------------- @app.route('/admin') @admin_required def admin_panel(): return render_template('admin/index.html') @app.route('/admin/orders') @admin_required def admin_orders(): sort = request.args.get('sort', 'newest') status = request.args.get('status', 'all') conn = get_db_connection() query = ''' SELECT o.*, u.username, (SELECT COUNT(*) FROM messages m WHERE m.order_id = o.id AND m.is_read = 0 AND m.sender_type != 'admin') as unread_count FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE 1=1 ''' params = [] if status != 'all': query += ' AND o.status = ?' params.append(status) if sort == 'newest': query += ' ORDER BY o.created_at DESC' elif sort == 'oldest': query += ' ORDER BY o.created_at ASC' elif sort == 'price_high': query += ' ORDER BY o.price DESC' elif sort == 'price_low': query += ' ORDER BY o.price ASC' orders = conn.execute(query, params).fetchall() conn.close() return render_template('admin/orders.html', orders=orders, sort=sort, status=status) @app.route('/admin/orders//update', methods=['POST']) @admin_required def update_order(order_id): status = request.form['status'] sort = request.args.get('sort', 'newest') status_filter = request.args.get('status', 'all') conn = get_db_connection() order = conn.execute('SELECT user_id FROM orders WHERE id = ?', (order_id,)).fetchone() conn.execute('UPDATE orders SET status = ? WHERE id = ?', (status, order_id)) if order: conn.execute(''' INSERT INTO messages (order_id, user_id, admin_id, message, sender_type) VALUES (?, ?, ?, ?, 'system') ''', (order_id, order['user_id'], session['user_id'], f'Статус заказа изменен на: {status}')) conn.commit() conn.close() flash('Статус заказа обновлен', 'success') return redirect(url_for('admin_orders', sort=sort, status=status_filter)) @app.route('/admin/orders//delete', methods=['POST']) @admin_required def delete_order(order_id): sort = request.args.get('sort', 'newest') status_filter = request.args.get('status', 'all') conn = get_db_connection() conn.execute('DELETE FROM messages WHERE order_id = ?', (order_id,)) conn.execute('DELETE FROM orders WHERE id = ?', (order_id,)) conn.commit() conn.close() flash('Заказ удален', 'success') return redirect(url_for('admin_orders', sort=sort, status=status_filter)) @app.route('/admin/reviews') @admin_required def admin_reviews(): sort = request.args.get('sort', 'newest') status = request.args.get('status', 'all') rating = request.args.get('rating', 'all') conn = get_db_connection() query = 'SELECT * FROM reviews WHERE 1=1' params = [] if status != 'all': query += ' AND status = ?' params.append(status) if rating != 'all': query += ' AND rating = ?' params.append(rating) if sort == 'newest': query += ' ORDER BY created_at DESC' elif sort == 'oldest': query += ' ORDER BY created_at ASC' elif sort == 'rating_high': query += ' ORDER BY rating DESC' elif sort == 'rating_low': query += ' ORDER BY rating ASC' reviews = conn.execute(query, params).fetchall() conn.close() return render_template('admin/reviews.html', reviews=reviews, sort=sort, status=status, rating=rating) @app.route('/admin/reviews//moderate', methods=['POST']) @admin_required def moderate_review(review_id): status = request.form['status'] sort = request.args.get('sort', 'newest') status_filter = request.args.get('status', 'all') rating_filter = request.args.get('rating', 'all') conn = get_db_connection() conn.execute('UPDATE reviews SET status = ? WHERE id = ?', (status, review_id)) conn.commit() conn.close() flash('Отзыв обработан', 'success') return redirect(url_for('admin_reviews', sort=sort, status=status_filter, rating=rating_filter)) @app.route('/admin/reviews//delete', methods=['POST']) @admin_required def delete_review(review_id): sort = request.args.get('sort', 'newest') status_filter = request.args.get('status', 'all') rating_filter = request.args.get('rating', 'all') conn = get_db_connection() conn.execute('DELETE FROM reviews WHERE id = ?', (review_id,)) conn.commit() conn.close() flash('Отзыв удален', 'success') return redirect(url_for('admin_reviews', sort=sort, status=status_filter, rating=rating_filter)) @app.route('/admin/users') @admin_required def admin_users(): sort = request.args.get('sort', 'newest') role = request.args.get('role', 'all') conn = get_db_connection() query = 'SELECT * FROM users WHERE 1=1' params = [] if role != 'all': query += ' AND role = ?' params.append(role) if sort == 'newest': query += ' ORDER BY created_at DESC' elif sort == 'oldest': query += ' ORDER BY created_at ASC' elif sort == 'name_asc': query += ' ORDER BY username ASC' elif sort == 'name_desc': query += ' ORDER BY username DESC' users = conn.execute(query, params).fetchall() conn.close() return render_template('admin/users.html', users=users, sort=sort, role=role) @app.route('/admin/users//delete', methods=['POST']) @admin_required def delete_user(user_id): sort = request.args.get('sort', 'newest') role_filter = request.args.get('role', 'all') if user_id == session['user_id']: flash('Нельзя удалить самого себя', 'danger') return redirect(url_for('admin_users', sort=sort, role=role_filter)) conn = get_db_connection() conn.execute('DELETE FROM messages WHERE user_id = ?', (user_id,)) conn.execute('DELETE FROM orders WHERE user_id = ?', (user_id,)) conn.execute('DELETE FROM reviews WHERE user_id = ?', (user_id,)) conn.execute('DELETE FROM users WHERE id = ?', (user_id,)) conn.commit() conn.close() flash('Пользователь удален', 'success') return redirect(url_for('admin_users', sort=sort, role=role_filter)) @app.route('/admin/users//make-admin', methods=['POST']) @admin_required def make_admin(user_id): sort = request.args.get('sort', 'newest') role_filter = request.args.get('role', 'all') conn = get_db_connection() conn.execute('UPDATE users SET role = "admin" WHERE id = ?', (user_id,)) conn.commit() conn.close() flash('Пользователь назначен администратором', 'success') return redirect(url_for('admin_users', sort=sort, role=role_filter)) @app.route('/admin/messages') @admin_required def admin_messages(): conn = get_db_connection() messages = conn.execute(''' SELECT m.*, o.id as order_id, u.username as user_name FROM messages m JOIN orders o ON m.order_id = o.id JOIN users u ON o.user_id = u.id WHERE m.sender_type = 'user' AND m.is_read = 0 ORDER BY m.created_at DESC ''').fetchall() conn.close() return render_template('admin/messages.html', messages=messages) # ---------------------- ЗАПУСК ---------------------- if __name__ == '__main__': conn = sqlite3.connect('horse_farm.db') cursor = conn.cursor() cursor.execute("PRAGMA table_info(orders)") existing = [col[1] for col in cursor.fetchall()] for col in ['discount_applied', 'discount_amount', 'original_price', 'payment_status']: if col not in existing: cursor.execute(f"ALTER TABLE orders ADD COLUMN {col} DEFAULT 'not_paid' if col == 'payment_status' else 0") print(f"Добавлена колонка {col}") cursor.execute("PRAGMA table_info(reviews)") if 'order_id' not in [c[1] for c in cursor.fetchall()]: cursor.execute("ALTER TABLE reviews ADD COLUMN order_id INTEGER") cursor.execute("PRAGMA table_info(messages)") if 'is_read' not in [c[1] for c in cursor.fetchall()]: cursor.execute("ALTER TABLE messages ADD COLUMN is_read BOOLEAN DEFAULT 0") cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='messages'") if not cursor.fetchone(): cursor.execute(''' CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER, user_id INTEGER, admin_id INTEGER, message TEXT NOT NULL, sender_type TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_read BOOLEAN DEFAULT 0 ) ''') cursor.execute("SELECT * FROM users WHERE username = 'admin'") if not cursor.fetchone(): hashed = generate_password_hash('admin123') cursor.execute("INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?)", ('admin', hashed, 'admin@koniydvor.ru', 'admin')) print("Создан администратор: admin / admin123") conn.commit() conn.close() app.run(debug=True)