Skip to content

Commit b42aaef

Browse files
committed
Merge branch 'main' of https://github.com/dataease/SQLBot
2 parents c75fb54 + 2b14322 commit b42aaef

File tree

11 files changed

+107
-28
lines changed

11 files changed

+107
-28
lines changed

backend/apps/system/crud/user.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from sqlmodel import Session, func, select, delete as sqlmodel_delete
44
from apps.system.models.system_model import UserWsModel, WorkspaceModel
55
from apps.system.schemas.auth import CacheName, CacheNamespace
6-
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO, UserWs
6+
from apps.system.schemas.system_schema import EMAIL_REGEX, PWD_REGEX, BaseUserDTO, UserInfoDTO, UserWs
77
from common.core.deps import SessionDep
88
from common.core.sqlbot_cache import cache, clear_cache
99
from common.utils.locale import I18n
@@ -78,18 +78,7 @@ def check_email_exists(*, session: Session, email: str) -> bool:
7878
return session.exec(select(func.count()).select_from(UserModel).where(UserModel.email == email)).one() > 0
7979

8080

81-
# 预编译正则表达式,提高效率
82-
EMAIL_REGEX = re.compile(
83-
r"^[a-zA-Z0-9]+([._-][a-zA-Z0-9]+)*@"
84-
r"([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+"
85-
r"[a-zA-Z]{2,}$"
86-
)
8781

88-
PWD_REGEX = re.compile(
89-
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)"
90-
r"(?=.*[~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./])"
91-
r"[A-Za-z\d~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./]{8,20}$"
92-
)
9382
def check_email_format(email: str) -> bool:
9483
return bool(EMAIL_REGEX.fullmatch(email))
9584

backend/apps/system/middleware/auth.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ async def validateToken(self, token: Optional[str]):
6262
try:
6363
with Session(engine) as session:
6464
session_user = await get_user_info(session = session, user_id = token_data.id)
65+
if not session_user:
66+
raise Exception(f"User not found with id: {token_data.id}")
6567
session_user = UserInfoDTO.model_validate(session_user)
68+
if session_user.status != 1:
69+
raise Exception(f"User is not active!")
70+
if not session_user.oid or session_user.oid == 0:
71+
raise Exception(f"User default space is not set!")
6672
""" if token_data.oid != session_user.oid:
6773
raise HTTPException(
6874
status_code=401,

backend/apps/system/schemas/system_schema.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
from typing import Optional
2-
from pydantic import BaseModel
2+
from pydantic import BaseModel, Field, field_validator
33

44
from common.core.schemas import BaseCreatorDTO
5+
import re
56

7+
EMAIL_REGEX = re.compile(
8+
r"^[a-zA-Z0-9]+([._-][a-zA-Z0-9]+)*@"
9+
r"([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+"
10+
r"[a-zA-Z]{2,}$"
11+
)
12+
PWD_REGEX = re.compile(
13+
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)"
14+
r"(?=.*[~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./])"
15+
r"[A-Za-z\d~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./]{8,20}$"
16+
)
617
class model_status(BaseModel):
718
status: bool
819
ids: list[int]
@@ -13,11 +24,11 @@ class UserLanguage(BaseModel):
1324

1425

1526
class BaseUser(BaseModel):
16-
account: str
27+
account: str = Field(min_length=1, max_length=100, description="用户账号")
1728
oid: int
18-
29+
1930
class BaseUserDTO(BaseUser, BaseCreatorDTO):
20-
language: str
31+
language: str = Field(pattern=r"^(zh-CN|en)$", default="zh-CN", description="用户语言")
2132
password: str
2233
status: int = 1
2334
def to_dict(self):
@@ -26,12 +37,23 @@ def to_dict(self):
2637
"account": self.account,
2738
"oid": self.oid
2839
}
40+
@field_validator("language")
41+
def validate_language(cls, lang: str) -> str:
42+
if not re.fullmatch(r"^(zh-CN|en)$", lang):
43+
raise ValueError("Language must be 'zh-CN' or 'en'")
44+
return lang
2945

3046
class UserCreator(BaseUser):
31-
name: str
32-
email: str
47+
name: str = Field(min_length=1, max_length=100, description="用户名")
48+
email: str = Field(min_length=1, max_length=100, description="用户邮箱")
3349
status: int = 1
3450
oid_list: Optional[list[int]] = None
51+
52+
""" @field_validator("email")
53+
def validate_email(cls, lang: str) -> str:
54+
if not re.fullmatch(EMAIL_REGEX, lang):
55+
raise ValueError("Email format is invalid!")
56+
return lang """
3557

3658
class UserEditor(UserCreator, BaseCreatorDTO):
3759
pass
Lines changed: 3 additions & 0 deletions
Loading

frontend/src/i18n/en.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
"earlier": "Earlier",
119119
"no_time": "No Time",
120120
"rename_conversation_title": "Rename conversation title",
121-
"conversation_title": "Conversation title"
121+
"conversation_title": "Conversation title",
122+
"copied": "Copied"
122123
},
123124
"ds": {
124125
"title": "Data Source",
@@ -513,4 +514,4 @@
513514
"back_community": "Restore to Community Edition",
514515
"confirm_tips": "Are you sure to restore to Community Edition?"
515516
}
516-
}
517+
}

frontend/src/i18n/zh-CN.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@
121121
"earlier": "更早以前",
122122
"no_time": "没有时间",
123123
"rename_conversation_title": "重命名对话标题",
124-
"conversation_title": "对话标题"
124+
"conversation_title": "对话标题",
125+
"copied": "已复制"
125126
},
126127
"ds": {
127128
"title": "数据源",
@@ -525,4 +526,4 @@
525526
"back_community": "还原至社区版",
526527
"confirm_tips": "确定还原至社区版?"
527528
}
528-
}
529+
}

frontend/src/utils/request.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,21 @@ class HttpService {
168168
errorMessage = 'Invalid request parameters'
169169
break
170170
case 401:
171-
errorMessage = 'Unauthorized, please login again'
171+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
172+
// @ts-ignore
173+
errorMessage = error.response?.data?.data?.msg || 'Unauthorized, please login again'
172174
// Redirect to login page if needed
173-
break
175+
ElMessage({
176+
message: errorMessage,
177+
type: 'error',
178+
showClose: true,
179+
})
180+
setTimeout(() => {
181+
wsCache.delete('user.token')
182+
window.location.reload()
183+
}, 1000)
184+
return
185+
// break
174186
case 403:
175187
errorMessage = 'Access denied'
176188
break

frontend/src/views/chat/chat-block/ChartBlock.vue

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
1515
import icon_into_item_outlined from '@/assets/svg/icon_into-item_outlined.svg'
1616
import icon_window_max_outlined from '@/assets/svg/icon_window-max_outlined.svg'
1717
import icon_window_mini_outlined from '@/assets/svg/icon_window-mini_outlined.svg'
18+
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
1819
import { useI18n } from 'vue-i18n'
1920
import SQLComponent from '@/views/chat/component/SQLComponent.vue'
2021
@@ -189,6 +190,13 @@ function showSql() {
189190
function addToDashboard() {
190191
console.log('todo')
191192
}
193+
194+
function copy() {
195+
if (props.message?.record?.sql) {
196+
navigator.clipboard.writeText(props.message.record.sql)
197+
ElMessage.info(t('qa.copied'))
198+
}
199+
}
192200
</script>
193201

194202
<template>
@@ -318,12 +326,17 @@ function addToDashboard() {
318326
direction="rtl"
319327
body-class="chart-sql-drawer-body"
320328
>
321-
<div>
329+
<div class="sql-block">
322330
<SQLComponent
323331
v-if="message.record?.sql"
324332
:sql="message.record?.sql"
325333
style="margin-top: 12px"
326334
/>
335+
<el-button v-if="message.record?.sql" circle class="input-icon" @click="copy">
336+
<el-icon size="16">
337+
<icon_copy_outlined />
338+
</el-icon>
339+
</el-button>
327340
</div>
328341
</el-drawer>
329342
</div>
@@ -490,4 +503,18 @@ function addToDashboard() {
490503
margin-top: 16px;
491504
}
492505
}
506+
507+
.sql-block {
508+
position: relative;
509+
510+
.input-icon {
511+
min-width: unset;
512+
position: absolute;
513+
top: 12px;
514+
right: 12px;
515+
516+
border-color: #dee0e3;
517+
box-shadow: 0px 4px 8px 0px #1f23291a;
518+
}
519+
}
493520
</style>

frontend/src/views/chat/component/charts/Bar.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ export class Bar extends BaseG2Chart {
106106
},
107107
labels: [
108108
{
109-
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
109+
text: (data: any) => {
110+
const value = data[y[0].value]
111+
if (value === undefined || value === null) {
112+
return ''
113+
}
114+
return `${value}${_data.isPercent ? '%' : ''}`
115+
},
110116
transform: [
111117
{ type: 'contrastReverse' },
112118
{ type: 'exceedAdjust' },

frontend/src/views/chat/component/charts/Column.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ export class Column extends BaseG2Chart {
9595
},
9696
labels: [
9797
{
98-
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
98+
text: (data: any) => {
99+
const value = data[y[0].value]
100+
if (value === undefined || value === null) {
101+
return ''
102+
}
103+
return `${value}${_data.isPercent ? '%' : ''}`
104+
},
99105
position: 'top',
100106
transform: [
101107
{ type: 'contrastReverse' },

0 commit comments

Comments
 (0)