yao/openapi/kb/document.go
Max 748cf253da Refactor extractCollectionIDFromDocID Function
- Simplified the extractCollectionIDFromDocID function by directly returning the first part of the docID, removing unnecessary logic for prefix handling.
- Commented out the previous logic for clarity and potential future reference, maintaining code readability.
2026-01-05 10:47:22 +08:00

376 lines
10 KiB
Go

package kb
import (
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/yaoapp/gou/model"
"github.com/yaoapp/yao/kb"
kbapi "github.com/yaoapp/yao/kb/api"
"github.com/yaoapp/yao/openapi/oauth/authorized"
"github.com/yaoapp/yao/openapi/response"
)
// Document Management Handlers
// ListDocuments lists documents with pagination
func ListDocuments(c *gin.Context) {
// Check if kb.API is available
if !checkKBAPI(c) {
return
}
// Parse pagination parameters
page := 1
if pageStr := c.Query("page"); pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
page = p
}
}
pagesize := 20
if pagesizeStr := c.Query("pagesize"); pagesizeStr != "" {
if ps, err := strconv.Atoi(pagesizeStr); err == nil && ps > 0 && ps <= 100 {
pagesize = ps
}
}
// Parse select parameter
var selectFields []interface{}
if selectParam := strings.TrimSpace(c.Query("select")); selectParam != "" {
requestedFields := strings.Split(selectParam, ",")
for _, field := range requestedFields {
field = strings.TrimSpace(field)
if field != "" && kbapi.AvailableDocumentFields[field] {
selectFields = append(selectFields, field)
}
}
// If no valid fields found, use default
if len(selectFields) == 0 {
selectFields = kbapi.DefaultDocumentFields
}
} else {
selectFields = kbapi.DefaultDocumentFields
}
// Get authorized information
authInfo := authorized.GetInfo(c)
// Build filter for kb.API
filter := &kbapi.ListDocumentsFilter{
Page: page,
PageSize: pagesize,
Keywords: strings.TrimSpace(c.Query("keywords")),
Tag: strings.TrimSpace(c.Query("tag")),
Select: selectFields,
}
// Filter by collection_id
collectionID := strings.TrimSpace(c.Query("collection_id"))
if collectionID != "" {
// Validate collection permission
hasPermission, err := checkCollectionPermission(authInfo, collectionID, true)
if err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: err.Error(),
}
response.RespondWithError(c, response.StatusForbidden, errorResp)
return
}
// 403 Forbidden
if !hasPermission {
errorResp := &response.ErrorResponse{
Code: response.ErrAccessDenied.Code,
ErrorDescription: "Forbidden: No permission to view collection",
}
response.RespondWithError(c, response.StatusForbidden, errorResp)
return
}
filter.CollectionID = collectionID
} else {
// Filter by authorization constraints
filter.AuthFilters = AuthFilter(c, authInfo)
}
// Filter by status (support multiple values separated by comma)
if statusParam := strings.TrimSpace(c.Query("status")); statusParam != "" {
statusList := strings.Split(statusParam, ",")
var statusValues []string
for _, status := range statusList {
status = strings.TrimSpace(status)
if status != "" {
statusValues = append(statusValues, status)
}
}
filter.Status = statusValues
}
// Filter by status_not (exclude specific statuses)
if statusNotParam := strings.TrimSpace(c.Query("status_not")); statusNotParam != "" {
statusNotList := strings.Split(statusNotParam, ",")
var statusNotValues []string
for _, status := range statusNotList {
status = strings.TrimSpace(status)
if status != "" {
statusNotValues = append(statusNotValues, status)
}
}
filter.StatusNot = statusNotValues
}
// Add ordering
sortParam := strings.TrimSpace(c.Query("sort"))
if sortParam == "" {
sortParam = "created_at desc" // Default sort
}
// Parse sort parameter (format: "field1 direction1,field2 direction2")
var orders []model.QueryOrder
sortItems := strings.Split(sortParam, ",")
for _, sortItem := range sortItems {
sortItem = strings.TrimSpace(sortItem)
if sortItem == "" {
continue
}
// Parse each sort item (format: "field direction")
sortParts := strings.Fields(sortItem)
sortField := "created_at" // Default field
sortOrder := "desc" // Default order
if len(sortParts) >= 1 {
sortField = sortParts[0]
}
if len(sortParts) >= 2 {
sortOrder = strings.ToLower(sortParts[1])
}
// Validate sort field
if !kbapi.ValidDocumentSortFields[sortField] {
continue // Skip invalid fields
}
// Validate sort order
if sortOrder != "asc" && sortOrder != "desc" {
sortOrder = "desc" // Default order
}
orders = append(orders, model.QueryOrder{
Column: sortField,
Option: sortOrder,
})
}
// If no valid orders found, use default
if len(orders) == 0 {
orders = []model.QueryOrder{
{Column: "created_at", Option: "desc"},
}
}
filter.Sort = orders
// Query documents using kb.API
result, err := kb.API.ListDocuments(c.Request.Context(), filter)
if err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: "Failed to search documents: " + err.Error(),
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return
}
c.JSON(http.StatusOK, result)
}
// GetDocument gets document details by document ID
func GetDocument(c *gin.Context) {
// Check if kb.API is available
if !checkKBAPI(c) {
return
}
docID := c.Param("docID")
if docID == "" {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Document ID is required",
}
response.RespondWithError(c, response.StatusBadRequest, errorResp)
return
}
// Parse select parameter - same logic as ListDocuments
var selectFields []interface{}
if selectParam := strings.TrimSpace(c.Query("select")); selectParam != "" {
requestedFields := strings.Split(selectParam, ",")
for _, field := range requestedFields {
field = strings.TrimSpace(field)
if field != "" && kbapi.AvailableDocumentFields[field] {
selectFields = append(selectFields, field)
}
}
// If no valid fields found, use default
if len(selectFields) == 0 {
selectFields = kbapi.DefaultDocumentFields
}
} else {
selectFields = kbapi.DefaultDocumentFields
}
// Build params for kb.API
params := &kbapi.GetDocumentParams{
Select: selectFields,
}
// Query single document using kb.API
result, err := kb.API.GetDocument(c.Request.Context(), docID, params)
if err != nil {
if strings.Contains(err.Error(), "document not found") {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Document not found: " + docID,
}
response.RespondWithError(c, response.StatusNotFound, errorResp)
return
}
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: "Failed to get document: " + err.Error(),
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return
}
c.JSON(http.StatusOK, result)
}
// RemoveDocs removes documents by IDs
func RemoveDocs(c *gin.Context) {
// Check if kb.API is available
if !checkKBAPI(c) {
return
}
// Parse document_ids from query parameter (comma-separated string)
docIDsParam := strings.TrimSpace(c.Query("document_ids"))
if docIDsParam == "" {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "document_ids query parameter is required",
}
response.RespondWithError(c, response.StatusBadRequest, errorResp)
return
}
// Split comma-separated document IDs
docIDs := strings.Split(docIDsParam, ",")
var validDocIDs []string
for _, id := range docIDs {
id = strings.TrimSpace(id)
if id != "" {
validDocIDs = append(validDocIDs, id)
}
}
if len(validDocIDs) == 0 {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "No valid document IDs provided",
}
response.RespondWithError(c, response.StatusBadRequest, errorResp)
return
}
// Validate document permissions
authInfo := authorized.GetInfo(c)
checkedCollections := make(map[string]bool)
for _, docID := range validDocIDs {
collectionID := extractCollectionIDFromDocID(docID)
if collectionID == "" {
collectionID = "default"
}
// Skip if already checked
if checkedCollections[collectionID] {
continue
}
checkedCollections[collectionID] = true
// Check update permission
hasPermission, err := checkCollectionPermission(authInfo, collectionID)
if err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: err.Error(),
}
response.RespondWithError(c, response.StatusForbidden, errorResp)
return
}
// 403 Forbidden
if !hasPermission {
errorResp := &response.ErrorResponse{
Code: response.ErrAccessDenied.Code,
ErrorDescription: "Forbidden: No permission to update collection",
}
response.RespondWithError(c, response.StatusForbidden, errorResp)
return
}
}
// Remove documents using kb.API
result, err := kb.API.RemoveDocuments(c.Request.Context(), &kbapi.RemoveDocumentsParams{
DocumentIDs: validDocIDs,
})
if err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: "Failed to remove documents: " + err.Error(),
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return
}
// Return success response with deletion count
c.JSON(http.StatusOK, result)
}
// checkKBAPI checks if kb.API is available
func checkKBAPI(c *gin.Context) bool {
if kb.API == nil {
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: "Knowledge base API not initialized",
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return false
}
return true
}
// extractCollectionIDFromDocID extracts collection ID from document ID
// Document ID format: {prefix}_{collection_id}__{random_id}
func extractCollectionIDFromDocID(docID string) string {
parts := strings.Split(docID, "__")
if len(parts) < 2 {
return ""
}
return parts[0]
// // First part contains prefix_collection_id
// prefix := parts[0]
// // Find the first underscore to skip the prefix
// idx := strings.Index(prefix, "_")
// if idx == -1 {
// return prefix
// }
// return prefix[idx+1:]
}