mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-04 01:07:48 +08:00
RAGFlow go API server (#13240)
# RAGFlow Go Implementation Plan 🚀 This repository tracks the progress of porting RAGFlow to Go. We'll implement core features and provide performance comparisons between Python and Go versions. ## Implementation Checklist - [x] User Management APIs - [x] Dataset Management Operations - [x] Retrieval Test - [x] Chat Management Operations - [x] Infinity Go SDK --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com> Co-authored-by: Yingfeng Zhang <yingfeng.zhang@gmail.com>
This commit is contained in:
314
internal/handler/chat.go
Normal file
314
internal/handler/chat.go
Normal file
@ -0,0 +1,314 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// ChatHandler chat handler
|
||||
type ChatHandler struct {
|
||||
chatService *service.ChatService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewChatHandler create chat handler
|
||||
func NewChatHandler(chatService *service.ChatService, userService *service.UserService) *ChatHandler {
|
||||
return &ChatHandler{
|
||||
chatService: chatService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListChats list chats
|
||||
// @Summary List Chats
|
||||
// @Description Get list of chats (dialogs) for the current user
|
||||
// @Tags chat
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} service.ListChatsResponse
|
||||
// @Router /v1/dialog/list [get]
|
||||
func (h *ChatHandler) ListChats(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// List chats - default to valid status "1" (same as Python StatusEnum.VALID.value)
|
||||
result, err := h.chatService.ListChats(userID, "1")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// ListChatsNext list chats with advanced filtering and pagination
|
||||
// @Summary List Chats Next
|
||||
// @Description Get list of chats with filtering, pagination and sorting (equivalent to list_dialogs_next)
|
||||
// @Tags chat
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param keywords query string false "search keywords"
|
||||
// @Param page query int false "page number"
|
||||
// @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.ListChatsNextRequest true "filter options including owner_ids"
|
||||
// @Success 200 {object} service.ListChatsNextResponse
|
||||
// @Router /v1/dialog/next [post]
|
||||
func (h *ChatHandler) ListChatsNext(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse query parameters
|
||||
keywords := c.Query("keywords")
|
||||
|
||||
page := 0
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
|
||||
pageSize := 0
|
||||
if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
|
||||
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
|
||||
pageSize = ps
|
||||
}
|
||||
}
|
||||
|
||||
orderby := c.DefaultQuery("orderby", "create_time")
|
||||
|
||||
desc := true
|
||||
if descStr := c.Query("desc"); descStr != "" {
|
||||
desc = descStr != "false"
|
||||
}
|
||||
|
||||
// Parse request body for owner_ids
|
||||
var req service.ListChatsNextRequest
|
||||
if c.Request.ContentLength > 0 {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// List chats with advanced filtering
|
||||
result, err := h.chatService.ListChatsNext(userID, keywords, page, pageSize, orderby, desc, req.OwnerIDs)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// SetDialog create or update a dialog
|
||||
// @Summary Set Dialog
|
||||
// @Description Create or update a dialog (chat). If dialog_id is provided, updates existing dialog; otherwise creates new one.
|
||||
// @Tags chat
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.SetDialogRequest true "dialog configuration"
|
||||
// @Success 200 {object} service.SetDialogResponse
|
||||
// @Router /v1/dialog/set [post]
|
||||
func (h *ChatHandler) SetDialog(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse request body
|
||||
var req service.SetDialogRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate required field: prompt_config
|
||||
if req.PromptConfig == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "prompt_config is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call service to set dialog
|
||||
result, err := h.chatService.SetDialog(userID, &req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveDialogsRequest remove dialogs request
|
||||
type RemoveDialogsRequest struct {
|
||||
DialogIDs []string `json:"dialog_ids" binding:"required"`
|
||||
}
|
||||
|
||||
// RemoveChats remove/delete dialogs (soft delete by setting status to invalid)
|
||||
// @Summary Remove Dialogs
|
||||
// @Description Remove dialogs by setting their status to invalid. Only the owner of the dialog can perform this operation.
|
||||
// @Tags chat
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body RemoveDialogsRequest true "dialog IDs to remove"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/dialog/rm [post]
|
||||
func (h *ChatHandler) RemoveChats(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse request body
|
||||
var req RemoveDialogsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call service to remove dialogs
|
||||
if err := h.chatService.RemoveChats(userID, req.DialogIDs); err != nil {
|
||||
// Check if it's an authorization error
|
||||
if err.Error() == "only owner of chat authorized for this operation" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"code": 403,
|
||||
"data": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": true,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
377
internal/handler/chat_session.go
Normal file
377
internal/handler/chat_session.go
Normal file
@ -0,0 +1,377 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// ChatSessionHandler chat session (conversation) handler
|
||||
type ChatSessionHandler struct {
|
||||
chatSessionService *service.ChatSessionService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewChatSessionHandler create chat session handler
|
||||
func NewChatSessionHandler(chatSessionService *service.ChatSessionService, userService *service.UserService) *ChatSessionHandler {
|
||||
return &ChatSessionHandler{
|
||||
chatSessionService: chatSessionService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// SetChatSession create or update a chat session
|
||||
// @Summary Set chat session
|
||||
// @Description Create or update a chat session. If is_new is true, creates new chat session; otherwise updates existing one.
|
||||
// @Tags chat_session
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.SetChatSessionRequest true "chat session configuration"
|
||||
// @Success 200 {object} service.SetChatSessionResponse
|
||||
// @Router /v1/conversation/set [post]
|
||||
func (h *ChatSessionHandler) SetChatSession(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse request body
|
||||
var req service.SetChatSessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call service to set chat session
|
||||
result, err := h.chatSessionService.SetChatSession(userID, &req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveChatSessionsRequest remove chat sessions request
|
||||
type RemoveChatSessionsRequest struct {
|
||||
ConversationIDs []string `json:"conversation_ids" binding:"required"`
|
||||
}
|
||||
|
||||
// RemoveChatSessions remove/delete chat sessions
|
||||
// @Summary Remove Chat Sessions
|
||||
// @Description Remove chat sessions by their IDs. Only the owner of the chat session can perform this operation.
|
||||
// @Tags chat_session
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body RemoveChatSessionsRequest true "chat session IDs to remove"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/conversation/rm [post]
|
||||
func (h *ChatSessionHandler) RemoveChatSessions(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse request body
|
||||
var req RemoveChatSessionsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call service to remove chat sessions
|
||||
if err := h.chatSessionService.RemoveChatSessions(userID, req.ConversationIDs); err != nil {
|
||||
// Check if it's an authorization error
|
||||
if err.Error() == "Only owner of chat session authorized for this operation" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"code": 403,
|
||||
"data": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": true,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// ListChatSessions list chat sessions for a dialog
|
||||
// @Summary List Chat Sessions
|
||||
// @Description Get list of chat sessions for a specific dialog
|
||||
// @Tags chat_session
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param dialog_id query string true "dialog ID"
|
||||
// @Success 200 {object} service.ListChatSessionsResponse
|
||||
// @Router /v1/conversation/list [get]
|
||||
func (h *ChatSessionHandler) ListChatSessions(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Get dialog_id from query parameter
|
||||
dialogID := c.Query("dialog_id")
|
||||
if dialogID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "dialog_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call service to list chat sessions
|
||||
result, err := h.chatSessionService.ListChatSessions(userID, dialogID)
|
||||
if err != nil {
|
||||
// Check if it's an authorization error
|
||||
if err.Error() == "Only owner of dialog authorized for this operation" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"code": 403,
|
||||
"data": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result.Sessions,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// CompletionRequest completion request
|
||||
type CompletionRequest struct {
|
||||
ConversationID string `json:"conversation_id" binding:"required"`
|
||||
Messages []map[string]interface{} `json:"messages" binding:"required"`
|
||||
LLMID string `json:"llm_id,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
}
|
||||
|
||||
// Completion chat completion
|
||||
// @Summary Chat Completion
|
||||
// @Description Send messages to the chat model and get a response. Supports streaming and non-streaming modes.
|
||||
// @Tags chat_session
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body CompletionRequest true "completion request"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/conversation/completion [post]
|
||||
func (h *ChatSessionHandler) Completion(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse request body
|
||||
var req CompletionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Build chat model config
|
||||
chatModelConfig := make(map[string]interface{})
|
||||
if req.Temperature != 0 {
|
||||
chatModelConfig["temperature"] = req.Temperature
|
||||
}
|
||||
if req.TopP != 0 {
|
||||
chatModelConfig["top_p"] = req.TopP
|
||||
}
|
||||
if req.FrequencyPenalty != 0 {
|
||||
chatModelConfig["frequency_penalty"] = req.FrequencyPenalty
|
||||
}
|
||||
if req.PresencePenalty != 0 {
|
||||
chatModelConfig["presence_penalty"] = req.PresencePenalty
|
||||
}
|
||||
if req.MaxTokens != 0 {
|
||||
chatModelConfig["max_tokens"] = req.MaxTokens
|
||||
}
|
||||
|
||||
// Process messages - filter out system messages and initial assistant messages
|
||||
var processedMessages []map[string]interface{}
|
||||
for i, m := range req.Messages {
|
||||
role, _ := m["role"].(string)
|
||||
if role == "system" {
|
||||
continue
|
||||
}
|
||||
if role == "assistant" && len(processedMessages) == 0 {
|
||||
continue
|
||||
}
|
||||
processedMessages = append(processedMessages, m)
|
||||
_ = i
|
||||
}
|
||||
|
||||
// Get last message ID if present
|
||||
var messageID string
|
||||
if len(processedMessages) > 0 {
|
||||
if id, ok := processedMessages[len(processedMessages)-1]["id"].(string); ok {
|
||||
messageID = id
|
||||
}
|
||||
}
|
||||
|
||||
// Call service
|
||||
if req.Stream {
|
||||
// Streaming response
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.Header("X-Accel-Buffering", "no")
|
||||
|
||||
// Create a channel for streaming data
|
||||
streamChan := make(chan string)
|
||||
go func() {
|
||||
defer close(streamChan)
|
||||
err := h.chatSessionService.CompletionStream(userID, req.ConversationID, processedMessages, req.LLMID, chatModelConfig, messageID, streamChan)
|
||||
if err != nil {
|
||||
streamChan <- fmt.Sprintf("data: %s\n\n", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// Stream data to client
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
data, ok := <-streamChan
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
c.Writer.Write([]byte(data))
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
// Non-streaming response
|
||||
result, err := h.chatSessionService.Completion(userID, req.ConversationID, processedMessages, req.LLMID, chatModelConfig, messageID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "",
|
||||
})
|
||||
}
|
||||
}
|
||||
180
internal/handler/chunk.go
Normal file
180
internal/handler/chunk.go
Normal file
@ -0,0 +1,180 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// ChunkHandler chunk handler
|
||||
type ChunkHandler struct {
|
||||
chunkService *service.ChunkService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewChunkHandler create chunk handler
|
||||
func NewChunkHandler(chunkService *service.ChunkService, userService *service.UserService) *ChunkHandler {
|
||||
return &ChunkHandler{
|
||||
chunkService: chunkService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// RetrievalTest performs retrieval test for chunks
|
||||
// @Summary Retrieval Test
|
||||
// @Description Test retrieval of chunks based on question and knowledge base
|
||||
// @Tags chunks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.RetrievalTestRequest true "retrieval test parameters"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/chunk/retrieval_test [post]
|
||||
func (h *ChunkHandler) RetrievalTest(c *gin.Context) {
|
||||
// Extract access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Bind JSON request
|
||||
var req service.RetrievalTestRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set default values for optional parameters
|
||||
if req.Page == nil {
|
||||
defaultPage := 1
|
||||
req.Page = &defaultPage
|
||||
}
|
||||
if req.Size == nil {
|
||||
defaultSize := 30
|
||||
req.Size = &defaultSize
|
||||
}
|
||||
if req.TopK == nil {
|
||||
defaultTopK := 1024
|
||||
req.TopK = &defaultTopK
|
||||
}
|
||||
if req.UseKG == nil {
|
||||
defaultUseKG := false
|
||||
req.UseKG = &defaultUseKG
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if req.Question == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "question is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
if req.KbID == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "kb_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate kb_id type: string or []string
|
||||
switch v := req.KbID.(type) {
|
||||
case string:
|
||||
if v == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "kb_id cannot be empty string",
|
||||
})
|
||||
return
|
||||
}
|
||||
case []interface{}:
|
||||
// Convert to []string
|
||||
var kbIDs []string
|
||||
for _, item := range v {
|
||||
if str, ok := item.(string); ok && str != "" {
|
||||
kbIDs = append(kbIDs, str)
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "kb_id array must contain non-empty strings",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(kbIDs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "kb_id array cannot be empty",
|
||||
})
|
||||
return
|
||||
}
|
||||
// Convert back to interface{} for service
|
||||
req.KbID = kbIDs
|
||||
case []string:
|
||||
// Already correct type
|
||||
if len(v) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "kb_id array cannot be empty",
|
||||
})
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "kb_id must be string or array of strings",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Call service with user ID for permission checks
|
||||
resp, err := h.chunkService.RetrievalTest(&req, user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": resp,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
86
internal/handler/connector.go
Normal file
86
internal/handler/connector.go
Normal file
@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// ConnectorHandler connector handler
|
||||
type ConnectorHandler struct {
|
||||
connectorService *service.ConnectorService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewConnectorHandler create connector handler
|
||||
func NewConnectorHandler(connectorService *service.ConnectorService, userService *service.UserService) *ConnectorHandler {
|
||||
return &ConnectorHandler{
|
||||
connectorService: connectorService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListConnectors list connectors
|
||||
// @Summary List Connectors
|
||||
// @Description Get list of connectors for the current user (equivalent to Python's list_connector)
|
||||
// @Tags connector
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} service.ListConnectorsResponse
|
||||
// @Router /connector/list [get]
|
||||
func (h *ConnectorHandler) ListConnectors(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// List connectors
|
||||
result, err := h.connectorService.ListConnectors(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result.Connectors,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
258
internal/handler/document.go
Normal file
258
internal/handler/document.go
Normal file
@ -0,0 +1,258 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// DocumentHandler document handler
|
||||
type DocumentHandler struct {
|
||||
documentService *service.DocumentService
|
||||
}
|
||||
|
||||
// NewDocumentHandler create document handler
|
||||
func NewDocumentHandler(documentService *service.DocumentService) *DocumentHandler {
|
||||
return &DocumentHandler{
|
||||
documentService: documentService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDocument create document
|
||||
// @Summary Create Document
|
||||
// @Description Create new document
|
||||
// @Tags documents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.CreateDocumentRequest true "document info"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/documents [post]
|
||||
func (h *DocumentHandler) CreateDocument(c *gin.Context) {
|
||||
var req service.CreateDocumentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
document, err := h.documentService.CreateDocument(&req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "created successfully",
|
||||
"data": document,
|
||||
})
|
||||
}
|
||||
|
||||
// GetDocumentByID get document by ID
|
||||
// @Summary Get Document Info
|
||||
// @Description Get document details by ID
|
||||
// @Tags documents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "document ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/documents/{id} [get]
|
||||
func (h *DocumentHandler) GetDocumentByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid document id",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
document, err := h.documentService.GetDocumentByID(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "document not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": document,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDocument update document
|
||||
// @Summary Update Document
|
||||
// @Description Update document info
|
||||
// @Tags documents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "document ID"
|
||||
// @Param request body service.UpdateDocumentRequest true "update info"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/documents/{id} [put]
|
||||
func (h *DocumentHandler) UpdateDocument(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid document id",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateDocumentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.documentService.UpdateDocument(id, &req); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteDocument delete document
|
||||
// @Summary Delete Document
|
||||
// @Description Delete specified document
|
||||
// @Tags documents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "document ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/documents/{id} [delete]
|
||||
func (h *DocumentHandler) DeleteDocument(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid document id",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.documentService.DeleteDocument(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// ListDocuments document list
|
||||
// @Summary Document List
|
||||
// @Description Get paginated document list
|
||||
// @Tags documents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "page number" default(1)
|
||||
// @Param page_size query int false "items per page" default(10)
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/documents [get]
|
||||
func (h *DocumentHandler) ListDocuments(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
documents, total, err := h.documentService.ListDocuments(page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "failed to get documents",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"items": documents,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetDocumentsByAuthorID get documents by author ID
|
||||
// @Summary Get Author Documents
|
||||
// @Description Get paginated document list by author ID
|
||||
// @Tags documents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param author_id path int true "author ID"
|
||||
// @Param page query int false "page number" default(1)
|
||||
// @Param page_size query int false "items per page" default(10)
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/authors/{author_id}/documents [get]
|
||||
func (h *DocumentHandler) GetDocumentsByAuthorID(c *gin.Context) {
|
||||
authorIDStr := c.Param("author_id")
|
||||
authorID, err := strconv.Atoi(authorIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid author id",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
documents, total, err := h.documentService.GetDocumentsByAuthorID(authorID, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "failed to get documents",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"items": documents,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
46
internal/handler/error.go
Normal file
46
internal/handler/error.go
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"ragflow/internal/logger"
|
||||
)
|
||||
|
||||
// HandleNoRoute handles requests to undefined routes
|
||||
func HandleNoRoute(c *gin.Context) {
|
||||
// Log the request details on server side
|
||||
logger.Logger.Warn("The requested URL was not found",
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("query", c.Request.URL.RawQuery),
|
||||
zap.String("remote_addr", c.ClientIP()),
|
||||
zap.String("user_agent", c.Request.UserAgent()),
|
||||
)
|
||||
|
||||
// Return JSON error response
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "Not Found: " + c.Request.URL.Path,
|
||||
"data": nil,
|
||||
"error": "Not Found",
|
||||
})
|
||||
}
|
||||
283
internal/handler/file.go
Normal file
283
internal/handler/file.go
Normal file
@ -0,0 +1,283 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// FileHandler file handler
|
||||
type FileHandler struct {
|
||||
fileService *service.FileService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewFileHandler create file handler
|
||||
func NewFileHandler(fileService *service.FileService, userService *service.UserService) *FileHandler {
|
||||
return &FileHandler{
|
||||
fileService: fileService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListFiles list files
|
||||
// @Summary List Files
|
||||
// @Description Get list of files for the current user with filtering, pagination and sorting
|
||||
// @Tags file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param parent_id query string false "parent folder ID"
|
||||
// @Param keywords query string false "search keywords"
|
||||
// @Param page query int false "page number (default: 1)"
|
||||
// @Param page_size query int false "items per page (default: 15)"
|
||||
// @Param orderby query string false "order by field (default: create_time)"
|
||||
// @Param desc query bool false "descending order (default: true)"
|
||||
// @Success 200 {object} service.ListFilesResponse
|
||||
// @Router /v1/file/list [get]
|
||||
func (h *FileHandler) ListFiles(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse query parameters
|
||||
parentID := c.Query("parent_id")
|
||||
keywords := c.Query("keywords")
|
||||
|
||||
// Parse page (default: 1)
|
||||
page := 1
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
|
||||
// Parse page_size (default: 15)
|
||||
pageSize := 15
|
||||
if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
|
||||
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
|
||||
pageSize = ps
|
||||
}
|
||||
}
|
||||
|
||||
// Parse orderby (default: create_time)
|
||||
orderby := c.DefaultQuery("orderby", "create_time")
|
||||
|
||||
// Parse desc (default: true)
|
||||
desc := true
|
||||
if descStr := c.Query("desc"); descStr != "" {
|
||||
desc = descStr != "false"
|
||||
}
|
||||
|
||||
// List files
|
||||
result, err := h.fileService.ListFiles(userID, parentID, page, pageSize, orderby, desc, keywords)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// GetRootFolder gets root folder for current user
|
||||
// @Summary Get Root Folder
|
||||
// @Description Get or create root folder for the current user
|
||||
// @Tags file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/file/root_folder [get]
|
||||
func (h *FileHandler) GetRootFolder(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Get root folder
|
||||
rootFolder, err := h.fileService.GetRootFolder(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": gin.H{"root_folder": rootFolder},
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// GetParentFolder gets parent folder of a file
|
||||
// @Summary Get Parent Folder
|
||||
// @Description Get parent folder of a file by file ID
|
||||
// @Tags file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param file_id query string true "file ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/file/parent_folder [get]
|
||||
func (h *FileHandler) GetParentFolder(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token (for validation)
|
||||
_, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get file_id from query
|
||||
fileID := c.Query("file_id")
|
||||
if fileID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "file_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get parent folder
|
||||
parentFolder, err := h.fileService.GetParentFolder(fileID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": gin.H{"parent_folder": parentFolder},
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllParentFolders gets all parent folders in path
|
||||
// @Summary Get All Parent Folders
|
||||
// @Description Get all parent folders in path from file to root
|
||||
// @Tags file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param file_id query string true "file ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/file/all_parent_folder [get]
|
||||
func (h *FileHandler) GetAllParentFolders(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token (for validation)
|
||||
_, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get file_id from query
|
||||
fileID := c.Query("file_id")
|
||||
if fileID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "file_id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get all parent folders
|
||||
parentFolders, err := h.fileService.GetAllParentFolders(fileID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": gin.H{"parent_folders": parentFolders},
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
158
internal/handler/kb.go
Normal file
158
internal/handler/kb.go
Normal file
@ -0,0 +1,158 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// KnowledgebaseHandler knowledge base handler
|
||||
type KnowledgebaseHandler struct {
|
||||
kbService *service.KnowledgebaseService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewKnowledgebaseHandler create knowledge base handler
|
||||
func NewKnowledgebaseHandler(kbService *service.KnowledgebaseService, userService *service.UserService) *KnowledgebaseHandler {
|
||||
return &KnowledgebaseHandler{
|
||||
kbService: kbService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListKbs list knowledge bases
|
||||
// @Summary List Knowledge Bases
|
||||
// @Description Get list of knowledge bases with filtering and pagination
|
||||
// @Tags knowledgebase
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param keywords query string false "search keywords"
|
||||
// @Param page query int false "page number"
|
||||
// @Param page_size query int false "items per page"
|
||||
// @Param parser_id query string false "parser ID filter"
|
||||
// @Param orderby query string false "order by field"
|
||||
// @Param desc query bool false "descending order"
|
||||
// @Param request body service.ListKbsRequest true "filter options"
|
||||
// @Success 200 {object} service.ListKbsResponse
|
||||
// @Router /v1/kb/list [post]
|
||||
func (h *KnowledgebaseHandler) ListKbs(c *gin.Context) {
|
||||
// Parse request body - allow empty body
|
||||
var req service.ListKbsRequest
|
||||
if c.Request.ContentLength > 0 {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Extract parameters from query or request body with defaults
|
||||
keywords := ""
|
||||
if req.Keywords != nil {
|
||||
keywords = *req.Keywords
|
||||
} else if queryKeywords := c.Query("keywords"); queryKeywords != "" {
|
||||
keywords = queryKeywords
|
||||
}
|
||||
|
||||
page := 0
|
||||
if req.Page != nil {
|
||||
page = *req.Page
|
||||
} else if pageStr := c.Query("page"); pageStr != "" {
|
||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
|
||||
pageSize := 0
|
||||
if req.PageSize != nil {
|
||||
pageSize = *req.PageSize
|
||||
} else if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
|
||||
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
|
||||
pageSize = ps
|
||||
}
|
||||
}
|
||||
|
||||
parserID := ""
|
||||
if req.ParserID != nil {
|
||||
parserID = *req.ParserID
|
||||
} else if queryParserID := c.Query("parser_id"); queryParserID != "" {
|
||||
parserID = queryParserID
|
||||
}
|
||||
|
||||
orderby := "update_time"
|
||||
if req.Orderby != nil {
|
||||
orderby = *req.Orderby
|
||||
} else if queryOrderby := c.Query("orderby"); queryOrderby != "" {
|
||||
orderby = queryOrderby
|
||||
}
|
||||
|
||||
desc := true
|
||||
if req.Desc != nil {
|
||||
desc = *req.Desc
|
||||
} else if descStr := c.Query("desc"); descStr != "" {
|
||||
desc = descStr == "true"
|
||||
}
|
||||
|
||||
var ownerIDs []string
|
||||
if req.OwnerIDs != nil {
|
||||
ownerIDs = *req.OwnerIDs
|
||||
}
|
||||
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// List knowledge bases
|
||||
result, err := h.kbService.ListKbs(keywords, page, pageSize, parserID, orderby, desc, ownerIDs, userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
247
internal/handler/llm.go
Normal file
247
internal/handler/llm.go
Normal file
@ -0,0 +1,247 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/dao"
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// FactoryResponse represents a model provider factory
|
||||
type FactoryResponse struct {
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo"`
|
||||
Tags string `json:"tags"`
|
||||
Status string `json:"status"`
|
||||
Rank string `json:"rank"`
|
||||
ModelTypes []string `json:"model_types"`
|
||||
}
|
||||
|
||||
// LLMHandler LLM handler
|
||||
type LLMHandler struct {
|
||||
llmService *service.LLMService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewLLMHandler create LLM handler
|
||||
func NewLLMHandler(llmService *service.LLMService, userService *service.UserService) *LLMHandler {
|
||||
return &LLMHandler{
|
||||
llmService: llmService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMyLLMs get my LLMs
|
||||
// @Summary Get My LLMs
|
||||
// @Description Get LLM list for current tenant
|
||||
// @Tags llm
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param include_details query string false "Include detailed fields" default(false)
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/llm/my_llms [get]
|
||||
func (h *LLMHandler) GetMyLLMs(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get tenant ID from user
|
||||
tenantID := user.ID
|
||||
if tenantID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "User has no tenant ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse include_details query parameter
|
||||
includeDetailsStr := c.DefaultQuery("include_details", "false")
|
||||
includeDetails := includeDetailsStr == "true"
|
||||
|
||||
// Get LLMs for tenant
|
||||
llms, err := h.llmService.GetMyLLMs(tenantID, includeDetails)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to get LLMs",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": llms,
|
||||
})
|
||||
}
|
||||
|
||||
// Factories get model provider factories
|
||||
// @Summary Get Model Provider Factories
|
||||
// @Description Get list of model provider factories
|
||||
// @Tags llm
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {array} FactoryResponse
|
||||
// @Router /v1/llm/factories [get]
|
||||
func (h *LLMHandler) Factories(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
_, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get model providers
|
||||
dao := dao.NewModelProviderDAO()
|
||||
providers := dao.GetAllProviders()
|
||||
|
||||
// Filter out unwanted providers
|
||||
filtered := make([]FactoryResponse, 0)
|
||||
excluded := map[string]bool{
|
||||
"Youdao": true,
|
||||
"FastEmbed": true,
|
||||
"BAAI": true,
|
||||
"Builtin": true,
|
||||
}
|
||||
|
||||
for _, provider := range providers {
|
||||
if excluded[provider.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect unique model types from LLMs
|
||||
modelTypes := make(map[string]bool)
|
||||
for _, llm := range provider.LLMs {
|
||||
modelTypes[llm.ModelType] = true
|
||||
}
|
||||
|
||||
// Convert to slice
|
||||
modelTypeSlice := make([]string, 0, len(modelTypes))
|
||||
for mt := range modelTypes {
|
||||
modelTypeSlice = append(modelTypeSlice, mt)
|
||||
}
|
||||
|
||||
// If no model types found, use defaults
|
||||
if len(modelTypeSlice) == 0 {
|
||||
modelTypeSlice = []string{"chat", "embedding", "rerank", "image2text", "speech2text", "tts", "ocr"}
|
||||
}
|
||||
|
||||
filtered = append(filtered, FactoryResponse{
|
||||
Name: provider.Name,
|
||||
Logo: provider.Logo,
|
||||
Tags: provider.Tags,
|
||||
Status: provider.Status,
|
||||
Rank: provider.Rank,
|
||||
ModelTypes: modelTypeSlice,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": filtered,
|
||||
})
|
||||
}
|
||||
|
||||
// ListApp lists LLMs grouped by factory
|
||||
// @Summary List LLMs
|
||||
// @Description Get list of LLMs grouped by factory with availability info
|
||||
// @Tags llm
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param model_type query string false "Filter by model type"
|
||||
// @Success 200 {object} map[string][]service.LLMListItem
|
||||
// @Router /v1/llm/list [get]
|
||||
func (h *LLMHandler) ListApp(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get tenant ID from user
|
||||
tenantID := user.ID
|
||||
if tenantID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "User has no tenant ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse model_type query parameter
|
||||
modelType := c.Query("model_type")
|
||||
|
||||
// Get LLM list
|
||||
llms, err := h.llmService.ListLLMs(tenantID, modelType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": llms,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
129
internal/handler/search.go
Normal file
129
internal/handler/search.go
Normal file
@ -0,0 +1,129 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// SearchHandler search handler
|
||||
type SearchHandler struct {
|
||||
searchService *service.SearchService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewSearchHandler create search handler
|
||||
func NewSearchHandler(searchService *service.SearchService, userService *service.UserService) *SearchHandler {
|
||||
return &SearchHandler{
|
||||
searchService: searchService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListSearchApps list search apps
|
||||
// @Summary List Search Apps
|
||||
// @Description Get list of search apps for the current user with filtering, pagination and sorting
|
||||
// @Tags search
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param keywords query string false "search keywords"
|
||||
// @Param page query int false "page number"
|
||||
// @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"
|
||||
// @Success 200 {object} service.ListSearchAppsResponse
|
||||
// @Router /v1/search/list [post]
|
||||
func (h *SearchHandler) ListSearchApps(c *gin.Context) {
|
||||
// Get access token from Authorization header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by access token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := user.ID
|
||||
|
||||
// Parse query parameters
|
||||
keywords := c.Query("keywords")
|
||||
|
||||
page := 0
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
|
||||
pageSize := 0
|
||||
if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
|
||||
if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
|
||||
pageSize = ps
|
||||
}
|
||||
}
|
||||
|
||||
orderby := c.DefaultQuery("orderby", "create_time")
|
||||
|
||||
desc := true
|
||||
if descStr := c.Query("desc"); descStr != "" {
|
||||
desc = descStr != "false"
|
||||
}
|
||||
|
||||
// Parse request body for owner_ids
|
||||
var req service.ListSearchAppsRequest
|
||||
if c.Request.ContentLength > 0 {
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// List search apps with filtering
|
||||
result, err := h.searchService.ListSearchApps(userID, keywords, page, pageSize, orderby, desc, req.OwnerIDs)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": result,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
125
internal/handler/system.go
Normal file
125
internal/handler/system.go
Normal file
@ -0,0 +1,125 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"ragflow/internal/server"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// SystemHandler system handler
|
||||
type SystemHandler struct {
|
||||
systemService *service.SystemService
|
||||
}
|
||||
|
||||
// NewSystemHandler create system handler
|
||||
func NewSystemHandler(systemService *service.SystemService) *SystemHandler {
|
||||
return &SystemHandler{
|
||||
systemService: systemService,
|
||||
}
|
||||
}
|
||||
|
||||
// Ping health check endpoint
|
||||
// @Summary Ping
|
||||
// @Description Simple ping endpoint
|
||||
// @Tags system
|
||||
// @Produce plain
|
||||
// @Success 200 {string} string "pong"
|
||||
// @Router /v1/system/ping [get]
|
||||
func (h *SystemHandler) Ping(c *gin.Context) {
|
||||
c.String(http.StatusOK, "pong")
|
||||
}
|
||||
|
||||
// GetConfig get system configuration
|
||||
// @Summary Get System Configuration
|
||||
// @Description Get system configuration including register enabled status
|
||||
// @Tags system
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/system/config [get]
|
||||
func (h *SystemHandler) GetConfig(c *gin.Context) {
|
||||
config, err := h.systemService.GetConfig()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "Failed to get system configuration",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
// GetConfigs get all system configurations
|
||||
// @Summary Get All System Configurations
|
||||
// @Description Get all system configurations from globalConfig
|
||||
// @Tags system
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} config.Config
|
||||
// @Router /v1/system/configs [get]
|
||||
func (h *SystemHandler) GetConfigs(c *gin.Context) {
|
||||
cfg := server.GetConfig()
|
||||
if cfg == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "Configuration not initialized",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": cfg,
|
||||
})
|
||||
}
|
||||
|
||||
// GetVersion get RAGFlow version
|
||||
// @Summary Get RAGFlow Version
|
||||
// @Description Get the current version of the application
|
||||
// @Tags system
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/system/version [get]
|
||||
func (h *SystemHandler) GetVersion(c *gin.Context) {
|
||||
version, err := h.systemService.GetVersion()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "Failed to get version",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": version.Version,
|
||||
})
|
||||
}
|
||||
135
internal/handler/tenant.go
Normal file
135
internal/handler/tenant.go
Normal file
@ -0,0 +1,135 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// TenantHandler tenant handler
|
||||
type TenantHandler struct {
|
||||
tenantService *service.TenantService
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewTenantHandler create tenant handler
|
||||
func NewTenantHandler(tenantService *service.TenantService, userService *service.UserService) *TenantHandler {
|
||||
return &TenantHandler{
|
||||
tenantService: tenantService,
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// TenantInfo get tenant information
|
||||
// @Summary Get Tenant Information
|
||||
// @Description Get current user's tenant information (owner tenant)
|
||||
// @Tags tenants
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/tenant_info [get]
|
||||
func (h *TenantHandler) TenantInfo(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get tenant info
|
||||
tenantInfo, err := h.tenantService.GetTenantInfo(user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to get tenant information",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if tenantInfo == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "Tenant not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": tenantInfo,
|
||||
})
|
||||
}
|
||||
|
||||
// TenantList get tenant list for current user
|
||||
// @Summary Get Tenant List
|
||||
// @Description Get all tenants that the current user belongs to
|
||||
// @Tags tenants
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/tenant/list [get]
|
||||
func (h *TenantHandler) TenantList(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get tenant list
|
||||
tenantList, err := h.tenantService.GetTenantList(user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "Failed to get tenant list",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": tenantList,
|
||||
})
|
||||
}
|
||||
456
internal/handler/user.go
Normal file
456
internal/handler/user.go
Normal file
@ -0,0 +1,456 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"ragflow/internal/server"
|
||||
"ragflow/internal/utility"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"ragflow/internal/service"
|
||||
)
|
||||
|
||||
// UserHandler user handler
|
||||
type UserHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewUserHandler create user handler
|
||||
func NewUserHandler(userService *service.UserService) *UserHandler {
|
||||
return &UserHandler{
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
// Register user registration
|
||||
// @Summary User Registration
|
||||
// @Description Create new user
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.RegisterRequest true "registration info"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/users/register [post]
|
||||
func (h *UserHandler) Register(c *gin.Context) {
|
||||
var req service.RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.Register(&req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "registration successful",
|
||||
"data": gin.H{
|
||||
"id": user.ID,
|
||||
"nickname": user.Nickname,
|
||||
"email": user.Email,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Login user login
|
||||
// @Summary User Login
|
||||
// @Description User login verification
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.LoginRequest true "login info"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/users/login [post]
|
||||
func (h *UserHandler) Login(c *gin.Context) {
|
||||
var req service.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.Login(&req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set Authorization header with access_token
|
||||
if user.AccessToken != nil {
|
||||
c.Header("Authorization", *user.AccessToken)
|
||||
}
|
||||
// Set CORS headers
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "*")
|
||||
c.Header("Access-Control-Allow-Headers", "*")
|
||||
c.Header("Access-Control-Expose-Headers", "Authorization")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "Welcome back!",
|
||||
"data": user,
|
||||
})
|
||||
}
|
||||
|
||||
// LoginByEmail user login by email
|
||||
// @Summary User Login by Email
|
||||
// @Description User login verification using email
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body service.EmailLoginRequest true "login info with email"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/login [post]
|
||||
func (h *UserHandler) LoginByEmail(c *gin.Context) {
|
||||
var req service.EmailLoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.LoginByEmail(&req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
variables := server.GetVariables()
|
||||
secretKey := variables.SecretKey
|
||||
authToken, err := utility.DumpAccessToken(*user.AccessToken, secretKey)
|
||||
|
||||
// Set Authorization header with access_token
|
||||
if user.AccessToken != nil {
|
||||
c.Header("Authorization", authToken)
|
||||
}
|
||||
// Set CORS headers
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "*")
|
||||
c.Header("Access-Control-Allow-Headers", "*")
|
||||
c.Header("Access-Control-Expose-Headers", "Authorization")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "Welcome back!",
|
||||
"data": user,
|
||||
})
|
||||
}
|
||||
|
||||
// GetUserByID get user by ID
|
||||
// @Summary Get User Info
|
||||
// @Description Get user details by ID
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "user ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/users/{id} [get]
|
||||
func (h *UserHandler) GetUserByID(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid user id",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.GetUserByID(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "user not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": user,
|
||||
})
|
||||
}
|
||||
|
||||
// ListUsers user list
|
||||
// @Summary User List
|
||||
// @Description Get paginated user list
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "page number" default(1)
|
||||
// @Param page_size query int false "items per page" default(10)
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/users [get]
|
||||
func (h *UserHandler) ListUsers(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
users, total, err := h.userService.ListUsers(page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "failed to get users",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"items": users,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Logout user logout
|
||||
// @Summary User Logout
|
||||
// @Description Logout user and invalidate access token
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/logout [post]
|
||||
func (h *UserHandler) Logout(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Logout user
|
||||
if err := h.userService.Logout(user); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": true,
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// Info get user profile information
|
||||
// @Summary Get User Profile
|
||||
// @Description Get current user's profile information
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/info [get]
|
||||
func (h *UserHandler) Info(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"error": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user profile
|
||||
profile := h.userService.GetUserProfile(user)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"data": profile,
|
||||
})
|
||||
}
|
||||
|
||||
// Setting update user settings
|
||||
// @Summary Update User Settings
|
||||
// @Description Update current user's settings
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param request body service.UpdateSettingsRequest true "user settings"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/setting [post]
|
||||
func (h *UserHandler) Setting(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request
|
||||
var req service.UpdateSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Update user settings
|
||||
if err := h.userService.UpdateUserSettings(user, &req); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "settings updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// ChangePassword change user password
|
||||
// @Summary Change User Password
|
||||
// @Description Change current user's password
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param request body service.ChangePasswordRequest true "password change info"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/setting/password [post]
|
||||
func (h *UserHandler) ChangePassword(c *gin.Context) {
|
||||
// Extract token from request
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "Missing Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get user by token
|
||||
user, err := h.userService.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "Invalid access token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request
|
||||
var req service.ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Change password
|
||||
if err := h.userService.ChangePassword(user, &req); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "password changed successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// GetLoginChannels get all supported authentication channels
|
||||
// @Summary Get Login Channels
|
||||
// @Description Get all supported OAuth authentication channels
|
||||
// @Tags users
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /v1/user/login/channels [get]
|
||||
func (h *UserHandler) GetLoginChannels(c *gin.Context) {
|
||||
channels, err := h.userService.GetLoginChannels()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "Load channels failure, error: " + err.Error(),
|
||||
"data": []interface{}{},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": channels,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user