import os from flask import Flask, session, request from dotenv import load_dotenv # 1. Загрузка переменных окружения - в самом верху load_dotenv() # 2. Импорт расширений из центрального файла from extensions import scheduler, db, migrate, login_manager, babel from models import init_encryption # 3. Фабрика приложений def create_app(): """ Создает и конфигурирует экземпляр Flask приложения. """ app = Flask(__name__) # --- Конфигурация приложения --- app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-super-secret-key-for-dev') # --- НАДЕЖНАЯ НАСТРОЙКА ПУТЕЙ --- # Получаем абсолютный путь к директории, где находится app.py basedir = os.path.abspath(os.path.dirname(__file__)) # Устанавливаем путь к папке data data_dir = os.path.join(basedir, os.environ.get('DATA_DIR', 'data')) # Создаем эту директорию, если ее не существует. Это ключевой момент. os.makedirs(data_dir, exist_ok=True) app.config['DATA_DIR'] = data_dir # Устанавливаем путь к БД db_path = os.path.join(data_dir, 'app.db') app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', f"sqlite:///{db_path}") app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['BABEL_DEFAULT_LOCALE'] = 'ru' app.config['ENCRYPTION_KEY'] = os.environ.get('ENCRYPTION_KEY') # --- Определяем селектор языка --- def get_locale(): if 'language' in session: return session['language'] return request.accept_languages.best_match(['ru', 'en']) # --- Инициализация расширений с приложением --- db.init_app(app) migrate.init_app(app, db) login_manager.init_app(app) babel.init_app(app, locale_selector=get_locale) scheduler.init_app(app) init_encryption(app) # --- Регистрация блюпринтов --- from routes import main_bp, execute_olap_export app.register_blueprint(main_bp) login_manager.login_view = 'main.login' login_manager.login_message = "Пожалуйста, войдите, чтобы получить доступ к этой странице." login_manager.login_message_category = "info" with app.app_context(): from models import User, UserConfig all_configs = UserConfig.query.all() for config in all_configs: user_id = config.user_id mappings = config.mappings for sheer_title, params in mappings.items(): cron_schedule = params.get('schedule_cron') if cron_schedule: job_id = f"user_{user_id}_sheet_{sheer_title}" try: scheduler.add_job( id=job_id, func=execute_olap_export, trigger='cron', args=[user_id, sheer_title], **_parse_cron_string(cron_schedule) ) app.logger.info(f"Job {job_id} loaded on startup.") except Exception as e: app.logger.error(f"Failed to load job {job_id}: {e}") scheduler.start() # --- Регистрация команд CLI --- from models import User, UserConfig @app.cli.command('init-db') def init_db_command(): """Создает или пересоздает таблицы в базе данных.""" print("Creating database tables...") db.create_all() print("Database tables created successfully.") return app # --- Точка входа для запуска --- app = create_app() # --- Вспомогательная функция для парсинга cron --- def _parse_cron_string(cron_str): """Парсит строку cron в словарь для APScheduler.""" parts = cron_str.split() if len(parts) != 5: raise ValueError("Invalid cron string format. Expected 5 parts.") keys = ['minute', 'hour', 'day', 'month', 'day_of_week'] return {keys[i]: part for i, part in enumerate(parts)} if __name__ == '__main__': # Для прямого запуска через `python app.py` (удобно для отладки) app.run(host='0.0.0.0', port=int(os.environ.get("PORT", 5005)))