Skip to content
Merged
35 changes: 35 additions & 0 deletions backend/alembic/versions/059_chat_log_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""empty message

Revision ID: db1a95567cbb
Revises: fb2e8dd19158
Create Date: 2025-12-30 12:29:14.290394

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'db1a95567cbb'
down_revision = 'fb2e8dd19158'
branch_labels = None
depends_on = None


def upgrade():
op.create_table('sys_logs_resource',
sa.Column('id', sa.BIGINT(),
sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=999999999999999,
cycle=False, cache=1), autoincrement=True, nullable=False),
sa.Column('resource_id', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('module', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('resource_name', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('log_id', sa.BIGINT(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('sys_logs_resource_pkey'))
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('sys_logs_resource')
# ### end Alembic commands ###
76 changes: 66 additions & 10 deletions backend/apps/chat/api/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from sqlalchemy import and_, select
from starlette.responses import JSONResponse

from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, rename_chat, \
from apps.chat.curd.chat import delete_chat_with_user, get_chart_data_with_user, get_chat_predict_data_with_user, list_chats, get_chat_with_records, create_chat, rename_chat, \
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id, \
format_json_data, format_json_list_data, get_chart_config, list_recent_questions,get_chat as get_chat_exec
format_json_data, format_json_list_data, get_chart_config, list_recent_questions,get_chat as get_chat_exec, rename_chat_with_user
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, AxisObj, QuickCommand, \
ChatInfo, Chat, ChatFinishStep
from apps.chat.task.llm import LLMService
Expand Down Expand Up @@ -52,7 +52,7 @@ def inner():
return await asyncio.to_thread(inner)


@router.get("/record/{chat_record_id}/data", summary=f"{PLACEHOLDER_PREFIX}get_chart_data")
""" @router.get("/record/{chat_record_id}/data", summary=f"{PLACEHOLDER_PREFIX}get_chart_data")
async def chat_record_data(session: SessionDep, chat_record_id: int):
def inner():
data = get_chat_chart_data(chat_record_id=chat_record_id, session=session)
Expand All @@ -67,10 +67,27 @@ def inner():
data = get_chat_predict_data(chat_record_id=chat_record_id, session=session)
return format_json_list_data(data)

return await asyncio.to_thread(inner) """

@router.get("/record/{chat_record_id}/data", summary=f"{PLACEHOLDER_PREFIX}get_chart_data")
async def chat_record_data(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
def inner():
data = get_chart_data_with_user(chat_record_id=chat_record_id, session=session, current_user=current_user)
return format_json_data(data)

return await asyncio.to_thread(inner)


@router.post("/rename", response_model=str, summary=f"{PLACEHOLDER_PREFIX}rename_chat")
@router.get("/record/{chat_record_id}/predict_data", summary=f"{PLACEHOLDER_PREFIX}get_chart_predict_data")
async def chat_predict_data(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
def inner():
data = get_chat_predict_data_with_user(chat_record_id=chat_record_id, session=session, current_user=current_user)
return format_json_list_data(data)

return await asyncio.to_thread(inner)


""" @router.post("/rename", response_model=str, summary=f"{PLACEHOLDER_PREFIX}rename_chat")
@system_log(LogConfig(
operation_type=OperationType.UPDATE,
module=OperationModules.CHAT,
Expand All @@ -83,10 +100,24 @@ async def rename(session: SessionDep, chat: RenameChat):
raise HTTPException(
status_code=500,
detail=str(e)
)
) """

@router.post("/rename", response_model=str, summary=f"{PLACEHOLDER_PREFIX}rename_chat")
@system_log(LogConfig(
operation_type=OperationType.UPDATE,
module=OperationModules.CHAT,
resource_id_expr="chat.id"
))
async def rename(session: SessionDep, current_user: CurrentUser, chat: RenameChat):
try:
return rename_chat_with_user(session=session, current_user=current_user, rename_object=chat)
except Exception as e:
raise HTTPException(
status_code=500,
detail=str(e)
)

@router.delete("/{chart_id}/{brief}", response_model=str, summary=f"{PLACEHOLDER_PREFIX}delete_chat")
""" @router.delete("/{chart_id}/{brief}", response_model=str, summary=f"{PLACEHOLDER_PREFIX}delete_chat")
@system_log(LogConfig(
operation_type=OperationType.DELETE,
module=OperationModules.CHAT,
Expand All @@ -100,8 +131,23 @@ async def delete(session: SessionDep, chart_id: int, brief: str):
raise HTTPException(
status_code=500,
detail=str(e)
)
) """

@router.delete("/{chart_id}/{brief}", response_model=str, summary=f"{PLACEHOLDER_PREFIX}delete_chat")
@system_log(LogConfig(
operation_type=OperationType.DELETE,
module=OperationModules.CHAT,
resource_id_expr="chart_id",
remark_expr="brief"
))
async def delete(session: SessionDep, current_user: CurrentUser, chart_id: int, brief: str):
try:
return delete_chat_with_user(session=session, current_user=current_user, chart_id=chart_id)
except Exception as e:
raise HTTPException(
status_code=500,
detail=str(e)
)

@router.post("/start", response_model=ChatInfo, summary=f"{PLACEHOLDER_PREFIX}start_chat")
@require_permissions(permission=SqlbotPermission(type='ds', keyExpression="create_chat_obj.datasource"))
Expand All @@ -121,6 +167,11 @@ async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat


@router.post("/assistant/start", response_model=ChatInfo, summary=f"{PLACEHOLDER_PREFIX}assistant_start_chat")
@system_log(LogConfig(
operation_type=OperationType.CREATE,
module=OperationModules.CHAT,
result_id_expr="id"
))
async def start_chat(session: SessionDep, current_user: CurrentUser):
try:
return create_chat(session, current_user, CreateChat(origin=2), False)
Expand Down Expand Up @@ -390,15 +441,20 @@ def _err(_e: Exception):
)


@router.get("/record/{chat_record_id}/excel/export", summary=f"{PLACEHOLDER_PREFIX}export_chart_data")
async def export_excel(session: SessionDep, chat_record_id: int, trans: Trans):
@router.get("/record/{chat_record_id}/excel/export/{chat_id}", summary=f"{PLACEHOLDER_PREFIX}export_chart_data")
@system_log(LogConfig(operation_type=OperationType.EXPORT,module=OperationModules.CHAT,resource_id_expr="chat_id",))
async def export_excel(session: SessionDep, current_user: CurrentUser, chat_record_id: int,chat_id: int, trans: Trans):
chat_record = session.get(ChatRecord, chat_record_id)
if not chat_record:
raise HTTPException(
status_code=500,
detail=f"ChatRecord with id {chat_record_id} not found"
)

if chat_record.create_by != current_user.id:
raise HTTPException(
status_code=500,
detail=f"ChatRecord with id {chat_record_id} not Owned by the current user"
)
is_predict_data = chat_record.predict_record_id is not None

_origin_data = format_json_data(get_chat_chart_data(chat_record_id=chat_record_id, session=session))
Expand Down
47 changes: 46 additions & 1 deletion backend/apps/chat/curd/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ def list_recent_questions(session: SessionDep, current_user: CurrentUser, dataso
)
return [record[0] for record in chat_records] if chat_records else []

def rename_chat_with_user(session: SessionDep, current_user: CurrentUser, rename_object: RenameChat) -> str:
chat = session.get(Chat, rename_object.id)
if not chat:
raise Exception(f"Chat with id {rename_object.id} not found")
if chat.create_by != current_user.id:
raise Exception(f"Chat with id {rename_object.id} not Owned by the current user")
chat.brief = rename_object.brief.strip()[:20]
chat.brief_generate = rename_object.brief_generate
session.add(chat)
session.flush()
session.refresh(chat)

brief = chat.brief
session.commit()
return brief

def rename_chat(session: SessionDep, rename_object: RenameChat) -> str:
chat = session.get(Chat, rename_object.id)
Expand Down Expand Up @@ -86,6 +101,17 @@ def delete_chat(session, chart_id) -> str:

return f'Chat with id {chart_id} has been deleted'

def delete_chat_with_user(session, current_user: CurrentUser, chart_id) -> str:
chat = session.query(Chat).filter(Chat.id == chart_id).first()
if not chat:
return f'Chat with id {chart_id} has been deleted'
if chat.create_by != current_user.id:
raise Exception(f"Chat with id {chart_id} not Owned by the current user")
session.delete(chat)
session.commit()

return f'Chat with id {chart_id} has been deleted'


def get_chart_config(session: SessionDep, chart_record_id: int):
stmt = select(ChatRecord.chart).where(and_(ChatRecord.id == chart_record_id))
Expand Down Expand Up @@ -173,6 +199,15 @@ def get_chat_chart_config(session: SessionDep, chat_record_id: int):
pass
return {}

def get_chart_data_with_user(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
stmt = select(ChatRecord.data).where(and_(ChatRecord.id == chat_record_id, ChatRecord.create_by == current_user.id))
res = session.execute(stmt)
for row in res:
try:
return orjson.loads(row.data)
except Exception:
pass
return {}

def get_chat_chart_data(session: SessionDep, chat_record_id: int):
stmt = select(ChatRecord.data).where(and_(ChatRecord.id == chat_record_id))
Expand All @@ -184,6 +219,15 @@ def get_chat_chart_data(session: SessionDep, chat_record_id: int):
pass
return {}

def get_chat_predict_data_with_user(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
stmt = select(ChatRecord.predict_data).where(and_(ChatRecord.id == chat_record_id, ChatRecord.create_by == current_user.id))
res = session.execute(stmt)
for row in res:
try:
return orjson.loads(row.predict_data)
except Exception:
pass
return {}

def get_chat_predict_data(session: SessionDep, chat_record_id: int):
stmt = select(ChatRecord.predict_data).where(and_(ChatRecord.id == chat_record_id))
Expand All @@ -210,7 +254,8 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
chat = session.get(Chat, chart_id)
if not chat:
raise Exception(f"Chat with id {chart_id} not found")

if chat.create_by != current_user.id:
raise Exception(f"Chat with id {chart_id} not Owned by the current user")
chat_info = ChatInfo(**chat.model_dump())

if current_assistant and current_assistant.type in dynamic_ds_types:
Expand Down
15 changes: 11 additions & 4 deletions backend/apps/dashboard/api/dashboard_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ async def list_resource_api(session: SessionDep, dashboard: QueryDashboard, curr


@router.post("/load_resource", summary=f"{PLACEHOLDER_PREFIX}load_resource_api")
async def load_resource_api(session: SessionDep, dashboard: QueryDashboard):
return load_resource(session=session, dashboard=dashboard)
async def load_resource_api(session: SessionDep, current_user: CurrentUser, dashboard: QueryDashboard):
resource_dict = load_resource(session=session, dashboard=dashboard)
if resource_dict and resource_dict.get("create_by") != str(current_user.id):
raise HTTPException(
status_code=403,
detail="You do not have permission to access this resource"
)

return resource_dict


@router.post("/create_resource", response_model=BaseDashboard, summary=f"{PLACEHOLDER_PREFIX}create_resource_api")
Expand All @@ -45,8 +52,8 @@ async def update_resource_api(session: SessionDep, user: CurrentUser, dashboard:
resource_id_expr="resource_id",
remark_expr="name"
))
async def delete_resource_api(session: SessionDep, resource_id: str, name: str):
return delete_resource(session, resource_id)
async def delete_resource_api(session: SessionDep, current_user: CurrentUser, resource_id: str, name: str):
return delete_resource(session, current_user, resource_id)


@router.post("/create_canvas", response_model=BaseDashboard, summary=f"{PLACEHOLDER_PREFIX}create_canvas_api")
Expand Down
9 changes: 7 additions & 2 deletions backend/apps/dashboard/crud/dashboard_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def list_resource(session: SessionDep, dashboard: QueryDashboard, current_user:
nodes = [DashboardBaseResponse(**row) for row in result.mappings()]
tree = build_tree_generic(nodes, root_pid="root")
return tree


def load_resource(session: SessionDep, dashboard: QueryDashboard):
sql = text("""
Expand Down Expand Up @@ -130,7 +130,12 @@ def validate_name(session: SessionDep,user: CurrentUser, dashboard: QueryDashbo
return not session.query(query.exists()).scalar()


def delete_resource(session: SessionDep, resource_id: str):
def delete_resource(session: SessionDep, current_user: CurrentUser, resource_id: str):
coreDashboard = session.get(CoreDashboard, resource_id)
if not coreDashboard:
raise ValueError(f"Resource with id {resource_id} does not exist")
if coreDashboard.create_by != str(current_user.id):
raise ValueError(f"Resource with id {resource_id} not owned by the current user")
sql = text("DELETE FROM core_dashboard WHERE id = :resource_id")
result = session.execute(sql, {"resource_id": resource_id})
session.commit()
Expand Down
3 changes: 3 additions & 0 deletions backend/apps/data_training/api/data_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
enable_training, get_all_data_training, batch_create_training
from apps.data_training.models.data_training_model import DataTrainingInfo
from apps.swagger.i18n import PLACEHOLDER_PREFIX
from apps.system.schemas.permission import SqlbotPermission, require_permissions
from common.core.config import settings
from common.core.deps import SessionDep, CurrentUser, Trans
from common.utils.data_format import DataFormat
Expand Down Expand Up @@ -53,12 +54,14 @@ async def create_or_update(session: SessionDep, current_user: CurrentUser, trans

@router.delete("", summary=f"{PLACEHOLDER_PREFIX}delete_dt")
@system_log(LogConfig(operation_type=OperationType.DELETE, module=OperationModules.DATA_TRAINING,resource_id_expr='id_list'))
@require_permissions(permission=SqlbotPermission(role=['ws_admin']))
async def delete(session: SessionDep, id_list: list[int]):
delete_training(session, id_list)


@router.get("/{id}/enable/{enabled}", summary=f"{PLACEHOLDER_PREFIX}enable_dt")
@system_log(LogConfig(operation_type=OperationType.UPDATE, module=OperationModules.DATA_TRAINING,resource_id_expr='id'))
@require_permissions(permission=SqlbotPermission(role=['ws_admin']))
async def enable(session: SessionDep, id: int, enabled: bool, trans: Trans):
enable_training(session, id, enabled, trans)

Expand Down
Loading