mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: exclude subagent chats from sidebar pagination (#24404)
GetChats now returns only root chats (parent_chat_id IS NULL). A new GetChildChatsByParentIDs query fetches children for visible roots and embeds them in each parent's Children field. The singular getChat endpoint does the same. Archive invariant is one-way: parent archived implies child archived. Parent archive/unarchive cascades via root_chat_id. Individual child archive is permitted; child unarchive while the parent is archived is rejected atomically (row lock on child, re-read parent inside the transaction). Embedded children are filtered by the caller's archive state so individually-archived children stay hidden from active-parent views. Gitsync MarkStale uses GetChatsByWorkspaceIDs directly; MarkStaleParams.OwnerID removed (dead after the switch). Frontend: buildChatTree reads from the embedded children field, WebSocket handlers route child events into the parent's children array, and archiving a child strips it from the parent cache.
This commit is contained in:
committed by
GitHub
parent
df429b7f60
commit
fc2493780f
@@ -2944,6 +2944,14 @@ func (q *querier) GetChatsUpdatedAfter(ctx context.Context, updatedAfter time.Ti
|
||||
return q.db.GetChatsUpdatedAfter(ctx, updatedAfter)
|
||||
}
|
||||
|
||||
func (q *querier) GetChildChatsByParentIDs(ctx context.Context, arg database.GetChildChatsByParentIDsParams) ([]database.GetChildChatsByParentIDsRow, error) {
|
||||
// Each child is independently authorized via post-filter.
|
||||
// The handler calls this after GetChats already authorized
|
||||
// the parent chats, but we still verify read access on
|
||||
// every child row for defense in depth.
|
||||
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetChildChatsByParentIDs)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetConnectionLogsOffset(ctx context.Context, arg database.GetConnectionLogsOffsetParams) ([]database.GetConnectionLogsOffsetRow, error) {
|
||||
// Just like with the audit logs query, shortcut if the user is an owner.
|
||||
err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceConnectionLog)
|
||||
|
||||
@@ -820,6 +820,27 @@ func (s *MethodTestSuite) TestChats() {
|
||||
// No asserts here because SQLFilter.
|
||||
check.Args(params).Asserts()
|
||||
}))
|
||||
s.Run("GetChildChatsByParentIDs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
parentA := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
parentB := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
childA := testutil.Fake(s.T(), faker, database.Chat{
|
||||
ParentChatID: uuid.NullUUID{UUID: parentA.ID, Valid: true},
|
||||
})
|
||||
childB := testutil.Fake(s.T(), faker, database.Chat{
|
||||
ParentChatID: uuid.NullUUID{UUID: parentB.ID, Valid: true},
|
||||
})
|
||||
parentIDs := []uuid.UUID{parentA.ID, parentB.ID}
|
||||
params := database.GetChildChatsByParentIDsParams{
|
||||
ParentIds: parentIDs,
|
||||
Archived: sql.NullBool{Bool: false, Valid: true},
|
||||
}
|
||||
rows := []database.GetChildChatsByParentIDsRow{
|
||||
{Chat: childA},
|
||||
{Chat: childB},
|
||||
}
|
||||
dbm.EXPECT().GetChildChatsByParentIDs(gomock.Any(), params).Return(rows, nil).AnyTimes()
|
||||
check.Args(params).Asserts(childA, policy.ActionRead, childB, policy.ActionRead).Returns(rows)
|
||||
}))
|
||||
s.Run("GetAuthorizedChats", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
params := database.GetChatsParams{}
|
||||
dbm.EXPECT().GetAuthorizedChats(gomock.Any(), params, gomock.Any()).Return([]database.GetChatsRow{}, nil).AnyTimes()
|
||||
|
||||
Reference in New Issue
Block a user