Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
210ab7ce
authored
Jul 17, 2026
by
Xianquan
Committed by
GitHub
Jul 17, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(chat): allow users to update own history metadata (#7325)
parent
70e837d4
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
140 additions
and
4 deletions
+140
-4
projects/app/src/pages/api/core/chat/history/updateHistory.ts
+6
-3
projects/app/test/api/core/chat/history/updateHistory.test.ts
+134
-1
No files found.
projects/app/src/pages/api/core/chat/history/updateHistory.ts
View file @
210ab7ce
...
@@ -3,25 +3,28 @@ import { UpdateHistoryBodySchema } from '@fastgpt/global/openapi/core/chat/histo
...
@@ -3,25 +3,28 @@ import { UpdateHistoryBodySchema } from '@fastgpt/global/openapi/core/chat/histo
import
{
MongoChat
}
from
'@fastgpt/service/core/chat/chatSchema'
;
import
{
MongoChat
}
from
'@fastgpt/service/core/chat/chatSchema'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
{
NextAPI
}
from
'@/service/middleware/entry'
;
import
{
type
ApiRequestProps
}
from
'@fastgpt/next/type'
;
import
{
type
ApiRequestProps
}
from
'@fastgpt/next/type'
;
import
{
WritePermissionVal
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
ReadPermissionVal
,
WritePermissionVal
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
parseApiInput
}
from
'@fastgpt/service/common/zod/requestParseError'
;
import
{
parseApiInput
}
from
'@fastgpt/service/common/zod/requestParseError'
;
import
{
ChatErrEnum
}
from
'@fastgpt/global/common/error/code/chat'
;
import
{
ChatErrEnum
}
from
'@fastgpt/global/common/error/code/chat'
;
import
{
buildChatHistoryMatch
}
from
'@/service/core/chat/history'
;
import
{
buildChatHistoryMatch
}
from
'@/service/core/chat/history'
;
import
{
ChatSourceTypeEnum
}
from
'@fastgpt/global/core/chat/constants'
;
/*
update chat history: title, customTitle, top
*/
/*
* 更新会话标题、用户自定义标题或置顶状态,并限制操作范围为当前用户有权访问的会话。
*/
export
async
function
handler
(
req
:
ApiRequestProps
,
_res
:
NextApiResponse
)
{
export
async
function
handler
(
req
:
ApiRequestProps
,
_res
:
NextApiResponse
)
{
const
{
sourceType
,
sourceId
,
chatId
,
title
,
customTitle
,
top
,
outLinkAuthData
}
=
parseApiInput
({
const
{
sourceType
,
sourceId
,
chatId
,
title
,
customTitle
,
top
,
outLinkAuthData
}
=
parseApiInput
({
req
,
req
,
bodySchema
:
UpdateHistoryBodySchema
bodySchema
:
UpdateHistoryBodySchema
}).
body
;
}).
body
;
// App 历史属于用户个人数据;Skill Edit 历史仍要求资源写权限。
const
per
=
sourceType
===
ChatSourceTypeEnum
.
skillEdit
?
WritePermissionVal
:
ReadPermissionVal
;
const
match
=
await
buildChatHistoryMatch
({
const
match
=
await
buildChatHistoryMatch
({
req
,
req
,
sourceType
,
sourceType
,
sourceId
,
sourceId
,
chatId
,
chatId
,
outLinkAuthData
,
outLinkAuthData
,
per
:
WritePermissionVal
per
});
});
if
(
!
match
)
return
Promise
.
reject
(
ChatErrEnum
.
unAuthChat
);
if
(
!
match
)
return
Promise
.
reject
(
ChatErrEnum
.
unAuthChat
);
...
...
projects/app/test/api/core/chat/history/updateHistory.test.ts
View file @
210ab7ce
...
@@ -11,8 +11,13 @@ import { Call } from '@test/utils/request';
...
@@ -11,8 +11,13 @@ import { Call } from '@test/utils/request';
import
{
describe
,
expect
,
it
,
beforeEach
}
from
'vitest'
;
import
{
describe
,
expect
,
it
,
beforeEach
}
from
'vitest'
;
import
{
MongoResourcePermission
}
from
'@fastgpt/service/support/permission/schema'
;
import
{
MongoResourcePermission
}
from
'@fastgpt/service/support/permission/schema'
;
import
{
AppReadChatLogPerVal
}
from
'@fastgpt/global/support/permission/app/constant'
;
import
{
AppReadChatLogPerVal
}
from
'@fastgpt/global/support/permission/app/constant'
;
import
{
PerResourceTypeEnum
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
PerResourceTypeEnum
,
ReadPermissionVal
}
from
'@fastgpt/global/support/permission/constant'
;
import
{
PublishChannelEnum
}
from
'@fastgpt/global/support/outLink/constant'
;
import
{
PublishChannelEnum
}
from
'@fastgpt/global/support/outLink/constant'
;
import
{
MongoAgentSkills
}
from
'@fastgpt/service/core/ai/skill/model/schema'
;
import
{
AgentSkillSourceEnum
}
from
'@fastgpt/global/core/ai/skill/constants'
;
describe
(
'updateHistory api test'
,
()
=>
{
describe
(
'updateHistory api test'
,
()
=>
{
let
testUser
:
Awaited
<
ReturnType
<
typeof
getUser
>>
;
let
testUser
:
Awaited
<
ReturnType
<
typeof
getUser
>>
;
...
@@ -123,6 +128,134 @@ describe('updateHistory api test', () => {
...
@@ -123,6 +128,134 @@ describe('updateHistory api test', () => {
expect
(
updatedChat
?.
top
).
toBe
(
true
);
expect
(
updatedChat
?.
top
).
toBe
(
true
);
});
});
it
(
'should allow a read-only app member to pin their own history'
,
async
()
=>
{
const
readonlyUser
=
await
getUser
(
`readonly-update-history-
${
getNanoid
(
6
)}
`
,
testUser
.
teamId
);
const
readonlyUserChatId
=
getNanoid
();
await
Promise
.
all
([
MongoResourcePermission
.
create
({
resourceType
:
PerResourceTypeEnum
.
app
,
teamId
:
testUser
.
teamId
,
resourceId
:
appId
,
tmbId
:
readonlyUser
.
tmbId
,
permission
:
ReadPermissionVal
}),
MongoChat
.
create
({
teamId
:
testUser
.
teamId
,
tmbId
:
readonlyUser
.
tmbId
,
sourceType
:
ChatSourceTypeEnum
.
app
,
appId
,
chatId
:
readonlyUserChatId
,
source
:
ChatSourceEnum
.
online
,
title
:
'Readonly user chat'
})
]);
const
res
=
await
Call
<
UpdateHistoryBodyType
,
unknown
>
(
handler
,
{
auth
:
readonlyUser
,
body
:
{
appId
,
chatId
:
readonlyUserChatId
,
top
:
true
}
});
expect
(
res
.
code
).
toBe
(
200
);
expect
(
res
.
error
).
toBeUndefined
();
const
updatedChat
=
await
MongoChat
.
findOne
({
appId
,
chatId
:
readonlyUserChatId
}).
lean
();
expect
(
updatedChat
?.
top
).
toBe
(
true
);
});
it
(
'should reject a read-only app member updating another member history'
,
async
()
=>
{
const
readonlyUser
=
await
getUser
(
`readonly-update-history-
${
getNanoid
(
6
)}
`
,
testUser
.
teamId
);
const
otherUser
=
await
getUser
(
`other-update-history-
${
getNanoid
(
6
)}
`
,
testUser
.
teamId
);
const
otherUserChatId
=
getNanoid
();
await
Promise
.
all
([
MongoResourcePermission
.
create
({
resourceType
:
PerResourceTypeEnum
.
app
,
teamId
:
testUser
.
teamId
,
resourceId
:
appId
,
tmbId
:
readonlyUser
.
tmbId
,
permission
:
ReadPermissionVal
}),
MongoChat
.
create
({
teamId
:
testUser
.
teamId
,
tmbId
:
otherUser
.
tmbId
,
sourceType
:
ChatSourceTypeEnum
.
app
,
appId
,
chatId
:
otherUserChatId
,
source
:
ChatSourceEnum
.
online
,
title
:
'Other user chat'
,
top
:
false
})
]);
const
res
=
await
Call
<
UpdateHistoryBodyType
,
unknown
>
(
handler
,
{
auth
:
readonlyUser
,
body
:
{
appId
,
chatId
:
otherUserChatId
,
top
:
true
}
});
expect
(
res
.
code
).
not
.
toBe
(
200
);
const
unchangedChat
=
await
MongoChat
.
findOne
({
appId
,
chatId
:
otherUserChatId
}).
lean
();
expect
(
unchangedChat
?.
top
).
toBe
(
false
);
});
it
(
'should reject a read-only skill collaborator updating skill edit history'
,
async
()
=>
{
const
readonlyUser
=
await
getUser
(
`readonly-skill-history-
${
getNanoid
(
6
)}
`
,
testUser
.
teamId
);
const
skill
=
await
MongoAgentSkills
.
create
({
name
:
'Readonly Update Skill History'
,
source
:
AgentSkillSourceEnum
.
personal
,
teamId
:
testUser
.
teamId
,
tmbId
:
testUser
.
tmbId
});
const
skillId
=
String
(
skill
.
_id
);
const
skillChatId
=
getNanoid
();
await
Promise
.
all
([
MongoResourcePermission
.
create
({
resourceType
:
PerResourceTypeEnum
.
agentSkill
,
teamId
:
testUser
.
teamId
,
resourceId
:
skillId
,
tmbId
:
readonlyUser
.
tmbId
,
permission
:
ReadPermissionVal
}),
MongoChat
.
create
({
teamId
:
testUser
.
teamId
,
tmbId
:
readonlyUser
.
tmbId
,
sourceType
:
ChatSourceTypeEnum
.
skillEdit
,
appId
:
skillId
,
chatId
:
skillChatId
,
source
:
ChatSourceEnum
.
test
,
top
:
false
})
]);
const
res
=
await
Call
<
UpdateHistoryBodyType
,
unknown
>
(
handler
,
{
auth
:
readonlyUser
,
body
:
{
skillId
,
chatId
:
skillChatId
,
top
:
true
}
});
expect
(
res
.
code
).
not
.
toBe
(
200
);
const
unchangedChat
=
await
MongoChat
.
findOne
({
sourceType
:
ChatSourceTypeEnum
.
skillEdit
,
appId
:
skillId
,
chatId
:
skillChatId
}).
lean
();
expect
(
unchangedChat
?.
top
).
toBe
(
false
);
});
it
(
'should update top status for share history without appId'
,
async
()
=>
{
it
(
'should update top status for share history without appId'
,
async
()
=>
{
const
shareId
=
`share-update-history-
${
getNanoid
()}
`
;
const
shareId
=
`share-update-history-
${
getNanoid
()}
`
;
const
outLinkUid
=
`share-user-
${
getNanoid
()}
`
;
const
outLinkUid
=
`share-user-
${
getNanoid
()}
`
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment