From 9624f70b2237d920af3fe6dbf93f4337c8f28e2e Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Wed, 24 Jun 2026 16:50:40 +0800 Subject: [PATCH] Go CLI: refactor (#16299) ``` RAGFlow(api/default)> list dataset 'e93ab2c04ad111f1b17438a74640adcc' documents; Total: 1 RAGFlow(api/default)> list datasets; RAGFlow(api/default)> list chats; Total: 2 RAGFlow(api/default)> list agents; Total: 1 RAGFlow(api/default)> list searches; Total: 1 RAGFlow(api/default)> list keys; +----------------------------------+---------------+----------------------------------+-----------------------------------------------------+---------------+ | beta | create_time | tenant_id | token | update_time | +----------------------------------+---------------+----------------------------------+-----------------------------------------------------+---------------+ | GKsLEdSUkl76gJz1k_4fJpSQRIlWsiki | 1782285917523 | 2ba4881420fa11f19e9c38a74640adcc | ragflow-JgnarFSCUiV99oOvvMDei7ZzZg1cVlqGd1AMHrHeKE4 | 1782285917523 | +----------------------------------+---------------+----------------------------------+-----------------------------------------------------+---------------+ RAGFlow(api/default)> create key; SUCCESS RAGFlow(api/default)> drop key 'ragflow-aA4R7AuUD158yh2LDh7IDBiqwOKFDKeTwUSQSLVdPdM'; SUCCESS ``` --------- Signed-off-by: Jin Hai --- internal/cli/admin_parser.go | 35 +----- internal/cli/benchmark.go | 4 +- internal/cli/cli.go | 24 +--- internal/cli/cli_http.go | 40 +++--- internal/cli/parser.go | 10 +- internal/cli/response.go | 108 ++++++++++++++++ internal/cli/user_command.go | 226 ++++++++++++++++++++++++++++++---- internal/cli/user_parser.go | 209 +++++++++++++++---------------- internal/handler/api_token.go | 67 +++------- internal/handler/document.go | 2 +- internal/router/router.go | 18 ++- internal/service/api_token.go | 73 ++++++----- internal/service/chat.go | 2 + 13 files changed, 516 insertions(+), 302 deletions(-) diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index aae4403e4b..f5bc48b001 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -24,7 +24,7 @@ import ( // region AUTH commands func (p *Parser) parseAdminLoginUser() (*Command, error) { - cmd := NewCommand("login_user") + cmd := NewCommand("admin_login_user") p.nextToken() // consume LOGIN if p.curToken.Type != TokenAdmin { @@ -60,7 +60,7 @@ func (p *Parser) parseAdminLoginUser() (*Command, error) { } func (p *Parser) parseAdminLogout() (*Command, error) { - cmd := NewCommand("logout") + cmd := NewCommand("admin_logout") p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { @@ -70,7 +70,7 @@ func (p *Parser) parseAdminLogout() (*Command, error) { } func (p *Parser) parseAdminPingServer() (*Command, error) { - cmd := NewCommand("ping_server") + cmd := NewCommand("admin_ping_server") p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { @@ -1837,35 +1837,6 @@ func (p *Parser) parseAdminBenchmarkCommand() (*Command, error) { return cmd, nil } -func (p *Parser) parseAdminUserStatement() (*Command, error) { - switch p.curToken.Type { - case TokenPing: - return p.parsePingServer() - case TokenShow: - return p.parseShowCommand() - case TokenCreate: - return p.parseCreateCommand() - case TokenDrop: - return p.parseDropCommand() - case TokenSet: - return p.parseSetCommand() - case TokenUnset: - return p.parseUnsetCommand() - case TokenReset: - return p.parseResetCommand() - case TokenList: - return p.parseListCommand() - case TokenParse: - return p.parseParseCommand() - case TokenImport: - return p.parseImportCommand() - case TokenRetrieve: - return p.parseRetrieveCommand() - default: - return nil, fmt.Errorf("invalid user statement: %s", p.curToken.Value) - } -} - func (p *Parser) parseAdminStartService() (*Command, error) { p.nextToken() // consume START diff --git a/internal/cli/benchmark.go b/internal/cli/benchmark.go index 70257d29df..f73b7af80d 100644 --- a/internal/cli/benchmark.go +++ b/internal/cli/benchmark.go @@ -237,7 +237,7 @@ func (c *CLI) executeBenchmarkSilent(cmd *Command, iterations int) []*Response { resp, err = httpClient.Request("GET", "/system/ping", "web", nil, nil) case "list_user_datasets": resp, err = httpClient.Request("POST", "/kb/list", "web", nil, nil) - case "list_datasets": + case "api_list_datasets": userName, _ := cmd.Params["user_name"].(string) resp, err = httpClient.Request("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil) case "search_on_datasets": @@ -275,7 +275,7 @@ func isSuccess(resp *Response, commandType string) bool { switch commandType { case "ping": return resp.StatusCode == 200 && string(resp.Body) == "pong" - case "list_user_datasets", "list_datasets", "search_on_datasets": + case "list_user_datasets", "api_list_datasets", "search_on_datasets": // Check status code and JSON response code for dataset commands if resp.StatusCode != 200 { return false diff --git a/internal/cli/cli.go b/internal/cli/cli.go index b9242ddac6..2e0ac7239b 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -827,25 +827,6 @@ func (c *CLI) RunSingleCommand(command *string) error { return nil } -// VerifyAuth verifies authentication if needed -func (c *CLI) NewVerifyAuth(username, password *string) error { - // Otherwise, use username/password authentication - if username == nil { - return fmt.Errorf("username is required") - } - - if password == nil { - return fmt.Errorf("password is required") - } - - // Create login command with username and password - cmd := NewCommand("login_user") - cmd.Params["email"] = *username - cmd.Params["password"] = *password - _, err := c.ExecuteCommand(cmd) - return err -} - // VerifyAuth verifies authentication if needed func (c *CLI) VerifyAuth(username, password string) error { // Otherwise, use username/password authentication @@ -858,10 +839,11 @@ func (c *CLI) VerifyAuth(username, password string) error { } // Create login command with username and password - cmd := NewCommand("login_user") + cmd := NewCommand("login_user_on_startup") cmd.Params["email"] = username cmd.Params["password"] = password - _, err := c.ExecuteCommand(cmd) + + _, err := c.LoginUserByCommand(cmd) return err } diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go index 50af6bfa3c..29a1a03f20 100644 --- a/internal/cli/cli_http.go +++ b/internal/cli/cli_http.go @@ -37,11 +37,11 @@ func (c *CLI) ExecuteCommand(cmd *Command) (ResponseIf, error) { func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { switch cmd.Type { - case "login_user": + case "admin_login_user": return c.LoginUserByCommand(cmd) - case "logout": + case "admin_logout": return c.Logout() - case "ping_server": + case "admin_ping_server": return c.PingByCommand(cmd) case "benchmark": return c.RunBenchmark(cmd) @@ -284,23 +284,31 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { } func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { switch cmd.Type { - case "register_user": + case "api_register_user": return c.RegisterUser(cmd) - case "login_user": + case "api_login_user": return c.LoginUserByCommand(cmd) - case "logout": + case "api_logout": return c.Logout() - case "ping_server": + case "api_ping_server": return c.PingByCommand(cmd) // Configuration commands - case "list_configs": + case "api_list_configs": return c.ListConfigs(cmd) case "set_log_level": return c.SetLogLevel(cmd) case "benchmark": return c.RunBenchmark(cmd) - case "list_datasets": - return c.ListDatasets(cmd) + case "api_list_datasets": + return c.APIListDatasetsCommand(cmd) + case "api_list_dataset_documents": + return c.APIListDatasetDocumentsCommand(cmd) + case "api_list_agents": + return c.APIListAgentsCommand(cmd) + case "api_list_chats": + return c.APIListChatsCommand(cmd) + case "api_list_searches": + return c.APIListSearchesCommand(cmd) case "list_dataset_documents": return c.ListDatasetDocumentUserCommand(cmd) case "search_on_datasets": @@ -308,12 +316,12 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { case "search_help": printSearchHelp() return nil, nil - case "create_token": - return c.CreateToken(cmd) - case "list_tokens": - return c.ListTokens(cmd) - case "drop_token": - return c.DropToken(cmd) + case "api_create_api_key": + return c.APICreateAPIKeyCommand(cmd) + case "api_list_api_keys": + return c.APIListAPIKeysCommand(cmd) + case "api_delete_api_key": + return c.APIDeleteAPIKeyCommand(cmd) case "set_token": return c.SetToken(cmd) case "show_token": diff --git a/internal/cli/parser.go b/internal/cli/parser.go index 41596e0ffa..8e17e1e18e 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -143,13 +143,13 @@ func (p *Parser) parseUserCommand() (*Command, error) { switch p.curToken.Type { case TokenLogin: - return p.parseLoginUser() + return p.parseAPILoginUser() case TokenLogout: - return p.parseLogout() + return p.parseAPILogout() case TokenPing: - return p.parsePingServer() + return p.parseAPIPingServer() case TokenList: - return p.parseListCommand() + return p.parseAPIListCommands() case TokenShow: return p.parseShowCommand() case TokenCreate: @@ -181,7 +181,7 @@ func (p *Parser) parseUserCommand() (*Command, error) { case TokenBenchmark: return p.parseBenchmarkCommand() case TokenRegister: - return p.parseRegisterCommand() + return p.parseAPIRegisterCommand() case TokenEnable: return p.parseEnableCommand() case TokenDisable: diff --git a/internal/cli/response.go b/internal/cli/response.go index 076a010555..fbc121a20a 100644 --- a/internal/cli/response.go +++ b/internal/cli/response.go @@ -180,6 +180,114 @@ func (r *ListDocumentsResponse) PrintOut() { } } +type ListAgentsResponse struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + Message string `json:"message"` + Duration float64 + OutputFormat OutputFormat +} + +func (r *ListAgentsResponse) Type() string { + return "list_agents" +} + +func (r *ListAgentsResponse) TimeCost() float64 { + return r.Duration +} + +func (r *ListAgentsResponse) SetOutputFormat(format OutputFormat) { + r.OutputFormat = format +} + +func (r *ListAgentsResponse) PrintOut() { + if r.Code == 0 { + total := r.Data["total"].(float64) + fmt.Printf("Total: %0.0f\n", total) + docs := r.Data["canvas"].([]interface{}) + table := make([]map[string]interface{}, 0) + for _, doc := range docs { + table = append(table, doc.(map[string]interface{})) + } + PrintTableSimpleByFormat(table, r.OutputFormat) + } else { + fmt.Println("ERROR") + fmt.Printf("%d, %s\n", r.Code, r.Message) + } +} + +type ListChatsResponse struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + Message string `json:"message"` + Duration float64 + OutputFormat OutputFormat +} + +func (r *ListChatsResponse) Type() string { + return "list_chats" +} + +func (r *ListChatsResponse) TimeCost() float64 { + return r.Duration +} + +func (r *ListChatsResponse) SetOutputFormat(format OutputFormat) { + r.OutputFormat = format +} + +func (r *ListChatsResponse) PrintOut() { + if r.Code == 0 { + total := r.Data["total"].(float64) + fmt.Printf("Total: %0.0f\n", total) + docs := r.Data["chats"].([]interface{}) + table := make([]map[string]interface{}, 0) + for _, doc := range docs { + table = append(table, doc.(map[string]interface{})) + } + PrintTableSimpleByFormat(table, r.OutputFormat) + } else { + fmt.Println("ERROR") + fmt.Printf("%d, %s\n", r.Code, r.Message) + } +} + +type ListSearchesResponse struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + Message string `json:"message"` + Duration float64 + OutputFormat OutputFormat +} + +func (r *ListSearchesResponse) Type() string { + return "list_searches" +} + +func (r *ListSearchesResponse) TimeCost() float64 { + return r.Duration +} + +func (r *ListSearchesResponse) SetOutputFormat(format OutputFormat) { + r.OutputFormat = format +} + +func (r *ListSearchesResponse) PrintOut() { + if r.Code == 0 { + total := r.Data["total"].(float64) + fmt.Printf("Total: %0.0f\n", total) + docs := r.Data["search_apps"].([]interface{}) + table := make([]map[string]interface{}, 0) + for _, doc := range docs { + table = append(table, doc.(map[string]interface{})) + } + PrintTableSimpleByFormat(table, r.OutputFormat) + } else { + fmt.Println("ERROR") + fmt.Printf("%d, %s\n", r.Code, r.Message) + } +} + type ChunkResponse struct { Code int `json:"code"` Data map[string]interface{} `json:"data"` diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index 8f2aa25170..8eb56e3376 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -329,9 +329,9 @@ func (c *CLI) RegisterUser(cmd *Command) (ResponseIf, error) { return &result, nil } -// ListDatasets lists datasets for current user (user mode) +// APIListDatasetsCommand lists datasets for current user (user mode) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *CLI) ListDatasets(cmd *Command) (ResponseIf, error) { +func (c *CLI) APIListDatasetsCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -386,6 +386,186 @@ func (c *CLI) ListDatasets(cmd *Command) (ResponseIf, error) { return &result, nil } +func (c *CLI) APIListDatasetDocumentsCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + // Determine auth kind based on whether API token is being used + if httpClient.LoginToken == nil && !httpClient.useAPIToken { + return nil, fmt.Errorf("no authorization") + } + + datasetID, ok := cmd.Params["dataset_id"].(string) + if !ok { + return nil, fmt.Errorf("no dataset id") + } + + page := 1 + pageSize := 10 + keywords := "" + returnEmptyMetadata := "true" + url := fmt.Sprintf("/datasets/%s/documents?page=%d&page_size=%d&keywords=%s&return_empty_metadata=%s", datasetID, page, pageSize, keywords, returnEmptyMetadata) + + // Normal mode + resp, err := httpClient.Request("GET", url, "web", nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list documents: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list documents: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result ListDocumentsResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list documents failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + + return &result, nil +} + +// APIListAgentsCommand lists agents +func (c *CLI) APIListAgentsCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + // Determine auth kind based on whether API token is being used + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken { + return nil, fmt.Errorf("no authorization") + } + + authKind := "web" + if httpClient.useAPIToken { + authKind = "api" + } + + if httpClient.LoginToken != nil { + authKind = "web" + } + + // Normal mode + resp, err := httpClient.Request("GET", "/agents", authKind, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list agents: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list agents: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result ListAgentsResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list agents failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + + return &result, nil +} + +// APIListChatsCommand lists chats +func (c *CLI) APIListChatsCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + // Determine auth kind based on whether API token is being used + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken { + return nil, fmt.Errorf("no authorization") + } + + authKind := "web" + if httpClient.useAPIToken { + authKind = "api" + } + + if httpClient.LoginToken != nil { + authKind = "web" + } + + // Normal mode + resp, err := httpClient.Request("GET", "/chats", authKind, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list chats: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list chats: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result ListChatsResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list chats failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + + return &result, nil +} + +// APIListSearchesCommand lists searches +func (c *CLI) APIListSearchesCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + + // Determine auth kind based on whether API token is being used + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken { + return nil, fmt.Errorf("no authorization") + } + + authKind := "web" + if httpClient.useAPIToken { + authKind = "api" + } + + if httpClient.LoginToken != nil { + authKind = "web" + } + + // Normal mode + resp, err := httpClient.Request("GET", "/searches", authKind, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to list searches: %w", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to list searches: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + } + + var result ListSearchesResponse + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list searches failed: invalid JSON (%w)", err) + } + + if result.Code != 0 { + return nil, fmt.Errorf("%s", result.Message) + } + result.Duration = resp.Duration + + return &result, nil +} + // ListDatasetDocumentUserCommand lists dataset documents func (c *CLI) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { @@ -734,25 +914,25 @@ func (c *CLI) SearchOnDatasets(cmd *Command) (ResponseIf, error) { return nil, nil } -// CreateToken creates a new API token -func (c *CLI) CreateToken(cmd *Command) (ResponseIf, error) { +// APICreateAPIKeyCommand creates a new API key +func (c *CLI) APICreateAPIKeyCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] - resp, err := httpClient.Request("POST", "/system/tokens", "web", nil, nil) + resp, err := httpClient.Request("POST", "/system/keys", "web", nil, nil) if err != nil { - return nil, fmt.Errorf("failed to create token: %w", err) + return nil, fmt.Errorf("failed to create key: %w", err) } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to create token: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + return nil, fmt.Errorf("failed to create key: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } var createResult CommonDataResponse if err = json.Unmarshal(resp.Body, &createResult); err != nil { - return nil, fmt.Errorf("create token failed: invalid JSON (%w)", err) + return nil, fmt.Errorf("create key failed: invalid JSON (%w)", err) } if createResult.Code != 0 { @@ -761,30 +941,30 @@ func (c *CLI) CreateToken(cmd *Command) (ResponseIf, error) { var result SimpleResponse result.Code = 0 - result.Message = "Token created successfully" + result.Message = "API Key created successfully" result.Duration = resp.Duration return &result, nil } -// ListTokens lists all API tokens for the current user -func (c *CLI) ListTokens(cmd *Command) (ResponseIf, error) { +// APIListAPIKeysCommand lists all API keys for the current user +func (c *CLI) APIListAPIKeysCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] - resp, err := httpClient.Request("GET", "/system/tokens", "web", nil, nil) + resp, err := httpClient.Request("GET", "/system/keys", "web", nil, nil) if err != nil { - return nil, fmt.Errorf("failed to list tokens: %w", err) + return nil, fmt.Errorf("failed to list keys: %w", err) } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to list tokens: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + return nil, fmt.Errorf("failed to list keys: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } var result CommonResponse if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("list tokens failed: invalid JSON (%w)", err) + return nil, fmt.Errorf("list keys failed: invalid JSON (%w)", err) } if result.Code != 0 { @@ -794,29 +974,29 @@ func (c *CLI) ListTokens(cmd *Command) (ResponseIf, error) { return &result, nil } -// DropToken deletes an API token -func (c *CLI) DropToken(cmd *Command) (ResponseIf, error) { +// APIDeleteAPIKeyCommand deletes an API key +func (c *CLI) APIDeleteAPIKeyCommand(cmd *Command) (ResponseIf, error) { if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - token, ok := cmd.Params["token"].(string) + apiKey, ok := cmd.Params["api_key"].(string) if !ok { - return nil, fmt.Errorf("token not provided") + return nil, fmt.Errorf("key not provided") } - resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", fmt.Sprintf("/system/tokens/%s", token), "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", fmt.Sprintf("/system/keys/%s", apiKey), "web", nil, nil) if err != nil { - return nil, fmt.Errorf("failed to drop token: %w", err) + return nil, fmt.Errorf("failed to delete key: %w", err) } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to drop token: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) + return nil, fmt.Errorf("failed to delete key: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } var result SimpleResponse if err = json.Unmarshal(resp.Body, &result); err != nil { - return nil, fmt.Errorf("drop token failed: invalid JSON (%w)", err) + return nil, fmt.Errorf("delete key failed: invalid JSON (%w)", err) } if result.Code != 0 { diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 1afe7da264..c0b801a8eb 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -17,8 +17,8 @@ func tokenTypeDescription(t int, tok Token) string { } // Command parsers -func (p *Parser) parseLogout() (*Command, error) { - cmd := NewCommand("logout") +func (p *Parser) parseAPILogout() (*Command, error) { + cmd := NewCommand("api_logout") p.nextToken() // Semicolon is optional for UNSET TOKEN if p.curToken.Type == TokenSemicolon { @@ -27,8 +27,8 @@ func (p *Parser) parseLogout() (*Command, error) { return cmd, nil } -func (p *Parser) parseLoginUser() (*Command, error) { - cmd := NewCommand("login_user") +func (p *Parser) parseAPILoginUser() (*Command, error) { + cmd := NewCommand("api_login_user") p.nextToken() // consume LOGIN if p.curToken.Type != TokenUser { @@ -46,7 +46,8 @@ func (p *Parser) parseLoginUser() (*Command, error) { // Optional: PASSWORD 'password' if p.curToken.Type == TokenPassword { p.nextToken() - password, err := p.parseQuotedString() + var password string + password, err = p.parseQuotedString() if err != nil { return nil, err } @@ -62,8 +63,8 @@ func (p *Parser) parseLoginUser() (*Command, error) { return cmd, nil } -func (p *Parser) parsePingServer() (*Command, error) { - cmd := NewCommand("ping_server") +func (p *Parser) parseAPIPingServer() (*Command, error) { + cmd := NewCommand("api_ping_server") p.nextToken() // Semicolon is optional for UNSET TOKEN if p.curToken.Type == TokenSemicolon { @@ -72,8 +73,8 @@ func (p *Parser) parsePingServer() (*Command, error) { return cmd, nil } -func (p *Parser) parseRegisterCommand() (*Command, error) { - cmd := NewCommand("register_user") +func (p *Parser) parseAPIRegisterCommand() (*Command, error) { + cmd := NewCommand("api_register_user") if err := p.expectPeek(TokenUser); err != nil { return nil, err @@ -119,42 +120,28 @@ func (p *Parser) parseRegisterCommand() (*Command, error) { return cmd, nil } +// LIST CONFIGS; // LIST PROVIDER 'provider_name' MODELS; // LIST PROVIDER 'provider_name' INSTANCE 'instance_name' MODELS // LIST MODELS; -func (p *Parser) parseListCommand() (*Command, error) { +func (p *Parser) parseAPIListCommands() (*Command, error) { p.nextToken() // consume LIST switch p.curToken.Type { - case TokenVars: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_variables"), nil case TokenConfigs: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_configs"), nil - case TokenEnvs: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_environments"), nil + return p.parseAPIListConfigs() case TokenDatasets: - return p.parseListDatasets() - case TokenDocuments: - return p.parseListDatasetDocuments() + return p.parseAPIListDatasets() + case TokenDataset: + return p.parseAPIListDatasetDocuments() case TokenAgents: - return p.parseListAgents() - case TokenTokens: - return p.parseListTokens() + return p.parseAPIListAgents() + case TokenChats: + return p.parseAPIListChats() + case TokenSearches: + return p.parseAPIListSearches() + case TokenKeys: + return p.parseAPIListAPIKeys() case TokenModel: return p.parseListModelProviders() case TokenSupported: @@ -171,13 +158,6 @@ func (p *Parser) parseListCommand() (*Command, error) { return p.parseListDefaultModels() case TokenAvailable: return p.parseListAvailableProviders() - case TokenChats: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_user_chats"), nil case TokenFiles: return p.parseListFiles() case TokenQuotedString: @@ -189,8 +169,19 @@ func (p *Parser) parseListCommand() (*Command, error) { } } -func (p *Parser) parseListDatasets() (*Command, error) { - cmd := NewCommand("list_datasets") +// LIST CONFIGS; +func (p *Parser) parseAPIListConfigs() (*Command, error) { + p.nextToken() + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("api_list_configs"), nil +} + +func (p *Parser) parseAPIListDatasets() (*Command, error) { + cmd := NewCommand("api_list_datasets") p.nextToken() // consume DATASETS // Semicolon is optional for UNSET TOKEN @@ -200,13 +191,9 @@ func (p *Parser) parseListDatasets() (*Command, error) { return cmd, nil } -func (p *Parser) parseListDatasetDocuments() (*Command, error) { - p.nextToken() // consume DOCUMENTS - - if p.curToken.Type != TokenFrom { - return nil, fmt.Errorf("expected FROM") - } - p.nextToken() +// LIST DATASET 'dataset_name' DOCUMENTS; +func (p *Parser) parseAPIListDatasetDocuments() (*Command, error) { + p.nextToken() // consume DATASET datasetID, err := p.parseQuotedString() if err != nil { @@ -214,17 +201,53 @@ func (p *Parser) parseListDatasetDocuments() (*Command, error) { } p.nextToken() - cmd := NewCommand("list_dataset_documents") + if p.curToken.Type != TokenDocuments { + return nil, fmt.Errorf("expected DOCUMENTS") + } + + cmd := NewCommand("api_list_dataset_documents") cmd.Params["dataset_id"] = datasetID // Semicolon is optional for UNSET TOKEN if p.curToken.Type == TokenSemicolon { p.nextToken() } - return cmd, nil } +func (p *Parser) parseAPIListAgents() (*Command, error) { + p.nextToken() // consume AGENTS + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("api_list_agents"), nil +} + +func (p *Parser) parseAPIListChats() (*Command, error) { + p.nextToken() // consume CHATS + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("api_list_chats"), nil +} + +func (p *Parser) parseAPIListSearches() (*Command, error) { + p.nextToken() // consume SEARCHES + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("api_list_searches"), nil +} + func (p *Parser) parseGetMetadata() (*Command, error) { p.nextToken() // consume METADATA @@ -273,38 +296,10 @@ func (p *Parser) parseGetMetadata() (*Command, error) { return cmd, nil } -func (p *Parser) parseListAgents() (*Command, error) { - p.nextToken() // consume AGENTS - - if p.curToken.Type == TokenSemicolon { - return NewCommand("list_user_agents"), nil - } - - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("list_agents") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseListTokens() (*Command, error) { - p.nextToken() // consume TOKENS - cmd := NewCommand("list_tokens") - // Semicolon is optional for UNSET TOKEN +func (p *Parser) parseAPIListAPIKeys() (*Command, error) { + p.nextToken() // consume KEYS + cmd := NewCommand("api_list_api_keys") + // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() } @@ -567,8 +562,8 @@ func (p *Parser) parseCreateCommand() (*Command, error) { return p.parseCreateDataset() case TokenChat: return p.parseCreateChat() - case TokenToken: - return p.parseCreateToken() + case TokenKey: + return p.parseAPICreateKey() case TokenChunkStore: return p.parseCreateChunkStore() case TokenMetadata: @@ -596,15 +591,15 @@ func (p *Parser) parseAddCommand() (*Command, error) { } } -func (p *Parser) parseCreateToken() (*Command, error) { - p.nextToken() // consume TOKEN +func (p *Parser) parseAPICreateKey() (*Command, error) { + p.nextToken() // consume KEY - // Semicolon is optional for UNSET TOKEN + // Semicolon is optional for UNSET KEY if p.curToken.Type == TokenSemicolon { p.nextToken() } - return NewCommand("create_token"), nil + return NewCommand("api_create_api_key"), nil } // Internal CLI for GO @@ -1213,8 +1208,8 @@ func (p *Parser) parseDropCommand() (*Command, error) { return p.parseDropDataset() case TokenChat: return p.parseDropChat() - case TokenToken: - return p.parseDropToken() + case TokenKey: + return p.parseAPIDeleteAPIKey() case TokenChunkStore: return p.parseDropChunkStore() case TokenMetadata: @@ -1262,30 +1257,18 @@ func (p *Parser) parseRemoveCommand() (*Command, error) { } } -func (p *Parser) parseDropToken() (*Command, error) { - p.nextToken() // consume TOKEN +func (p *Parser) parseAPIDeleteAPIKey() (*Command, error) { + p.nextToken() // consume KEY - tokenValue, err := p.parseQuotedString() + apiKey, err := p.parseQuotedString() if err != nil { return nil, err } - - p.nextToken() - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } p.nextToken() - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } + cmd := NewCommand("api_delete_api_key") + cmd.Params["api_key"] = apiKey - cmd := NewCommand("drop_token") - cmd.Params["token"] = tokenValue - cmd.Params["user_name"] = userName - - p.nextToken() // Semicolon is optional if p.curToken.Type == TokenSemicolon { p.nextToken() @@ -3647,7 +3630,7 @@ func (p *Parser) parseBenchmarkCommand() (*Command, error) { func (p *Parser) parseUserStatement() (*Command, error) { switch p.curToken.Type { case TokenPing: - return p.parsePingServer() + return p.parseAPIPingServer() case TokenDelete: return p.parseDeleteCommand() case TokenShow: @@ -3663,7 +3646,7 @@ func (p *Parser) parseUserStatement() (*Command, error) { case TokenReset: return p.parseResetCommand() case TokenList: - return p.parseListCommand() + return p.parseAPIListCommands() case TokenParse: return p.parseParseCommand() case TokenImport: diff --git a/internal/handler/api_token.go b/internal/handler/api_token.go index 137e011c1e..4e0d700c56 100644 --- a/internal/handler/api_token.go +++ b/internal/handler/api_token.go @@ -26,16 +26,7 @@ import ( "github.com/gin-gonic/gin" ) -// ListTokens list all API tokens for the current user's tenant -// @Summary List API Tokens -// @Description List all API tokens for the current user's tenant -// @Tags system -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Success 200 {object} map[string]interface{} -// @Router /api/v1/system/tokens [get] -func (h *SystemHandler) ListTokens(c *gin.Context) { +func (h *SystemHandler) ListAPIKeys(c *gin.Context) { // Get current user from context user, exists := c.Get("user") if !exists { @@ -68,12 +59,12 @@ func (h *SystemHandler) ListTokens(c *gin.Context) { tenantID := tenants[0].TenantID - // Get tokens for the tenant - tokens, err := h.systemService.ListAPITokens(tenantID) + // Get keys for the tenant + keys, err := h.systemService.ListAPIKeys(tenantID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, - "message": "Failed to list tokens", + "message": "Failed to list keys", }) return } @@ -81,21 +72,11 @@ func (h *SystemHandler) ListTokens(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", - "data": tokens, + "data": keys, }) } -// CreateToken creates a new API token for the current user's tenant -// @Summary Create API Token -// @Description Generate a new API token for the current user's tenant -// @Tags system -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param name query string false "Name of the token" -// @Success 200 {object} map[string]interface{} -// @Router /api/v1/system/tokens [post] -func (h *SystemHandler) CreateToken(c *gin.Context) { +func (h *SystemHandler) CreateKey(c *gin.Context) { // Get current user from context user, exists := c.Get("user") if !exists { @@ -129,7 +110,7 @@ func (h *SystemHandler) CreateToken(c *gin.Context) { tenantID := tenants[0].TenantID // Parse request - var req service.CreateAPITokenRequest + var req service.CreateAPIKeyRequest if err := c.ShouldBind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, @@ -138,12 +119,12 @@ func (h *SystemHandler) CreateToken(c *gin.Context) { return } - // Create token - token, err := h.systemService.CreateAPIToken(tenantID, &req) + // Create key + key, err := h.systemService.CreateAPIKey(tenantID, &req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, - "message": "Failed to create token", + "message": "Failed to create key", }) return } @@ -151,21 +132,11 @@ func (h *SystemHandler) CreateToken(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", - "data": token, + "data": key, }) } -// DeleteToken deletes an API token -// @Summary Delete API Token -// @Description Remove an API token for the current user's tenant -// @Tags system -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param token path string true "The API token to remove" -// @Success 200 {object} map[string]interface{} -// @Router /api/v1/system/tokens/{token} [delete] -func (h *SystemHandler) DeleteToken(c *gin.Context) { +func (h *SystemHandler) DeleteKey(c *gin.Context) { // Get current user from context user, exists := c.Get("user") if !exists { @@ -198,21 +169,21 @@ func (h *SystemHandler) DeleteToken(c *gin.Context) { tenantID := tenants[0].TenantID - // Get token from path parameter - token := c.Param("token") - if token == "" { + // Get key from path parameter + key := c.Param("key") + if key == "" { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, - "message": "Token is required", + "message": "Key is required", }) return } - // Delete token - if err := h.systemService.DeleteAPIToken(tenantID, token); err != nil { + // Delete key + if err = h.systemService.DeleteAPIKey(tenantID, key); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, - "message": "Failed to delete token", + "message": "Failed to delete key", }) return } diff --git a/internal/handler/document.go b/internal/handler/document.go index 7054789558..384b75268b 100644 --- a/internal/handler/document.go +++ b/internal/handler/document.go @@ -489,7 +489,7 @@ func (h *DocumentHandler) ListDocuments(c *gin.Context) { userID := c.GetString("user_id") if !h.datasetService.Accessible(datasetID, userID) { - jsonError(c, common.CodeAuthenticationError, "No authorization.") + jsonError(c, common.CodeAuthenticationError, "No authorization to access the dataset.") return } diff --git a/internal/router/router.go b/internal/router/router.go index 34ac580e77..6973962068 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -554,11 +554,21 @@ func (r *Router) Setup(engine *gin.Engine) { tokens := system.Group("/tokens") { // list tokens /api/v1/system/tokens GET - tokens.GET("", r.systemHandler.ListTokens) + tokens.GET("", r.systemHandler.ListAPIKeys) // create token /api/v1/system/tokens POST - tokens.POST("", r.systemHandler.CreateToken) - // delete token /api/v1/system/tokens/:token DELETE - tokens.DELETE("/:token", r.systemHandler.DeleteToken) + tokens.POST("", r.systemHandler.CreateKey) + // delete token /api/v1/system/tokens/:key DELETE + tokens.DELETE("/:key", r.systemHandler.DeleteKey) + } + + keys := system.Group("/keys") + { + // list keys /api/v1/system/keys GET + keys.GET("", r.systemHandler.ListAPIKeys) + // create key /api/v1/system/keys POST + keys.POST("", r.systemHandler.CreateKey) + // delete key /api/v1/system/keys/:key DELETE + keys.DELETE("/:key", r.systemHandler.DeleteKey) } } } diff --git a/internal/service/api_token.go b/internal/service/api_token.go index 30cf29f4aa..1fe936abb7 100644 --- a/internal/service/api_token.go +++ b/internal/service/api_token.go @@ -22,8 +22,8 @@ import ( "ragflow/internal/utility" ) -// TokenResponse token response -type TokenResponse struct { +// APIKeyResponse key response +type APIKeyResponse struct { TenantID string `json:"tenant_id"` Token string `json:"token"` DialogID *string `json:"dialog_id,omitempty"` @@ -33,83 +33,82 @@ type TokenResponse struct { UpdateTime *int64 `json:"update_time,omitempty"` } -// ListAPITokens list all API tokens for a tenant -func (s *SystemService) ListAPITokens(tenantID string) ([]*TokenResponse, error) { +// ListAPIKeys list all API keys for a tenant +func (s *SystemService) ListAPIKeys(tenantID string) ([]*APIKeyResponse, error) { APITokenDAO := dao.NewAPITokenDAO() - tokens, err := APITokenDAO.GetByTenantID(tenantID) + keys, err := APITokenDAO.GetByTenantID(tenantID) if err != nil { return nil, err } - responses := make([]*TokenResponse, len(tokens)) - for i, token := range tokens { - beta := token.Beta + responses := make([]*APIKeyResponse, len(keys)) + for i, key := range keys { + beta := key.Beta if beta == nil || *beta == "" { generatedBeta := utility.GenerateBetaAPIToken(utility.GenerateAPIToken()) - if err := dao.DB.Model(&entity.APIToken{}). - Where("tenant_id = ? AND token = ?", tenantID, token.Token). + if err = dao.DB.Model(&entity.APIToken{}). + Where("tenant_id = ? AND token = ?", tenantID, key.Token). Updates(map[string]interface{}{ "beta": generatedBeta, }).Error; err != nil { return nil, err } beta = &generatedBeta - token.Beta = beta + key.Beta = beta } - responses[i] = &TokenResponse{ - TenantID: token.TenantID, - Token: token.Token, - DialogID: token.DialogID, - Source: token.Source, + responses[i] = &APIKeyResponse{ + TenantID: key.TenantID, + Token: key.Token, + DialogID: key.DialogID, + Source: key.Source, Beta: beta, - CreateTime: token.CreateTime, - UpdateTime: token.UpdateTime, + CreateTime: key.CreateTime, + UpdateTime: key.UpdateTime, } } return responses, nil } -// CreateAPITokenRequest create token request -type CreateAPITokenRequest struct { +// CreateAPIKeyRequest create key request +type CreateAPIKeyRequest struct { Name string `json:"name" form:"name"` } -// CreateAPIToken creates a new API token for a tenant -func (s *SystemService) CreateAPIToken(tenantID string, req *CreateAPITokenRequest) (*TokenResponse, error) { +// CreateAPIKey creates a new API key for a tenant +func (s *SystemService) CreateAPIKey(tenantID string, req *CreateAPIKeyRequest) (*APIKeyResponse, error) { APITokenDAO := dao.NewAPITokenDAO() - // Generate token and beta values - // token: "ragflow-" + secrets.token_urlsafe(32) + // Generate key and beta values + // key: "ragflow-" + secrets.token_urlsafe(32) APIToken := utility.GenerateAPIToken() // beta: generate_confirmation_token().replace("ragflow-", "")[:32] betaAPIKey := utility.GenerateBetaAPIToken(utility.GenerateAPIToken()) - APITokenData := &entity.APIToken{ + APIKeyData := &entity.APIToken{ TenantID: tenantID, Token: APIToken, Beta: &betaAPIKey, } - if err := APITokenDAO.Create(APITokenData); err != nil { + if err := APITokenDAO.Create(APIKeyData); err != nil { return nil, err } - return &TokenResponse{ - TenantID: APITokenData.TenantID, - Token: APITokenData.Token, - DialogID: APITokenData.DialogID, - Source: APITokenData.Source, - Beta: APITokenData.Beta, - CreateTime: APITokenData.CreateTime, - UpdateTime: APITokenData.UpdateTime, + return &APIKeyResponse{ + TenantID: APIKeyData.TenantID, + Token: APIKeyData.Token, + DialogID: APIKeyData.DialogID, + Source: APIKeyData.Source, + Beta: APIKeyData.Beta, + CreateTime: APIKeyData.CreateTime, + UpdateTime: APIKeyData.UpdateTime, }, nil } -// DeleteAPIToken deletes an API token by tenant ID and token value -func (s *SystemService) DeleteAPIToken(tenantID, token string) error { +func (s *SystemService) DeleteAPIKey(tenantID, key string) error { APITokenDAO := dao.NewAPITokenDAO() - _, err := APITokenDAO.DeleteByTenantIDAndToken(tenantID, token) + _, err := APITokenDAO.DeleteByTenantIDAndToken(tenantID, key) return err } diff --git a/internal/service/chat.go b/internal/service/chat.go index b11ba48dc6..fa03080b52 100644 --- a/internal/service/chat.go +++ b/internal/service/chat.go @@ -54,6 +54,7 @@ type ChatWithKBNames struct { // ListChatsResponse list chats response type ListChatsResponse struct { + Total int64 `json:"total"` Chats []*ChatWithKBNames `json:"chats"` } @@ -107,6 +108,7 @@ func (s *ChatService) ListChats(userID, status, keywords string, page, pageSize } return &ListChatsResponse{ + Total: total, Chats: chatsWithKBNames, }, nil }