288 lines
12 KiB
Python
288 lines
12 KiB
Python
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
|
|
import os
|
|
import logging
|
|
from functools import wraps
|
|
from datetime import datetime
|
|
from utils.word_processor import WordProcessor
|
|
from utils.google_drive import GoogleDriveUploader
|
|
from config import Config
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
|
|
# 세션 설정
|
|
app.secret_key = app.config['SECRET_KEY']
|
|
|
|
# 인증 데코레이터
|
|
def require_auth(f):
|
|
@wraps(f)
|
|
def decorated_function(*args, **kwargs):
|
|
if not session.get('authenticated', False):
|
|
return redirect(url_for('auth'))
|
|
return f(*args, **kwargs)
|
|
return decorated_function
|
|
|
|
# 전역 객체 초기화
|
|
word_processor = WordProcessor(app.config['WORD_TEMPLATE_DIR'])
|
|
|
|
# 구글 드라이브 업로더 초기화 (선택사항)
|
|
drive_uploader = None
|
|
try:
|
|
# credentials.json 파일이 있는 경우에만 초기화 시도
|
|
if os.path.exists(app.config['GOOGLE_DRIVE_CREDENTIALS_FILE']):
|
|
logger.info(
|
|
"구글 드라이브 업로더 초기화 시도 | credentials=%s token=%s",
|
|
app.config['GOOGLE_DRIVE_CREDENTIALS_FILE'],
|
|
app.config['GOOGLE_DRIVE_TOKEN_FILE'],
|
|
)
|
|
drive_uploader = GoogleDriveUploader(
|
|
app.config['GOOGLE_DRIVE_CREDENTIALS_FILE'],
|
|
app.config['GOOGLE_DRIVE_TOKEN_FILE']
|
|
)
|
|
logger.info("구글 드라이브 업로더 초기화 성공")
|
|
else:
|
|
logger.info("credentials.json이 없어 구글 드라이브 업로더를 초기화하지 않았습니다.")
|
|
except FileNotFoundError:
|
|
logger.warning("구글 드라이브 인증 파일을 찾을 수 없습니다. 업로드 기능 비활성화.")
|
|
except Exception as e:
|
|
# 브라우저 관련 오류나 기타 오류는 조용히 처리
|
|
# 서버 환경에서는 브라우저가 없을 수 있으므로 정상적인 상황
|
|
logger.warning("구글 드라이브 초기화 중 오류 발생: %s", str(e))
|
|
|
|
@app.route('/auth', methods=['GET', 'POST'])
|
|
def auth():
|
|
"""인증 페이지"""
|
|
if request.method == 'POST':
|
|
access_key = request.form.get('access_key', '')
|
|
if access_key == app.config['ACCESS_KEY']:
|
|
session['authenticated'] = True
|
|
return redirect(url_for('index'))
|
|
else:
|
|
return render_template('auth.html', error='인증키가 올바르지 않습니다.')
|
|
return render_template('auth.html')
|
|
|
|
@app.route('/logout')
|
|
def logout():
|
|
"""로그아웃 - 세션 전체 삭제"""
|
|
session.clear()
|
|
return redirect(url_for('auth'))
|
|
|
|
@app.route('/')
|
|
@require_auth
|
|
def index():
|
|
"""메인 페이지"""
|
|
return render_template('index.html')
|
|
|
|
@app.route('/step1', methods=['GET', 'POST'])
|
|
@require_auth
|
|
def step1():
|
|
"""1단계: 기본 정보 입력"""
|
|
if request.method == 'POST':
|
|
data = request.get_json()
|
|
session['basic_info'] = data
|
|
return jsonify({'success': True, 'next_step': '/step2'})
|
|
basic_info = session.get('basic_info', {})
|
|
return render_template('step1.html',
|
|
districts=app.config['DISTRICTS'],
|
|
cults=app.config['CULTS'],
|
|
basic_info=basic_info)
|
|
|
|
@app.route('/step2', methods=['GET', 'POST'])
|
|
@require_auth
|
|
def step2():
|
|
"""2단계: 기독교대한감리회 신앙고백 교리 점검"""
|
|
if request.method == 'POST':
|
|
data = request.get_json()
|
|
session['methodist_doctrine'] = data
|
|
return jsonify({'success': True, 'next_step': '/step3'})
|
|
methodist_doctrine = session.get('methodist_doctrine', {})
|
|
return render_template('step2.html', methodist_doctrine=methodist_doctrine)
|
|
|
|
@app.route('/step3', methods=['GET', 'POST'])
|
|
@require_auth
|
|
def step3():
|
|
"""3단계: 이단 일반 교리 점검"""
|
|
if request.method == 'POST':
|
|
data = request.get_json()
|
|
|
|
# 7번 문항 검증 (기타의견란에 작성 필요)
|
|
other_opinions = data.get('other_opinions', '').strip()
|
|
if not other_opinions or '7번:' not in other_opinions:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '7번 문항은 기타의견란에 "7번: "으로 시작하여 답변해주세요.'
|
|
}), 400
|
|
|
|
session['general_cult_doctrine'] = data
|
|
|
|
# 기본 정보에서 이단교단 확인
|
|
basic_info = session.get('basic_info', {})
|
|
cult_name = basic_info.get('cult', '')
|
|
|
|
# "기타" 선택 시 step4 건너뛰고 step5로
|
|
if cult_name == '기타 (위 선택지에 없을 경우)':
|
|
session['specific_cult_doctrine'] = {} # 빈 딕셔너리로 설정
|
|
return jsonify({'success': True, 'next_step': '/step5'})
|
|
|
|
return jsonify({'success': True, 'next_step': '/step4'})
|
|
general_cult_doctrine = session.get('general_cult_doctrine', {})
|
|
return render_template('step3.html', general_cult_doctrine=general_cult_doctrine)
|
|
|
|
@app.route('/step4', methods=['GET', 'POST'])
|
|
@require_auth
|
|
def step4():
|
|
"""4단계: 출신 이단별 교리 점검"""
|
|
if request.method == 'POST':
|
|
data = request.get_json()
|
|
|
|
# 세션에서 출신 이단 정보 가져오기
|
|
basic_info = session.get('basic_info', {})
|
|
cult_name = basic_info.get('cult', '')
|
|
|
|
# 이단별 상세점검 문항 가져오기
|
|
questions = app.config.get('CULT_DETAIL_QUESTIONS', {}).get(cult_name, [])
|
|
|
|
# 기타의견란에 작성해야 하는 문항 번호 찾기
|
|
required_question_numbers = []
|
|
for i, question in enumerate(questions, 1):
|
|
if '기타의견란' in question or '기타의견' in question:
|
|
required_question_numbers.append(i)
|
|
|
|
# 기타의견란 검증
|
|
if required_question_numbers:
|
|
other_opinions = data.get('other_opinions', '').strip()
|
|
if not other_opinions:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'{", ".join([str(n) + "번" for n in required_question_numbers])} 문항은 기타의견란에 작성해주세요.'
|
|
}), 400
|
|
|
|
# 각 필수 문항 번호가 기타의견란에 포함되어 있는지 확인
|
|
missing_questions = []
|
|
for q_num in required_question_numbers:
|
|
if f'{q_num}번:' not in other_opinions:
|
|
missing_questions.append(q_num)
|
|
|
|
if missing_questions:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'{", ".join([str(n) + "번" for n in missing_questions])} 문항은 기타의견란에 "{", ".join([str(n) + "번:" for n in missing_questions])}"으로 시작하여 답변해주세요.'
|
|
}), 400
|
|
|
|
session['specific_cult_doctrine'] = data
|
|
return jsonify({'success': True, 'next_step': '/step5'})
|
|
|
|
# 세션에서 출신 이단 정보 가져오기
|
|
basic_info = session.get('basic_info', {})
|
|
cult_name = basic_info.get('cult', '')
|
|
|
|
# 이단별 상세점검 문항 가져오기
|
|
questions = app.config.get('CULT_DETAIL_QUESTIONS', {}).get(cult_name, [])
|
|
|
|
# 문항이 없으면 step5로 리다이렉트 (안전장치)
|
|
if not questions:
|
|
return redirect(url_for('step5'))
|
|
|
|
specific_cult_doctrine = session.get('specific_cult_doctrine', {})
|
|
return render_template('step4.html', cult_name=cult_name, questions=questions, specific_cult_doctrine=specific_cult_doctrine)
|
|
|
|
@app.route('/step5', methods=['GET', 'POST'])
|
|
@require_auth
|
|
def step5():
|
|
"""5단계: 간증문 입력"""
|
|
if request.method == 'GET':
|
|
testimony = session.get('testimony', {})
|
|
return render_template('step5.html', testimony=testimony)
|
|
|
|
if request.method == 'POST':
|
|
data = request.get_json()
|
|
session['testimony'] = data
|
|
|
|
# 모든 데이터 수집 (specific_cult_doctrine는 없을 수 있음)
|
|
all_data = {
|
|
'basic_info': session.get('basic_info', {}),
|
|
'methodist_doctrine': session.get('methodist_doctrine', {}),
|
|
'general_cult_doctrine': session.get('general_cult_doctrine', {}),
|
|
'specific_cult_doctrine': session.get('specific_cult_doctrine', {}),
|
|
'testimony': session.get('testimony', {})
|
|
}
|
|
|
|
# specific_cult_doctrine가 없으면 빈 딕셔너리로 설정
|
|
if not all_data.get('specific_cult_doctrine'):
|
|
all_data['specific_cult_doctrine'] = {}
|
|
|
|
# 워드 문서 생성
|
|
try:
|
|
cult_name = all_data['basic_info'].get('cult', '기타')
|
|
# 이단별 상세점검 문항 가져오기
|
|
cult_questions = app.config.get('CULT_DETAIL_QUESTIONS', {}).get(cult_name, [])
|
|
output_path = word_processor.generate_document(all_data, cult_name, cult_questions)
|
|
|
|
# 구글 드라이브 업로드
|
|
upload_success = False
|
|
if drive_uploader and app.config['GOOGLE_DRIVE_FOLDER_ID']:
|
|
upload_filename = f"교리점검표_{all_data['basic_info'].get('name', '무명')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
|
logger.info("구글 드라이브 업로드 시도 | file=%s, folder=%s", upload_filename, app.config['GOOGLE_DRIVE_FOLDER_ID'])
|
|
try:
|
|
file_id = drive_uploader.upload_file(
|
|
output_path,
|
|
upload_filename,
|
|
app.config['GOOGLE_DRIVE_FOLDER_ID']
|
|
)
|
|
logger.info("구글 드라이브 업로드 성공 | file_id=%s", file_id)
|
|
upload_success = True
|
|
# 업로드 성공 시 로컬 파일 삭제
|
|
try:
|
|
os.remove(output_path)
|
|
logger.info("로컬 파일 삭제 완료 | file=%s", output_path)
|
|
except Exception as e:
|
|
logger.warning("로컬 파일 삭제 실패: %s", str(e))
|
|
|
|
# 세션은 complete 페이지에서 로그아웃 시 삭제
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '제출이 완료되었습니다. 구글 드라이브에 업로드되었습니다.',
|
|
'file_id': file_id
|
|
})
|
|
except Exception as e:
|
|
logger.warning("구글 드라이브 업로드 실패: %s", str(e))
|
|
# 업로드 실패 시 로컬 파일 유지
|
|
# 세션은 유지 (재시도 가능하도록)
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'제출이 완료되었습니다. (구글 드라이브 업로드 실패: {str(e)})',
|
|
'file_path': output_path
|
|
})
|
|
else:
|
|
# 구글 드라이브 설정이 없는 경우 로컬 파일 유지
|
|
# 세션은 유지
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'제출이 완료되었습니다. 파일: {output_path}',
|
|
'file_path': output_path
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'오류가 발생했습니다: {str(e)}'
|
|
}), 500
|
|
|
|
return render_template('step5.html')
|
|
|
|
@app.route('/complete')
|
|
@require_auth
|
|
def complete():
|
|
"""완료 페이지"""
|
|
return render_template('complete.html')
|
|
|
|
if __name__ == '__main__':
|
|
# 필요한 디렉토리 생성
|
|
os.makedirs(app.config['WORD_TEMPLATE_DIR'], exist_ok=True)
|
|
os.makedirs('output', exist_ok=True)
|
|
|
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
|
|