Refactor system/version API to RESTful style (#13956)

### What problem does this PR solve?

Refactor version API to RESTful style. Python and go server API also
updated.
### Type of change

- [x] Refactoring



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Migrated core API endpoints to the `/api/v1/` namespace for improved
consistency and organization.
* Standardized system version, search, and chat list endpoints under the
new API versioning structure.

* **New Features**
* Added MinIO region configuration support, allowing specification of
storage engine regional settings via environment variables or
configuration files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-04-07 19:07:47 +08:00
committed by GitHub
parent bc8d67ce78
commit 931021875a
12 changed files with 87 additions and 45 deletions

View File

@@ -1655,7 +1655,7 @@ class RAGFlowClient:
if self.server_type == "admin":
response = self.http_client.request("GET", "/admin/version", use_api_base=True, auth_kind="admin")
else:
response = self.http_client.request("GET", "/system/version", use_api_base=False, auth_kind="admin")
response = self.http_client.request("GET", "/system/version", use_api_base=True, auth_kind="admin")
res_json = response.json()
if response.status_code == 200:

View File

@@ -0,0 +1,42 @@
#
# 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.
#
from api.apps import login_required
from api.utils.api_utils import get_json_result
from common.versions import get_ragflow_version
@manager.route("/system/version", methods=["GET"]) # noqa: F821
@login_required
def version():
"""
Get the current version of the application.
---
tags:
- System
security:
- ApiKeyAuth: []
responses:
200:
description: Version retrieved successfully.
schema:
type: object
properties:
version:
type: string
description: Version number.
"""
return get_json_result(data=get_ragflow_version())

View File

@@ -29,7 +29,6 @@ from api.utils.api_utils import (
server_error_response,
generate_confirmation_token,
)
from common.versions import get_ragflow_version
from common.time_utils import current_timestamp, datetime_format
from common.log_utils import get_log_levels, set_log_level
from timeit import default_timer as timer
@@ -39,30 +38,6 @@ from quart import jsonify
from api.utils.health_utils import run_health_checks, get_oceanbase_status
from common import settings
@manager.route("/version", methods=["GET"]) # noqa: F821
@login_required
def version():
"""
Get the current version of the application.
---
tags:
- System
security:
- ApiKeyAuth: []
responses:
200:
description: Version retrieved successfully.
schema:
type: object
properties:
version:
type: string
description: Version number.
"""
return get_json_result(data=get_ragflow_version())
@manager.route("/status", methods=["GET"]) # noqa: F821
@login_required
def status():

View File

@@ -69,11 +69,11 @@ func (c *RAGFlowClient) ShowServerVersion(cmd *Command) (ResponseIf, error) {
if iterations > 1 {
// Benchmark mode: multiple iterations
return c.HTTPClient.RequestWithIterations("GET", "/system/version", false, "web", nil, nil, iterations)
return c.HTTPClient.RequestWithIterations("GET", "/system/version", true, "web", nil, nil, iterations)
}
// Single mode
resp, err := c.HTTPClient.Request("GET", "/system/version", false, "web", nil, nil)
resp, err := c.HTTPClient.Request("GET", "/system/version", true, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to show version: %w", err)
}

View File

@@ -40,7 +40,7 @@ func NewSearchHandler(searchService *service.SearchService, userService *service
}
}
// ListSearchApps list search apps
// ListSearches list search apps
// @Summary List Search Apps
// @Description Get list of search apps for the current user with filtering, pagination and sorting
// @Tags search
@@ -53,8 +53,8 @@ func NewSearchHandler(searchService *service.SearchService, userService *service
// @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) {
// @Router /api/v1/searches [post]
func (h *SearchHandler) ListSearches(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
@@ -99,7 +99,7 @@ func (h *SearchHandler) ListSearchApps(c *gin.Context) {
}
// List search apps with filtering
result, err := h.searchService.ListSearchApps(userID, keywords, page, pageSize, orderby, desc, req.OwnerIDs)
result, err := h.searchService.ListSearches(userID, keywords, page, pageSize, orderby, desc, req.OwnerIDs)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,

View File

@@ -48,7 +48,7 @@ func (h *SystemHandler) Ping(c *gin.Context) {
c.String(http.StatusOK, "pong")
}
// Health health check
// Health check
func (h *SystemHandler) Health(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",

View File

@@ -22,7 +22,6 @@ import (
"ragflow/internal/handler"
)
// Router router
type Router struct {
authHandler *handler.AuthHandler
userHandler *handler.UserHandler
@@ -196,6 +195,16 @@ func (r *Router) Setup(engine *gin.Engine) {
// message.GET("/:memory_id/:message_id/content", r.memoryHandler.GetMessageContent)
// }
chats := v1.Group("/chats")
{
chats.GET("", r.chatHandler.ListChats)
}
searches := v1.Group("/searches")
{
searches.GET("", r.searchHandler.ListSearches)
}
file := v1.Group("/files")
{
file.POST("", r.fileHandler.UploadFile)
@@ -220,6 +229,11 @@ func (r *Router) Setup(engine *gin.Engine) {
provider.PUT("/:provider_name/instances/:instance_name/models/:model_name", r.providerHandler.EnableOrDisableModel)
provider.POST("/:provider_name/instances/:instance_name/models/:model_name", r.providerHandler.ChatToModel)
}
system := v1.Group("/system")
{
system.GET("/version", r.systemHandler.GetVersion)
}
}
// Knowledge base routes
@@ -285,7 +299,6 @@ func (r *Router) Setup(engine *gin.Engine) {
// Chat routes
chat := authorized.Group("/v1/dialog")
{
chat.GET("/list", r.chatHandler.ListChats)
chat.POST("/next", r.chatHandler.ListChatsNext)
chat.POST("/set", r.chatHandler.SetDialog)
chat.POST("/rm", r.chatHandler.RemoveChats)
@@ -306,12 +319,6 @@ func (r *Router) Setup(engine *gin.Engine) {
connector.GET("/list", r.connectorHandler.ListConnectors)
}
// Search routes
search := authorized.Group("/v1/search")
{
search.POST("/list", r.searchHandler.ListSearchApps)
}
// File routes
file := authorized.Group("/v1/file")
{

View File

@@ -179,6 +179,7 @@ type MinioConfig struct {
Password string `mapstructure:"password"` // Secret key
Secure bool `mapstructure:"secure"` // Use HTTPS
Verify bool `mapstructure:"verify"` // Verify SSL certificates
Region string `mapstructure:"region"` // optional
Bucket string `mapstructure:"bucket"` // Default bucket (optional)
PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional)
}
@@ -448,6 +449,9 @@ func FromEnvironments() error {
// Minio
minioIP := strings.ToLower(os.Getenv("MINIO_IP"))
if minioIP != "" {
if globalConfig.StorageEngine.Minio == nil {
return fmt.Errorf("Minio config not found")
}
_, port, err := net.SplitHostPort(globalConfig.StorageEngine.Minio.Host)
if err != nil {
return fmt.Errorf("Error parsing host address %s: %v\n", globalConfig.StorageEngine.Minio.Host, err)
@@ -458,6 +462,9 @@ func FromEnvironments() error {
minioPort := strings.ToLower(os.Getenv("MINIO_PORT"))
// println(fmt.Sprintf("MINIO ip and port from env: %s:%s", minioIP, minioPort))
if minioPort != "" {
if globalConfig.StorageEngine.Minio == nil {
return fmt.Errorf("Minio config not found")
}
ip, _, err := net.SplitHostPort(globalConfig.StorageEngine.Minio.Host)
if err != nil {
return fmt.Errorf("Error parsing host address %s: %v\n", globalConfig.StorageEngine.Minio.Host, err)
@@ -465,6 +472,14 @@ func FromEnvironments() error {
globalConfig.StorageEngine.Minio.Host = fmt.Sprintf("%s:%s", ip, minioPort)
}
minioRegion := strings.ToLower(os.Getenv("MINIO_REGION"))
if minioRegion != "" {
if globalConfig.StorageEngine.Minio == nil {
return fmt.Errorf("Minio config not found")
}
globalConfig.StorageEngine.Minio.Region = minioRegion
}
// Language
if globalConfig.Language == "" {
globalConfig.Language = GetLanguage()
@@ -644,6 +659,7 @@ func FromConfigFile(configPath string) error {
Secure: minioConfig.GetBool("secure"),
PrefixPath: minioConfig.GetString("prefix_path"),
Verify: minioConfig.GetBool("verify"),
Region: minioConfig.GetString("region"),
Bucket: minioConfig.GetString("bucket"),
}
}

View File

@@ -53,8 +53,8 @@ type ListSearchAppsResponse struct {
Total int64 `json:"total"`
}
// ListSearchApps list search apps with advanced filtering (equivalent to list_search_app)
func (s *SearchService) ListSearchApps(userID string, keywords string, page, pageSize int, orderby string, desc bool, ownerIDs []string) (*ListSearchAppsResponse, error) {
// ListSearches list search apps with advanced filtering (equivalent to list_search_app)
func (s *SearchService) ListSearches(userID string, keywords string, page, pageSize int, orderby string, desc bool, ownerIDs []string) (*ListSearchAppsResponse, error) {
var searches []*entity.Search
var total int64
var err error

View File

@@ -71,6 +71,7 @@ func (m *MinioStorage) connect() error {
Creds: credentials.NewStaticV4(m.config.User, m.config.Password, ""),
Secure: m.config.Secure,
Transport: transport,
Region: m.config.Region,
})
if err != nil {
return fmt.Errorf("failed to connect to MinIO: %w", err)

View File

@@ -35,6 +35,7 @@ MEMORY_API_URL = f"/api/{VERSION}/memories"
MESSAGE_API_URL = f"/api/{VERSION}/messages"
API_APP_URL = f"/{VERSION}/api"
SYSTEM_APP_URL = f"/{VERSION}/system"
SYSTEM_API_URL = f"/api/{VERSION}/system"
LLM_APP_URL = f"/{VERSION}/llm"
PLUGIN_APP_URL = f"/{VERSION}/plugin"
SEARCHES_URL = f"/api/{VERSION}/searches"
@@ -113,7 +114,7 @@ def system_status(auth, params=None, *, headers=HEADERS):
def system_version(auth, params=None, *, headers=HEADERS):
res = requests.get(url=f"{HOST_ADDRESS}{SYSTEM_APP_URL}/version", headers=headers, auth=auth, params=params)
res = requests.get(url=f"{HOST_ADDRESS}{SYSTEM_API_URL}/version", headers=headers, auth=auth, params=params)
return res.json()

View File

@@ -170,7 +170,7 @@ export default {
moveFile: `${restAPIv1}/files/move`,
// system
getSystemVersion: `${webAPI}/system/version`,
getSystemVersion: `${restAPIv1}/system/version`,
getSystemStatus: `${webAPI}/system/status`,
getSystemTokenList: `${webAPI}/system/token_list`,
createSystemToken: `${webAPI}/system/new_token`,