diff --git a/internal/dao/search.go b/internal/dao/search.go index ca5b26013c..49dfd84242 100644 --- a/internal/dao/search.go +++ b/internal/dao/search.go @@ -56,8 +56,13 @@ func (dao *SearchDAO) ListByTenantIDs(tenantIDs []string, userID string, page, p user.nickname, user.avatar as tenant_avatar `). - Joins("LEFT JOIN user ON search.tenant_id = user.id"). - Where("(search.tenant_id IN ? OR search.tenant_id = ?) AND search.status = ?", tenantIDs, userID, "1") + Joins("LEFT JOIN user ON search.tenant_id = user.id") + + if len(tenantIDs) > 0 { + query = query.Where("(search.tenant_id IN ? OR search.tenant_id = ?) AND search.status = ?", tenantIDs, userID, "1") + } else { + query = query.Where("search.tenant_id = ? AND search.status = ?", userID, "1") + } // Apply keyword filter if keywords != "" { diff --git a/internal/handler/search.go b/internal/handler/search.go index ff58928a23..37303339a7 100644 --- a/internal/handler/search.go +++ b/internal/handler/search.go @@ -20,6 +20,7 @@ import ( "net/http" "ragflow/internal/common" "strconv" + "strings" "github.com/gin-gonic/gin" @@ -50,6 +51,23 @@ func (h *SearchHandler) SetCompletionDependencies(streamLLM *service.ModelProvid h.askService = askService } +func getSearchOwnerIDs(c *gin.Context) []string { + values := c.QueryArray("owner_ids") + if len(values) == 0 { + values = c.QueryArray("owner_id") + } + ownerIDs := make([]string, 0, len(values)) + for _, value := range values { + for _, ownerID := range strings.Split(value, ",") { + ownerID = strings.TrimSpace(ownerID) + if ownerID != "" { + ownerIDs = append(ownerIDs, ownerID) + } + } + } + return ownerIDs +} + // ListSearches list search apps // @Summary List Search Apps // @Description Get list of search apps for the current user with filtering, pagination and sorting @@ -61,9 +79,9 @@ func (h *SearchHandler) SetCompletionDependencies(streamLLM *service.ModelProvid // @Param page_size query int false "items per page" // @Param orderby query string false "order by field (default: create_time)" // @Param desc query bool false "descending order (default: true)" -// @Param request body service.ListSearchAppsRequest true "filter options including owner_ids" +// @Param owner_ids query []string false "owner IDs" // @Success 200 {object} service.ListSearchAppsResponse -// @Router /api/v1/searches [post] +// @Router /api/v1/searches [get] func (h *SearchHandler) ListSearches(c *gin.Context) { user, errorCode, errorMessage := GetUser(c) if errorCode != common.CodeSuccess { @@ -96,9 +114,12 @@ func (h *SearchHandler) ListSearches(c *gin.Context) { desc = descStr != "false" } - // Parse request body for owner_ids + ownerIDs := getSearchOwnerIDs(c) + + // Keep body parsing as a compatibility fallback for existing callers that + // send owner_ids in a GET body. Python reads owner_ids from the query. var req service.ListSearchAppsRequest - if c.Request.ContentLength > 0 { + if len(ownerIDs) == 0 && c.Request.ContentLength > 0 { if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, @@ -106,10 +127,11 @@ func (h *SearchHandler) ListSearches(c *gin.Context) { }) return } + ownerIDs = req.OwnerIDs } // List search apps with filtering - result, err := h.searchService.ListSearches(userID, keywords, page, pageSize, orderby, desc, req.OwnerIDs) + result, err := h.searchService.ListSearches(userID, keywords, page, pageSize, orderby, desc, ownerIDs) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, diff --git a/internal/service/search.go b/internal/service/search.go index 8ee26a616f..0b016f73c1 100644 --- a/internal/service/search.go +++ b/internal/service/search.go @@ -85,25 +85,27 @@ func (s *SearchService) ListSearches(userID string, keywords string, page, pageS var err error if len(ownerIDs) == 0 { - // Get tenant IDs by user ID (joined tenants) - tenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID) - if err != nil { - return nil, err - } - - // Use database pagination - searches, total, err = s.searchDAO.ListByTenantIDs(tenantIDs, userID, page, pageSize, orderby, desc, keywords) + searches, total, err = s.searchDAO.ListByTenantIDs(nil, userID, page, pageSize, orderby, desc, keywords) if err != nil { return nil, err } } else { - // Filter by owner IDs, manual pagination + ownerIDs, err = s.filterAccessibleSearchOwnerIDs(userID, ownerIDs) + if err != nil { + return nil, err + } + if len(ownerIDs) == 0 { + return &ListSearchAppsResponse{ + SearchApps: []map[string]interface{}{}, + Total: 0, + }, nil + } + searches, total, err = s.searchDAO.ListByOwnerIDs(ownerIDs, userID, orderby, desc, keywords) if err != nil { return nil, err } - // Manual pagination if page > 0 && pageSize > 0 { start := (page - 1) * pageSize end := start + pageSize @@ -130,6 +132,39 @@ func (s *SearchService) ListSearches(userID string, keywords string, page, pageS }, nil } +func (s *SearchService) filterAccessibleSearchOwnerIDs(userID string, ownerIDs []string) ([]string, error) { + tenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID) + if err != nil { + return nil, err + } + + allowed := map[string]struct{}{userID: {}} + for _, tenantID := range tenantIDs { + tenantID = strings.TrimSpace(tenantID) + if tenantID != "" { + allowed[tenantID] = struct{}{} + } + } + + filtered := make([]string, 0, len(ownerIDs)) + seen := make(map[string]struct{}, len(ownerIDs)) + for _, ownerID := range ownerIDs { + ownerID = strings.TrimSpace(ownerID) + if ownerID == "" { + continue + } + if _, ok := allowed[ownerID]; !ok { + continue + } + if _, ok := seen[ownerID]; ok { + continue + } + seen[ownerID] = struct{}{} + filtered = append(filtered, ownerID) + } + return filtered, nil +} + // toSearchAppResponse converts search model to response format func (s *SearchService) toSearchAppResponse(search *entity.Search) map[string]interface{} { result := map[string]interface{}{