-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
317 lines (243 loc) · 10.2 KB
/
app.py
File metadata and controls
317 lines (243 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# app.py
from flask import Flask, render_template, request, jsonify, redirect, url_for
from config import DATABASES, DATABASE_DIR, METADATA_FILE, load_databases
import os
import json
app = Flask(__name__)
@app.route('/')
def home():
"""메인 페이지 - DB 선택 화면"""
# 매번 최신 DB 목록 로드
databases = load_databases()
return render_template('home.html', databases=databases)
@app.route('/dashboard/<db_name>')
def dashboard(db_name):
"""DB별 대시보드"""
databases = load_databases()
if db_name not in databases:
return "Database not found", 404
db_info = databases[db_name]
return render_template('dashboard.html', db_name=db_name, db_info=db_info)
@app.route('/delete_db/<db_name>', methods=['POST'])
def delete_db(db_name):
"""DB 삭제"""
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
try:
# .db 파일 삭제
db_file = databases[db_name]['file']
if os.path.exists(db_file):
os.remove(db_file)
# metadata.json에서 제거
if os.path.exists(METADATA_FILE):
with open(METADATA_FILE, 'r', encoding='utf-8') as f:
metadata = json.load(f)
if db_name in metadata:
del metadata[db_name]
with open(METADATA_FILE, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
return jsonify({'success': True, 'message': f'{db_name} deleted'})
except Exception as e:
return jsonify({'success': False, 'message': str(e)}), 500
@app.route('/add_db', methods=['POST'])
def add_db():
"""새 DB 추가"""
data = request.get_json()
db_key = data.get('db_key', '').strip()
db_name = data.get('db_name', '').strip()
db_description = data.get('db_description', '').strip()
db_icon = data.get('db_icon', '📁').strip()
if not db_key or not db_name:
return jsonify({'success': False, 'message': 'DB key와 이름은 필수입니다.'}), 400
# 파일명으로 사용 가능한 문자인지 확인
if not db_key.replace('_', '').isalnum():
return jsonify({'success': False, 'message': 'DB key는 영문, 숫자, _만 사용 가능합니다.'}), 400
db_file = os.path.join(DATABASE_DIR, f'{db_key}.db')
# 이미 존재하는지 확인
if os.path.exists(db_file):
return jsonify({'success': False, 'message': '이미 존재하는 DB입니다.'}), 400
try:
# 빈 SQLite DB 생성
import sqlite3
conn = sqlite3.connect(db_file)
conn.close()
# metadata.json 업데이트
if os.path.exists(METADATA_FILE):
with open(METADATA_FILE, 'r', encoding='utf-8') as f:
metadata = json.load(f)
else:
metadata = {}
metadata[db_key] = {
'name': db_name,
'description': db_description,
'icon': db_icon
}
with open(METADATA_FILE, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
return jsonify({'success': True, 'message': f'{db_name} DB가 생성되었습니다.'})
except Exception as e:
return jsonify({'success': False, 'message': str(e)}), 500
@app.route('/api/analyze_schema/<db_name>')
def analyze_schema(db_name):
"""DB 스키마 분석"""
from utils.schema_analyzer import analyze_schema_with_llm
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
db_path = databases[db_name]['file']
analysis = analyze_schema_with_llm(db_path)
return jsonify({'success': True, 'analysis': analysis})
@app.route('/api/suggest_queries/<db_name>')
def suggest_queries(db_name):
"""추천 질문 생성"""
from utils.schema_analyzer import suggest_queries_with_llm
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
db_path = databases[db_name]['file']
queries = suggest_queries_with_llm(db_path)
return jsonify({'success': True, 'queries': queries})
@app.route('/api/generate_sql/<db_name>', methods=['POST'])
def generate_sql(db_name):
"""자연어 → SQL 생성"""
from utils.query_generator import generate_sql_from_question
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
data = request.get_json()
user_question = data.get('question', '').strip()
model_name = data.get('model', 'gemini-2.0-flash-lite') # 모델 선택 추가
if not user_question:
return jsonify({'success': False, 'message': '질문을 입력해주세요.'}), 400
db_path = databases[db_name]['file']
result = generate_sql_from_question(db_path, user_question, model_name)
return jsonify({
'success': True,
'reasoning': result['reasoning'],
'sql': result['sql']
})
@app.route('/api/execute_sql/<db_name>', methods=['POST'])
def execute_sql_api(db_name):
"""SQL 실행"""
from utils.query_generator import execute_sql, save_to_history
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
data = request.get_json()
sql_query = data.get('sql', '').strip()
question = data.get('question', '').strip() # 질문도 함께 받기
if not sql_query:
return jsonify({'success': False, 'message': 'SQL을 입력해주세요.'}), 400
db_path = databases[db_name]['file']
result = execute_sql(db_path, sql_query)
# 히스토리 저장
if result['success']:
save_to_history(db_name, question, sql_query, len(result['rows']))
return jsonify(result)
@app.route('/api/history/<db_name>')
def get_history_api(db_name):
"""쿼리 히스토리 조회"""
from utils.query_generator import get_history
history = get_history(db_name)
return jsonify({'success': True, 'history': history})
@app.route('/api/bookmark/<int:history_id>', methods=['POST'])
def toggle_bookmark_api(history_id):
"""북마크 토글"""
from utils.query_generator import toggle_bookmark
success = toggle_bookmark(history_id)
return jsonify({'success': success})
@app.route('/api/export/<format>/<db_name>', methods=['POST'])
def export_data(format, db_name):
"""
쿼리 결과를 CSV 또는 Excel로 내보내기
Args:
format: 'csv' 또는 'excel'
db_name: DB 이름
"""
from flask import send_file
from utils.query_generator import execute_sql
import csv
import io
from openpyxl import Workbook
from datetime import datetime
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
data = request.get_json()
sql_query = data.get('sql', '').strip()
if not sql_query:
return jsonify({'success': False, 'message': 'SQL을 입력해주세요.'}), 400
db_path = databases[db_name]['file']
result = execute_sql(db_path, sql_query)
if not result['success']:
return jsonify({'success': False, 'message': result['error']}), 400
columns = result['columns']
rows = result['rows']
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
if format == 'csv':
# CSV 생성
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(columns)
writer.writerows(rows)
output.seek(0)
return send_file(
io.BytesIO(output.getvalue().encode('utf-8-sig')), # UTF-8 BOM 추가 (한글 깨짐 방지)
mimetype='text/csv',
as_attachment=True,
download_name=f'{db_name}_export_{timestamp}.csv'
)
elif format == 'excel':
# Excel 생성
wb = Workbook()
ws = wb.active
ws.title = "Query Result"
# 헤더
ws.append(columns)
# 데이터
for row in rows:
ws.append(row)
# 헤더 스타일
from openpyxl.styles import Font, PatternFill
for cell in ws[1]:
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
# 바이트 스트림으로 저장
output = io.BytesIO()
wb.save(output)
output.seek(0)
return send_file(
output,
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
as_attachment=True,
download_name=f'{db_name}_export_{timestamp}.xlsx'
)
else:
return jsonify({'success': False, 'message': 'Invalid format'}), 400
@app.route('/api/schema_diagram/<db_name>')
def get_schema_diagram(db_name):
"""스키마 다이어그램 (Mermaid)"""
from utils.schema_analyzer import generate_schema_diagram
databases = load_databases()
if db_name not in databases:
return jsonify({'success': False, 'message': 'DB not found'}), 404
db_path = databases[db_name]['file']
diagram = generate_schema_diagram(db_path)
return jsonify({'success': True, 'diagram': diagram})
@app.route('/api/clear_cache/<db_name>', methods=['POST'])
def clear_schema_cache(db_name):
"""스키마 캐시 초기화"""
from utils.schema_analyzer import clear_cache
try:
clear_cache(db_name)
return jsonify({'success': True, 'message': f'{db_name} 캐시가 초기화되었습니다.'})
except Exception as e:
return jsonify({'success': False, 'message': str(e)}), 500
@app.route('/api/models')
def get_models():
"""사용 가능한 모델 목록"""
from utils.gemini_client import get_available_models
return jsonify({'success': True, 'models': get_available_models()})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)