first commit
This commit is contained in:
170
README.md
Normal file
170
README.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# 이단 탈퇴자 교리점검표 웹서비스
|
||||||
|
|
||||||
|
기독교대한감리회 신앙고백에 따른 이단 탈퇴자를 위한 교리점검표 설문 폼 서비스입니다.
|
||||||
|
|
||||||
|
## 주요 기능
|
||||||
|
|
||||||
|
1. **설문응답자 기본 정보 입력** - 이름, 생년월일, 연락처, 출신 이단, 재적 기간
|
||||||
|
2. **기독교대한감리회 신앙고백 교리 점검** - 삼위일체, 예수 그리스도, 성경, 구원 등
|
||||||
|
3. **이단 일반 교리 점검** - 추가 계시, 지도자 신격화, 구원관, 세계관
|
||||||
|
4. **출신 이단별 교리 점검** - 각 이단별 특정 교리 점검
|
||||||
|
5. **간증문 입력** - 탈퇴 과정과 소감 작성
|
||||||
|
6. **워드 문서 자동 생성** - 응답 내용을 워드 템플릿에 자동 입력
|
||||||
|
7. **구글 드라이브 자동 업로드** - 생성된 문서를 구글 드라이브에 업로드
|
||||||
|
|
||||||
|
## 설치 방법
|
||||||
|
|
||||||
|
### 1. 의존성 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 환경 변수 설정
|
||||||
|
|
||||||
|
`.env` 파일을 생성하고 다음 내용을 입력하세요:
|
||||||
|
|
||||||
|
```env
|
||||||
|
SECRET_KEY=your-secret-key-here
|
||||||
|
ACCESS_KEY=your-access-key-here
|
||||||
|
GOOGLE_DRIVE_CREDENTIALS_FILE=credentials.json
|
||||||
|
GOOGLE_DRIVE_TOKEN_FILE=token.json
|
||||||
|
GOOGLE_DRIVE_FOLDER_ID=your-google-drive-folder-id
|
||||||
|
```
|
||||||
|
|
||||||
|
**인증키 설정:**
|
||||||
|
- `ACCESS_KEY`: 서비스 접근을 위한 인증키입니다.
|
||||||
|
- 기본값: `Kx9mP2vQ7nR4tY8wZ3bC6hJ1fL5dN0sA8uE2iM7o` (프로덕션에서는 반드시 변경하세요)
|
||||||
|
- 사용자는 이 인증키를 입력해야만 설문에 접근할 수 있습니다.
|
||||||
|
- `.env` 파일에서 `ACCESS_KEY`를 설정하면 기본값을 덮어씁니다.
|
||||||
|
|
||||||
|
### 3. 구글 드라이브 API 설정
|
||||||
|
|
||||||
|
1. [Google Cloud Console](https://console.cloud.google.com/)에서 프로젝트 생성
|
||||||
|
2. Google Drive API 활성화
|
||||||
|
3. OAuth 2.0 클라이언트 ID 생성 (데스크톱 애플리케이션)
|
||||||
|
4. `credentials.json` 파일을 프로젝트 루트에 저장
|
||||||
|
5. 구글 드라이브에 업로드할 폴더를 생성하고 폴더 ID를 `.env`에 설정
|
||||||
|
|
||||||
|
### 4. 워드 템플릿 준비 (선택사항)
|
||||||
|
|
||||||
|
`word_templates/` 디렉토리에 각 이단별 템플릿 파일을 준비할 수 있습니다:
|
||||||
|
- `신천지_template.docx`
|
||||||
|
- `구원파_template.docx`
|
||||||
|
- `하나님의교회_template.docx`
|
||||||
|
- `통일교_template.docx`
|
||||||
|
- `기타_template.docx`
|
||||||
|
- `default_template.docx` (기본 템플릿)
|
||||||
|
|
||||||
|
템플릿 파일이 없으면 자동으로 기본 템플릿이 생성됩니다.
|
||||||
|
|
||||||
|
## 실행 방법
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
서버가 실행되면 브라우저에서 `http://localhost:5000`으로 접속하세요.
|
||||||
|
|
||||||
|
## 프로젝트 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── app.py # Flask 메인 애플리케이션
|
||||||
|
├── config.py # 설정 파일
|
||||||
|
├── requirements.txt # Python 의존성
|
||||||
|
├── .env # 환경 변수 (생성 필요)
|
||||||
|
├── credentials.json # 구글 드라이브 인증 파일 (생성 필요)
|
||||||
|
├── token.json # 구글 드라이브 토큰 (자동 생성)
|
||||||
|
├── templates/ # HTML 템플릿
|
||||||
|
│ ├── base.html
|
||||||
|
│ ├── step1.html
|
||||||
|
│ ├── step2.html
|
||||||
|
│ ├── step3.html
|
||||||
|
│ ├── step4.html
|
||||||
|
│ ├── step5.html
|
||||||
|
│ └── complete.html
|
||||||
|
├── static/ # 정적 파일
|
||||||
|
│ ├── style.css
|
||||||
|
│ └── script.js
|
||||||
|
├── word_templates/ # 워드 템플릿 파일
|
||||||
|
├── output/ # 생성된 문서 저장 폴더 (자동 생성)
|
||||||
|
└── utils/ # 유틸리티 모듈
|
||||||
|
├── word_processor.py
|
||||||
|
└── google_drive.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 리눅스 서버 배포
|
||||||
|
|
||||||
|
### 1. Gunicorn 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install gunicorn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Gunicorn으로 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gunicorn -w 4 -b 0.0.0.0:5000 app:app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. systemd 서비스로 등록 (선택사항)
|
||||||
|
|
||||||
|
`/etc/systemd/system/doctrine-check.service` 파일 생성:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Doctrine Check Web Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=your-user
|
||||||
|
WorkingDirectory=/path/to/doctrine-check
|
||||||
|
Environment="PATH=/path/to/venv/bin"
|
||||||
|
ExecStart=/path/to/venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 app:app
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
서비스 시작:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start doctrine-check
|
||||||
|
sudo systemctl enable doctrine-check
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Nginx 리버스 프록시 설정 (선택사항)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:5000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 주의사항
|
||||||
|
|
||||||
|
- 프로덕션 환경에서는 `SECRET_KEY`와 `ACCESS_KEY`를 안전하게 설정하세요
|
||||||
|
- 인증키는 링크를 받은 사람에게만 전달하세요
|
||||||
|
- 구글 드라이브 API 인증 정보는 절대 공개하지 마세요
|
||||||
|
- `output/` 디렉토리의 파일은 정기적으로 정리하세요
|
||||||
|
- HTTPS를 사용하여 데이터 보안을 강화하세요
|
||||||
|
|
||||||
|
## 인증키 관리
|
||||||
|
|
||||||
|
- 기본 인증키: `Kx9mP2vQ7nR4tY8wZ3bC6hJ1fL5dN0sA8uE2iM7o`
|
||||||
|
- 인증키는 `.env` 파일의 `ACCESS_KEY`로 변경할 수 있습니다
|
||||||
|
- 사용자는 서비스 접속 시 인증 페이지에서 인증키를 입력해야 합니다
|
||||||
|
- 인증 후 세션에 저장되며, 로그아웃 시 인증이 해제됩니다
|
||||||
|
|
||||||
|
## 라이선스
|
||||||
|
|
||||||
|
이 프로젝트는 기독교대한감리회를 위한 내부 사용 목적으로 제작되었습니다.
|
||||||
|
|
||||||
202
app.py
Normal file
202
app.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
|
||||||
|
import os
|
||||||
|
from functools import wraps
|
||||||
|
from datetime import datetime
|
||||||
|
from utils.word_processor import WordProcessor
|
||||||
|
from utils.google_drive import GoogleDriveUploader
|
||||||
|
from config import Config
|
||||||
|
|
||||||
|
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:
|
||||||
|
drive_uploader = GoogleDriveUploader(
|
||||||
|
app.config['GOOGLE_DRIVE_CREDENTIALS_FILE'],
|
||||||
|
app.config['GOOGLE_DRIVE_TOKEN_FILE']
|
||||||
|
)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(f"경고: 구글 드라이브 인증 파일을 찾을 수 없습니다. 구글 드라이브 업로드 기능이 비활성화됩니다.")
|
||||||
|
print(f"상세: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"경고: 구글 드라이브 초기화 중 오류가 발생했습니다: {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.pop('authenticated', None)
|
||||||
|
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'})
|
||||||
|
return render_template('step1.html',
|
||||||
|
districts=app.config['DISTRICTS'],
|
||||||
|
cults=app.config['CULTS'])
|
||||||
|
|
||||||
|
@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'})
|
||||||
|
return render_template('step2.html')
|
||||||
|
|
||||||
|
@app.route('/step3', methods=['GET', 'POST'])
|
||||||
|
@require_auth
|
||||||
|
def step3():
|
||||||
|
"""3단계: 이단 일반 교리 점검"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
data = request.get_json()
|
||||||
|
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'})
|
||||||
|
return render_template('step3.html')
|
||||||
|
|
||||||
|
@app.route('/step4', methods=['GET', 'POST'])
|
||||||
|
@require_auth
|
||||||
|
def step4():
|
||||||
|
"""4단계: 출신 이단별 교리 점검"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
data = request.get_json()
|
||||||
|
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'))
|
||||||
|
|
||||||
|
return render_template('step4.html', cult_name=cult_name, questions=questions)
|
||||||
|
|
||||||
|
@app.route('/step5', methods=['GET', 'POST'])
|
||||||
|
@require_auth
|
||||||
|
def step5():
|
||||||
|
"""5단계: 간증문 입력"""
|
||||||
|
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', '기타')
|
||||||
|
output_path = word_processor.generate_document(all_data, cult_name)
|
||||||
|
|
||||||
|
# 구글 드라이브 업로드
|
||||||
|
if drive_uploader and app.config['GOOGLE_DRIVE_FOLDER_ID']:
|
||||||
|
try:
|
||||||
|
file_id = drive_uploader.upload_file(
|
||||||
|
output_path,
|
||||||
|
f"교리점검표_{all_data['basic_info'].get('name', '무명')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx",
|
||||||
|
app.config['GOOGLE_DRIVE_FOLDER_ID']
|
||||||
|
)
|
||||||
|
# 업로드 후 로컬 파일 삭제 (선택사항)
|
||||||
|
# os.remove(output_path)
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': '제출이 완료되었습니다. 구글 드라이브에 업로드되었습니다.',
|
||||||
|
'file_id': file_id
|
||||||
|
})
|
||||||
|
except Exception as 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)
|
||||||
|
|
||||||
204
config.py
Normal file
204
config.py
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
|
||||||
|
|
||||||
|
# 접근 인증키 (링크를 받은 사람만 사용 가능)
|
||||||
|
ACCESS_KEY = os.environ.get('ACCESS_KEY') or 'Kx9mP2vQ7nR4tY8wZ3bC6hJ1fL5dN0sA8uE2iM7o'
|
||||||
|
|
||||||
|
# Google Drive API 설정
|
||||||
|
GOOGLE_DRIVE_CREDENTIALS_FILE = os.environ.get('GOOGLE_DRIVE_CREDENTIALS_FILE', 'credentials.json')
|
||||||
|
GOOGLE_DRIVE_TOKEN_FILE = os.environ.get('GOOGLE_DRIVE_TOKEN_FILE', 'token.json')
|
||||||
|
GOOGLE_DRIVE_FOLDER_ID = os.environ.get('GOOGLE_DRIVE_FOLDER_ID', '')
|
||||||
|
|
||||||
|
# 워드 템플릿 경로
|
||||||
|
WORD_TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'word_templates')
|
||||||
|
|
||||||
|
# 교구 목록
|
||||||
|
DISTRICTS = [
|
||||||
|
'성남교구',
|
||||||
|
'서울교구',
|
||||||
|
'경기남교구',
|
||||||
|
'경기북교구',
|
||||||
|
'송파교구',
|
||||||
|
'위례교구',
|
||||||
|
'바울교구',
|
||||||
|
'다윗공동체',
|
||||||
|
'요셉1교구',
|
||||||
|
'요셉2교구',
|
||||||
|
'요셉3교구',
|
||||||
|
'온라인교회',
|
||||||
|
'교회학교',
|
||||||
|
'젊은이교회',
|
||||||
|
'갈렙교회',
|
||||||
|
'기타'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 이단교단 목록
|
||||||
|
CULTS = [
|
||||||
|
'신천지',
|
||||||
|
'하나님의교회',
|
||||||
|
'다락방',
|
||||||
|
'구원파 (박옥수,이요한,유병언)',
|
||||||
|
'JMS',
|
||||||
|
'성락교회 (베뢰아)',
|
||||||
|
'통일교',
|
||||||
|
'몰몬교',
|
||||||
|
'안식교',
|
||||||
|
'사랑하는교회',
|
||||||
|
'예수중심교회 (이초석)',
|
||||||
|
'기타 (위 선택지에 없을 경우)'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 이단별 상세점검 문항 (기타는 제외)
|
||||||
|
CULT_DETAIL_QUESTIONS = {
|
||||||
|
'신천지': [
|
||||||
|
'1. 성경 내용을 \'역사, 교훈, 예언, 성취\'로 구분해 가르쳤다.',
|
||||||
|
'2. \'성경은 <계시록 시대>등 8개 시대로 구분되며 반드시 예언을 깨달아야 한다\'고 가르쳤다.',
|
||||||
|
'3. \'죄 사함은 예수를 믿고 비유를 깨달으며 새 언약을 지킬 때 가능하다\'고 가르쳤다.',
|
||||||
|
'4. \'사단이 성전에 앉아 하나님으로 가장해 신앙인들을 미혹한다\'고 말했다.',
|
||||||
|
'5. \'천국 비밀은 감춰져 있으며 비유로 된 계시의 말씀을 깨달아야 한다\'고 가르쳤다.',
|
||||||
|
'6. \'시대별 예언과 성취가 있으며, 일반 교회에서 봉함된 말씀을 계속 배우는 것으로는 구원받을 수 없다\'고 충고했다.',
|
||||||
|
'7. \'육적 이스라엘, 영적 이스라엘, 영적 새 이스라엘(영적 새 선민)\'에 대해 가르쳤다.',
|
||||||
|
'8. \'재림의 때 출현하는 약속의 목자, 이긴 자가 있다\'고 강조했다.',
|
||||||
|
'9. 성경공부를 시작한 뒤 주일설교가 잘 들리지 않고 목사님이 거짓 목자처럼 느껴진 적이 있다.',
|
||||||
|
'10. 성경공부 후 기성 교회가 \'바벨론 교회\'라는 느낌이 든 적이 있다.',
|
||||||
|
'11. 이만희교주의 육체 영생을 믿으십니까?',
|
||||||
|
'12. 비유풀이 성경해석이 옳다고 믿으십니까?',
|
||||||
|
'13. 성경에 짝이 있다고 생각하십니까?',
|
||||||
|
'14. \'모략\'을 사용하는 신천지가 잘못된 성경해석을 주장한다고 생각하십니까?',
|
||||||
|
'15. 144,000이 숫자 그대로의 144,000명을 의미한다고 믿으십니까?',
|
||||||
|
'16. 세례요한은 \'배도자\'라고 생각하십니까?',
|
||||||
|
'17. 성경에 의하면 예수 이후로 새 구도자를 보내신다는 언약이 있다고 생각하십니까?'
|
||||||
|
],
|
||||||
|
'하나님의교회': [
|
||||||
|
'1. 창세기1장27절에서 \'하나님이 자기 형상 곧 하나님의 형상대로 사람을 창조하시되 남자와 여자를 창조하시고\'를 근거로 하나님이\'아버지하나님\' 어머니하나님\'으로 계시다고 생각하십니까?',
|
||||||
|
'2. 물이 얼음이 되고 수증기가 되듯이 \'성부\'가 \'성자\'가되고 \'성자\'가 \'성령\'이 되는 것이 삼위일체에 대한 바른 설명이라고 생각하십니까?',
|
||||||
|
'3. 성령시대의 보혜사가 이땅에 사람의 몸으로 오셨다고 생각하십니까?',
|
||||||
|
'4. 갈라디아서 4장 26절 \'예루살렘은 자유자니 곧 우리 어머니라\' 이 구절에서 \'어머니\'는 \'여자하나님\'을 말하는 것이라 생각하십니까?',
|
||||||
|
'5. 계시록 22장 17절에서 \'성령과 신부가 말씀하시기를 오라 하시는도다\'에서 신부는 \'어머니 하나님\' 이라고 생각하십니까?',
|
||||||
|
'6. 유월절을 지켜야 구원을 받을 수 있다고 생각하십니까?',
|
||||||
|
'7. 지금도 안식일(토요일)을 지켜야한다고 생각하십니까?',
|
||||||
|
'8. 주일은 태양신 숭배사상의 유물이라고 생각하십니까?',
|
||||||
|
'9. 성탄절을 지키고 십자가를 세운 것이 우상숭배라고 생각하십니까?'
|
||||||
|
],
|
||||||
|
'다락방': [
|
||||||
|
'1. 이 땅에 인간의 몸을 입고 오신 하나님이 예수님이며, 죽으신 예수님이 부활하여 우리 안에 성령님으로 계신다고 생각하십니까?',
|
||||||
|
'2. 성경에는 마귀계시 및 성경 외의 계시가 있다고 생각하십니까?',
|
||||||
|
'3. 예수께서 십자가에서 고난 받으신 것은 사탄에게 우리의 모든 실패의 대가를 갚아버리는 것이라고 생각하십니까?',
|
||||||
|
'4. 예수는 마귀를 멸하러 오셨습니까?',
|
||||||
|
'5. 예수를 믿고 마귀를 쫓아내면 모든 문제가 해결된다고 생각하십니까?',
|
||||||
|
'6. 영접이란 \'하나님의 아들이 오신 것은 마귀 일을 멸하려 하심이라는 진리를 받아들이는 것\'이라고 믿으십니까?',
|
||||||
|
'7. 기존 교인들은 새로운 복음으로 예수를 다시 영접해야 한다고 생각하십니까?',
|
||||||
|
'8. 올바른 신자는 사탄을 결박하는 힘이 있고 기도할 때 천사가 움직인다고 생각하십니까?',
|
||||||
|
'9. 성도들에게 내주하는 성령과 예수의 성육신은 동일한 개념인가요?',
|
||||||
|
'10. 예수그리스도를 영접했다면 더 이상의 회개가 필요할까요?',
|
||||||
|
'11. 렘넌트 운동은 성도님의 신앙과 생활에 있어 어떤 의미였습니까?',
|
||||||
|
'12. 예수님이 십자가에서 죽으신 것이 사단과 어떤 상관이 있다고 보시나요?'
|
||||||
|
],
|
||||||
|
'구원파 (박옥수,이요한,유병언)': [
|
||||||
|
'1. 구원 받은 날짜를 정확히 알아야 진짜 구원 받았다고 생각하십니까?',
|
||||||
|
'2. 죄사함과 거듭남의 비밀을 \'깨달음\'으로 구원 받는다고 생각하십니까?',
|
||||||
|
'3. 예수 그리스도가 우리의 마음을 지배하시면 더 이상 우리 자신이 죄와 싸울 필요가 없다고 생각하십니까?',
|
||||||
|
'4. 예수의 십자가 사건으로 과거와 현재와 미래의 죄까지 다 사함 받았기 때문에 구원을 받은 후에 다시 회개할 필요가 없다고 생각하십니까?',
|
||||||
|
'5. 회개하는 자는 죄가 있다는 증거고, 죄가 있으면 구원 받지 못한다고 생각하십니까?',
|
||||||
|
'6. 고백으로 회개를 대신할 수 있다고 생각하십니까?',
|
||||||
|
'7. 예수 시대에는 기도가 필요했으나 지금은 기도가 필요 없다고 믿습니까?',
|
||||||
|
'8. 구원 받기 전의 기도는 하나님께서 받지 않으신다고 생각하십니까?',
|
||||||
|
'9. 새벽기도나 철야기도는 형식이므로 중요하지 않다고 보십니까?',
|
||||||
|
'10. 성도의 교제가 곧 기도이며 예배라고 생각하십니까?',
|
||||||
|
'11. 주일성수, 십일조 등 기존의 교회 제도는 율법의 산물이기에 지킬 필요가 없다고 생각하십니까?',
|
||||||
|
'12. 예배는 형식이 없으므로 축도와 신앙고백도 필요 없다고 생각하십니까?',
|
||||||
|
'13. 우리는 죽지 않고 살아 있을 때 주님이 오실 것이라고 믿습니까?'
|
||||||
|
],
|
||||||
|
'JMS': [
|
||||||
|
'1. 예수님은 인간이지만 성자의 영이 임한 메시아입니까?',
|
||||||
|
'2. 오늘 날 재림주가 온다면 초림때의 구세주처럼 육을 입은 존재입니까?',
|
||||||
|
'3. 예수님이 타고 오신다는 구름은 무엇을 의미합니까? (이 문항은 아래 기타의견란에 문항번호를 적은 후 서술 부탁 드립니다.)',
|
||||||
|
'4. 동방은 한국이며 그땅의 의인은 재림주입니까?',
|
||||||
|
'5. 성령은 모성체 격의 신입니까?',
|
||||||
|
'6. 선악과는 여성의 생식기(하와)를, 생명나무 열매는 남성(아담)의 생식기를 상징합니까?',
|
||||||
|
'7. <30개론>, <70개론>은 교주가 하나님께 직접 계시받은 진리라는 주장에 동의하십니까?',
|
||||||
|
'8. 성경은 비유와 상징이며 모두 짝이 있습니까?',
|
||||||
|
'9. 인류의 역사는 구약 시대(주인과 종의 시대), 신약 시대(아버지와 자녀의 시대), 성약 시대(신랑과 신부의 시대)등 삼시대로 나뉘어집니까?',
|
||||||
|
'10. 로뎀나무 아래에 있던 엘리야를 하나님이 먹여 살리신 사건, 여호수아서에서 왕벌 사건은 단지 비유입니까?',
|
||||||
|
'11. 해당 단체를 나왔기에 영이 총에 맞을까 두려우십니까?',
|
||||||
|
'12. 이성교제 자체가 죄입니까?',
|
||||||
|
'13. 각 시대마다 구원자들이 존재했습니까?',
|
||||||
|
'14. 때로는 해당 단체에 대해서 그리움과 후회가 남습니까?'
|
||||||
|
],
|
||||||
|
'성락교회 (베뢰아)': [
|
||||||
|
'1. 예수님이 이땅에 오신 목적과 사람을 창조한 이유가 마귀를 멸하기 위함이라고 생각하고 계십니까?',
|
||||||
|
'2. 창세기 1장의 사람과 창세기 2장의 사람(아담)이 다르다고 생각하고 계십니까?',
|
||||||
|
'3. 창세기2장에서 \'사람이 생령이 되었다\' 부분에서 \'생령\'은 영적인 존재라는 뜻이라고 생각하십니까?',
|
||||||
|
'4. 선과 악을 아는 나무의 실과를 먹은 아담의 죄와 하와의 죄가 다르다고 생각하십니까?',
|
||||||
|
'5. 사람의 죄의 종류가 원죄,본죄,자범죄로 나뉜다고 생각하고 계십니까?',
|
||||||
|
'6. 불신자의 사후존재가 귀신이라고 생각하십니까?',
|
||||||
|
'7. 삼위일체 하나님의 이름이 \'예수\'라고 생각하십니까?',
|
||||||
|
'8. 여호와의 이름은 누구의 이름이라고 생각하십니까? (이 문항은 아래 기타의견란에 문항번호를 적고 서술 부탁드립니다.)',
|
||||||
|
'9. 모든 질병이 귀신으로부터 온다고 생각하십니까?',
|
||||||
|
'10. 하나님은 우리의 심령에 거하며 귀신은 육체에 거한다고 생각하십니까?'
|
||||||
|
],
|
||||||
|
'통일교': [
|
||||||
|
'1. 사탄이며 뱀인 타락한 천사 루시엘과 하와 사이의 성적 관계가 영적 타락이며, 하와와 아담의 성적관계가 육적 타락이라고 생각하십니까?',
|
||||||
|
'2. 인간은 오직 재림주와의 피가름 법칙을 통해서만 육체',
|
||||||
|
'3. 제3의 아담이 사탄의 타락한 피를 회복시켜 온 인류의 참 부모가 되며, 세계의 왕이 되어 한국을 중심으로 지상천국을 건설한다고 생각하십니까?',
|
||||||
|
'4. 예수의 십자가 구속사업은 영적인 면에서만 완성되',
|
||||||
|
'5. 전역사 노정의 복귀섭리를 담당한 중심인물은 네 사람이 있는데, \'기대시대\'의 아담, \'구약시대\'의 아브라함, \'신약시대\'의 예수, 그리고 \'성약시대\'의 재림주가 있다고 생각하십니까?',
|
||||||
|
'6. 구름은 타락한 인간이 중생(重生)하여 그 마음이 항상 땅에 있지 않고 하늘에 있는 독실한 성도들을 의미하는 것이라고 생각하십니까?',
|
||||||
|
'7. 예수님의 재림이 지상에서 육신을 쓰고 탄생하시는 것으로써 이루어진다고 생각하십니까?',
|
||||||
|
'8. 예수님이 재림하실 동방의 나라가 바로 \'한국\'이라고 생각하십니까?'
|
||||||
|
],
|
||||||
|
'몰몬교': [
|
||||||
|
'1. 몰몬교회만이 유일한 예수 그리스도의 교회라 생각하십니까?',
|
||||||
|
'2. 모든 인간은 하늘의 부모에게서 받은 영을 가지고 있으며, 하나님처럼 되기 위해 이땅에 태어났다고 생각하십니까?',
|
||||||
|
'3. 몰몬경도 하나님 말씀이라고 생각하십니까?',
|
||||||
|
'4. 에스겔 37장 15-17절에 나오는 두 개의 막대기가 \'성경\'과 \'몰몬경\'을 가리킨다고 생각하십니까?',
|
||||||
|
'5. 몰몬경은 아메리카 인디언들의 조상이 기원전 600년에 예루살렘으로부터 배를 타고 이주해 왔다고 생각하십니까?',
|
||||||
|
'6. 요셉스미스는 계시를 받아 황금판의 고대 히브리어를 영어로 번역하였다고 생각하십니까?',
|
||||||
|
'7. 엘로힘 하나님이 구약에서는 여호와로, 신약에서는 예수로 나타났다고 생각하십니까?',
|
||||||
|
'8. 예수그리스도는 하나님의 독생자가 아니며 창조주로서 신성을 가진 분도 아니며, 어느 때에 하나님의 불꽃으로 감화되어 신적인 존재가 된 것에 불과하다고 생각하십니까?',
|
||||||
|
'9. 그리스도의 신성이나 십자가에서 대속사건은 아무 의미가 없다고 생각하십니까?',
|
||||||
|
'10. 인간은 하나님이 창조하신 것이 아니다. 인간의 영은 영원부터 하나님과 함께힌 존재라고 생각하십니까?',
|
||||||
|
'11. 아담의 죄로 인해 인류가 벌 받는 것이 아니며, 자기 자신의 죄로 인해 벌 받는 것이라고 생각하십니까?',
|
||||||
|
'12. 온 인류는 모두 구원을 받을 수 있다는 보편적 구원관. 지옥이나 죄에 대한 형벌이 없다고 생각하십니까?'
|
||||||
|
],
|
||||||
|
'안식교': [
|
||||||
|
'1. 안식교가 시한부 종말론에 의해 시작되었다는 것을 알고 계십니까?',
|
||||||
|
'2. 엘렌 G. 화이트 여사의 말과 저서와 예언들은 성경과 동일한 권위를 지니고 있습니까?',
|
||||||
|
'3. 믿음으로 얻어지는 구원과 율법을 지키는 행위로 얻어지는 구원이 구분되어 있다고 생각하십니까?',
|
||||||
|
'4. 믿음으로 말미암는 의는 그리스도의 공로를 의지하여 의롭다고 선언 받는 칭의를 말하는 것이라고 생각하십니까?',
|
||||||
|
'5. 우리의 품성 가운데 점이나 흠이 있는 한 우리 중 아무라도 하나님의 인을 결코 받지 못한다고 생각하십니까?',
|
||||||
|
'6. 인간이 율법을 완전하게 지킬 수 있다고 생각하십니까?',
|
||||||
|
'7. 예수님이 성소에서 지성소로 들어가신 이유는 우리가 정결한 성도가 되길 원하신 것이라고 생각하십니까?',
|
||||||
|
'8. 인간의 몸을 입으신 그리스도께서는 인간의 죄스러운 성품을 그대로 지니고 있습니까?',
|
||||||
|
'9. 토요일인 안식일을 지키는 것이 구원의 조건이라고 생각하십니까?',
|
||||||
|
'10. 율법의 행위는 구원의 조건이며 현세의 안전한 성화를 주장하고 품성의 변화를 위해서 육식을 금하고 채식을 강조해야 합니까?',
|
||||||
|
'11. 지옥은 존재하지 않으며 불신자가 죽으면 그 영혼은 멸절됩니까?',
|
||||||
|
'12. 2300주야는 2300년으로 계산하는 것이 성경적이라고 생각하십니까?'
|
||||||
|
],
|
||||||
|
'사랑하는교회': [
|
||||||
|
'1. 진짜 구원받은 사람도 진짜 버림받을 수 있다고 생각하십니까?',
|
||||||
|
'2. 사도와 선지자가 오늘 날도 존재한다고 생각하십니까?',
|
||||||
|
'3. 직통계시가 여전히 존재한다고 생각하십니까?',
|
||||||
|
'4. 구원에 이르는 통로로써 성경 외에 예언이나 치유 등 다른 은사도 중요하다고 생각하십니까?',
|
||||||
|
'5. 누구든지 훈련을 통해 예언 사역이 가능하다고 생각하십니까?',
|
||||||
|
'6. 은사 사역을 하지 않고 제자훈련, 강해설교만 하는 교회들은 종교적인 교회라고 생각하십니까?',
|
||||||
|
'7. 하늘나라 전략회의에 참여해 새로운 전략을 받아온 분이 계시다고 믿으십니까?',
|
||||||
|
'8. 사랑하는 교회(구 큰믿음교회)에서 주관한 예전전도학교 (구 선지자학교)에 참여한 적이 있으신지요?'
|
||||||
|
],
|
||||||
|
'예수중심교회 (이초석)': [
|
||||||
|
'1. 성부와 성자와 성령의 이름이 예수라고 생각하고 계십니까?',
|
||||||
|
'2. 이땅이 마귀가 갇혀 있는 음부라고 생각하십니까?',
|
||||||
|
'3. 귀신의 정체가 불신자의 사후 존재라고 생각하십니까?',
|
||||||
|
'4. 모든 질병의 원인이 귀신에 의해 생긴다고 생각하십니까?',
|
||||||
|
'5. 귀신을 추방함으로 신자의 이름이 하늘나라에 기록된다고 생각하십니까?',
|
||||||
|
'6. 예수께서 영의 육체를 입고 오셨다고 생각하십니까?',
|
||||||
|
'7. 예수님의 오신 목적중 가장 큰 것이 마귀를 멸하기 위해 오셨다고 생각하십니까?',
|
||||||
|
'8. 구원이란 인간을 억누르고 있는 귀신의 세력으로부터 자유함을 입는 것이라고 생각하십니까?'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Flask==3.0.0
|
||||||
|
python-docx==1.1.0
|
||||||
|
google-api-python-client==2.108.0
|
||||||
|
google-auth-httplib2==0.1.1
|
||||||
|
google-auth-oauthlib==1.1.0
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
Werkzeug==3.0.1
|
||||||
|
|
||||||
27
static/script.js
Normal file
27
static/script.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 공통 JavaScript 함수들
|
||||||
|
|
||||||
|
// 폼 유효성 검사
|
||||||
|
function validateForm(formId) {
|
||||||
|
const form = document.getElementById(formId);
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 로딩 표시
|
||||||
|
function showLoading() {
|
||||||
|
const modal = document.getElementById('loadingModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
const modal = document.getElementById('loadingModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
475
static/style.css
Normal file
475
static/style.css
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-link {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 8px 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-link:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.1em;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.active {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.completed {
|
||||||
|
background: #4caf50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-line {
|
||||||
|
width: 80px;
|
||||||
|
height: 3px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cult-name {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
background: #fafafa;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"],
|
||||||
|
.form-group input[type="date"],
|
||||||
|
.form-group input[type="tel"],
|
||||||
|
.form-group select,
|
||||||
|
.form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus,
|
||||||
|
.form-group select:focus,
|
||||||
|
.form-group textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group input[type="radio"] {
|
||||||
|
width: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctrine-section {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doctrine-section h3 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 30px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #d0d0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #4caf50;
|
||||||
|
color: white;
|
||||||
|
font-size: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 30px;
|
||||||
|
animation: scaleIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-message h2 {
|
||||||
|
color: #4caf50;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-message p {
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 모달 스타일 */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-description {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-form {
|
||||||
|
background: #fafafa;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-left: 4px solid #c62828;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message h2 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message p {
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
padding: 25px;
|
||||||
|
margin: 30px 0;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box h3 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box li {
|
||||||
|
padding: 8px 0;
|
||||||
|
padding-left: 25px;
|
||||||
|
position: relative;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box li:before {
|
||||||
|
content: "✓";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: #4caf50;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-box {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-text {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-list {
|
||||||
|
list-style: decimal;
|
||||||
|
padding-left: 30px;
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-list li {
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-note {
|
||||||
|
margin-top: 25px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #fff3cd;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #856404;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 반응형 디자인 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-line {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
templates/auth.html
Normal file
29
templates/auth.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="auth-container">
|
||||||
|
<h2>접근 인증</h2>
|
||||||
|
<p class="auth-description">이 서비스는 인증키가 있는 사용자만 이용할 수 있습니다.</p>
|
||||||
|
<p class="auth-description">인증키를 입력해주세요.</p>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="error-message">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST" class="auth-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="access_key">인증키 *</label>
|
||||||
|
<input type="text" id="access_key" name="access_key" required autofocus placeholder="인증키를 입력하세요">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">인증하기</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
34
templates/base.html
Normal file
34
templates/base.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>이단 탈퇴자 교리점검표</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>이단 탈퇴자 교리점검표</h1>
|
||||||
|
<p class="subtitle">기독교대한감리회 신앙고백에 따른 교리 점검</p>
|
||||||
|
{% if session.get('authenticated') %}
|
||||||
|
<div class="header-actions">
|
||||||
|
<a href="{{ url_for('logout') }}" class="logout-link">로그아웃</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2024 기독교대한감리회. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
17
templates/complete.html
Normal file
17
templates/complete.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="complete-message">
|
||||||
|
<div class="checkmark">✓</div>
|
||||||
|
<h2>제출이 완료되었습니다!</h2>
|
||||||
|
<p>교리점검표가 성공적으로 제출되었습니다.</p>
|
||||||
|
<p>담당자가 검토 후 연락드리겠습니다.</p>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="window.location.href='/'">처음으로</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
43
templates/index.html
Normal file
43
templates/index.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="welcome-message">
|
||||||
|
<h2>환영합니다</h2>
|
||||||
|
<p>이단 탈퇴자를 위한 교리점검표 설문에 오신 것을 환영합니다.</p>
|
||||||
|
<p>기독교대한감리회 신앙고백에 따른 교리 점검을 진행합니다.</p>
|
||||||
|
|
||||||
|
<div class="info-box description-box">
|
||||||
|
<p class="description-text">총 4개의 섹션으로 되어 있습니다.</p>
|
||||||
|
|
||||||
|
<ol class="section-list">
|
||||||
|
<li>감리회 신앙고백 점검</li>
|
||||||
|
<li>이단 일반 점검</li>
|
||||||
|
<li>이단 상세 점검</li>
|
||||||
|
<li>상담 소감문</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p class="description-note">
|
||||||
|
본 점검표는 평가의 목적이 아니며, 점수를 매기지도 않습니다.
|
||||||
|
10주 양육에 들어가시기 전에 기본교리를 스스로 점검하는 차원에서 진행하는 것이니
|
||||||
|
부담없이 임해 주시면 감사하겠습니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<h3>설문 안내</h3>
|
||||||
|
<ul>
|
||||||
|
<li>총 5단계로 구성되어 있습니다</li>
|
||||||
|
<li>각 단계는 약 5-10분 정도 소요됩니다</li>
|
||||||
|
<li>진행 중 이전 단계로 돌아갈 수 있습니다</li>
|
||||||
|
<li>모든 항목은 필수 입력 사항입니다</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="window.location.href='/step1'">설문 시작하기</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
81
templates/step1.html
Normal file
81
templates/step1.html
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-step active">1</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">2</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">3</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">4</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">5</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>1단계: 기본 정보 입력</h2>
|
||||||
|
|
||||||
|
<form id="step1Form" class="form-container">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">이름 *</label>
|
||||||
|
<input type="text" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="district">교구 *</label>
|
||||||
|
<select id="district" name="district" required>
|
||||||
|
<option value="">선택하세요</option>
|
||||||
|
{% for district in districts %}
|
||||||
|
<option value="{{ district }}">{{ district }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cult">이단교단 *</label>
|
||||||
|
<select id="cult" name="cult" required>
|
||||||
|
<option value="">선택하세요</option>
|
||||||
|
{% for cult in cults %}
|
||||||
|
<option value="{{ cult }}">{{ cult }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">다음 단계</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.getElementById('step1Form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/step1', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
window.location.href = result.next_step;
|
||||||
|
} else {
|
||||||
|
alert('오류가 발생했습니다: ' + (result.message || '알 수 없는 오류'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('오류가 발생했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
145
templates/step2.html
Normal file
145
templates/step2.html
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-step completed">1</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step active">2</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">3</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">4</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">5</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>2단계: 기독교대한감리회 신앙고백 교리 점검</h2>
|
||||||
|
|
||||||
|
<form id="step2Form" class="form-container">
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>1. 우주 만물을 창조하시고 섭리하시며 주관하시는 거룩하시고 자비하시며 오직 한 분이신 아버지 하나님을 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q1_father_god" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q1_father_god" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q1_father_god" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>2. 말씀이 육신이 되어 우리 가운데 오셔서 하나님의 나라를 선포하시고 십자가에 달려 죽으셨다가 부활승천 하심으로 대속자가 되시고 구세주가 되시는 예수 그리스도를 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q2_jesus_christ" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q2_jesus_christ" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q2_jesus_christ" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>3. 우리와 함께 계셔서 우리를 거듭나게 하시고 거룩하게 하시며 완전하게 하시며 위안과 힘이 되시는 성령을 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q3_holy_spirit" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q3_holy_spirit" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q3_holy_spirit" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>4. 성령의 감동으로 기록된 하나님의 말씀인 성경이 구원에 이르는 도리와 신앙생활에 충분한 표준이 됨을 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q4_bible" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q4_bible" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q4_bible" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>5. 하나님의 은혜로 믿음을 통해 죄사함을 받아 거룩해지며 하나님의 구원의 역사에 동참하도록 부름받음을 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q5_salvation" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q5_salvation" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q5_salvation" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>6. 예배와 친교, 교육과 봉사, 전도와 선교를 위해 하나가 된 그리스도의 몸인 교회를 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q6_church" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q6_church" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q6_church" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>7. 만민에게 복음을 전파함으로 하나님의 정의와 사랑을 나누고 평화의 세계를 이루는 모든 사람들이 하나님 앞에 형제됨을 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q7_mission" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q7_mission" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q7_mission" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>8. 예수 그리스도의 재림과 심판 우리 몸의 부활과 영생 그리고 의의 최후 승리와 영원한 하나님 나라를 믿습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q8_second_coming" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q8_second_coming" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q8_second_coming" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="window.location.href='/step1'">이전</button>
|
||||||
|
<button type="submit" class="btn btn-primary">다음 단계</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.getElementById('step2Form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/step2', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
window.location.href = result.next_step;
|
||||||
|
} else {
|
||||||
|
alert('오류가 발생했습니다: ' + (result.message || '알 수 없는 오류'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('오류가 발생했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
247
templates/step3.html
Normal file
247
templates/step3.html
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-step completed">1</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step completed">2</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step active">3</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">4</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">5</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>3단계: 이단 일반 교리 점검</h2>
|
||||||
|
|
||||||
|
<form id="step3Form" class="form-container">
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>1. 우리의 구원을 위해서 성경 이외의 가르침이 필요하다고 생각하십니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q1_additional_teaching" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q1_additional_teaching" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q1_additional_teaching" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>2. 어떤 특별한 성경 번역본만이 진리이고 다른 번역본에는 문제가 있다고 생각하십니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q2_special_bible" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q2_special_bible" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q2_special_bible" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>3. 성경의 진리 가운데 그동안 숨겨져 온 부분이 있고, 그 내용을 계시 받은 특별한 사람이 있다고 믿고 있습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q3_hidden_truth" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q3_hidden_truth" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q3_hidden_truth" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>4. 성경을 해석하기 위해서 비유가 매우 중요하다고 생각하십니까? 비유를 깨닫기 위해서 별도의 성경공부가 필요하다고 생각하십니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q4_parable" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q4_parable" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q4_parable" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>5. 성경의 내용들 가운데 모든 것이 서로 짝이 있습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q5_pairs" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q5_pairs" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q5_pairs" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>6. 구세주 되시는 예수님은 모든 인류의 구원을 위해서 죽으셨는데 아직도 특별한 사람을 대리자로 세워서 구원사역을 이루실 필요가 있습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q6_mediator" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q6_mediator" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q6_mediator" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>7. 나는 죄인이라고 생각하십니까? 나의 구원은 확실하십니까? 그 이유는 무엇입니까? (이 문항은 아래 기타의견란에 문항번호를 적은 후 서술해 주세요)</label>
|
||||||
|
<p class="help-text">※ 이 문항은 아래 "기타의견"란에 "7번: "으로 시작하여 답변해주세요.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>8. 우리는 구원 받았을 때 회개했습니다. 그리고 이후에 다시 회개가 필요합니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q8_repentance" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q8_repentance" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q8_repentance" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>9. 우리가 주일을 성수하는 것은 성경적입니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q9_sunday" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q9_sunday" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q9_sunday" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>10. 안식일을 지키는 것이 구원의 조건입니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q10_sabbath" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q10_sabbath" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q10_sabbath" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>11. 성탄절은 예수님이 탄생한 날입니다. 12월 25일에 기념해도 되는 겁니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q11_christmas" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q11_christmas" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q11_christmas" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>12. 예수 그리스도의 재림의 때가 구체적으로 언제인지 정해져 있다고 믿으십니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q12_second_coming_date" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q12_second_coming_date" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q12_second_coming_date" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>13. 성경에서 말하는 동방은 한국을 말하는 것이 맞습니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q13_east" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q13_east" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q13_east" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>14. 당신의 종교적 신념을 지키는 것이 가정을 지키는 것보다 더 중요합니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q14_family" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q14_family" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q14_family" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>15. 당신이 믿는 바를 전하고 사명을 감당하기 위해서 모략을 사용하는 것이 바람직합니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q15_deception" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q15_deception" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q15_deception" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>16. 주일성수, 새벽기도, 십일조 등 기존의 교회제도는 율법의 산물이기에 지킬 필요가 없다고 생각하십니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q16_church_system" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q16_church_system" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q16_church_system" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>17. 사람이 질병에 걸리는 이유는 귀신의 영향 때문입니까?</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="q17_disease" value="예" required> 예</label>
|
||||||
|
<label><input type="radio" name="q17_disease" value="아니오"> 아니오</label>
|
||||||
|
<label><input type="radio" name="q17_disease" value="모르겠습니다"> 모르겠습니다</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="other_opinions">기타의견 (7번 문항 답변 포함)</label>
|
||||||
|
<textarea id="other_opinions" name="other_opinions" rows="5" placeholder="7번 문항 답변 및 기타 의견을 작성해주세요. 7번 문항은 '7번: '으로 시작하여 답변해주세요."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="window.location.href='/step2'">이전</button>
|
||||||
|
<button type="submit" class="btn btn-primary">다음 단계</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.getElementById('step3Form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/step3', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
window.location.href = result.next_step;
|
||||||
|
} else {
|
||||||
|
alert('오류가 발생했습니다: ' + (result.message || '알 수 없는 오류'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('오류가 발생했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
78
templates/step4.html
Normal file
78
templates/step4.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-step completed">1</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step completed">2</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step completed">3</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step active">4</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step">5</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>4단계: 이단 상세 점검</h2>
|
||||||
|
<p class="cult-name">출신 이단: <strong>{{ cult_name }}</strong></p>
|
||||||
|
|
||||||
|
<form id="step4Form" class="form-container">
|
||||||
|
{% for question in questions %}
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ question }}</label>
|
||||||
|
{% if '기타의견란' in question or '기타의견' in question %}
|
||||||
|
<p class="help-text">※ 이 문항은 아래 "기타의견"란에 "{{ loop.index }}번: "으로 시작하여 답변해주세요.</p>
|
||||||
|
{% else %}
|
||||||
|
<textarea name="q{{ loop.index }}" rows="4" required placeholder="답변을 작성해주세요"></textarea>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="doctrine-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="other_opinions">기타의견 (3번 문항 답변 포함)</label>
|
||||||
|
<textarea id="other_opinions" name="other_opinions" rows="5" placeholder="3번 문항 답변 및 기타 의견을 작성해주세요. 3번 문항은 '3번: '으로 시작하여 답변해주세요."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="window.location.href='/step3'">이전</button>
|
||||||
|
<button type="submit" class="btn btn-primary">다음 단계</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.getElementById('step4Form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/step4', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
window.location.href = result.next_step;
|
||||||
|
} else {
|
||||||
|
alert('오류가 발생했습니다: ' + (result.message || '알 수 없는 오류'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('오류가 발생했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
79
templates/step5.html
Normal file
79
templates/step5.html
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="step-container">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-step completed">1</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step completed">2</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step completed">3</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step completed">4</div>
|
||||||
|
<div class="progress-line"></div>
|
||||||
|
<div class="progress-step active">5</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>5단계: 간증문 입력</h2>
|
||||||
|
|
||||||
|
<form id="step5Form" class="form-container">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="testimony">간증문 *</label>
|
||||||
|
<p class="help-text">이단에서 탈퇴하고 정통 기독교로 돌아온 과정과 소감을 자유롭게 작성해주세요.</p>
|
||||||
|
<textarea id="testimony" name="content" rows="15" required placeholder="간증문을 작성해주세요..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="window.location.href='/step4'">이전</button>
|
||||||
|
<button type="submit" class="btn btn-primary">제출하기</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="loadingModal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>문서를 생성하고 업로드하는 중입니다...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.getElementById('step5Form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
// 로딩 모달 표시
|
||||||
|
document.getElementById('loadingModal').style.display = 'flex';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/step5', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// 로딩 모달 숨기기
|
||||||
|
document.getElementById('loadingModal').style.display = 'none';
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
alert(result.message);
|
||||||
|
window.location.href = '/complete';
|
||||||
|
} else {
|
||||||
|
alert('오류가 발생했습니다: ' + (result.message || '알 수 없는 오류'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById('loadingModal').style.display = 'none';
|
||||||
|
alert('오류가 발생했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
2
utils/__init__.py
Normal file
2
utils/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Utils package
|
||||||
|
|
||||||
66
utils/google_drive.py
Normal file
66
utils/google_drive.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import os
|
||||||
|
from google.oauth2.credentials import Credentials
|
||||||
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
|
from google.auth.transport.requests import Request
|
||||||
|
from googleapiclient.discovery import build
|
||||||
|
from googleapiclient.http import MediaFileUpload
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
SCOPES = ['https://www.googleapis.com/auth/drive.file']
|
||||||
|
|
||||||
|
class GoogleDriveUploader:
|
||||||
|
def __init__(self, credentials_file, token_file):
|
||||||
|
self.credentials_file = credentials_file
|
||||||
|
self.token_file = token_file
|
||||||
|
self.service = None
|
||||||
|
self._authenticate()
|
||||||
|
|
||||||
|
def _authenticate(self):
|
||||||
|
"""구글 드라이브 인증"""
|
||||||
|
creds = None
|
||||||
|
|
||||||
|
# 기존 토큰 파일이 있으면 로드
|
||||||
|
if os.path.exists(self.token_file):
|
||||||
|
with open(self.token_file, 'rb') as token:
|
||||||
|
creds = pickle.load(token)
|
||||||
|
|
||||||
|
# 유효한 인증 정보가 없으면 새로 인증
|
||||||
|
if not creds or not creds.valid:
|
||||||
|
if creds and creds.expired and creds.refresh_token:
|
||||||
|
creds.refresh(Request())
|
||||||
|
else:
|
||||||
|
if not os.path.exists(self.credentials_file):
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"구글 드라이브 인증 파일을 찾을 수 없습니다: {self.credentials_file}\n"
|
||||||
|
"Google Cloud Console에서 OAuth 2.0 클라이언트 ID를 다운로드하여 "
|
||||||
|
"credentials.json으로 저장해주세요."
|
||||||
|
)
|
||||||
|
flow = InstalledAppFlow.from_client_secrets_file(
|
||||||
|
self.credentials_file, SCOPES)
|
||||||
|
creds = flow.run_local_server(port=0)
|
||||||
|
|
||||||
|
# 토큰 저장
|
||||||
|
with open(self.token_file, 'wb') as token:
|
||||||
|
pickle.dump(creds, token)
|
||||||
|
|
||||||
|
self.service = build('drive', 'v3', credentials=creds)
|
||||||
|
|
||||||
|
def upload_file(self, file_path, file_name, folder_id=None):
|
||||||
|
"""파일을 구글 드라이브에 업로드"""
|
||||||
|
if not self.service:
|
||||||
|
raise Exception("구글 드라이브 서비스가 초기화되지 않았습니다.")
|
||||||
|
|
||||||
|
file_metadata = {'name': file_name}
|
||||||
|
if folder_id:
|
||||||
|
file_metadata['parents'] = [folder_id]
|
||||||
|
|
||||||
|
media = MediaFileUpload(file_path, mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
|
||||||
|
|
||||||
|
file = self.service.files().create(
|
||||||
|
body=file_metadata,
|
||||||
|
media_body=media,
|
||||||
|
fields='id'
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
return file.get('id')
|
||||||
|
|
||||||
118
utils/word_processor.py
Normal file
118
utils/word_processor.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
from docx import Document
|
||||||
|
from docx.shared import Pt
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class WordProcessor:
|
||||||
|
def __init__(self, template_dir):
|
||||||
|
self.template_dir = template_dir
|
||||||
|
self.output_dir = 'output'
|
||||||
|
os.makedirs(self.output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def generate_document(self, data, cult_name='기타'):
|
||||||
|
"""데이터를 워드 문서로 생성"""
|
||||||
|
# 템플릿 파일 경로
|
||||||
|
template_file = os.path.join(self.template_dir, f'{cult_name}_template.docx')
|
||||||
|
|
||||||
|
# 템플릿이 없으면 기본 템플릿 사용
|
||||||
|
if not os.path.exists(template_file):
|
||||||
|
template_file = os.path.join(self.template_dir, 'default_template.docx')
|
||||||
|
|
||||||
|
# 템플릿이 있으면 사용, 없으면 새 문서 생성
|
||||||
|
if os.path.exists(template_file):
|
||||||
|
doc = Document(template_file)
|
||||||
|
else:
|
||||||
|
doc = Document()
|
||||||
|
self._create_default_template(doc)
|
||||||
|
|
||||||
|
# 데이터 채우기
|
||||||
|
self._fill_document(doc, data)
|
||||||
|
|
||||||
|
# 출력 파일명
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
name = data.get('basic_info', {}).get('name', '무명')
|
||||||
|
output_filename = f'교리점검표_{name}_{timestamp}.docx'
|
||||||
|
output_path = os.path.join(self.output_dir, output_filename)
|
||||||
|
|
||||||
|
doc.save(output_path)
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
def _create_default_template(self, doc):
|
||||||
|
"""기본 템플릿 생성"""
|
||||||
|
# 제목
|
||||||
|
title = doc.add_heading('이단 탈퇴자 교리점검표', 0)
|
||||||
|
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
|
||||||
|
# 기본 정보 섹션
|
||||||
|
doc.add_heading('1. 기본 정보', level=1)
|
||||||
|
doc.add_paragraph('이름: {name}')
|
||||||
|
doc.add_paragraph('교구: {district}')
|
||||||
|
doc.add_paragraph('이단교단: {cult}')
|
||||||
|
|
||||||
|
# 기독교대한감리회 신앙고백
|
||||||
|
doc.add_heading('2. 기독교대한감리회 신앙고백 교리 점검', level=1)
|
||||||
|
doc.add_paragraph('{methodist_doctrine}')
|
||||||
|
|
||||||
|
# 이단 일반 교리 점검
|
||||||
|
doc.add_heading('3. 이단 일반 교리 점검', level=1)
|
||||||
|
doc.add_paragraph('{general_cult_doctrine}')
|
||||||
|
|
||||||
|
# 출신 이단별 교리 점검
|
||||||
|
doc.add_heading('4. 출신 이단별 교리 점검', level=1)
|
||||||
|
doc.add_paragraph('{specific_cult_doctrine}')
|
||||||
|
|
||||||
|
# 간증문
|
||||||
|
doc.add_heading('5. 간증문', level=1)
|
||||||
|
doc.add_paragraph('{testimony}')
|
||||||
|
|
||||||
|
doc.add_paragraph('')
|
||||||
|
doc.add_paragraph(f'작성일: {datetime.now().strftime("%Y년 %m월 %d일")}')
|
||||||
|
|
||||||
|
def _fill_document(self, doc, data):
|
||||||
|
"""문서에 데이터 채우기"""
|
||||||
|
basic_info = data.get('basic_info', {})
|
||||||
|
methodist = data.get('methodist_doctrine', {})
|
||||||
|
general = data.get('general_cult_doctrine', {})
|
||||||
|
specific = data.get('specific_cult_doctrine', {})
|
||||||
|
testimony = data.get('testimony', {})
|
||||||
|
|
||||||
|
# 모든 단락을 순회하며 플레이스홀더 교체
|
||||||
|
replacements = {
|
||||||
|
'{name}': basic_info.get('name', ''),
|
||||||
|
'{district}': basic_info.get('district', ''),
|
||||||
|
'{cult}': basic_info.get('cult', ''),
|
||||||
|
'{methodist_doctrine}': self._format_answers(methodist),
|
||||||
|
'{general_cult_doctrine}': self._format_answers(general),
|
||||||
|
'{specific_cult_doctrine}': self._format_answers(specific),
|
||||||
|
'{testimony}': testimony.get('content', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
for paragraph in doc.paragraphs:
|
||||||
|
for key, value in replacements.items():
|
||||||
|
if key in paragraph.text:
|
||||||
|
paragraph.text = paragraph.text.replace(key, str(value))
|
||||||
|
|
||||||
|
# 테이블도 처리
|
||||||
|
for table in doc.tables:
|
||||||
|
for row in table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
for key, value in replacements.items():
|
||||||
|
if key in cell.text:
|
||||||
|
cell.text = cell.text.replace(key, str(value))
|
||||||
|
|
||||||
|
def _format_answers(self, answers_dict):
|
||||||
|
"""답변 딕셔너리를 텍스트로 포맷팅"""
|
||||||
|
if not answers_dict:
|
||||||
|
return '답변 없음'
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for key, value in answers_dict.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
# 중첩된 구조 처리
|
||||||
|
result.append(f"{key}: {self._format_answers(value)}")
|
||||||
|
else:
|
||||||
|
result.append(f"{key}: {value}")
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
3
word_templates/.gitkeep
Normal file
3
word_templates/.gitkeep
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 이 디렉토리에 워드 템플릿 파일을 저장하세요
|
||||||
|
# 예: 신천지_template.docx, 구원파_template.docx 등
|
||||||
|
|
||||||
Reference in New Issue
Block a user