mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-10 21:32:43 +08:00
feat: add user ownership to resources (#21914)
This commit is contained in:
1 parent
58360341ab
commit
84705f75c8
37 files changed
+247
-8
No files matched your search
@@ -286,6 +286,7 @@ func (s *Service) CreateUser(ctx context.Context, username, password, role strin
|
||||
file := &entity.File{
|
||||
ID: fileID,
|
||||
ParentID: fileID,
|
||||
UserID: userID,
|
||||
TenantID: userID,
|
||||
CreatedBy: userID,
|
||||
Name: "/",
|
||||
|
||||
@@ -439,6 +439,9 @@ func InitDB(ctx context.Context, migrateDB bool) error {
|
||||
if err = migrateConversationReferences(ctx, DB); err != nil {
|
||||
return fmt.Errorf("failed to move references into messages: %w", err)
|
||||
}
|
||||
if err = migrateResourceUserIDs(ctx, DB); err != nil {
|
||||
return fmt.Errorf("failed to backfill resource user IDs: %w", err)
|
||||
}
|
||||
common.Info("Database schema migrated successfully")
|
||||
} else {
|
||||
for _, column := range []string{"backlog_doc_ids", "inflight_doc_ids"} {
|
||||
|
||||
@@ -221,6 +221,7 @@ func (dao *FileDAO) GetRootFolder(ctx context.Context, db *gorm.DB, tenantID str
|
||||
file = entity.File{
|
||||
ID: fileID,
|
||||
ParentID: fileID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: "/",
|
||||
@@ -323,6 +324,9 @@ func (dao *FileDAO) GetAllParentFolders(ctx context.Context, db *gorm.DB, startI
|
||||
|
||||
// Create creates a new file
|
||||
func (dao *FileDAO) Create(ctx context.Context, db *gorm.DB, file *entity.File) error {
|
||||
if file.UserID == "" {
|
||||
file.UserID = file.CreatedBy
|
||||
}
|
||||
return db.WithContext(ctx).Create(file).Error
|
||||
}
|
||||
|
||||
@@ -406,6 +410,7 @@ func (dao *FileDAO) CreateFolder(ctx context.Context, db *gorm.DB, parentID, ten
|
||||
file := &entity.File{
|
||||
ID: common.GenerateToken(),
|
||||
ParentID: parentID,
|
||||
UserID: createdBy,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: createdBy,
|
||||
Name: name,
|
||||
@@ -421,6 +426,9 @@ func (dao *FileDAO) CreateFolder(ctx context.Context, db *gorm.DB, parentID, ten
|
||||
|
||||
// Insert inserts a new file record
|
||||
func (dao *FileDAO) Insert(ctx context.Context, db *gorm.DB, file *entity.File) error {
|
||||
if file.UserID == "" {
|
||||
file.UserID = file.CreatedBy
|
||||
}
|
||||
return db.WithContext(ctx).Create(file).Error
|
||||
}
|
||||
|
||||
@@ -615,6 +623,7 @@ func (dao *FileDAO) newAFileFromDataset(ctx context.Context, db *gorm.DB, tenant
|
||||
file := &entity.File{
|
||||
ID: fileID,
|
||||
ParentID: parentID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: name,
|
||||
@@ -657,6 +666,7 @@ func (dao *FileDAO) addFileFromKB(ctx context.Context, db *gorm.DB, doc *entity.
|
||||
file := &entity.File{
|
||||
ID: fileID,
|
||||
ParentID: datasetFolderID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: docName,
|
||||
|
||||
@@ -128,6 +128,9 @@ func (dao *KnowledgebaseDAO) EmbeddingBaseName(ctx context.Context, db *gorm.DB,
|
||||
|
||||
// Create creates a new knowledge base record
|
||||
func (dao *KnowledgebaseDAO) Create(ctx context.Context, db *gorm.DB, kb *entity.Knowledgebase) error {
|
||||
if kb.UserID == "" {
|
||||
kb.UserID = kb.CreatedBy
|
||||
}
|
||||
return db.WithContext(ctx).Create(kb).Error
|
||||
}
|
||||
|
||||
|
||||
@@ -126,18 +126,28 @@ func shouldSkipMigration(currentDBVersion, targetVersion string) bool {
|
||||
// compareMigrationVersion orders two canonical migration versions, returning -1,
|
||||
// 0 or 1 as current sorts before, equal to, or after target.
|
||||
//
|
||||
// It follows semver except for development prereleases, where it matches PEP
|
||||
// 440, the ordering the Python migration gets from packaging.version: a
|
||||
// trailing ".devN" identifier marks a build leading the version it is attached
|
||||
// to, so v1.0.0-rc1.dev1 sorts below v1.0.0-rc1 and below v1.0.0-rc1.dev2.
|
||||
// semver orders a longer prerelease above its prefix, which would place a dev
|
||||
// build ahead of the very release it precedes.
|
||||
// It follows semver except for development and post-release versions, where it
|
||||
// matches PEP 440, the ordering the Python migration gets from
|
||||
// packaging.version. A trailing ".devN" marks a build leading the version it is
|
||||
// attached to, while a numeric "-N" suffix marks a post-release version.
|
||||
func compareMigrationVersion(current, target string) int {
|
||||
currentCore := migrationVersionCore(current)
|
||||
targetCore := migrationVersionCore(target)
|
||||
if cmp := semver.Compare(currentCore, targetCore); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
currentPost, isCurrentPost := postReleaseNumber(current)
|
||||
targetPost, isTargetPost := postReleaseNumber(target)
|
||||
if isCurrentPost || isTargetPost {
|
||||
switch {
|
||||
case isCurrentPost && isTargetPost:
|
||||
return comparePostRelease(currentPost, targetPost)
|
||||
case isCurrentPost:
|
||||
return 1
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
currentBase, currentDev := splitDevPrerelease(semver.Prerelease(current))
|
||||
targetBase, targetDev := splitDevPrerelease(semver.Prerelease(target))
|
||||
if cmp := semver.Compare(currentCore+currentBase, targetCore+targetBase); cmp != 0 {
|
||||
@@ -157,6 +167,63 @@ func compareMigrationVersion(current, target string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
type postReleaseVersion struct {
|
||||
number int
|
||||
devNumber int
|
||||
isDev bool
|
||||
}
|
||||
|
||||
// postReleaseNumber recognizes the PEP 440 "-N" post-release shorthand, such
|
||||
// as v1.0.0-1. semver parses it as a numeric prerelease, which sorts before rc
|
||||
// versions and would make a later database migration appear older.
|
||||
func postReleaseNumber(version string) (postReleaseVersion, bool) {
|
||||
prerelease := strings.TrimPrefix(semver.Prerelease(version), "-")
|
||||
if prerelease == "" {
|
||||
return postReleaseVersion{}, false
|
||||
}
|
||||
parts := strings.Split(prerelease, ".")
|
||||
number, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return postReleaseVersion{}, false
|
||||
}
|
||||
post := postReleaseVersion{number: number}
|
||||
if len(parts) == 1 {
|
||||
return post, true
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
return postReleaseVersion{}, false
|
||||
}
|
||||
dev, ok := devIdentifierNumber(parts[1])
|
||||
if !ok {
|
||||
return postReleaseVersion{}, false
|
||||
}
|
||||
post.devNumber = dev
|
||||
post.isDev = true
|
||||
return post, true
|
||||
}
|
||||
|
||||
func comparePostRelease(current, target postReleaseVersion) int {
|
||||
if current.number < target.number {
|
||||
return -1
|
||||
}
|
||||
if current.number > target.number {
|
||||
return 1
|
||||
}
|
||||
if current.isDev && !target.isDev {
|
||||
return -1
|
||||
}
|
||||
if !current.isDev && target.isDev {
|
||||
return 1
|
||||
}
|
||||
if current.devNumber < target.devNumber {
|
||||
return -1
|
||||
}
|
||||
if current.devNumber > target.devNumber {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// migrationVersionCore strips prerelease and build metadata, leaving the
|
||||
// "vMAJOR.MINOR.PATCH" core the two versions are first compared on.
|
||||
func migrationVersionCore(version string) string {
|
||||
@@ -196,8 +263,9 @@ func devIdentifierNumber(identifier string) (int, bool) {
|
||||
return value, true
|
||||
}
|
||||
|
||||
// parseMigrationVersion normalizes a version to the "vMAJOR.MINOR.PATCH" form
|
||||
// understood by semver, returning an empty string when it cannot be parsed.
|
||||
// parseMigrationVersion normalizes a version to the semver form used for
|
||||
// ordering, returning an empty string when it cannot be parsed. A numeric
|
||||
// fourth component is a post-release counter: v1.0.0.1 becomes v1.0.0-1.
|
||||
func parseMigrationVersion(version string) string {
|
||||
normalized := strings.TrimSpace(version)
|
||||
normalized = strings.TrimPrefix(normalized, "v")
|
||||
@@ -206,6 +274,17 @@ func parseMigrationVersion(version string) string {
|
||||
return ""
|
||||
}
|
||||
normalized = "v" + normalized
|
||||
if !semver.IsValid(normalized) {
|
||||
parts := strings.Split(normalized, ".")
|
||||
if len(parts) == 4 {
|
||||
if _, err := strconv.Atoi(parts[3]); err == nil {
|
||||
candidate := strings.Join(parts[:3], ".") + "-" + parts[3]
|
||||
if semver.IsValid(candidate) {
|
||||
normalized = candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !semver.IsValid(normalized) {
|
||||
common.Warn("Invalid migration version format", zap.String("version", version))
|
||||
return ""
|
||||
|
||||
@@ -53,6 +53,9 @@ func TestShouldSkipMigration(t *testing.T) {
|
||||
{name: "missing v prefix", current: "0.27.1", target: "v0.27.1", want: true},
|
||||
{name: "invalid current", current: "not-a-version", target: "v0.27.1", want: false},
|
||||
{name: "invalid target", current: "v0.27.1", target: "not-a-version", want: false},
|
||||
{name: "four-part post-release version", current: "v1.0.0.1", target: "v1.0.0.1", want: true},
|
||||
{name: "four-part post-release follows release candidate", current: "v1.0.0.1", target: "v1.0.0-rc3.dev3", want: true},
|
||||
{name: "release candidate precedes four-part post-release", current: "v1.0.0-rc3.dev3", target: "v1.0.0.1", want: false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// 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 dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"ragflow/internal/entity"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const resourceUserIDMigrationTargetVersion = "v1.0.0.1"
|
||||
|
||||
// migrateResourceUserIDs fills owner IDs added to resource tables. Existing
|
||||
// created_by values identify the resource owner; tables without created_by
|
||||
// retain the OSS invariant that tenant_id identifies the owner.
|
||||
func migrateResourceUserIDs(ctx context.Context, db *gorm.DB) error {
|
||||
version, err := GetDatabaseMigrationVersion(ctx, db)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read database migration version: %w", err)
|
||||
}
|
||||
if shouldSkipMigration(version, resourceUserIDMigrationTargetVersion) {
|
||||
return nil
|
||||
}
|
||||
|
||||
resources := []struct {
|
||||
table string
|
||||
model interface{}
|
||||
ownerField string
|
||||
ownerColumn string
|
||||
fallbackField string
|
||||
fallbackColumn string
|
||||
}{
|
||||
{table: "chat_channel", model: &entity.ChatChannel{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "compilation_template_group", model: &entity.CompilationTemplateGroup{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "compilation_template", model: &entity.CompilationTemplate{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "connector", model: &entity.Connector{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "dialog", model: &entity.Chat{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "file", model: &entity.File{}, ownerField: "CreatedBy", ownerColumn: "created_by", fallbackField: "TenantID", fallbackColumn: "tenant_id"},
|
||||
{table: "knowledgebase", model: &entity.Knowledgebase{}, ownerField: "CreatedBy", ownerColumn: "created_by", fallbackField: "TenantID", fallbackColumn: "tenant_id"},
|
||||
{table: "memory", model: &entity.Memory{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "search", model: &entity.Search{}, ownerField: "CreatedBy", ownerColumn: "created_by", fallbackField: "TenantID", fallbackColumn: "tenant_id"},
|
||||
{table: "mcp_server", model: &entity.MCPServer{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "skill_spaces", model: &entity.SkillSpace{}, ownerField: "TenantID", ownerColumn: "tenant_id"},
|
||||
{table: "document", model: &entity.Document{}, ownerField: "CreatedBy", ownerColumn: "created_by"},
|
||||
{table: "evaluation_datasets", model: &entity.EvaluationDataset{}, ownerField: "CreatedBy", ownerColumn: "created_by", fallbackField: "TenantID", fallbackColumn: "tenant_id"},
|
||||
}
|
||||
|
||||
scoped := db.WithContext(ctx)
|
||||
migrator := scoped.Migrator()
|
||||
for _, resource := range resources {
|
||||
if !migrator.HasTable(resource.model) {
|
||||
continue
|
||||
}
|
||||
if !migrator.HasColumn(resource.model, "UserID") {
|
||||
return fmt.Errorf("%s.user_id is missing after GORM migration", resource.table)
|
||||
}
|
||||
if !migrator.HasColumn(resource.model, resource.ownerField) {
|
||||
return fmt.Errorf("%s.%s is missing", resource.table, resource.ownerColumn)
|
||||
}
|
||||
if resource.fallbackField != "" && !migrator.HasColumn(resource.model, resource.fallbackField) {
|
||||
return fmt.Errorf("%s.%s is missing", resource.table, resource.fallbackColumn)
|
||||
}
|
||||
|
||||
ownerExpr := fmt.Sprintf("NULLIF(%s, '')", resource.ownerColumn)
|
||||
if resource.fallbackColumn != "" {
|
||||
ownerExpr = fmt.Sprintf("COALESCE(%s, NULLIF(%s, ''), '')", ownerExpr, resource.fallbackColumn)
|
||||
} else {
|
||||
ownerExpr = fmt.Sprintf("COALESCE(%s, '')", ownerExpr)
|
||||
}
|
||||
|
||||
if err := scoped.Table(resource.table).
|
||||
Where("user_id IS NULL OR user_id = ?", "").
|
||||
UpdateColumn("user_id", gorm.Expr(ownerExpr)).Error; err != nil {
|
||||
return fmt.Errorf("backfill %s.user_id from %s: %w", resource.table, resource.ownerColumn, err)
|
||||
}
|
||||
}
|
||||
if err := setDatabaseMigrationVersion(ctx, db, resourceUserIDMigrationTargetVersion); err != nil {
|
||||
return fmt.Errorf("mark database migration version %s: %w", resourceUserIDMigrationTargetVersion, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -151,6 +151,9 @@ func (dao *SearchDAO) GetByNameAndTenant(ctx context.Context, db *gorm.DB, name
|
||||
|
||||
// Create creates a new search
|
||||
func (dao *SearchDAO) Create(ctx context.Context, db *gorm.DB, search *entity.Search) error {
|
||||
if search.UserID == "" {
|
||||
search.UserID = search.CreatedBy
|
||||
}
|
||||
return db.WithContext(ctx).Create(search).Error
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import "encoding/json"
|
||||
// Chat chat model (mapped to dialog table)
|
||||
type Chat struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name *string `gorm:"column:name;size:255;index" json:"name,omitempty"`
|
||||
Description *string `gorm:"column:description;type:longtext" json:"description,omitempty"`
|
||||
|
||||
@@ -19,6 +19,7 @@ package entity
|
||||
// ChatChannel chat channel model
|
||||
type ChatChannel struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:128;not null" json:"name"`
|
||||
Channel string `gorm:"column:channel;size:128;not null;index" json:"channel"`
|
||||
|
||||
@@ -19,6 +19,7 @@ package entity
|
||||
// CompilationTemplate represents a compilation template
|
||||
type CompilationTemplate struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID *string `gorm:"column:tenant_id;size:32;index" json:"tenant_id,omitempty"`
|
||||
GroupID *string `gorm:"column:group_id;size:32;index" json:"group_id,omitempty"`
|
||||
Name string `gorm:"column:name;size:128;not null;index" json:"name"`
|
||||
|
||||
@@ -19,6 +19,7 @@ package entity
|
||||
// CompilationTemplateGroup represents a group for compilation templates
|
||||
type CompilationTemplateGroup struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:128;not null;index" json:"name"`
|
||||
Description *string `gorm:"column:description;type:text" json:"description,omitempty"`
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
// Connector connector model
|
||||
type Connector struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:128;not null" json:"name"`
|
||||
Source string `gorm:"column:source;size:128;not null;index" json:"source"`
|
||||
|
||||
@@ -102,6 +102,7 @@ const (
|
||||
type Knowledgebase struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Avatar *string `gorm:"column:avatar;type:longtext" json:"avatar,omitempty"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:128;not null;index" json:"name"`
|
||||
Language *string `gorm:"column:language;size:32;index;default:'English'" json:"language,omitempty"`
|
||||
|
||||
@@ -23,6 +23,7 @@ type Document struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Thumbnail *string `gorm:"column:thumbnail;type:longtext" json:"thumbnail,omitempty"`
|
||||
KbID string `gorm:"column:kb_id;size:256;not null;index" json:"kb_id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
ParserID string `gorm:"column:parser_id;size:32;not null;index" json:"parser_id"`
|
||||
PipelineID *string `gorm:"column:pipeline_id;size:32;index" json:"pipeline_id,omitempty"`
|
||||
PipelineVersionID *string `gorm:"column:pipeline_version_id;size:32;index" json:"pipeline_version_id,omitempty"`
|
||||
|
||||
@@ -20,6 +20,7 @@ package entity
|
||||
// Note: Python defines custom create_time/update_time (not null) instead of using BaseModel's
|
||||
type EvaluationDataset struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:255;not null;index" json:"name"`
|
||||
Description *string `gorm:"column:description;type:longtext" json:"description,omitempty"`
|
||||
|
||||
@@ -20,6 +20,7 @@ package entity
|
||||
type File struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
ParentID string `gorm:"column:parent_id;size:32;not null;index" json:"parent_id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
CreatedBy string `gorm:"column:created_by;size:32;not null;index" json:"created_by"`
|
||||
Name string `gorm:"column:name;size:255;not null;index" json:"name"`
|
||||
|
||||
@@ -20,6 +20,7 @@ package entity
|
||||
type MCPServer struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Name string `gorm:"column:name;size:255;not null" json:"name"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
URL string `gorm:"column:url;size:2048;not null" json:"url"`
|
||||
ServerType string `gorm:"column:server_type;size:32;not null" json:"server_type"`
|
||||
|
||||
@@ -21,6 +21,7 @@ type Memory struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Name string `gorm:"column:name;size:128;not null" json:"name"`
|
||||
Avatar *string `gorm:"column:avatar;type:longtext" json:"avatar,omitempty"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
MemoryType int64 `gorm:"column:memory_type;default:1;index" json:"memory_type"`
|
||||
StorageType string `gorm:"column:storage_type;size:32;not null;default:table;index" json:"storage_type"`
|
||||
|
||||
@@ -20,6 +20,7 @@ package entity
|
||||
type Search struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Avatar *string `gorm:"column:avatar;type:longtext" json:"avatar,omitempty"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:128;not null;index" json:"name"`
|
||||
Description *string `gorm:"column:description;type:longtext" json:"description,omitempty"`
|
||||
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
// SkillSpace represents a skills space (library) that contains skills
|
||||
type SkillSpace struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
UserID string `gorm:"column:user_id;size:32;not null;default:'';index" json:"user_id"`
|
||||
TenantID string `gorm:"column:tenant_id;size:32;not null;index" json:"tenant_id"`
|
||||
Name string `gorm:"column:name;size:128;not null" json:"name"`
|
||||
FolderID string `gorm:"column:folder_id;size:32;not null" json:"folder_id"`
|
||||
|
||||
@@ -355,6 +355,7 @@ func (s *ChatService) Create(ctx context.Context, userID string, req map[string]
|
||||
req["name"] = name
|
||||
|
||||
chat := buildCreateChatEntity(req, tenantID)
|
||||
chat.UserID = userID
|
||||
if err = dao.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.chatDAO.Create(ctx, tx, chat); err != nil {
|
||||
return err
|
||||
|
||||
@@ -110,6 +110,7 @@ func (s *ChatChannelService) CreateChatChannel(ctx context.Context, userID, name
|
||||
}
|
||||
row := &entity.ChatChannel{
|
||||
ID: common.GenerateUUID(),
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Name: name,
|
||||
Channel: channelType,
|
||||
|
||||
@@ -133,9 +133,11 @@ func (s *CompilationTemplateGroupService) CreateGroup(ctx context.Context, tenan
|
||||
}
|
||||
|
||||
groupID := common.GenerateUUID()
|
||||
creatorID := identityForTemplateTenant(ctx, tenantID).UserID
|
||||
if err := dao.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
group := &entity.CompilationTemplateGroup{
|
||||
ID: groupID,
|
||||
UserID: creatorID,
|
||||
TenantID: tenantID,
|
||||
Name: groupName,
|
||||
Description: strptr(req.Description),
|
||||
@@ -400,6 +402,7 @@ func (s *CompilationTemplateGroupService) insertChildren(ctx context.Context, db
|
||||
valid := string(entity.StatusValid)
|
||||
tmpl := &entity.CompilationTemplate{
|
||||
ID: common.GenerateUUID(),
|
||||
UserID: identityForTemplateTenant(ctx, tenantID).UserID,
|
||||
TenantID: &tenantID,
|
||||
GroupID: &groupID,
|
||||
Name: name,
|
||||
@@ -524,6 +527,7 @@ func (s *CompilationTemplateGroupService) reconcileChildren(ctx context.Context,
|
||||
valid := string(entity.StatusValid)
|
||||
tmpl := &entity.CompilationTemplate{
|
||||
ID: newID,
|
||||
UserID: identityForTemplateTenant(ctx, tenantID).UserID,
|
||||
TenantID: &tenantID,
|
||||
GroupID: &groupID,
|
||||
Name: newName,
|
||||
|
||||
@@ -308,6 +308,7 @@ func (s *ConnectorService) CreateConnector(ctx context.Context, userID string, r
|
||||
|
||||
connector := &entity.Connector{
|
||||
ID: common.GenerateUUID(),
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Name: req.Name,
|
||||
Source: req.Source,
|
||||
|
||||
@@ -283,6 +283,7 @@ func (d *DatasetService) CreateDataset(ctx context.Context, req *service.CreateD
|
||||
ID: kbID,
|
||||
PipelineVersionID: pipelineVersionID,
|
||||
Name: name,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: userID,
|
||||
ParserID: parserID,
|
||||
|
||||
@@ -359,6 +359,9 @@ func (s *DocumentService) RemoveDocumentKeepFile(ctx context.Context, docID stri
|
||||
// updates dataset/document counters on insert. The document's ID and timestamps
|
||||
// are populated by the caller / model hooks before insertion.
|
||||
func (s *DocumentService) InsertDocument(ctx context.Context, doc *entity.Document) error {
|
||||
if doc.UserID == "" {
|
||||
doc.UserID = doc.CreatedBy
|
||||
}
|
||||
return dao.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(doc).Error; err != nil {
|
||||
return fmt.Errorf("failed to create document: %w", err)
|
||||
|
||||
@@ -175,6 +175,7 @@ func (s *DocumentService) newAFileFromKB(ctx context.Context, tenantID, name, pa
|
||||
folder := &entity.File{
|
||||
ID: common.GenerateToken(),
|
||||
ParentID: parentID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: name,
|
||||
@@ -208,6 +209,7 @@ func (s *DocumentService) addFileFromKB(ctx context.Context, doc *entity.Documen
|
||||
file := &entity.File{
|
||||
ID: fileID,
|
||||
ParentID: kbFolderID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: name,
|
||||
@@ -324,6 +326,7 @@ func (s *DocumentService) newDatasetDocument(kb *entity.Knowledgebase, tenantID,
|
||||
doc := &entity.Document{
|
||||
ID: docID,
|
||||
KbID: kb.ID,
|
||||
UserID: tenantID,
|
||||
ParserID: parserID,
|
||||
PipelineID: pipelineID,
|
||||
PipelineVersionID: kb.PipelineVersionID,
|
||||
|
||||
@@ -247,6 +247,7 @@ func (s *File2DocumentService) convertFiles(ctx context.Context, fileIDs, kbIDs
|
||||
doc := &entity.Document{
|
||||
ID: common.GenerateUUID(),
|
||||
KbID: kb.ID,
|
||||
UserID: userID,
|
||||
ParserID: parserID,
|
||||
PipelineID: pipelineID,
|
||||
PipelineVersionID: kb.PipelineVersionID,
|
||||
|
||||
@@ -138,6 +138,7 @@ func (s *DocumentService) insertSyncDocument(ctx context.Context, input service.
|
||||
doc := s.newDatasetDocument(kb, tenantID, filename, "", filetype, selection, parserConfig, input.SourceType, int64(len(input.SourceDocument.Blob)), input.SourceDocument.Blob)
|
||||
if ownerID := strings.TrimSpace(kb.CreatedBy); ownerID != "" {
|
||||
doc.CreatedBy = ownerID
|
||||
doc.UserID = ownerID
|
||||
}
|
||||
doc.ID = input.DocumentID
|
||||
// A fingerprint is acknowledged only after all dependent work succeeds.
|
||||
|
||||
@@ -235,6 +235,7 @@ func (s *FileService) initSkillsFolder(ctx context.Context, rootID, tenantID str
|
||||
folder := &entity.File{
|
||||
ID: common.GenerateToken(),
|
||||
ParentID: rootID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: SkillsFolderName,
|
||||
|
||||
@@ -146,6 +146,7 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string,
|
||||
fileRecord := &entity.File{
|
||||
ID: common.GenerateToken(),
|
||||
ParentID: lastFolder.ID,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: userID,
|
||||
Name: uniqueName,
|
||||
|
||||
@@ -154,6 +154,7 @@ func (s *MCPService) CreateMCPServer(ctx context.Context, userID string, req Cre
|
||||
server := &entity.MCPServer{
|
||||
ID: common.GenerateUUID(),
|
||||
Name: req.Name,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
URL: req.URL,
|
||||
ServerType: req.ServerType,
|
||||
@@ -826,6 +827,7 @@ func (s *MCPService) ImportServers(ctx context.Context, userID string, servers m
|
||||
|
||||
server := &entity.MCPServer{
|
||||
ID: common.GenerateUUID(),
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Name: newName,
|
||||
URL: url,
|
||||
|
||||
@@ -572,6 +572,7 @@ func (s *MemoryService) CreateMemory(ctx context.Context, userID string, req *Cr
|
||||
memory := &entity.Memory{
|
||||
ID: newID,
|
||||
Name: memoryName,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
MemoryType: memoryTypeInt,
|
||||
StorageType: "table",
|
||||
|
||||
@@ -184,6 +184,7 @@ func (s *SearchService) CreateSearch(ctx context.Context, name string, descripti
|
||||
// Create search entity
|
||||
search := &entity.Search{
|
||||
ID: searchID,
|
||||
UserID: identity.UserID,
|
||||
TenantID: identity.TenantID,
|
||||
Name: uniqueName,
|
||||
CreatedBy: identity.UserID,
|
||||
|
||||
@@ -148,6 +148,7 @@ func (s *SkillSpaceService) getSkillsFolderID(ctx context.Context, tenantID stri
|
||||
folder := &entity.File{
|
||||
ID: folderID,
|
||||
ParentID: rootFolder.ID,
|
||||
UserID: tenantID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: tenantID,
|
||||
Name: "skills",
|
||||
@@ -266,6 +267,7 @@ func (s *SkillSpaceService) CreateSpace(ctx context.Context, userID string, req
|
||||
folder := &entity.File{
|
||||
ID: folderID,
|
||||
ParentID: skillsFolderID,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
CreatedBy: userID,
|
||||
Name: spaceName,
|
||||
@@ -277,6 +279,7 @@ func (s *SkillSpaceService) CreateSpace(ctx context.Context, userID string, req
|
||||
// Create the space
|
||||
space := &entity.SkillSpace{
|
||||
ID: spaceID,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Name: spaceName,
|
||||
FolderID: folderID,
|
||||
|
||||
@@ -225,6 +225,7 @@ func (s *UserService) Register(ctx context.Context, req *RegisterRequest) (*enti
|
||||
rootFile := &entity.File{
|
||||
ID: fileID,
|
||||
ParentID: fileID,
|
||||
UserID: userID,
|
||||
TenantID: userID,
|
||||
CreatedBy: userID,
|
||||
Name: "/",
|
||||
|
||||
Reference in new issue
Block a user