|
|
"""
|
|
|
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 sqlalchemy import create_engine, text
|
|
|
|
|
|
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'
|
|
|
|
|
|
@login_manager.unauthorized_handler
|
|
|
def unauthorized():
|
|
|
"""์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์ ์ฒ๋ฆฌ"""
|
|
|
from flask import redirect, url_for, request
|
|
|
|
|
|
if request.path.startswith('/api/'):
|
|
|
from flask import jsonify
|
|
|
return jsonify({'error': '๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.'}), 401
|
|
|
|
|
|
return redirect(url_for('main.login', next=request.path))
|
|
|
|
|
|
|
|
|
@login_manager.user_loader
|
|
|
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()
|
|
|
|
|
|
|
|
|
template_folder = str(config.TEMPLATES_FOLDER)
|
|
|
static_folder = str(config.STATIC_FOLDER)
|
|
|
|
|
|
app = Flask(__name__, template_folder=template_folder, static_folder=static_folder)
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
|
|
'pool_pre_ping': True,
|
|
|
'pool_recycle': 300
|
|
|
}
|
|
|
|
|
|
|
|
|
app.config['SESSION_COOKIE_SECURE'] = True
|
|
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
|
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
|
|
app.config['PERMANENT_SESSION_LIFETIME'] = 86400
|
|
|
|
|
|
|
|
|
db.init_app(app)
|
|
|
login_manager.init_app(app)
|
|
|
|
|
|
|
|
|
from app.routes import main_bp
|
|
|
app.register_blueprint(main_bp)
|
|
|
|
|
|
|
|
|
with app.app_context():
|
|
|
logger.info(f"๋ฑ๋ก๋ ๋ผ์ฐํธ ์: {len([r for r in app.url_map.iter_rules()])}")
|
|
|
logger.info(f"๋ฑ๋ก๋ Blueprint: {list(app.blueprints.keys())}")
|
|
|
|
|
|
routes = [str(r) for r in app.url_map.iter_rules() if r.endpoint.startswith('main.')]
|
|
|
logger.info(f"๋ฑ๋ก๋ main ๋ผ์ฐํธ: {routes[:10]}...")
|
|
|
|
|
|
|
|
|
@app.route('/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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.before_request
|
|
|
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)}")
|
|
|
|
|
|
@app.after_request
|
|
|
def log_response_info(response):
|
|
|
"""๊ฐ HTTP ์๋ต ์ ๋ณด๋ฅผ ๋ก๊น
"""
|
|
|
logger.info(f"[์๋ต] {request.method} {request.path} - ์ํ: {response.status_code}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
permissions_policy = (
|
|
|
"camera=(), "
|
|
|
"microphone=(), "
|
|
|
"geolocation=(), "
|
|
|
"payment=(), "
|
|
|
"usb=()"
|
|
|
)
|
|
|
response.headers['Permissions-Policy'] = permissions_policy
|
|
|
|
|
|
|
|
|
|
|
|
if request.path == '/' or request.path.startswith('/login') or request.path.startswith('/admin') or request.path.startswith('/webnovels'):
|
|
|
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
|
|
response.headers['Pragma'] = 'no-cache'
|
|
|
response.headers['Expires'] = '0'
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
with app.app_context():
|
|
|
|
|
|
db_uri = app.config['SQLALCHEMY_DATABASE_URI']
|
|
|
if db_uri.startswith('postgresql://') or db_uri.startswith('postgres://'):
|
|
|
|
|
|
masked_uri = db_uri.split('@')[0].split('://')[0] + '://***@' + '@'.join(db_uri.split('@')[1:]) if '@' in db_uri else db_uri
|
|
|
logger.info(f"[๋ฐ์ดํฐ๋ฒ ์ด์ค] PostgreSQL ์ฐ๊ฒฐ ์๋: {masked_uri}")
|
|
|
|
|
|
try:
|
|
|
engine = create_engine(
|
|
|
db_uri,
|
|
|
pool_pre_ping=True,
|
|
|
pool_recycle=300
|
|
|
)
|
|
|
with engine.connect() as conn:
|
|
|
result = conn.execute(text("SELECT version()"))
|
|
|
version = result.fetchone()[0]
|
|
|
logger.info(f"[๋ฐ์ดํฐ๋ฒ ์ด์ค] PostgreSQL ์ฐ๊ฒฐ ์ฑ๊ณต! ๋ฒ์ : {version[:50]}...")
|
|
|
except Exception as e:
|
|
|
logger.error(f"[๋ฐ์ดํฐ๋ฒ ์ด์ค] PostgreSQL ์ฐ๊ฒฐ ์คํจ: {str(e)}")
|
|
|
logger.warning("[๋ฐ์ดํฐ๋ฒ ์ด์ค] SQLite๋ก ํด๋ฐฑํฉ๋๋ค.")
|
|
|
|
|
|
db_uri = f'sqlite:///{config.INSTANCE_FOLDER / "finance_analysis.db"}'
|
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = db_uri
|
|
|
else:
|
|
|
logger.info(f"[๋ฐ์ดํฐ๋ฒ ์ด์ค] SQLite ์ฌ์ฉ: {db_uri}")
|
|
|
|
|
|
|
|
|
try:
|
|
|
db.create_all()
|
|
|
logger.info("[๋ฐ์ดํฐ๋ฒ ์ด์ค] ํ
์ด๋ธ ์์ฑ ์๋ฃ")
|
|
|
except Exception as e:
|
|
|
logger.error(f"[๋ฐ์ดํฐ๋ฒ ์ด์ค] ํ
์ด๋ธ ์์ฑ ์คํจ: {str(e)}")
|
|
|
raise
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
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 ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ")
|
|
|
|
|
|
|
|
|
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 ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ")
|
|
|
|
|
|
|
|
|
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 ์ปฌ๋ผ ์ถ๊ฐ ์๋ฃ")
|
|
|
|
|
|
|
|
|
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()
|
|
|
|