mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-18 13:47:21 +08:00
## Summary Port the canvas-template catalogue endpoint to the Go API server. Listed in the Go-API port checklist of #15240. Mirrors `list_agent_template` in `api/apps/restful_apis/agent_api.py`: returns every row from the `canvas_template` table so that the UI can render the template gallery on the New-Agent screen. ## What - `internal/dao/canvas_template.go` — new `CanvasTemplateDAO.GetAll()` ordered by `create_time desc` (newest templates first). - `internal/service/agent.go` — wire the new DAO into `AgentService` and expose `ListTemplates() ([]*entity.CanvasTemplate, error)`. - `internal/handler/agent.go` — new `AgentHandler.ListTemplates` HTTP handler (auth-gated, mirrors Python `@login_required`). - `internal/router/router.go` — `agents.GET("/templates", r.agentHandler.ListTemplates)` registered alongside the existing `GET /agents`. - `internal/handler/agent_test.go` — three new tests covering: success path, empty-list → JSON array (not `null`), and the auth gate. ## Notes - `CanvasTemplate` entity, GORM tags, and DB migration already exist in `internal/entity/canvas.go` and `internal/dao/database.go` — no schema change required. - The handler coerces a `nil` slice to `[]*entity.CanvasTemplate{}` so the JSON payload is always an array (the frontend does `data.map(...)` on it). ## Test plan - [x] `go vet ./internal/handler ./internal/service ./internal/dao ./internal/router` clean - [x] Three unit tests added; existing `TestListAgents_Success` untouched - [ ] CI runs `go test ./internal/handler` with cgo binding linked ## Related - Tracker: #15240
185 lines
5.5 KiB
Go
185 lines
5.5 KiB
Go
//
|
|
// 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 service
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"ragflow/internal/common"
|
|
"ragflow/internal/dao"
|
|
"ragflow/internal/entity"
|
|
)
|
|
|
|
// AgentService agent service
|
|
type AgentService struct {
|
|
canvasDAO *dao.UserCanvasDAO
|
|
userTenantDAO *dao.UserTenantDAO
|
|
userCanvasVersionDAO *dao.UserCanvasVersionDAO
|
|
canvasTemplateDAO *dao.CanvasTemplateDAO
|
|
}
|
|
|
|
// NewAgentService create agent service
|
|
func NewAgentService() *AgentService {
|
|
return &AgentService{
|
|
canvasDAO: dao.NewUserCanvasDAO(),
|
|
userTenantDAO: dao.NewUserTenantDAO(),
|
|
userCanvasVersionDAO: dao.NewUserCanvasVersionDAO(),
|
|
canvasTemplateDAO: dao.NewCanvasTemplateDAO(),
|
|
}
|
|
}
|
|
|
|
// ListTemplates returns every canvas template. Mirrors Python
|
|
// agent_api.list_agent_template, which iterates CanvasTemplateService.get_all()
|
|
// and serialises each row.
|
|
func (s *AgentService) ListTemplates() ([]*entity.CanvasTemplate, error) {
|
|
return s.canvasTemplateDAO.GetAll()
|
|
}
|
|
|
|
// AgentItem is one entry in the list response.
|
|
type AgentItem struct {
|
|
ID string `json:"id"`
|
|
Avatar *string `json:"avatar,omitempty"`
|
|
Title *string `json:"title,omitempty"`
|
|
Permission string `json:"permission"`
|
|
CanvasType *string `json:"canvas_type,omitempty"`
|
|
CanvasCategory string `json:"canvas_category"`
|
|
CreateTime *int64 `json:"create_time,omitempty"`
|
|
UpdateTime *int64 `json:"update_time,omitempty"`
|
|
}
|
|
|
|
// ListAgentsResponse is the response body for GET /api/v1/agents.
|
|
type ListAgentsResponse struct {
|
|
Canvas []*AgentItem `json:"canvas"`
|
|
Total int64 `json:"total"`
|
|
}
|
|
|
|
func toAgentItem(c *entity.UserCanvas) *AgentItem {
|
|
return &AgentItem{
|
|
ID: c.ID,
|
|
Avatar: c.Avatar,
|
|
Title: c.Title,
|
|
Permission: c.Permission,
|
|
CanvasType: c.CanvasType,
|
|
CanvasCategory: c.CanvasCategory,
|
|
CreateTime: c.CreateTime,
|
|
UpdateTime: c.UpdateTime,
|
|
}
|
|
}
|
|
|
|
// ListAgents returns agent canvases visible to userID.
|
|
// Mirrors Python agent_api.list_agents — validates owner_ids against joined tenants,
|
|
// then delegates to the DAO.
|
|
func (s *AgentService) ListAgents(
|
|
userID string,
|
|
keywords string,
|
|
page, pageSize int,
|
|
orderby string,
|
|
desc bool,
|
|
ownerIDs []string,
|
|
canvasCategory string,
|
|
) (*ListAgentsResponse, common.ErrorCode, error) {
|
|
// Build the set of tenant IDs the user is authorised to query.
|
|
tenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID)
|
|
if err != nil {
|
|
return nil, common.CodeServerError, fmt.Errorf("failed to get tenant IDs: %w", err)
|
|
}
|
|
authorised := make(map[string]struct{}, len(tenantIDs)+1)
|
|
for _, id := range tenantIDs {
|
|
authorised[id] = struct{}{}
|
|
}
|
|
authorised[userID] = struct{}{}
|
|
|
|
var effectiveOwnerIDs []string
|
|
if len(ownerIDs) > 0 {
|
|
for _, id := range ownerIDs {
|
|
if _, ok := authorised[id]; !ok {
|
|
return nil, common.CodeOperatingError, fmt.Errorf("only authorized owner_ids can be queried")
|
|
}
|
|
}
|
|
effectiveOwnerIDs = ownerIDs
|
|
} else {
|
|
effectiveOwnerIDs = make([]string, 0, len(authorised))
|
|
for id := range authorised {
|
|
effectiveOwnerIDs = append(effectiveOwnerIDs, id)
|
|
}
|
|
}
|
|
|
|
canvases, total, err := s.canvasDAO.ListByTenantIDs(
|
|
effectiveOwnerIDs,
|
|
userID,
|
|
page,
|
|
pageSize,
|
|
orderby,
|
|
desc,
|
|
keywords,
|
|
canvasCategory,
|
|
)
|
|
if err != nil {
|
|
return nil, common.CodeServerError, fmt.Errorf("failed to list agents: %w", err)
|
|
}
|
|
|
|
items := make([]*AgentItem, len(canvases))
|
|
for i, c := range canvases {
|
|
items[i] = toAgentItem(c)
|
|
}
|
|
return &ListAgentsResponse{Canvas: items, Total: total}, common.CodeSuccess, nil
|
|
}
|
|
|
|
// CheckCanvasAccess checks if a user has access to a canvas.
|
|
// Returns true if the user is the owner or has team-level permission.
|
|
func (s *AgentService) CheckCanvasAccess(userID, canvasID string) (bool, error) {
|
|
canvas, err := s.canvasDAO.GetByID(canvasID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// Owner always has access
|
|
if canvas.UserID == userID {
|
|
return true, nil
|
|
}
|
|
// Non-owner: only team-level permission grants tenant access
|
|
if canvas.Permission != string(entity.TenantPermissionTeam) {
|
|
return false, nil
|
|
}
|
|
tenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, tid := range tenantIDs {
|
|
if canvas.UserID == tid {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// ListVersions returns all versions for an agent canvas, ordered by update_time DESC.
|
|
func (s *AgentService) ListVersions(canvasID string) ([]*entity.UserCanvasVersion, error) {
|
|
return s.userCanvasVersionDAO.ListByCanvasID(canvasID)
|
|
}
|
|
|
|
// GetVersion returns a specific version by ID, verifying it belongs to the given canvas.
|
|
func (s *AgentService) GetVersion(canvasID, versionID string) (*entity.UserCanvasVersion, error) {
|
|
version, err := s.userCanvasVersionDAO.GetByID(versionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if version.UserCanvasID != canvasID {
|
|
return nil, fmt.Errorf("version not found")
|
|
}
|
|
return version, nil
|
|
}
|