Skip to content

feat[Go]: implement chat write and session-create APIs#15667

Open
hunnyboy1217 wants to merge 1 commit into
infiniflow:mainfrom
hunnyboy1217:feat/go-chat-write-apis
Open

feat[Go]: implement chat write and session-create APIs#15667
hunnyboy1217 wants to merge 1 commit into
infiniflow:mainfrom
hunnyboy1217:feat/go-chat-write-apis

Conversation

@hunnyboy1217
Copy link
Copy Markdown
Contributor

@hunnyboy1217 hunnyboy1217 commented Jun 4, 2026

What problem does this PR solve?

Closes #15665 — ports the remaining Chat write endpoints and session-create from Python (api/apps/restful_apis/chat_api.py) to the Go API layer.

GET /api/v1/chats and GET /api/v1/chats/:chat_id were already done. This PR adds the four missing ones:

Method Path Handler
POST /api/v1/chats CreateChat
PATCH /api/v1/chats/:chat_id PatchChat
DELETE /api/v1/chats/:chat_id DeleteChatByID
POST /api/v1/chats/:chat_id/sessions CreateChatSession

Type of change

  • New Feature (non-breaking change which adds functionality)

Implementation notes

Files changed:

internal/dao/chat.go      – NameConflictExists (case-insensitive dupe check)
internal/service/chat.go  – CreateChat, PatchChat, DeleteChatByID,
                             CreateChatSession + internal helpers
internal/handler/chat.go  – four new handler methods
internal/router/router.go – route registration

Functional parity with Python:

Concern Behaviour
tenant_id guard Returns error if caller supplies tenant_id on create
Name validation Required + non-empty on create; optional but non-empty on PATCH; max 255 UTF-8 bytes
Dataset validation Existence check, caller-ownership check, chunk_num > 0, same embedding model across all datasets
Prompt defaults applyPromptDefaults mirrors _apply_prompt_defaults: system prompt, prologue, parameters, quote, tts, refine_multiturn
Duplicate name Case-insensitive check via NameConflictExists; on PATCH excludes the current chat ID
PATCH merge prompt_config and llm_setting merged with existing values (not replaced), matching Python's deepcopy + update
Ownership guard ensureOwnedChat mirrors _ensure_owned_chat: chat.TenantID == userID && status == "1"
Soft delete status → "0" (same as Python StatusEnum.INVALID.value)
Session create First message seeded with prompt_config.prologue; response renames dialog_id → chat_id and message → messages matching _build_session_response
Chat response buildChatResponseMap mirrors _build_chat_response: resolves kb_idsdataset_ids + kb_names

Ports the following endpoints from Python (chat_api.py) to the Go layer,
closing issue infiniflow#15665:

  POST   /api/v1/chats                   (CreateChat)
  PATCH  /api/v1/chats/:chat_id          (PatchChat)
  DELETE /api/v1/chats/:chat_id          (DeleteChatByID)
  POST   /api/v1/chats/:chat_id/sessions (CreateChatSession)

Changed files:
  internal/dao/chat.go      – NameConflictExists helper (case-insensitive
                               duplicate-name check with optional excludeID)
  internal/service/chat.go  – CreateChat, PatchChat, DeleteChatByID,
                               CreateChatSession; helpers: validateName,
                               validateDatasetIDs, applyPromptDefaults,
                               validatePromptParams, buildChatResponseMap,
                               buildSessionResponseMap, ensureOwnedChat
  internal/handler/chat.go  – CreateChat, PatchChat, DeleteChatByID,
                               CreateChatSession handlers
  internal/router/router.go – route registration for all four endpoints

Functional parity with Python:
  - tenant_id must not be supplied on create
  - name required on create, optional (but non-empty) on patch
  - dataset_ids validated: existence, caller ownership, chunk_num > 0,
    same embedding model across all datasets
  - prompt_config defaults applied on create (system prompt, prologue,
    parameters, quote, tts, refine_multiturn)
  - prompt_config and llm_setting merged (not replaced) on PATCH
  - duplicate name check (case-insensitive) on create and patch
  - ownership guard (_ensure_owned_chat) on patch, delete, session-create
  - delete is soft (status → "0")
  - session create prefills first message with chat's prologue;
    response renames dialog_id → chat_id and message → messages
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements complete chat lifecycle management by adding chat creation, update, deletion, and session-creation endpoints with centralized validation, authorization checks, name-conflict detection, dataset ownership verification, prompt-configuration handling, and consistent JSON response formatting across DAO, service, handler, and routing layers.

Changes

Chat CRUD Operations

Layer / File(s) Summary
DAO & Service Foundation
internal/dao/chat.go, internal/service/chat.go
ChatDAO.NameConflictExists queries for case-insensitive duplicate chat names within a tenant and status; ChatService struct adds chatSessionDAO field and imports encoding/json for session message persistence.
Core Validation & Response Helpers
internal/service/chat.go
Helpers validate dataset ownership/existence/embedding-model consistency (validateDatasetIDs), verify chat ownership and valid status (ensureOwnedChat), construct response maps with dataset_ids/kb_names (buildChatResponseMap), validate chat name length/emptiness (validateName), and dereference string pointers (derefStr).
Create Chat Logic
internal/service/chat.go
CreateChatRequest type and CreateChat method validate name/datasets, resolve tenant and default LLM, apply prompt defaults including {knowledge} handling, validate non-optional prompt parameters, check for duplicate names, create the chat, and return response map; supporting helpers applyPromptDefaults and validatePromptParams manage prompt configuration merging and placeholder validation.
Update & Delete Chat Logic
internal/service/chat.go
PatchChatRequest type and PatchChat method implement PATCH semantics with ownership verification, conditional name revalidation with conflict checks, optional dataset revalidation, and merging of llm_setting/prompt_config with existing values; DeleteChatByID soft-deletes via ownership verification and status update.
Chat Session Creation
internal/service/chat.go
CreateChatSession verifies chat ownership, derives session name (defaulting to "New session"), reads prologue from chat.PromptConfig, initializes messages/reference as JSON, creates and fetches the session, and returns mapped response; buildSessionResponseMap maps dialog_id to chat_id and decodes message JSON.
HTTP Handlers & Routing
internal/handler/chat.go, internal/router/router.go
Four handlers (CreateChat, PatchChat, DeleteChatByID, CreateChatSession) bind requests, retrieve current user, call service methods, and return JSON responses with centralized error mapping: exact "No authorization." message maps to CodeAuthenticationError, others return CodeDataError; CreateChatSession treats request body as optional and defaults session name to "New session" when empty; router registers all four endpoints under the protected /api/v1/chats group.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • infiniflow/ragflow#15665: The PR directly implements the requested chat management endpoints (CreateChat, PatchChat, DeleteChatByID, CreateChatSession) and supporting DAO/service logic that fulfill this subtask's requirements.

Possibly related PRs

  • infiniflow/ragflow#14463: New CreateChatSession flow initializes session messages and derives prologue from prompt_config, which relates to that PR's updates for parsing buildChatConfig fields like max_tokens and stop during session creation.

Suggested labels

size:L

Poem

🐰 A rabbit builds four new routes with care,
Create and patch chats everywhere!
Sessions spring forth with validation true,
Name conflicts caught and datasets too—
CRUD operations, whole and sound! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main objective: implementing chat write endpoints (create, patch, delete) and session-creation APIs in Go, directly matching the changeset scope.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description comprehensively explains the problem being solved, details all four endpoints being added, provides implementation notes with specific file changes, and documents functional parity with the Python implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
internal/service/chat.go (1)

1072-1079: ⚡ Quick win

Redundant database fetch - reuse chat from ensureOwnedChat.

ensureOwnedChat already fetches and returns the chat entity, but the result is discarded (_) and then the same chat is fetched again on line 1076. Reusing the returned value avoids an unnecessary database round-trip.

♻️ Proposed fix
 func (s *ChatService) CreateChatSession(userID, chatID, name string) (map[string]interface{}, error) {
-	if _, err := s.ensureOwnedChat(userID, chatID); err != nil {
-		return nil, err
-	}
-	chat, err := s.chatDAO.GetByID(chatID)
-	if err != nil {
-		return nil, fmt.Errorf("Chat not found!")
-	}
+	chat, err := s.ensureOwnedChat(userID, chatID)
+	if err != nil {
+		return nil, err
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/chat.go` around lines 1072 - 1079, The CreateChatSession
function currently discards the chat returned by ensureOwnedChat and then calls
s.chatDAO.GetByID again; capture the returned chat from ensureOwnedChat (e.g.,
chat, err := s.ensureOwnedChat(userID, chatID)) and use that chat value directly
instead of calling s.chatDAO.GetByID(chatID) to avoid the redundant DB fetch;
ensure you preserve the same error handling/return behavior if ensureOwnedChat
returns an error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/handler/chat.go`:
- Around line 512-518: Do not ignore the binding error from c.ShouldBindJSON:
capture the returned error when binding into body and reject malformed JSON
(e.g., *json.SyntaxError or unexpected EOF) with a 400 response instead of
silently defaulting name; only treat an empty body (io.EOF / no content) as
acceptable and fall back to the default session name. In practice, change the
current anonymous bind call to check err := c.ShouldBindJSON(&body), return
c.JSON/AbortWithStatusJSON(400, ...) for JSON syntax/unmarshal errors (use
errors.As or check error types) and only allow the empty-body case to continue
to the name handling where name := body.Name and default to "New session".

In `@internal/service/chat.go`:
- Around line 756-761: The current code silently ignores errors from
s.userTenantDAO.GetTenantIDsByUserID, which can lead to incorrect authorization;
change the call to capture and handle the error from GetTenantIDsByUserID (check
the returned error variable), and on non-nil error either return/propagate the
error up from the enclosing function or log it and fail authorization explicitly
instead of continuing with an empty userTenantIDs; ensure the rest of the logic
that builds the authorised map (using userTenantIDs and userID) only runs when
the DAO call succeeded.
- Around line 800-814: RemoveChats currently permits deleting chats if
chat.TenantID is in the set returned by
userTenantDAO.GetTenantIDsByUserID(userID), which is more permissive than
ensureOwnedChat; update RemoveChats to apply the same strict owner check as
ensureOwnedChat (i.e., require chat.TenantID == userID and valid status) by
invoking or replicating ensureOwnedChat for each chat being removed (or
explicitly replace the tenant membership check in RemoveChats with the same
tenant equality and status checks used in ensureOwnedChat) so bulk deletion
matches Python bulk_delete_chats/_ensure_owned_chat behavior.

---

Nitpick comments:
In `@internal/service/chat.go`:
- Around line 1072-1079: The CreateChatSession function currently discards the
chat returned by ensureOwnedChat and then calls s.chatDAO.GetByID again; capture
the returned chat from ensureOwnedChat (e.g., chat, err :=
s.ensureOwnedChat(userID, chatID)) and use that chat value directly instead of
calling s.chatDAO.GetByID(chatID) to avoid the redundant DB fetch; ensure you
preserve the same error handling/return behavior if ensureOwnedChat returns an
error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7f38101-a3b9-4f4f-93ee-6201f4c441a0

📥 Commits

Reviewing files that changed from the base of the PR and between 98f2a2e and c4ab648.

📒 Files selected for processing (4)
  • internal/dao/chat.go
  • internal/handler/chat.go
  • internal/router/router.go
  • internal/service/chat.go

Comment thread internal/handler/chat.go
Comment on lines +512 to +518
// body is optional; ignore bind error and fall back to default name
_ = c.ShouldBindJSON(&body)

name := body.Name
if name == "" {
name = "New session"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For github.com/gin-gonic/gin v1.9.1, what error does Context.ShouldBindJSON return for (1) an empty request body and (2) malformed JSON?

💡 Result:

In gin-gonic/gin v1.9.1, the Context.ShouldBindJSON method utilizes the standard library's encoding/json package to decode the request body [1][2]. Consequently, it returns the following errors for the specified scenarios: (1) Empty Request Body: When the request body is empty, ShouldBindJSON returns an io.EOF error [3][4]. This occurs because the underlying json.Unmarshal process attempts to read from the request body stream, which is empty [4]. (2) Malformed JSON: When the JSON is malformed (e.g., missing brackets, trailing commas, or invalid syntax), ShouldBindJSON returns a *json.SyntaxError [1][2]. In some cases involving incomplete or unparseable payloads, it may also return an io.EOF or an "unexpected EOF" error [5]. It is important to note that because ShouldBindJSON reads the request body stream, subsequent attempts to read or bind the body will result in an io.EOF error [4][1][6]. If you need to bind the body multiple times, you should use Context.ShouldBindBodyWithJSON instead, which caches the body in the context [3][6].

Citations:


Reject malformed JSON payloads instead of silently defaulting the session name.

ShouldBindJSON in gin v1.9.1 returns io.EOF for an empty body, but malformed JSON returns *json.SyntaxError (and in some cases an unexpected-EOF-style error). Since the code currently discards the bind error, malformed payloads also fall through and create a session with the default "New session".

💡 Suggested change
 import (
+	"errors"
+	"io"
 	"net/http"
 	"ragflow/internal/common"
 	"strconv"
@@
 	var body struct {
 		Name string `json:"name"`
 	}
-	// body is optional; ignore bind error and fall back to default name
-	_ = c.ShouldBindJSON(&body)
+	// body is optional; malformed JSON should still fail fast
+	if err := c.ShouldBindJSON(&body); err != nil && !errors.Is(err, io.EOF) {
+		c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": err.Error()})
+		return
+	}
 
 	name := body.Name
 	if name == "" {
 		name = "New session"
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// body is optional; ignore bind error and fall back to default name
_ = c.ShouldBindJSON(&body)
name := body.Name
if name == "" {
name = "New session"
}
// body is optional; malformed JSON should still fail fast
if err := c.ShouldBindJSON(&body); err != nil && !errors.Is(err, io.EOF) {
c.JSON(http.StatusOK, gin.H{"code": common.CodeDataError, "data": false, "message": err.Error()})
return
}
name := body.Name
if name == "" {
name = "New session"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/handler/chat.go` around lines 512 - 518, Do not ignore the binding
error from c.ShouldBindJSON: capture the returned error when binding into body
and reject malformed JSON (e.g., *json.SyntaxError or unexpected EOF) with a 400
response instead of silently defaulting name; only treat an empty body (io.EOF /
no content) as acceptable and fall back to the default session name. In
practice, change the current anonymous bind call to check err :=
c.ShouldBindJSON(&body), return c.JSON/AbortWithStatusJSON(400, ...) for JSON
syntax/unmarshal errors (use errors.As or check error types) and only allow the
empty-body case to continue to the name handling where name := body.Name and
default to "New session".

Comment thread internal/service/chat.go
Comment on lines +756 to +761
userTenantIDs, _ := s.userTenantDAO.GetTenantIDsByUserID(userID)
authorised := make(map[string]struct{}, len(userTenantIDs)+1)
for _, tid := range userTenantIDs {
authorised[tid] = struct{}{}
}
authorised[userID] = struct{}{}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Silently ignoring DAO error can mask authorization failures.

If GetTenantIDsByUserID fails (e.g., database connectivity issue), the error is discarded and userTenantIDs will be empty. This causes the authorised set to contain only userID, potentially denying access to datasets owned by the user's other tenants or masking underlying infrastructure problems.

🐛 Proposed fix
-	userTenantIDs, _ := s.userTenantDAO.GetTenantIDsByUserID(userID)
+	userTenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to resolve user tenants: %w", err)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
userTenantIDs, _ := s.userTenantDAO.GetTenantIDsByUserID(userID)
authorised := make(map[string]struct{}, len(userTenantIDs)+1)
for _, tid := range userTenantIDs {
authorised[tid] = struct{}{}
}
authorised[userID] = struct{}{}
userTenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID)
if err != nil {
return nil, fmt.Errorf("failed to resolve user tenants: %w", err)
}
authorised := make(map[string]struct{}, len(userTenantIDs)+1)
for _, tid := range userTenantIDs {
authorised[tid] = struct{}{}
}
authorised[userID] = struct{}{}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/chat.go` around lines 756 - 761, The current code silently
ignores errors from s.userTenantDAO.GetTenantIDsByUserID, which can lead to
incorrect authorization; change the call to capture and handle the error from
GetTenantIDsByUserID (check the returned error variable), and on non-nil error
either return/propagate the error up from the enclosing function or log it and
fail authorization explicitly instead of continuing with an empty userTenantIDs;
ensure the rest of the logic that builds the authorised map (using userTenantIDs
and userID) only runs when the DAO call succeeded.

Comment thread internal/service/chat.go
Comment on lines +800 to +814
// ensureOwnedChat checks that userID is the owner of a valid chat.
// Mirrors Python _ensure_owned_chat.
func (s *ChatService) ensureOwnedChat(userID, chatID string) (*entity.Chat, error) {
chat, err := s.chatDAO.GetByID(chatID)
if err != nil {
return nil, fmt.Errorf("Chat not found!")
}
if chat.Status == nil || *chat.Status != "1" {
return nil, fmt.Errorf("Chat not found!")
}
if chat.TenantID != userID {
return nil, fmt.Errorf("No authorization.")
}
return chat, nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Python implementation for ownership check pattern
rg -n -A5 '_ensure_owned_chat|ensure_owned_chat' --type=py

Repository: infiniflow/ragflow

Length of output: 6875


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="internal/service/chat.go"

# Show ensureOwnedChat implementation and a few nearby call sites/usages
sed -n '780,860p' "$FILE" | cat -n

# Show RemoveChats implementation region (as referenced by original comment)
sed -n '540,680p' "$FILE" | cat -n

# Find ensureOwnedChat call sites to see what is passed as the first argument
rg -n "ensureOwnedChat\(" "$FILE" -n | head -n 50

Repository: infiniflow/ragflow

Length of output: 8664


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="internal/service/chat.go"

# Show the blocks around ensureOwnedChat call sites
sed -n '920,1020p' "$FILE" | cat -n
sed -n '1020,1105p' "$FILE" | cat -n

# Also show function definitions immediately around those line ranges if needed
# (already included by sed, but keep for context)

Repository: infiniflow/ragflow

Length of output: 6853


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "PatchChat\(" internal | head -n 50
rg -n "DeleteChatByID\(" internal | head -n 50
rg -n "RemoveChats\(" internal | head -n 50

Repository: infiniflow/ragflow

Length of output: 942


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="internal/handler/chat.go"

sed -n '200,320p' "$FILE" | cat -n
sed -n '380,520p' "$FILE" | cat -n

Repository: infiniflow/ragflow

Length of output: 9631


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find GetUser implementation
rg -n "func GetUser\\(" internal | head -n 50
# Show GetUser code
# (we'll re-run sed with the exact path after locating it, but try to print likely file if found)

Repository: infiniflow/ragflow

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' internal/handler/common.go | cat -n

Repository: infiniflow/ragflow

Length of output: 1393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find entity.User and entity.Chat definitions
rg -n "type User struct" internal/entity | head -n 50
rg -n "type Chat struct" internal/entity | head -n 50

# Inspect ChatService.CreateChat: find where chat entity is constructed
FILE="internal/service/chat.go"
rg -n "func \\(s \\*ChatService\\) CreateChat\\(" "$FILE" 
# Print a larger window around CreateChat creation (find chatDAO.Create call too)
rg -n "chatDAO\\.Create\\(" "$FILE"

Repository: infiniflow/ragflow

Length of output: 369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect user entity
sed -n '1,120p' internal/entity/user.go | cat -n

# Inspect chat entity
sed -n '1,140p' internal/entity/chat.go | cat -n

# Inspect CreateChat where chat is constructed and chatDAO.Create(chat) is called
sed -n '780,980p' internal/service/chat.go | cat -n

Repository: infiniflow/ragflow

Length of output: 14777


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any Chat construction that sets TenantID explicitly
rg -n "entity\\.Chat\\{[\\s\\S]*TenantID:" internal | head -n 50

# Also search for any assignment to ".TenantID =" related to chat/dialog
rg -n "\\.TenantID\\s*=\\s*" internal | head -n 50

# Search for other chatDAO.Create usages besides internal/service/chat.go
rg -n "chatDAO\\.Create\\(" internal | head -n 50

Repository: infiniflow/ragflow

Length of output: 44


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If earlier rg found nothing, widen search
rg -n "TenantID:" internal | head -n 50 || true
rg -n "\\.TenantID\\b" internal | head -n 50 || true

# Search for direct tenant_id column usage in dialog context
rg -n "tenant_id" internal | head -n 80 || true

# Search for dialog DAO/service creation/update patterns
rg -n "chatDAO\\.Create\\(|dialogDAO\\.|UpdateManyByID\\(|UpdateByID\\(|QueryByTenantIDAndID\\(" internal/service internal/dao internal/handler | head -n 100 || true

Repository: infiniflow/ragflow

Length of output: 19275


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect other chat creation path around the second chatDAO.Create (line ~926)
sed -n '880,980p' internal/service/chat.go | cat -n

# 2) Find Python "remove"/"delete" dialogs and check auth logic used there
PY="api/apps/restful_apis/chat_api.py"
rg -n "RemoveChats|remove|dialog.*rm|delete.*chat|/chats|dialog.*status" "$PY" | head -n 80

# Specifically search for usages of _ensure_owned_chat around delete endpoints, and any tenant membership query
rg -n "_ensure_owned_chat|user_tenant|tenant.*IDs|GetTenant" "$PY" | head -n 80

Repository: infiniflow/ragflow

Length of output: 7000


🏁 Script executed:

#!/bin/bash
set -euo pipefail

PY="api/apps/restful_apis/chat_api.py"

# Show _ensure_owned_chat definition
sed -n '140,190p' "$PY" | cat -n

# Show bulk_delete_chats implementation (route /chats methods=["DELETE"])
sed -n '680,790p' "$PY" | cat -n

Repository: infiniflow/ragflow

Length of output: 7055


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "GetTenantIDsByUserID" internal | head -n 50
# Show the implementation file for GetTenantIDsByUserID
FILE=$(rg -n "GetTenantIDsByUserID" internal -S --files-with-matches | head -n 1)
echo "First match file: $FILE"
sed -n '1,220p' "$FILE" | cat -n

Repository: infiniflow/ragflow

Length of output: 8336


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' internal/dao/user_tenant.go | cat -n

Repository: infiniflow/ragflow

Length of output: 6082


Align RemoveChats authorization with ensureOwnedChat (and Python _ensure_owned_chat)

ensureOwnedChat only allows when chat.TenantID == userID (and status is valid). In contrast, RemoveChats allows when chat.TenantID is in userTenantDAO.GetTenantIDsByUserID(userID) (plus userID), which can permit deleting chats belonging to other tenant IDs the user is a member of. Python’s bulk chat delete (bulk_delete_chats) also uses _ensure_owned_chat, which checks tenant_id=current_user.id, i.e., the stricter behavior.

Update RemoveChats to use the same ownership check as ensureOwnedChat (or explicitly justify/implement the intended member-vs-owner model consistently across single and bulk endpoints).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/chat.go` around lines 800 - 814, RemoveChats currently
permits deleting chats if chat.TenantID is in the set returned by
userTenantDAO.GetTenantIDsByUserID(userID), which is more permissive than
ensureOwnedChat; update RemoveChats to apply the same strict owner check as
ensureOwnedChat (i.e., require chat.TenantID == userID and valid status) by
invoking or replicating ensureOwnedChat for each chat being removed (or
explicitly replace the tenant membership check in RemoveChats with the same
tenant equality and status checks used in ensureOwnedChat) so bulk deletion
matches Python bulk_delete_chats/_ensure_owned_chat behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Subtask]: Implement Chat APIs in Go

1 participant