wiizm commited on
Commit
2cfce29
ยท
verified ยท
1 Parent(s): 40838e3

Upload app\__init__.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app//__init__.py +327 -0
app//__init__.py ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”
3
+ """
4
+
5
+ from flask import Flask, request, send_from_directory, jsonify
6
+ from flask_login import LoginManager
7
+ import sqlite3
8
+ from pathlib import Path
9
+ from typing import Optional
10
+ from sqlalchemy import create_engine, text
11
+
12
+ from app.database import db, User
13
+ from app.core.config import Config, get_config
14
+ from app.core.logger import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+ login_manager = LoginManager()
19
+ login_manager.login_view = 'main.login'
20
+ login_manager.login_message = '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'
21
+ login_manager.login_message_category = 'info'
22
+
23
+ @login_manager.unauthorized_handler
24
+ def unauthorized():
25
+ """์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ์ฒ˜๋ฆฌ"""
26
+ from flask import redirect, url_for, request
27
+ # API ์š”์ฒญ์ธ ๊ฒฝ์šฐ JSON ์‘๋‹ต
28
+ if request.path.startswith('/api/'):
29
+ from flask import jsonify
30
+ return jsonify({'error': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'}), 401
31
+ # ์ผ๋ฐ˜ ์š”์ฒญ์ธ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
32
+ return redirect(url_for('main.login', next=request.path))
33
+
34
+
35
+ @login_manager.user_loader
36
+ def load_user(user_id: str) -> Optional[User]:
37
+ """
38
+ ์‚ฌ์šฉ์ž ๋กœ๋“œ ํ•จ์ˆ˜ (Flask-Login์šฉ)
39
+
40
+ Args:
41
+ user_id: ์‚ฌ์šฉ์ž ID (๋ฌธ์ž์—ด)
42
+
43
+ Returns:
44
+ User ๊ฐ์ฒด ๋˜๋Š” None
45
+ """
46
+ try:
47
+ return User.query.get(int(user_id))
48
+ except (ValueError, TypeError):
49
+ return None
50
+
51
+
52
+ def create_app() -> Flask:
53
+ """
54
+ Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜
55
+
56
+ Returns:
57
+ ์„ค์ •๋œ Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค
58
+ """
59
+ config = get_config()
60
+
61
+ # ํ•„์ˆ˜ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
62
+ config.ensure_directories()
63
+
64
+ # ํ…œํ”Œ๋ฆฟ ํด๋” ๋ฐ static ํด๋” ๊ฒฝ๋กœ ์„ค์ •
65
+ template_folder = str(config.TEMPLATES_FOLDER)
66
+ static_folder = str(config.STATIC_FOLDER)
67
+
68
+ app = Flask(__name__, template_folder=template_folder, static_folder=static_folder)
69
+
70
+ # Flask ์„ค์ • ์ ์šฉ
71
+ app.config['SECRET_KEY'] = config.SECRET_KEY
72
+ app.config['SQLALCHEMY_DATABASE_URI'] = config.SQLALCHEMY_DATABASE_URI
73
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = config.SQLALCHEMY_TRACK_MODIFICATIONS
74
+ app.config['MAX_CONTENT_LENGTH'] = config.MAX_CONTENT_LENGTH
75
+
76
+ # SQLAlchemy ์—ฐ๊ฒฐ ํ’€ ์„ค์ • (ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ ์œ ํœด ์—ฐ๊ฒฐ ๋Š๊น€ ๋ฐฉ์ง€)
77
+ # pool_pre_ping: ์—ฐ๊ฒฐ ์‚ฌ์šฉ ์ „์— ์‚ด์•„์žˆ๋Š”์ง€ ํ™•์ธ (ping)
78
+ # pool_recycle: ์—ฐ๊ฒฐ์„ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์ „ ์ตœ๋Œ€ ์‹œ๊ฐ„(์ดˆ) - 300์ดˆ(5๋ถ„)
79
+ app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
80
+ 'pool_pre_ping': True,
81
+ 'pool_recycle': 300
82
+ }
83
+
84
+ # ์„ธ์…˜ ์ฟ ํ‚ค ์„ค์ • (๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๊ฐœ์„ )
85
+ app.config['SESSION_COOKIE_SECURE'] = True # HTTPS์—์„œ๋งŒ ์ „์†ก
86
+ app.config['SESSION_COOKIE_HTTPONLY'] = True # JavaScript ์ ‘๊ทผ ๋ฐฉ์ง€
87
+ app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF ๋ฐฉ์ง€ ๋ฐ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ
88
+ app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24์‹œ๊ฐ„
89
+
90
+ # ํ™•์žฅ ์ดˆ๊ธฐํ™”
91
+ db.init_app(app)
92
+ login_manager.init_app(app)
93
+
94
+ # Blueprint ๋“ฑ๋ก
95
+ from app.routes import main_bp
96
+ app.register_blueprint(main_bp)
97
+
98
+ # ๋“ฑ๋ก๋œ ๋ผ์šฐํŠธ ํ™•์ธ (๋””๋ฒ„๊น…์šฉ)
99
+ with app.app_context():
100
+ logger.info(f"๋“ฑ๋ก๋œ ๋ผ์šฐํŠธ ์ˆ˜: {len([r for r in app.url_map.iter_rules()])}")
101
+ logger.info(f"๋“ฑ๋ก๋œ Blueprint: {list(app.blueprints.keys())}")
102
+ # ์ฃผ์š” ๋ผ์šฐํŠธ ํ™•์ธ
103
+ routes = [str(r) for r in app.url_map.iter_rules() if r.endpoint.startswith('main.')]
104
+ logger.info(f"๋“ฑ๋ก๋œ main ๋ผ์šฐํŠธ: {routes[:10]}...") # ์ฒ˜์Œ 10๊ฐœ๋งŒ
105
+
106
+ # favicon.ico ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
107
+ @app.route('/favicon.ico')
108
+ def favicon():
109
+ """favicon.ico ์š”์ฒญ ์ฒ˜๋ฆฌ"""
110
+ try:
111
+ return send_from_directory(
112
+ str(config.STATIC_FOLDER),
113
+ 'logo.webp',
114
+ mimetype='image/webp'
115
+ )
116
+ except Exception as e:
117
+ logger.warning(f"favicon.ico ์ฒ˜๋ฆฌ ์‹คํŒจ: {e}")
118
+ return '', 204 # No Content
119
+
120
+ # 404 ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ (์ผ์‹œ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ ๋””๋ฒ„๊น…)
121
+ # ๋ผ์šฐํŠธ๊ฐ€ ์ œ๋Œ€๋กœ ๋“ฑ๋ก๋˜์—ˆ๋Š”์ง€ ํ™•์ธ ํ›„ ๋‹ค์‹œ ํ™œ์„ฑํ™”
122
+ # @app.errorhandler(404)
123
+ # def not_found(error):
124
+ # """404 ์—๋Ÿฌ ์ฒ˜๋ฆฌ"""
125
+ # logger.warning(f"404 ์—๋Ÿฌ: {request.path} - {request.method}")
126
+ # if request.path.startswith('/api/'):
127
+ # return jsonify({'error': '๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', 'path': request.path}), 404
128
+ # return '', 404
129
+
130
+ # ์š”์ฒญ ๋กœ๊น… ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€
131
+ @app.before_request
132
+ def log_request_info():
133
+ """๊ฐ HTTP ์š”์ฒญ ์ •๋ณด๋ฅผ ๋กœ๊น…"""
134
+ logger.info(f"[์š”์ฒญ] {request.method} {request.path} - IP: {request.remote_addr}")
135
+ if request.args:
136
+ logger.debug(f"[์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ] {dict(request.args)}")
137
+
138
+ @app.after_request
139
+ def log_response_info(response):
140
+ """๊ฐ HTTP ์‘๋‹ต ์ •๋ณด๋ฅผ ๋กœ๊น…"""
141
+ logger.info(f"[์‘๋‹ต] {request.method} {request.path} - ์ƒํƒœ: {response.status_code}")
142
+
143
+ # Permissions-Policy ํ—ค๋” ์ถ”๊ฐ€ (์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ๋งŒ ์‚ฌ์šฉ)
144
+ # ์ธ์‹๋˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ๋“ค์€ ์ œ๊ฑฐํ•˜์—ฌ ๊ฒฝ๊ณ  ๋ฐฉ์ง€
145
+ # ์ตœ์‹  ํ‘œ์ค€์— ๋งž๋Š” ๊ธฐ๋Šฅ๋“ค๋งŒ ํฌํ•จ
146
+ permissions_policy = (
147
+ "camera=(), "
148
+ "microphone=(), "
149
+ "geolocation=(), "
150
+ "payment=(), "
151
+ "usb=()"
152
+ )
153
+ response.headers['Permissions-Policy'] = permissions_policy
154
+
155
+ # ๋ธŒ๋ผ์šฐ์ € ์บ์‹œ ์ œ์–ด ํ—ค๋” ์ถ”๊ฐ€ (๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๊ฐœ์„ )
156
+ # HTML ํŽ˜์ด์ง€๋Š” ์บ์‹œํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €๋ณ„ ์ฐจ์ด ์ตœ์†Œํ™”
157
+ if request.path == '/' or request.path.startswith('/login') or request.path.startswith('/admin') or request.path.startswith('/webnovels'):
158
+ response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
159
+ response.headers['Pragma'] = 'no-cache'
160
+ response.headers['Expires'] = '0'
161
+
162
+ return response
163
+
164
+ # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™” ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
165
+ with app.app_context():
166
+ # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด ๋กœ๊น…
167
+ db_uri = app.config['SQLALCHEMY_DATABASE_URI']
168
+ if db_uri.startswith('postgresql://') or db_uri.startswith('postgres://'):
169
+ # PostgreSQL ์—ฐ๊ฒฐ ์ •๋ณด (๋ณด์•ˆ์„ ์œ„ํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋งˆ์Šคํ‚น)
170
+ masked_uri = db_uri.split('@')[0].split('://')[0] + '://***@' + '@'.join(db_uri.split('@')[1:]) if '@' in db_uri else db_uri
171
+ logger.info(f"[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] PostgreSQL ์—ฐ๊ฒฐ ์‹œ๋„: {masked_uri}")
172
+ # ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
173
+ try:
174
+ engine = create_engine(
175
+ db_uri,
176
+ pool_pre_ping=True,
177
+ pool_recycle=300
178
+ )
179
+ with engine.connect() as conn:
180
+ result = conn.execute(text("SELECT version()"))
181
+ version = result.fetchone()[0]
182
+ logger.info(f"[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] PostgreSQL ์—ฐ๊ฒฐ ์„ฑ๊ณต! ๋ฒ„์ „: {version[:50]}...")
183
+ except Exception as e:
184
+ logger.error(f"[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] PostgreSQL ์—ฐ๊ฒฐ ์‹คํŒจ: {str(e)}")
185
+ logger.warning("[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] SQLite๋กœ ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค.")
186
+ # SQLite๋กœ ํด๋ฐฑ
187
+ db_uri = f'sqlite:///{config.INSTANCE_FOLDER / "finance_analysis.db"}'
188
+ app.config['SQLALCHEMY_DATABASE_URI'] = db_uri
189
+ else:
190
+ logger.info(f"[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] SQLite ์‚ฌ์šฉ: {db_uri}")
191
+
192
+ # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ์ƒ์„ฑ
193
+ try:
194
+ db.create_all()
195
+ logger.info("[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์™„๋ฃŒ")
196
+ except Exception as e:
197
+ logger.error(f"[๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค] ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์‹คํŒจ: {str(e)}")
198
+ raise
199
+
200
+ # ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰ (SQLite๋งŒ ์ง€์›)
201
+ migrate_database(app)
202
+
203
+ # ๊ด€๋ฆฌ์ž ๊ณ„์ • ์ƒ์„ฑ
204
+ create_admin_user()
205
+
206
+ logger.info("Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
207
+
208
+ return app
209
+
210
+
211
+ def migrate_database(app: Flask) -> None:
212
+ """
213
+ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰
214
+
215
+ Args:
216
+ app: Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค
217
+ """
218
+ try:
219
+ db_uri = app.config['SQLALCHEMY_DATABASE_URI']
220
+
221
+ if not db_uri.startswith('sqlite:///'):
222
+ logger.warning(f"SQLite๊ฐ€ ์•„๋‹Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {db_uri}")
223
+ return
224
+
225
+ db_path_str = db_uri.replace('sqlite:///', '')
226
+ db_path = Path(db_path_str)
227
+
228
+ # ์ƒ๋Œ€ ๊ฒฝ๋กœ์ธ ๊ฒฝ์šฐ instance ํด๋” ๊ธฐ์ค€์œผ๋กœ ์ฒ˜๋ฆฌ
229
+ if not db_path.is_absolute():
230
+ db_path = Path(app.instance_path) / db_path
231
+
232
+ if not db_path.exists():
233
+ logger.info(f"๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค (์ƒˆ๋กœ ์ƒ์„ฑ๋จ): {db_path}")
234
+ return
235
+
236
+ logger.info(f"๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ์ž‘: {db_path}")
237
+
238
+ conn = sqlite3.connect(str(db_path))
239
+ cursor = conn.cursor()
240
+
241
+ # user ํ…Œ์ด๋ธ”์— nickname ์ปฌ๋Ÿผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
242
+ cursor.execute("PRAGMA table_info(user)")
243
+ user_columns = [column[1] for column in cursor.fetchall()]
244
+
245
+ if 'nickname' not in user_columns:
246
+ logger.info("user ํ…Œ์ด๋ธ”์— nickname ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์ค‘...")
247
+ cursor.execute("ALTER TABLE user ADD COLUMN nickname VARCHAR(80)")
248
+ conn.commit()
249
+ logger.info("user.nickname ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์™„๋ฃŒ")
250
+
251
+ # uploaded_file ํ…Œ์ด๋ธ”์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
252
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='uploaded_file'")
253
+ if cursor.fetchone():
254
+ cursor.execute("PRAGMA table_info(uploaded_file)")
255
+ uploaded_file_columns = [column[1] for column in cursor.fetchall()]
256
+
257
+ if 'uploaded_by' not in uploaded_file_columns:
258
+ logger.info("uploaded_file ํ…Œ์ด๋ธ”์— uploaded_by ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์ค‘...")
259
+ cursor.execute("ALTER TABLE uploaded_file ADD COLUMN uploaded_by INTEGER")
260
+ conn.commit()
261
+ logger.info("uploaded_file.uploaded_by ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์™„๋ฃŒ")
262
+
263
+ if 'parent_file_id' not in uploaded_file_columns:
264
+ logger.info("uploaded_file ํ…Œ์ด๋ธ”์— parent_file_id ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์ค‘...")
265
+ cursor.execute("ALTER TABLE uploaded_file ADD COLUMN parent_file_id INTEGER")
266
+ conn.commit()
267
+ logger.info("uploaded_file.parent_file_id ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์™„๋ฃŒ")
268
+
269
+ # document_chunk ํ…Œ์ด๋ธ”์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
270
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='document_chunk'")
271
+ if cursor.fetchone():
272
+ cursor.execute("PRAGMA table_info(document_chunk)")
273
+ document_chunk_columns = [column[1] for column in cursor.fetchall()]
274
+
275
+ if 'chunk_metadata' not in document_chunk_columns:
276
+ logger.info("document_chunk ํ…Œ์ด๋ธ”์— chunk_metadata ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์ค‘...")
277
+ cursor.execute("ALTER TABLE document_chunk ADD COLUMN chunk_metadata TEXT")
278
+ conn.commit()
279
+ logger.info("document_chunk.chunk_metadata ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์™„๋ฃŒ")
280
+
281
+ # chat_session ํ…Œ์ด๋ธ”์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
282
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='chat_session'")
283
+ if cursor.fetchone():
284
+ cursor.execute("PRAGMA table_info(chat_session)")
285
+ chat_session_columns = [column[1] for column in cursor.fetchall()]
286
+
287
+ if 'analysis_model' not in chat_session_columns:
288
+ logger.info("chat_session ํ…Œ์ด๋ธ”์— analysis_model ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์ค‘...")
289
+ cursor.execute("ALTER TABLE chat_session ADD COLUMN analysis_model VARCHAR(100)")
290
+ conn.commit()
291
+ logger.info("chat_session.analysis_model ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์™„๋ฃŒ")
292
+
293
+ if 'answer_model' not in chat_session_columns:
294
+ logger.info("chat_session ํ…Œ์ด๋ธ”์— answer_model ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์ค‘...")
295
+ cursor.execute("ALTER TABLE chat_session ADD COLUMN answer_model VARCHAR(100)")
296
+ conn.commit()
297
+ logger.info("chat_session.answer_model ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ์™„๋ฃŒ")
298
+
299
+ conn.close()
300
+ logger.info("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒ")
301
+
302
+ except sqlite3.Error as e:
303
+ logger.error(f"๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ค‘ SQLite ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
304
+ except Exception as e:
305
+ logger.error(f"๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", exc_info=True)
306
+
307
+
308
+ def create_admin_user() -> None:
309
+ """
310
+ ์ดˆ๊ธฐ ๊ด€๋ฆฌ์ž ๊ณ„์ • ์ƒ์„ฑ
311
+ """
312
+ admin_username = 'soymedia'
313
+ admin_password = 's0ymedi@1@34'
314
+
315
+ try:
316
+ admin = User.query.filter_by(username=admin_username).first()
317
+ if not admin:
318
+ admin = User(username=admin_username, is_admin=True, is_active=True)
319
+ admin.set_password(admin_password)
320
+ db.session.add(admin)
321
+ db.session.commit()
322
+ logger.info(f'๊ด€๋ฆฌ์ž ๊ณ„์ •์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: {admin_username}')
323
+ else:
324
+ logger.debug(f'๊ด€๋ฆฌ์ž ๊ณ„์ •์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค: {admin_username}')
325
+ except Exception as e:
326
+ logger.error(f'๊ด€๋ฆฌ์ž ๊ณ„์ • ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}', exc_info=True)
327
+ db.session.rollback()