diff --git a/app.py b/app.py index 65c15f2..6a0953c 100644 --- a/app.py +++ b/app.py @@ -16,10 +16,43 @@ def get_db_connection(): return conn def is_valid_email(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 + + # Ограничение длины + if len(email) > 50: + return False + + if '@' not in email: + return False + + local_part = email.split('@')[0] + domain = email.split('@')[1].lower() + + # Локальная часть: минимум 2 символа, не только цифры + if len(local_part) < 2 or len(local_part) > 30: + return False + + if local_part.isdigit(): + return False + + if not local_part[0].isalpha(): + return False + + # Разрешенные почтовые домены + allowed_domains = [ + 'mail.ru', 'yandex.ru', 'gmail.com', 'gmail.ru', + 'list.ru', 'bk.ru', 'inbox.ru', 'rambler.ru', + 'ya.ru', 'google.com', 'outlook.com', 'hotmail.com', + 'yahoo.com', 'protonmail.com', 'icloud.com' + ] + + # Проверяем, что домен есть в списке разрешенных + if domain not in allowed_domains: + return False + + return True def login_required(f): @wraps(f) @@ -191,15 +224,18 @@ def register(): 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') + flash('Введите корректный email адрес (допустимые домены: mail.ru, yandex.ru, gmail.com, list.ru, bk.ru, inbox.ru, rambler.ru, ya.ru, google.com, outlook.com, hotmail.com, yahoo.com, protonmail.com, icloud.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')) @@ -247,17 +283,20 @@ def order(): 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 = '' @@ -270,15 +309,18 @@ def order(): 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 @@ -289,15 +331,18 @@ def order(): 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) @@ -305,11 +350,13 @@ def order(): ''', (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') @@ -318,6 +365,7 @@ 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': @@ -331,6 +379,7 @@ def my_orders(): 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: @@ -341,6 +390,7 @@ def my_orders(): ''', (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) @@ -351,9 +401,11 @@ def reviews(): 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() @@ -361,12 +413,14 @@ def reviews(): 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') @@ -375,9 +429,11 @@ def reviews(): 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(''' @@ -386,6 +442,7 @@ def reviews(): 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': @@ -399,6 +456,7 @@ def reviews(): 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, @@ -417,8 +475,10 @@ def chat(order_id): 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 @@ -442,6 +502,7 @@ def send_message(): 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: @@ -450,6 +511,7 @@ def send_message(): 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'] @@ -458,6 +520,7 @@ def send_message(): 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 (?, ?, ?, ?, ?) @@ -497,6 +560,7 @@ def admin_orders(): 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) @@ -556,6 +620,7 @@ def admin_reviews(): 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) @@ -606,6 +671,7 @@ def admin_users(): 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) @@ -659,18 +725,25 @@ def admin_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") + if col == 'payment_status': + cursor.execute(f"ALTER TABLE orders ADD COLUMN {col} TEXT DEFAULT 'not_paid'") + else: + cursor.execute(f"ALTER TABLE orders ADD COLUMN {col} INTEGER DEFAULT 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(''' @@ -685,12 +758,14 @@ if __name__ == '__main__': 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) diff --git a/horse_farm.db b/horse_farm.db index 9c472f0..b06f84c 100644 Binary files a/horse_farm.db and b/horse_farm.db differ diff --git a/templates/register.html b/templates/register.html index 209714c..939b1c8 100644 --- a/templates/register.html +++ b/templates/register.html @@ -10,24 +10,20 @@ border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.2); } - .auth-container h2 { color: #f5cba0; margin-bottom: 1.5rem; text-align: center; } - .form-group { margin-bottom: 1rem; } - .form-group label { display: block; margin-bottom: 0.3rem; font-weight: 600; color: #f5cba0; } - .form-group input { width: 100%; padding: 0.6rem; @@ -37,15 +33,13 @@ background: rgba(255, 255, 255, 0.9); color: #2c2c2c; } - .form-group input:focus { outline: none; border-color: #f5cba0; } - .btn-primary { width: 100%; - background: linear-gradient(135deg, #f5cba0 0%, #d4a373 100%); + background: #f5cba0; color: #2c1810; padding: 0.75rem; border: none; @@ -53,34 +47,29 @@ font-size: 1rem; font-weight: bold; cursor: pointer; - transition: all 0.2s ease; + transition: 0.2s; } - .btn-primary:hover { - background: linear-gradient(135deg, #ffdd99 0%, #f5cba0 100%); + background: #ffdd99; transform: translateY(-2px); } - .auth-link { text-align: center; margin-top: 1rem; color: white; } - .auth-link a { color: #f5cba0; text-decoration: none; } - .auth-link a:hover { text-decoration: underline; } - small { display: block; margin-top: 0.25rem; font-size: 0.7rem; - color: #f5cba0; + color: #ffffff !important; } @@ -98,18 +87,18 @@
- +
- - Формат: name@domain.ru + + Максимум 50 символов, локальная часть (до @) - до 20 символов
- +