Fix
This commit is contained in:
parent
6f573999ef
commit
98bbc486e6
83
app.py
83
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)
|
||||
|
|
|
|||
BIN
horse_farm.db
BIN
horse_farm.db
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -98,18 +87,18 @@
|
|||
<form method="POST" class="auth-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Имя пользователя:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
<input type="text" id="username" name="username" required maxlength="50">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email (необязательно):</label>
|
||||
<input type="email" id="email" name="email" placeholder="example@mail.ru">
|
||||
<small>Формат: name@domain.ru</small>
|
||||
<input type="email" id="email" name="email" placeholder="example@mail.ru" maxlength="50">
|
||||
<small>Максимум 50 символов, локальная часть (до @) - до 20 символов</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<input type="password" id="password" name="password" required minlength="4" maxlength="100">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Зарегистрироваться</button>
|
||||
|
|
|
|||
Loading…
Reference in a new issue