좋은 지적입니다! 현재 구조에서는 새 앱을 추가할 때마다 app.py를 수동으로 수정해야 하므로 관리자 모드의 의미가 없죠.
동적 블루프린트 로딩으로 해결하겠습니다:
import os
import json
import importlib
from flask import Flask, render_template, session, redirect, url_for, flash
from config import Config
from auth import auth_bp, login_required
from admin.admin import admin_bp, load_settings, scan_services, init_context_processor
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
app.config.from_object(Config)
# Nginx 프록시 설정
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
# 세션 보안 설정
app.config.update(
SESSION_COOKIE_SECURE=False,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
PERMANENT_SESSION_LIFETIME=86400
)
# 기본 Blueprint 등록
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(admin_bp, url_prefix='/admin')
# ===== 동적 서비스 블루프린트 로딩 =====
def load_service_blueprints():
"""services 폴더의 모든 서비스를 자동으로 스캔하고 블루프린트 등록"""
services_dir = os.path.join(os.path.dirname(__file__), 'services')
if not os.path.exists(services_dir):
print("⚠️ services 폴더가 없습니다.")
return
loaded_count = 0
for item in os.listdir(services_dir):
item_path = os.path.join(services_dir, item)
# __pycache__, admin 등 제외
if item.startswith('__') or item == 'admin' or not os.path.isdir(item_path):
continue
# 서비스 모듈 파일 확인
module_file = os.path.join(item_path, f'{item}.py')
if not os.path.exists(module_file):
continue
try:
# 동적 import: services.calc.calc 형태로 모듈 로드
module_path = f'services.{item}.{item}'
module = importlib.import_module(module_path)
# 블루프린트 찾기 (관례: {service_name}_bp)
bp_name = f'{item}_bp'
if hasattr(module, bp_name):
blueprint = getattr(module, bp_name)
app.register_blueprint(blueprint, url_prefix=f'/{item}')
loaded_count += 1
print(f"✅ 블루프린트 등록: {item} -> /{item}")
else:
print(f"⚠️ {item}에 '{bp_name}' 블루프린트가 없습니다.")
except Exception as e:
print(f"❌ {item} 로딩 실패: {e}")
print(f"\n📦 총 {loaded_count}개의 서비스 블루프린트가 로드되었습니다.\n")
# 서비스 블루프린트 자동 로딩 실행
load_service_blueprints()
# Admin Context Processor 초기화
init_context_processor(app)
# 전역 컨텍스트 프로세서
@app.context_processor
def inject_global_data():
"""사용자 정보와 기타 전역 데이터 주입"""
user_data = session.get('user')
if user_data:
user_email = user_data.get('email')
user_data['is_admin'] = Config.is_admin(user_email)
settings = load_settings()
login_bg_url = settings.get('login_background_image', '')
return dict(
user=user_data,
login_bg_image=login_bg_url
)
@app.route('/')
@login_required
def index():
return render_template('index.html')
# 에러 핸들러
@app.errorhandler(404)
def not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(e):
return render_template('500.html'), 500
if __name__ == '__main__':
app.run(host=Config.APP_HOST, port=Config.APP_PORT, debug=(Config.FLASK_ENV == 'development'))
서비스 구조 규칙:
각 서비스는 다음 구조를 따라야 합니다:
services/
├── calc/
│ └── calc.py # calc_bp 정의
├── converter/
│ └── converter.py # converter_bp 정의
├── timer/
│ └── timer.py # timer_bp 정의
└── textcount/
└── textcount.py # textcount_bp 정의
서비스 파일 예시 (services/calc/calc.py):
from flask import Blueprint, render_template
# 블루프린트 이름 규칙: {폴더명}_bp
calc_bp = Blueprint('calc', __name__, template_folder='templates')
UTIL_INFO = {
'name': '계산기',
'icon': '🔢'
}
@calc_bp.route('/')
def index():
return render_template('calc.html')
이제 새 서비스를 추가하려면:
services/newapp/ 폴더 생성services/newapp/newapp.py 파일 생성newapp_bp 블루프린트 정의UTIL_INFO 추가완전한 비활성화가 필요하다면:
def load_service_blueprints():
"""settings.json의 active_services만 로드"""
settings = load_settings()
active_services = settings.get('active_services', [])
services_dir = os.path.join(os.path.dirname(__file__), 'services')
for item in os.listdir(services_dir):
if item not in active_services: # active_services에 없으면 스킵
continue
# ... 나머지 로딩 로직