Update: admin panel, SMTP/EWS/Outlook mail modes, QR fix, event folders, security improvements
This commit is contained in:
@@ -3,12 +3,17 @@ import logging
|
||||
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from sqlalchemy import text
|
||||
|
||||
from config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
db = SQLAlchemy()
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
csrf = CSRFProtect()
|
||||
|
||||
|
||||
def create_app(config_class=Config):
|
||||
@@ -22,6 +27,8 @@ def create_app(config_class=Config):
|
||||
os.makedirs(os.path.dirname(app.config["SQLALCHEMY_DATABASE_URI"].replace("sqlite:///", "")), exist_ok=True)
|
||||
|
||||
db.init_app(app)
|
||||
limiter.init_app(app)
|
||||
csrf.init_app(app)
|
||||
|
||||
from app.routes import main_bp
|
||||
app.register_blueprint(main_bp)
|
||||
@@ -30,10 +37,20 @@ def create_app(config_class=Config):
|
||||
db.create_all()
|
||||
_migrate_existing_data(app)
|
||||
_create_default_users(app)
|
||||
_load_smtp_settings(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def _load_smtp_settings(app):
|
||||
"""Загружает SMTP-настройки из базы в конфиг приложения."""
|
||||
try:
|
||||
from app.models import SmtpSettings
|
||||
SmtpSettings.apply_to_app(app)
|
||||
except Exception as e:
|
||||
logger.warning(f"SMTP settings load skipped: {e}")
|
||||
|
||||
|
||||
def _column_exists(table_name, column_name):
|
||||
return db.session.execute(
|
||||
text(f"SELECT COUNT(*) FROM pragma_table_info('{table_name}') WHERE name='{column_name}'")
|
||||
@@ -45,6 +62,65 @@ def _add_column_if_missing(table_name, column_name, column_def):
|
||||
db.session.execute(text(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_def}"))
|
||||
|
||||
|
||||
def _safe_move(src, dst):
|
||||
"""Перемещает файл, игнорируя ошибки."""
|
||||
try:
|
||||
import shutil
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(src), str(dst))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось переместить {src} -> {dst}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _migrate_files_to_event_folders():
|
||||
"""Переносит существующие файлы в папки по мероприятиям."""
|
||||
from pathlib import Path
|
||||
from flask import current_app
|
||||
from app.models import Event, Guest
|
||||
|
||||
photo_base = Path(current_app.config["PHOTO_FOLDER"])
|
||||
qr_base = Path(current_app.config["QR_FOLDER"])
|
||||
logo_base = Path(current_app.config["LOGO_FOLDER"])
|
||||
tickets_base = Path(current_app.config["UPLOAD_FOLDER"]) / "tickets"
|
||||
|
||||
# Фото гостей
|
||||
for guest in Guest.query.filter(Guest.photo_filename.isnot(None)).all():
|
||||
if not guest.photo_filename:
|
||||
continue
|
||||
src = photo_base / guest.photo_filename
|
||||
if src.exists() and src.is_file():
|
||||
dst = photo_base / f"event_{guest.event_id}" / guest.photo_filename
|
||||
_safe_move(src, dst)
|
||||
|
||||
# QR-коды гостей
|
||||
for guest in Guest.query.filter(Guest.qr_filename.isnot(None)).all():
|
||||
if not guest.qr_filename:
|
||||
continue
|
||||
src = qr_base / guest.qr_filename
|
||||
if src.exists() and src.is_file():
|
||||
dst = qr_base / f"event_{guest.event_id}" / guest.qr_filename
|
||||
_safe_move(src, dst)
|
||||
|
||||
# Логотипы мероприятий
|
||||
for event in Event.query.filter(Event.logo_filename.isnot(None)).all():
|
||||
if not event.logo_filename:
|
||||
continue
|
||||
src = logo_base / event.logo_filename
|
||||
if src.exists() and src.is_file():
|
||||
dst = logo_base / f"event_{event.id}" / event.logo_filename
|
||||
_safe_move(src, dst)
|
||||
|
||||
# Сгенерированные билеты
|
||||
if tickets_base.exists():
|
||||
for event in Event.query.all():
|
||||
event_tickets_dir = tickets_base / f"event_{event.id}"
|
||||
event_tickets_dir.mkdir(parents=True, exist_ok=True)
|
||||
for ticket_file in tickets_base.glob(f"ticket_{event.id}_*.png"):
|
||||
_safe_move(ticket_file, event_tickets_dir / ticket_file.name)
|
||||
|
||||
|
||||
def _migrate_existing_data(app):
|
||||
"""Миграция существующих гостей в дефолтное мероприятие и добавление новых колонок."""
|
||||
from app.models import Event, Guest
|
||||
@@ -68,6 +144,18 @@ def _migrate_existing_data(app):
|
||||
_add_column_if_missing("guests", "middle_name", "VARCHAR(255)")
|
||||
_add_column_if_missing("guests", "phone", "VARCHAR(50)")
|
||||
|
||||
# Версия сессии пользователя для инвалидации после смены пароля
|
||||
_add_column_if_missing("users", "session_version", "INTEGER DEFAULT 0")
|
||||
|
||||
# Флаг отправки через локальный Outlook (MAPI)
|
||||
_add_column_if_missing("smtp_settings", "use_local_outlook", "BOOLEAN DEFAULT 0")
|
||||
# Флаг отправки через Exchange Web Services (EWS)
|
||||
_add_column_if_missing("smtp_settings", "use_ews", "BOOLEAN DEFAULT 0")
|
||||
_add_column_if_missing("smtp_settings", "ews_url", "VARCHAR(500)")
|
||||
|
||||
# Переносим файлы в папки по мероприятиям
|
||||
_migrate_files_to_event_folders()
|
||||
|
||||
# Проверяем, есть ли колонка event_id в таблице guests
|
||||
if _column_exists("guests", "event_id"):
|
||||
db.session.commit()
|
||||
|
||||
Reference in New Issue
Block a user