SOY NV AI
Fix: Use Flask-Login unauthorized_handler instead of 404 handler for login redirect
1cf7ede
| """ | |
| Flask ์ ํ๋ฆฌ์ผ์ด์ ์ด๊ธฐํ | |
| """ | |
| from flask import Flask, request, send_from_directory, jsonify | |
| from flask_login import LoginManager | |
| import sqlite3 | |
| from pathlib import Path | |
| from typing import Optional | |
| from app.database import db, User | |
| from app.core.config import Config, get_config | |
| from app.core.logger import get_logger | |
| logger = get_logger(__name__) | |
| login_manager = LoginManager() | |
| login_manager.login_view = 'main.login' | |
| login_manager.login_message = '๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.' | |
| login_manager.login_message_category = 'info' | |
| def unauthorized(): | |
| """์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์ ์ฒ๋ฆฌ""" | |
| from flask import redirect, url_for, request | |
| # API ์์ฒญ์ธ ๊ฒฝ์ฐ JSON ์๋ต | |
| if request.path.startswith('/api/'): | |
| from flask import jsonify | |
| return jsonify({'error': '๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.'}), 401 | |
| # ์ผ๋ฐ ์์ฒญ์ธ ๊ฒฝ์ฐ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ | |
| return redirect(url_for('main.login', next=request.path)) | |
| def load_user(user_id: str) -> Optional[User]: | |
| """ | |
| ์ฌ์ฉ์ ๋ก๋ ํจ์ (Flask-Login์ฉ) | |
| Args: | |
| user_id: ์ฌ์ฉ์ ID (๋ฌธ์์ด) | |
| Returns: | |
| User ๊ฐ์ฒด ๋๋ None | |
| """ | |
| try: | |
| return User.query.get(int(user_id)) | |
| except (ValueError, TypeError): | |
| return None | |
| def create_app() -> Flask: | |
| """ | |
| Flask ์ ํ๋ฆฌ์ผ์ด์ ํฉํ ๋ฆฌ ํจ์ | |
| Returns: | |
| ์ค์ ๋ Flask ์ ํ๋ฆฌ์ผ์ด์ ์ธ์คํด์ค | |
| """ | |
| config = get_config() | |
| # ํ์ ๋๋ ํ ๋ฆฌ ์์ฑ | |
| config.ensure_directories() | |
| # ํ ํ๋ฆฟ ํด๋ ๋ฐ static ํด๋ ๊ฒฝ๋ก ์ค์ | |
| template_folder = str(config.TEMPLATES_FOLDER) | |
| static_folder = str(config.STATIC_FOLDER) | |
| app = Flask(__name__, template_folder=template_folder, static_folder=static_folder) | |
| # Flask ์ค์ ์ ์ฉ | |
| app.config['SECRET_KEY'] = config.SECRET_KEY | |
| app.config['SQLALCHEMY_DATABASE_URI'] = config.SQLALCHEMY_DATABASE_URI | |
| app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = config.SQLALCHEMY_TRACK_MODIFICATIONS | |
| app.config['MAX_CONTENT_LENGTH'] = config.MAX_CONTENT_LENGTH | |
| # ํ์ฅ ์ด๊ธฐํ | |
| db.init_app(app) | |
| login_manager.init_app(app) | |
| # Blueprint ๋ฑ๋ก | |
| from app.routes import main_bp | |
| app.register_blueprint(main_bp) | |
| # favicon.ico ํธ๋ค๋ฌ ์ถ๊ฐ | |
| def favicon(): | |
| """favicon.ico ์์ฒญ ์ฒ๋ฆฌ""" | |
| try: | |
| return send_from_directory( | |
| str(config.STATIC_FOLDER), | |
| 'logo.webp', | |
| mimetype='image/webp' | |
| ) | |
| except Exception as e: | |
| logger.warning(f"favicon.ico ์ฒ๋ฆฌ ์คํจ: {e}") | |
| return '', 204 # No Content | |
| # 404 ์๋ฌ ํธ๋ค๋ฌ (์ค์ ๋ก ์กด์ฌํ์ง ์๋ ๋ผ์ฐํธ์๋ง ์ ์ฉ) | |
| def not_found(error): | |
| """404 ์๋ฌ ์ฒ๋ฆฌ""" | |
| logger.warning(f"404 ์๋ฌ: {request.path} - {request.method}") | |
| # API ์์ฒญ์ธ ๊ฒฝ์ฐ JSON ์๋ต | |
| if request.path.startswith('/api/'): | |
| return jsonify({'error': '๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.', 'path': request.path}), 404 | |
| # ์ ์ ํ์ผ ์์ฒญ์ธ ๊ฒฝ์ฐ 404 ๋ฐํ | |
| if request.path.startswith('/static/'): | |
| return '', 404 | |
| # ๋ก๊ทธ์ธ ํ์ด์ง๋ 404๊ฐ ์๋ ์ ์ ๋ผ์ฐํธ์ด๋ฏ๋ก ๋ฆฌ๋ค์ด๋ ํธํ์ง ์์ | |
| # Flask-Login์ด ์๋์ผ๋ก ์ฒ๋ฆฌํ๋๋ก ํจ | |
| return jsonify({'error': 'ํ์ด์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.', 'path': request.path}), 404 | |
| # ์์ฒญ ๋ก๊น ๋ฏธ๋ค์จ์ด ์ถ๊ฐ | |
| def log_request_info(): | |
| """๊ฐ HTTP ์์ฒญ ์ ๋ณด๋ฅผ ๋ก๊น """ | |
| logger.info(f"[์์ฒญ] {request.method} {request.path} - IP: {request.remote_addr}") | |
| if request.args: | |
| logger.debug(f"[์์ฒญ ํ๋ผ๋ฏธํฐ] {dict(request.args)}") | |
| def log_response_info(response): | |
| """๊ฐ HTTP ์๋ต ์ ๋ณด๋ฅผ ๋ก๊น """ | |
| logger.info(f"[์๋ต] {request.method} {request.path} - ์ํ: {response.status_code}") | |
| # Permissions-Policy ํค๋ ์ถ๊ฐ (iframe ์๋ฒ ๋ ์ ๊ฒฝ๊ณ ๋ฐฉ์ง) | |
| # Hugging Face Spaces์์ iframe์ผ๋ก ์๋ฒ ๋๋ ๋ ๋ฐ์ํ๋ ๊ฒฝ๊ณ ๋ฅผ ๋ฐฉ์ง | |
| permissions_policy = ( | |
| "ambient-light-sensor=(), " | |
| "battery=(), " | |
| "document-domain=(), " | |
| "layout-animations=(), " | |
| "legacy-image-formats=(), " | |
| "oversized-images=(), " | |
| "vr=(), " | |
| "wake-lock=()" | |
| ) | |
| response.headers['Permissions-Policy'] = permissions_policy | |
| return response | |
| # ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ ๋ฐ ๋ง์ด๊ทธ๋ ์ด์ | |
| with app.app_context(): | |
| db.create_all() | |
| migrate_database(app) | |
| create_admin_user() | |
| logger.info("Flask ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๊ธฐํ๋์์ต๋๋ค.") | |
| return app | |
| def migrate_database(app: Flask) -> None: | |
| """ | |
| ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์คํ | |
| Args: | |
| app: Flask ์ ํ๋ฆฌ์ผ์ด์ ์ธ์คํด์ค | |
| """ | |
| try: | |
| db_uri = app.config['SQLALCHEMY_DATABASE_URI'] | |
| if not db_uri.startswith('sqlite:///'): | |
| logger.warning(f"SQLite๊ฐ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์๋ ๋ง์ด๊ทธ๋ ์ด์ ์ด ์ง์๋์ง ์์ต๋๋ค: {db_uri}") | |
| return | |
| db_path_str = db_uri.replace('sqlite:///', '') | |
| db_path = Path(db_path_str) | |
| # ์๋ ๊ฒฝ๋ก์ธ ๊ฒฝ์ฐ instance ํด๋ ๊ธฐ์ค์ผ๋ก ์ฒ๋ฆฌ | |
| if not db_path.is_absolute(): | |
| db_path = Path(app.instance_path) / db_path | |
| if not db_path.exists(): | |
| logger.info(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ผ์ด ์์ต๋๋ค (์๋ก ์์ฑ๋จ): {db_path}") | |
| return | |
| logger.info(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์์: {db_path}") | |
| conn = sqlite3.connect(str(db_path)) | |
| cursor = conn.cursor() | |
| # user ํ ์ด๋ธ์ nickname ์ปฌ๋ผ์ด ์๋์ง ํ์ธ | |
| cursor.execute("PRAGMA table_info(user)") | |
| user_columns = [column[1] for column in cursor.fetchall()] | |
| if 'nickname' not in user_columns: | |
| logger.info("user ํ ์ด๋ธ์ nickname ์ปฌ๋ผ ์ถ๊ฐ ์ค...") | |
| cursor.execute("ALTER TABLE user ADD COLUMN nickname VARCHAR(80)") | |
| conn.commit() | |
| logger.info("user.nickname ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ") | |
| # uploaded_file ํ ์ด๋ธ์ด ์กด์ฌํ๋์ง ํ์ธ | |
| cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='uploaded_file'") | |
| if cursor.fetchone(): | |
| cursor.execute("PRAGMA table_info(uploaded_file)") | |
| uploaded_file_columns = [column[1] for column in cursor.fetchall()] | |
| if 'uploaded_by' not in uploaded_file_columns: | |
| logger.info("uploaded_file ํ ์ด๋ธ์ uploaded_by ์ปฌ๋ผ ์ถ๊ฐ ์ค...") | |
| cursor.execute("ALTER TABLE uploaded_file ADD COLUMN uploaded_by INTEGER") | |
| conn.commit() | |
| logger.info("uploaded_file.uploaded_by ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ") | |
| if 'parent_file_id' not in uploaded_file_columns: | |
| logger.info("uploaded_file ํ ์ด๋ธ์ parent_file_id ์ปฌ๋ผ ์ถ๊ฐ ์ค...") | |
| cursor.execute("ALTER TABLE uploaded_file ADD COLUMN parent_file_id INTEGER") | |
| conn.commit() | |
| logger.info("uploaded_file.parent_file_id ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ") | |
| # document_chunk ํ ์ด๋ธ์ด ์กด์ฌํ๋์ง ํ์ธ | |
| cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='document_chunk'") | |
| if cursor.fetchone(): | |
| cursor.execute("PRAGMA table_info(document_chunk)") | |
| document_chunk_columns = [column[1] for column in cursor.fetchall()] | |
| if 'chunk_metadata' not in document_chunk_columns: | |
| logger.info("document_chunk ํ ์ด๋ธ์ chunk_metadata ์ปฌ๋ผ ์ถ๊ฐ ์ค...") | |
| cursor.execute("ALTER TABLE document_chunk ADD COLUMN chunk_metadata TEXT") | |
| conn.commit() | |
| logger.info("document_chunk.chunk_metadata ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ") | |
| # chat_session ํ ์ด๋ธ์ด ์กด์ฌํ๋์ง ํ์ธ | |
| cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='chat_session'") | |
| if cursor.fetchone(): | |
| cursor.execute("PRAGMA table_info(chat_session)") | |
| chat_session_columns = [column[1] for column in cursor.fetchall()] | |
| if 'analysis_model' not in chat_session_columns: | |
| logger.info("chat_session ํ ์ด๋ธ์ analysis_model ์ปฌ๋ผ ์ถ๊ฐ ์ค...") | |
| cursor.execute("ALTER TABLE chat_session ADD COLUMN analysis_model VARCHAR(100)") | |
| conn.commit() | |
| logger.info("chat_session.analysis_model ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ") | |
| if 'answer_model' not in chat_session_columns: | |
| logger.info("chat_session ํ ์ด๋ธ์ answer_model ์ปฌ๋ผ ์ถ๊ฐ ์ค...") | |
| cursor.execute("ALTER TABLE chat_session ADD COLUMN answer_model VARCHAR(100)") | |
| conn.commit() | |
| logger.info("chat_session.answer_model ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ") | |
| conn.close() | |
| logger.info("๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์๋ฃ") | |
| except sqlite3.Error as e: | |
| logger.error(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์ค SQLite ์ค๋ฅ ๋ฐ์: {e}", exc_info=True) | |
| except Exception as e: | |
| logger.error(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ์ค ์ค๋ฅ ๋ฐ์: {e}", exc_info=True) | |
| def create_admin_user() -> None: | |
| """ | |
| ์ด๊ธฐ ๊ด๋ฆฌ์ ๊ณ์ ์์ฑ | |
| """ | |
| admin_username = 'soymedia' | |
| admin_password = 's0ymedi@1@34' | |
| try: | |
| admin = User.query.filter_by(username=admin_username).first() | |
| if not admin: | |
| admin = User(username=admin_username, is_admin=True, is_active=True) | |
| admin.set_password(admin_password) | |
| db.session.add(admin) | |
| db.session.commit() | |
| logger.info(f'๊ด๋ฆฌ์ ๊ณ์ ์ด ์์ฑ๋์์ต๋๋ค: {admin_username}') | |
| else: | |
| logger.debug(f'๊ด๋ฆฌ์ ๊ณ์ ์ด ์ด๋ฏธ ์กด์ฌํฉ๋๋ค: {admin_username}') | |
| except Exception as e: | |
| logger.error(f'๊ด๋ฆฌ์ ๊ณ์ ์์ฑ ์ค ์ค๋ฅ ๋ฐ์: {e}', exc_info=True) | |
| db.session.rollback() | |