package accesscontrol import ( "context" "fmt" "strconv" "github.com/Tencent/WeKnora/internal/types" "github.com/gin-gonic/gin" "gorm.io/gorm" "gorm.io/gorm/clause" ) type knowledgeReviewDocument struct { ID string `json:"id"` Title string `json:"title"` Type string `json:"type"` } type knowledgeReviewChunk struct { ID string `json:"id"` Content string `json:"content"` ChunkType string `json:"chunk_type"` ChunkIndex int `json:"chunk_index"` IsEnabled bool `json:"is_enabled"` } type knowledgeReviewPage struct { Documents []knowledgeReviewDocument `json:"documents,omitempty"` Document *knowledgeReviewDocument `json:"document,omitempty"` Chunks []knowledgeReviewChunk `json:"chunks,omitempty"` Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` } func (s *Service) GetKnowledgeVersionReview(c *gin.Context) { page, err := strconv.Atoi(c.DefaultQuery("page", "1")) if err != nil || page < 1 || page > 100000 { versionResult(c, errInvalid, nil) return } result, err := s.knowledgeVersionReview(c.Request.Context(), c.Param("id"), c.Query("document_id"), page) versionResult(c, err, result) } // Review reads only an immutable stage, without granting ordinary KB access or // exposing file handles, provider configuration, or download URLs. func (s *Service) knowledgeVersionReview(ctx context.Context, id, document string, page int) (*knowledgeReviewPage, error) { if err := versionPermission(ctx, "knowledge", false); err != nil { return nil, err } tenant, user := identity(ctx) result := &knowledgeReviewPage{Page: page, PageSize: 20} err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var submission ResourceSubmission q := tx.Clauses(clause.Locking{Strength: "SHARE"}).Where("tenant_id=? AND id=? AND kind='knowledge'", tenant, id) if !administrator(ctx) { q = q.Where("owner_id=?", user) } if err := q.First(&submission).Error; err != nil { return err } if submission.SnapshotID == "" || (submission.Status != "pending" && submission.Status != "rejected" && submission.Status != "withdrawn") { return fmt.Errorf("%w: 当前记录没有可查看的只读知识快照;已批准记录请查看已发布知识库", errConflict) } var kb types.KnowledgeBase if err := tx.Clauses(clause.Locking{Strength: "SHARE"}).Where("tenant_id=? AND id=? AND is_temporary=true AND version_state='snapshot'", tenant, submission.SnapshotID).First(&kb).Error; err != nil { return err } docs := tx.Model(&types.Knowledge{}).Where("tenant_id=? AND knowledge_base_id=?", tenant, kb.ID) if document == "" { if err := docs.Count(&result.Total).Error; err != nil { return err } result.Documents = []knowledgeReviewDocument{} return docs.Select("id,title,type").Order("title,id").Offset((page - 1) * result.PageSize).Limit(result.PageSize).Find(&result.Documents).Error } result.Document = &knowledgeReviewDocument{} if err := docs.Select("id,title,type").Where("id=?", document).Take(result.Document).Error; err != nil { return err } chunks := tx.Model(&types.Chunk{}).Where("tenant_id=? AND knowledge_base_id=? AND knowledge_id=?", tenant, kb.ID, document) if err := chunks.Count(&result.Total).Error; err != nil { return err } result.Chunks = []knowledgeReviewChunk{} return chunks.Select("id,content,chunk_type,chunk_index,is_enabled").Order("chunk_index,id").Offset((page - 1) * result.PageSize).Limit(result.PageSize).Find(&result.Chunks).Error }) return result, err }