Skip to content

Commit 0e0b7de

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents f2d24ba + 522fa47 commit 0e0b7de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1297
-570
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ TBD
5656
- Backend:[Python / Django](https://www.djangoproject.com/)
5757
- Database:[PostgreSQL + pgvector](https://www.postgresql.org/)
5858

59+
## 飞致云的其他明星项目
60+
61+
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源 BI 工具
62+
- [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板
63+
- [MaxKB](https://github.com/1panel-dev/MaxKB/) - 基于 LLM 大语言模型的开源知识库问答系统
64+
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
65+
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
66+
- [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具
67+
5968
## Star History
6069

6170
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/SQLBot&type=Date)](https://star-history.com/#1Panel-dev/SQLBot&Date)

backend/apps/chat/task/llm.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,8 @@ def generate_filter(self, sql: str, tables: List):
536536
).first()
537537
if obj is not None:
538538
res.append(transRecord2DTO(self.session, permission))
539-
wheres = transFilterTree(self.session, res, self.ds)
540-
filters.append({"table": table.table_name, "filter": wheres})
539+
where_str = transFilterTree(self.session, res, self.ds)
540+
filters.append({"table": table.table_name, "filter": where_str})
541541

542542
filter = json.dumps(filters, ensure_ascii=False)
543543

@@ -662,6 +662,16 @@ def check_save_chart(self, res: str) -> Dict[str, Any]:
662662
if data['type'] and data['type'] != 'error':
663663
# todo type check
664664
chart = data
665+
if chart.get('columns'):
666+
for v in chart.get('columns'):
667+
v['value'] = v.get('value').lower()
668+
if chart.get('axis'):
669+
if chart.get('axis').get('x'):
670+
chart.get('axis').get('x')['value'] = chart.get('axis').get('x').get('value').lower()
671+
if chart.get('axis').get('y'):
672+
chart.get('axis').get('y')['value'] = chart.get('axis').get('y').get('value').lower()
673+
if chart.get('axis').get('series'):
674+
chart.get('axis').get('series')['value'] = chart.get('axis').get('series').get('value').lower()
665675
elif data['type'] == 'error':
666676
message = data['reason']
667677
error = True

backend/apps/datasource/api/datasource.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from apps.db.engine import create_table, get_data_engine, insert_data
1010
from common.core.deps import SessionDep, CurrentUser, Trans
11+
from common.utils.utils import SQLBotLogUtil
1112
from ..crud.datasource import get_datasource_list, check_status, create_ds, update_ds, delete_ds, getTables, getFields, \
1213
execSql, update_table_and_fields, getTablesByDs, chooseTables, preview, updateTable, updateField, get_ds, fieldEnum
1314
from ..crud.field import get_fields_by_table_id
@@ -17,6 +18,11 @@
1718
router = APIRouter(tags=["datasource"], prefix="/datasource")
1819
path = "/opt/sqlbot/data/excel"
1920

21+
@router.get("/ws/{oid}", include_in_schema=False)
22+
async def query_by_oid(session: SessionDep, user: CurrentUser, oid: int) -> List[CoreDatasource]:
23+
if not user.isAdmin:
24+
raise Exception("no permission to execute")
25+
return get_datasource_list(session=session, user=user, oid=oid)
2026

2127
@router.get("/list")
2228
async def datasource_list(session: SessionDep, user: CurrentUser):
@@ -60,7 +66,11 @@ async def get_tables(session: SessionDep, id: int):
6066

6167
@router.post("/getTablesByConf")
6268
async def get_tables_by_conf(session: SessionDep, ds: CoreDatasource):
63-
return getTablesByDs(session, ds)
69+
try:
70+
return getTablesByDs(session, ds)
71+
except Exception as e:
72+
SQLBotLogUtil.error(f"get table failed: {e}")
73+
raise HTTPException(status_code=500, detail=f'Get table Failed: {e.args}')
6474

6575

6676
@router.post("/getFields/{id}/{table_name}")
@@ -100,7 +110,11 @@ async def edit_field(session: SessionDep, field: CoreField):
100110

101111
@router.post("/previewData/{id}")
102112
async def edit_local(session: SessionDep, current_user: CurrentUser, id: int, data: TableObj):
103-
return preview(session, current_user, id, data)
113+
try:
114+
return preview(session, current_user, id, data)
115+
except Exception as e:
116+
SQLBotLogUtil.error(f"Preview failed: {e}")
117+
raise HTTPException(status_code=500, detail=f'Preview Failed: {e.args}')
104118

105119

106120
@router.post("/fieldEnum/{id}")

backend/apps/datasource/crud/datasource.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import datetime
22
import json
3-
from typing import List
3+
from typing import List, Optional
44

55
from fastapi import HTTPException
66
from sqlalchemy import and_, text, cast, or_, func
@@ -25,10 +25,13 @@
2525
DatasourceConf, TableAndFields
2626

2727

28-
def get_datasource_list(session: SessionDep, user: CurrentUser):
29-
oid = user.oid if user.oid is not None else 1
30-
return session.query(CoreDatasource).filter(CoreDatasource.oid == oid).order_by(
31-
func.convert_to(CoreDatasource.name, 'gbk')).all()
28+
def get_datasource_list(session: SessionDep, user: CurrentUser, oid: Optional[int] = None) -> List[CoreDatasource]:
29+
current_oid = user.oid if user.oid is not None else 1
30+
if user.isAdmin and oid:
31+
current_oid = oid
32+
return session.exec(select(CoreDatasource).where(CoreDatasource.oid == current_oid).order_by(
33+
func.convert_to(CoreDatasource.name, 'gbk'))).all()
34+
3235

3336

3437
def get_ds(session: SessionDep, id: int):
@@ -134,7 +137,7 @@ def getTables(session: SessionDep, id: int):
134137

135138

136139
def getTablesByDs(session: SessionDep, ds: CoreDatasource):
137-
check_status(session, ds, True)
140+
# check_status(session, ds, True)
138141
tables = get_tables(ds)
139142
return tables
140143

@@ -241,7 +244,7 @@ def updateField(session: SessionDep, field: CoreField):
241244

242245
def preview(session: SessionDep, current_user: CurrentUser, id: int, data: TableObj):
243246
ds = session.query(CoreDatasource).filter(CoreDatasource.id == id).first()
244-
check_status(session, ds, True)
247+
# check_status(session, ds, True)
245248

246249
if data.fields is None or len(data.fields) == 0:
247250
return {"fields": [], "data": [], "sql": ''}
@@ -278,8 +281,8 @@ def preview(session: SessionDep, current_user: CurrentUser, id: int, data: Table
278281
).first()
279282
if obj is not None:
280283
res.append(transRecord2DTO(session, permission))
281-
wheres = transFilterTree(session, res, ds)
282-
where = (' where ' + wheres) if wheres is not None and wheres != '' else ''
284+
where_str = transFilterTree(session, res, ds)
285+
where = (' where ' + where_str) if where_str is not None and where_str != '' else ''
283286

284287
fields = [f.field_name for f in f_list]
285288
if fields is None or len(fields) == 0:

backend/apps/db/db.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -254,20 +254,17 @@ def get_fields(ds: CoreDatasource, table_name: str = None):
254254

255255

256256
def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str):
257-
session = get_session(ds)
258-
result = session.execute(text(sql))
259-
try:
260-
columns = result.keys()._keys
261-
res = result.fetchall()
262-
result_list = [
263-
{columns[i]: float(value) if isinstance(value, Decimal) else value for i, value in enumerate(tuple_item)}
264-
for tuple_item in res
265-
]
266-
return {"fields": columns, "data": result_list, "sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))}
267-
except Exception as ex:
268-
raise ex
269-
finally:
270-
if result is not None:
271-
result.close()
272-
if session is not None:
273-
session.close()
257+
with get_session(ds) as session:
258+
with session.execute(text(sql)) as result:
259+
try:
260+
columns = result.keys()._keys
261+
res = result.fetchall()
262+
result_list = [
263+
{columns[i]: float(value) if isinstance(value, Decimal) else value for i, value in
264+
enumerate(tuple_item)}
265+
for tuple_item in res
266+
]
267+
return {"fields": columns, "data": result_list,
268+
"sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))}
269+
except Exception as ex:
270+
raise ex

backend/apps/system/api/aimodel.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
11
import json
22
from typing import List, Union
3+
from apps.ai_model.model_factory import LLMConfig, LLMFactory
34
from apps.system.schemas.ai_model_schema import AiModelConfigItem, AiModelCreator, AiModelEditor, AiModelGridItem
45
from fastapi import APIRouter, Query
56
from sqlmodel import func, select, update
67

78
from apps.system.models.system_model import AiModelDetail
8-
from common.core.deps import SessionDep
9+
from common.core.deps import SessionDep, Trans
910
from common.utils.time import get_timestamp
11+
from common.utils.utils import SQLBotLogUtil
1012

1113
router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
1214

15+
@router.post("/status")
16+
async def check_llm(info: AiModelCreator, trans: Trans):
17+
try:
18+
additional_params = {item.key: item.val for item in info.config_list}
19+
config = LLMConfig(
20+
model_type="openai",
21+
model_name=info.base_model,
22+
api_key=info.api_key,
23+
api_base_url=info.api_domain,
24+
additional_params=additional_params,
25+
)
26+
llm_instance = LLMFactory.create_llm(config)
27+
result = llm_instance.llm.invoke("who are you?")
28+
SQLBotLogUtil.info(f"check_llm result: {result}")
29+
except Exception as e:
30+
SQLBotLogUtil.error(f"Error checking LLM: {e}")
31+
raise Exception(trans('i18n_llm.validate_error', msg = str(e)))
32+
1333
@router.get("", response_model=list[AiModelGridItem])
1434
async def query(
1535
session: SessionDep,
@@ -74,19 +94,20 @@ async def update_model(
7494
data["config"] = json.dumps([item.model_dump(exclude_unset=True) for item in editor.config_list])
7595
data.pop("config_list", None)
7696
db_model = session.get(AiModelDetail, id)
77-
update_data = AiModelDetail.model_validate(data)
78-
db_model.sqlmodel_update(update_data)
97+
#update_data = AiModelDetail.model_validate(data)
98+
db_model.sqlmodel_update(data)
7999
session.add(db_model)
80100
session.commit()
81101

82102
@router.delete("/{id}")
83103
async def delete_model(
84104
session: SessionDep,
105+
trans: Trans,
85106
id: int
86107
):
87108
item = session.get(AiModelDetail, id)
88109
if item.default_model:
89-
raise RuntimeError(f"Can not delete [${item.name}], because it is default model!")
110+
raise Exception(trans('i18n_llm.delete_default_error', key = item.name))
90111
session.delete(item)
91112
session.commit()
92113

backend/apps/system/api/login.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from fastapi import APIRouter, Depends, HTTPException
33
from fastapi.security import OAuth2PasswordRequestForm
44
from apps.system.schemas.system_schema import BaseUserDTO
5-
from common.core.deps import SessionDep
5+
from common.core.deps import SessionDep, Trans
66
from ..crud.user import authenticate
77
from common.core.security import create_access_token
88
from datetime import timedelta
@@ -13,14 +13,16 @@
1313
@router.post("/access-token")
1414
def local_login(
1515
session: SessionDep,
16+
trans: Trans,
1617
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
1718
) -> Token:
1819
user: BaseUserDTO = authenticate(session=session, account=form_data.username, password=form_data.password)
1920
if not user:
20-
raise HTTPException(status_code=400, detail="Incorrect account or password")
21-
21+
raise HTTPException(status_code=400, detail=trans('i18n_login.account_pwd_error'))
2222
if not user.oid or user.oid == 0:
23-
raise HTTPException(status_code=400, detail="No associated workspace, Please contact the administrator")
23+
raise HTTPException(status_code=400, detail=trans('i18n_login.no_associated_ws', msg = trans('i18n_concat_admin')))
24+
if user.status != 1:
25+
raise HTTPException(status_code=400, detail=trans('i18n_login.user_disable', msg = trans('i18n_concat_admin')))
2426
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
2527
user_dict = user.to_dict()
2628
return Token(access_token=create_access_token(

backend/apps/system/api/user.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Optional
22
from fastapi import APIRouter, HTTPException, Query
33
from sqlmodel import func, or_, select, delete as sqlmodel_delete
4-
from apps.system.crud.user import get_db_user, single_delete, user_ws_options
5-
from apps.system.models.system_model import UserWsModel
4+
from apps.system.crud.user import check_account_exists, check_email_exists, check_email_format, check_pwd_format, get_db_user, single_delete, user_ws_options
5+
from apps.system.models.system_model import UserWsModel, WorkspaceModel
66
from apps.system.models.user import UserModel
77
from apps.system.schemas.auth import CacheName, CacheNamespace
8-
from apps.system.schemas.system_schema import PwdEditor, UserCreator, UserEditor, UserGrid, UserLanguage, UserWs
8+
from apps.system.schemas.system_schema import PwdEditor, UserCreator, UserEditor, UserGrid, UserLanguage, UserStatus, UserWs
99
from common.core.deps import CurrentUser, SessionDep, Trans
1010
from common.core.pagination import Paginator
1111
from common.core.schemas import PaginatedResponse, PaginationParams
@@ -96,10 +96,13 @@ async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Tran
9696

9797
@router.put("/ws/{oid}")
9898
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
99-
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
99+
async def ws_change(session: SessionDep, current_user: CurrentUser, trans:Trans, oid: int):
100100
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
101101
if not any(x.id == oid for x in ws_list):
102-
raise HTTPException(f"oid [{oid}] is invalid!")
102+
db_ws = session.get(WorkspaceModel, oid)
103+
if db_ws:
104+
raise Exception(trans('i18n_user.ws_miss', ws = db_ws.name))
105+
raise Exception(trans('i18n_not_exist', msg = f"{trans('i18n_ws.title')}[{oid}]"))
103106
user_model: UserModel = get_db_user(session = session, user_id = current_user.id)
104107
user_model.oid = oid
105108
session.add(user_model)
@@ -115,7 +118,13 @@ async def query(session: SessionDep, trans: Trans, id: int) -> UserEditor:
115118
return result
116119

117120
@router.post("")
118-
async def create(session: SessionDep, creator: UserCreator):
121+
async def create(session: SessionDep, creator: UserCreator, trans: Trans):
122+
if check_account_exists(session=session, account=creator.account):
123+
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.account')} [{creator.account}]"))
124+
if check_email_exists(session=session, email=creator.email):
125+
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email')} [{creator.email}]"))
126+
if not check_email_format(creator.email):
127+
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email')} [{creator.email}]"))
119128
data = creator.model_dump(exclude_unset=True)
120129
user_model = UserModel.model_validate(data)
121130
#user_model.create_time = get_timestamp()
@@ -138,8 +147,16 @@ async def create(session: SessionDep, creator: UserCreator):
138147

139148
@router.put("")
140149
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id")
141-
async def update(session: SessionDep, editor: UserEditor):
150+
async def update(session: SessionDep, editor: UserEditor, trans: Trans):
142151
user_model: UserModel = get_db_user(session = session, user_id = editor.id)
152+
if not user_model:
153+
raise Exception(f"User with id [{editor.id}] not found!")
154+
if editor.account != user_model.account:
155+
raise Exception(f"account cannot be changed!")
156+
if editor.email != user_model.email and check_email_exists(session=session, account=editor.email):
157+
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email')} [{editor.email}]"))
158+
if not check_email_format(editor.email):
159+
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email')} [{editor.email}]"))
143160
origin_oid: int = user_model.oid
144161
del_stmt = sqlmodel_delete(UserWsModel).where(UserWsModel.uid == editor.id)
145162
session.exec(del_stmt)
@@ -174,31 +191,47 @@ async def batch_del(session: SessionDep, id_list: list[int]):
174191

175192
@router.put("/language")
176193
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
177-
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
194+
async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, language: UserLanguage):
178195
lang = language.language
179196
if lang not in ["zh-CN", "en"]:
180-
return {"message": "Language not supported"}
197+
raise Exception(trans('i18n_user.language_not_support', key = lang))
181198
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
182199
db_user.language = lang
183200
session.add(db_user)
184201
session.commit()
185202

186203
@router.patch("/pwd/{id}")
187204
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
188-
async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
205+
async def pwdReset(session: SessionDep, current_user: CurrentUser, trans: Trans, id: int):
189206
if not current_user.isAdmin:
190-
raise HTTPException('only for admin')
207+
raise Exception(trans('i18n_permission.no_permission', url = " patch[/user/pwd/id],", msg = trans('i18n_permission.only_admin')))
191208
db_user: UserModel = get_db_user(session=session, user_id=id)
192209
db_user.password = default_md5_pwd()
193210
session.add(db_user)
194211
session.commit()
195212

196213
@router.put("/pwd")
197214
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
198-
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, editor: PwdEditor):
215+
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, trans: Trans, editor: PwdEditor):
216+
new_pwd = editor.new_pwd
217+
if not check_pwd_format(new_pwd):
218+
raise Exception(trans('i18n_format_invalid', key = trans('i18n_user.password')))
199219
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
200220
if not verify_md5pwd(editor.pwd, db_user.password):
201-
raise HTTPException("pwd error")
202-
db_user.password = md5pwd(editor.new_pwd)
221+
raise Exception(trans('i18n_error', key = trans('i18n_user.password')))
222+
db_user.password = md5pwd(new_pwd)
223+
session.add(db_user)
224+
session.commit()
225+
226+
@router.patch("/status")
227+
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="statusDto.id")
228+
async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, statusDto: UserStatus):
229+
if not current_user.isAdmin:
230+
raise Exception(trans('i18n_permission.no_permission', url = ", ", msg = trans('i18n_permission.only_admin')))
231+
status = statusDto.status
232+
if status not in [0, 1]:
233+
return {"message": "status not supported"}
234+
db_user: UserModel = get_db_user(session=session, user_id=statusDto.id)
235+
db_user.status = status
203236
session.add(db_user)
204237
session.commit()

0 commit comments

Comments
 (0)