refactor: introduce app factory + blueprints (auth, core, admin users, invite); add utils and wsgi entrypoint; keep legacy routes for now

This commit is contained in:
2025-09-04 14:25:39 +03:00
parent d54e12123b
commit a3d4fbb31d
9 changed files with 566 additions and 0 deletions

67
app/utils/auth.py Normal file
View File

@@ -0,0 +1,67 @@
from flask import redirect, url_for, flash
from flask_login import current_user
from functools import wraps
from werkzeug.security import generate_password_hash, check_password_hash
import os
from app.models import User
def admin_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not current_user.is_authenticated:
return redirect(url_for('login'))
if not getattr(current_user, 'is_admin', False):
flash('Недостаточно прав', 'danger')
return redirect(url_for('dashboard'))
return f(*args, **kwargs)
return decorated
def ensure_default_admin():
username = os.environ.get('ADMIN_USERNAME', 'ruslan')
password = os.environ.get('ADMIN_PASSWORD', '1234')
email = os.environ.get('ADMIN_EMAIL', 'ruslan@example.com')
user = User.get_or_none(User.username == username)
if user:
changed = False
if not user.is_admin:
user.is_admin = True
changed = True
try:
if not check_password_hash(user.password_hash, password):
user.password_hash = generate_password_hash(password)
changed = True
except Exception:
user.password_hash = generate_password_hash(password)
changed = True
if changed:
user.save()
else:
User.create(
username=username,
email=email,
full_name='Администратор',
password_hash=generate_password_hash(password),
is_admin=True
)
def ensure_test_users():
tests = [
('test1', 'test1@example.com', 'Пользователь 1'),
('test2', 'test2@example.com', 'Пользователь 2'),
]
for username, email, full_name in tests:
u = User.get_or_none(User.username == username)
if not u:
User.create(
username=username,
email=email,
full_name=full_name,
password_hash=generate_password_hash(os.environ.get('TEST_USER_PASSWORD', '1234')),
is_admin=False,
)

64
app/utils/mail.py Normal file
View File

@@ -0,0 +1,64 @@
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def send_invite_email(to_email, link, sender_name, survey_name):
msg = MIMEMultipart("alternative")
msg['Subject'] = f"Приглашение пройти опрос: {survey_name}"
msg['From'] = "quiz@4mont.ru"
msg['To'] = to_email
plain_text = f"Опрос: {survey_name}\nОтправитель: {sender_name}\n\nСсылка: {link}\n"
html = f"""
<html>
<body style="font-family: sans-serif;">
<h3>Приглашение пройти опрос</h3>
<p><strong>Опрос:</strong> {survey_name}<br>
<strong>Отправитель:</strong> {sender_name}</p>
<p><a href="{link}" style="font-size: 16px; font-weight: bold;">Перейти к опросу</a></p>
</body>
</html>
"""
msg.attach(MIMEText(plain_text, "plain"))
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP("mail.hosting.reg.ru", 587) as server:
server.starttls()
server.login("quiz@4mont.ru", "utOgbZ09quizpochta")
server.send_message(msg)
def send_result_email(to_email, from_email, survey_name, scores, unsupported, sender_name):
report_link = "https://quiz.4mont.ru/dashboard"
plain_text = (
f"Результаты опроса\nОтправитель: {sender_name} ({from_email})\n"
f"Опрос: {survey_name}\n\n" +
"\n".join(f"{p}: {v}%" for p, v in scores.items()) +
"\n\nПодробности: " + report_link
)
html = f"""
<html>
<body style="font-family: sans-serif;">
<h3>Результаты опроса: {survey_name}</h3>
<ul>
{''.join(f'<li><strong>{p}:</strong> {v}%</li>' for p, v in scores.items())}
</ul>
<p><a href="{report_link}" style="font-weight: bold;">Открыть отчеты</a></p>
</body>
</html>
"""
msg = MIMEMultipart("alternative")
msg['Subject'] = f"Результаты опроса: {survey_name}"
msg['From'] = "quiz@4mont.ru"
msg['To'] = to_email
msg.attach(MIMEText(plain_text, "plain"))
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP("mail.hosting.reg.ru", 587) as server:
server.starttls()
server.login("quiz@4mont.ru", "utOgbZ09quizpochta")
server.send_message(msg)