mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
Merge branch 'main' of https://github.com/dataease/SQLBot
This commit is contained in:
@@ -3,7 +3,7 @@ from typing import Optional
|
||||
from sqlmodel import Session, func, select, delete as sqlmodel_delete
|
||||
from apps.system.models.system_model import UserWsModel, WorkspaceModel
|
||||
from apps.system.schemas.auth import CacheName, CacheNamespace
|
||||
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO, UserWs
|
||||
from apps.system.schemas.system_schema import EMAIL_REGEX, PWD_REGEX, BaseUserDTO, UserInfoDTO, UserWs
|
||||
from common.core.deps import SessionDep
|
||||
from common.core.sqlbot_cache import cache, clear_cache
|
||||
from common.utils.locale import I18n
|
||||
@@ -78,18 +78,7 @@ def check_email_exists(*, session: Session, email: str) -> bool:
|
||||
return session.exec(select(func.count()).select_from(UserModel).where(UserModel.email == email)).one() > 0
|
||||
|
||||
|
||||
# 预编译正则表达式,提高效率
|
||||
EMAIL_REGEX = re.compile(
|
||||
r"^[a-zA-Z0-9]+([._-][a-zA-Z0-9]+)*@"
|
||||
r"([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+"
|
||||
r"[a-zA-Z]{2,}$"
|
||||
)
|
||||
|
||||
PWD_REGEX = re.compile(
|
||||
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)"
|
||||
r"(?=.*[~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./])"
|
||||
r"[A-Za-z\d~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./]{8,20}$"
|
||||
)
|
||||
def check_email_format(email: str) -> bool:
|
||||
return bool(EMAIL_REGEX.fullmatch(email))
|
||||
|
||||
|
||||
@@ -62,7 +62,13 @@ class TokenMiddleware(BaseHTTPMiddleware):
|
||||
try:
|
||||
with Session(engine) as session:
|
||||
session_user = await get_user_info(session = session, user_id = token_data.id)
|
||||
if not session_user:
|
||||
raise Exception(f"User not found with id: {token_data.id}")
|
||||
session_user = UserInfoDTO.model_validate(session_user)
|
||||
if session_user.status != 1:
|
||||
raise Exception(f"User is not active!")
|
||||
if not session_user.oid or session_user.oid == 0:
|
||||
raise Exception(f"User default space is not set!")
|
||||
""" if token_data.oid != session_user.oid:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from common.core.schemas import BaseCreatorDTO
|
||||
import re
|
||||
|
||||
EMAIL_REGEX = re.compile(
|
||||
r"^[a-zA-Z0-9]+([._-][a-zA-Z0-9]+)*@"
|
||||
r"([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+"
|
||||
r"[a-zA-Z]{2,}$"
|
||||
)
|
||||
PWD_REGEX = re.compile(
|
||||
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)"
|
||||
r"(?=.*[~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./])"
|
||||
r"[A-Za-z\d~!@#$%^&*()_+\-={}|:\"<>?`\[\];',./]{8,20}$"
|
||||
)
|
||||
class model_status(BaseModel):
|
||||
status: bool
|
||||
ids: list[int]
|
||||
@@ -13,11 +24,11 @@ class UserLanguage(BaseModel):
|
||||
|
||||
|
||||
class BaseUser(BaseModel):
|
||||
account: str
|
||||
account: str = Field(min_length=1, max_length=100, description="用户账号")
|
||||
oid: int
|
||||
|
||||
|
||||
class BaseUserDTO(BaseUser, BaseCreatorDTO):
|
||||
language: str
|
||||
language: str = Field(pattern=r"^(zh-CN|en)$", default="zh-CN", description="用户语言")
|
||||
password: str
|
||||
status: int = 1
|
||||
def to_dict(self):
|
||||
@@ -26,12 +37,23 @@ class BaseUserDTO(BaseUser, BaseCreatorDTO):
|
||||
"account": self.account,
|
||||
"oid": self.oid
|
||||
}
|
||||
@field_validator("language")
|
||||
def validate_language(cls, lang: str) -> str:
|
||||
if not re.fullmatch(r"^(zh-CN|en)$", lang):
|
||||
raise ValueError("Language must be 'zh-CN' or 'en'")
|
||||
return lang
|
||||
|
||||
class UserCreator(BaseUser):
|
||||
name: str
|
||||
email: str
|
||||
name: str = Field(min_length=1, max_length=100, description="用户名")
|
||||
email: str = Field(min_length=1, max_length=100, description="用户邮箱")
|
||||
status: int = 1
|
||||
oid_list: Optional[list[int]] = None
|
||||
|
||||
""" @field_validator("email")
|
||||
def validate_email(cls, lang: str) -> str:
|
||||
if not re.fullmatch(EMAIL_REGEX, lang):
|
||||
raise ValueError("Email format is invalid!")
|
||||
return lang """
|
||||
|
||||
class UserEditor(UserCreator, BaseCreatorDTO):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33333 5.3335V13.3335H10V5.3335H3.33333ZM11.3333 4.66683V14.0742C11.3333 14.4015 11.0548 14.6668 10.7111 14.6668H2.62222C2.27858 14.6668 2 14.4015 2 14.0742V4.59276C2 4.26548 2.27858 4.00016 2.62222 4.00016H10.6667C11.0349 4.00016 11.3333 4.29864 11.3333 4.66683ZM13.8047 1.52876C13.9254 1.6494 14 1.81607 14 2.00016V10.3335C14 10.5176 13.8508 10.6668 13.6667 10.6668H13C12.8159 10.6668 12.6667 10.5176 12.6667 10.3335V2.66683H6.33333C6.14924 2.66683 6 2.51759 6 2.3335V1.66683C6 1.48273 6.14924 1.3335 6.33333 1.3335H13.3333C13.5174 1.3335 13.6841 1.40812 13.8047 1.52876Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 706 B |
@@ -118,7 +118,8 @@
|
||||
"earlier": "Earlier",
|
||||
"no_time": "No Time",
|
||||
"rename_conversation_title": "Rename conversation title",
|
||||
"conversation_title": "Conversation title"
|
||||
"conversation_title": "Conversation title",
|
||||
"copied": "Copied"
|
||||
},
|
||||
"ds": {
|
||||
"title": "Data Source",
|
||||
@@ -513,4 +514,4 @@
|
||||
"back_community": "Restore to Community Edition",
|
||||
"confirm_tips": "Are you sure to restore to Community Edition?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
"earlier": "更早以前",
|
||||
"no_time": "没有时间",
|
||||
"rename_conversation_title": "重命名对话标题",
|
||||
"conversation_title": "对话标题"
|
||||
"conversation_title": "对话标题",
|
||||
"copied": "已复制"
|
||||
},
|
||||
"ds": {
|
||||
"title": "数据源",
|
||||
@@ -525,4 +526,4 @@
|
||||
"back_community": "还原至社区版",
|
||||
"confirm_tips": "确定还原至社区版?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,9 +168,21 @@ class HttpService {
|
||||
errorMessage = 'Invalid request parameters'
|
||||
break
|
||||
case 401:
|
||||
errorMessage = 'Unauthorized, please login again'
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
errorMessage = error.response?.data?.data?.msg || 'Unauthorized, please login again'
|
||||
// Redirect to login page if needed
|
||||
break
|
||||
ElMessage({
|
||||
message: errorMessage,
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
setTimeout(() => {
|
||||
wsCache.delete('user.token')
|
||||
window.location.reload()
|
||||
}, 1000)
|
||||
return
|
||||
// break
|
||||
case 403:
|
||||
errorMessage = 'Access denied'
|
||||
break
|
||||
|
||||
@@ -15,6 +15,7 @@ import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
|
||||
import icon_into_item_outlined from '@/assets/svg/icon_into-item_outlined.svg'
|
||||
import icon_window_max_outlined from '@/assets/svg/icon_window-max_outlined.svg'
|
||||
import icon_window_mini_outlined from '@/assets/svg/icon_window-mini_outlined.svg'
|
||||
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import SQLComponent from '@/views/chat/component/SQLComponent.vue'
|
||||
|
||||
@@ -189,6 +190,13 @@ function showSql() {
|
||||
function addToDashboard() {
|
||||
console.log('todo')
|
||||
}
|
||||
|
||||
function copy() {
|
||||
if (props.message?.record?.sql) {
|
||||
navigator.clipboard.writeText(props.message.record.sql)
|
||||
ElMessage.info(t('qa.copied'))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -318,12 +326,17 @@ function addToDashboard() {
|
||||
direction="rtl"
|
||||
body-class="chart-sql-drawer-body"
|
||||
>
|
||||
<div>
|
||||
<div class="sql-block">
|
||||
<SQLComponent
|
||||
v-if="message.record?.sql"
|
||||
:sql="message.record?.sql"
|
||||
style="margin-top: 12px"
|
||||
/>
|
||||
<el-button v-if="message.record?.sql" circle class="input-icon" @click="copy">
|
||||
<el-icon size="16">
|
||||
<icon_copy_outlined />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
@@ -490,4 +503,18 @@ function addToDashboard() {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.sql-block {
|
||||
position: relative;
|
||||
|
||||
.input-icon {
|
||||
min-width: unset;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
|
||||
border-color: #dee0e3;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -106,7 +106,13 @@ export class Bar extends BaseG2Chart {
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
|
||||
text: (data: any) => {
|
||||
const value = data[y[0].value]
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return `${value}${_data.isPercent ? '%' : ''}`
|
||||
},
|
||||
transform: [
|
||||
{ type: 'contrastReverse' },
|
||||
{ type: 'exceedAdjust' },
|
||||
|
||||
@@ -95,7 +95,13 @@ export class Column extends BaseG2Chart {
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
|
||||
text: (data: any) => {
|
||||
const value = data[y[0].value]
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return `${value}${_data.isPercent ? '%' : ''}`
|
||||
},
|
||||
position: 'top',
|
||||
transform: [
|
||||
{ type: 'contrastReverse' },
|
||||
|
||||
@@ -61,7 +61,13 @@ export class Line extends BaseG2Chart {
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
|
||||
text: (data: any) => {
|
||||
const value = data[y[0].value]
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return `${value}${_data.isPercent ? '%' : ''}`
|
||||
},
|
||||
style: {
|
||||
dx: -10,
|
||||
dy: -12,
|
||||
|
||||
Reference in New Issue
Block a user