diff --git a/internal/admin/service.go b/internal/admin/service.go index 8d5fd3d8bd..acc2bf0522 100644 --- a/internal/admin/service.go +++ b/internal/admin/service.go @@ -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: "/", diff --git a/internal/dao/database.go b/internal/dao/database.go index 22d57ad765..048654982f 100644 --- a/internal/dao/database.go +++ b/internal/dao/database.go @@ -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"} { diff --git a/internal/dao/file.go b/internal/dao/file.go index 387519fa73..489c2f1abd 100644 --- a/internal/dao/file.go +++ b/internal/dao/file.go @@ -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, diff --git a/internal/dao/kb.go b/internal/dao/kb.go index 413591b597..60c1c27d52 100644 --- a/internal/dao/kb.go +++ b/internal/dao/kb.go @@ -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 } diff --git a/internal/dao/migration_version.go b/internal/dao/migration_version.go index 098c1aced4..f4f3cd0cf2 100644 --- a/internal/dao/migration_version.go +++ b/internal/dao/migration_version.go @@ -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 "" diff --git a/internal/dao/migration_version_test.go b/internal/dao/migration_version_test.go index 20ea95fe08..010b725663 100644 --- a/internal/dao/migration_version_test.go +++ b/internal/dao/migration_version_test.go @@ -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) { diff --git a/internal/dao/resource_user_id_migration.go b/internal/dao/resource_user_id_migration.go new file mode 100644 index 0000000000..5c3cb4c455 --- /dev/null +++ b/internal/dao/resource_user_id_migration.go @@ -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 +} diff --git a/internal/dao/search.go b/internal/dao/search.go index bbe04abf32..acef003084 100644 --- a/internal/dao/search.go +++ b/internal/dao/search.go @@ -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 } diff --git a/internal/entity/chat.go b/internal/entity/chat.go index d82400147b..fee1b966f9 100644 --- a/internal/entity/chat.go +++ b/internal/entity/chat.go @@ -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"` diff --git a/internal/entity/chat_channel.go b/internal/entity/chat_channel.go index 3e71a426e2..d2be17a240 100644 --- a/internal/entity/chat_channel.go +++ b/internal/entity/chat_channel.go @@ -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"` diff --git a/internal/entity/compilation_template.go b/internal/entity/compilation_template.go index a4447d1b49..d7250f87ab 100644 --- a/internal/entity/compilation_template.go +++ b/internal/entity/compilation_template.go @@ -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"` diff --git a/internal/entity/compilation_template_group.go b/internal/entity/compilation_template_group.go index 4d60c2f76c..1491c820c3 100644 --- a/internal/entity/compilation_template_group.go +++ b/internal/entity/compilation_template_group.go @@ -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"` diff --git a/internal/entity/connector.go b/internal/entity/connector.go index c4be15f605..f520df5227 100644 --- a/internal/entity/connector.go +++ b/internal/entity/connector.go @@ -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"` diff --git a/internal/entity/dataset.go b/internal/entity/dataset.go index 73e1fe9f68..72b7344a2c 100644 --- a/internal/entity/dataset.go +++ b/internal/entity/dataset.go @@ -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"` diff --git a/internal/entity/document.go b/internal/entity/document.go index 4d26cbaef0..6305e45e4c 100644 --- a/internal/entity/document.go +++ b/internal/entity/document.go @@ -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"` diff --git a/internal/entity/evaluation.go b/internal/entity/evaluation.go index cb8b065c18..77b13fa1c9 100644 --- a/internal/entity/evaluation.go +++ b/internal/entity/evaluation.go @@ -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"` diff --git a/internal/entity/file.go b/internal/entity/file.go index 0a16ad1b74..42735d1839 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -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"` diff --git a/internal/entity/mcp.go b/internal/entity/mcp.go index 8ccea22f3e..dcb2fb4323 100644 --- a/internal/entity/mcp.go +++ b/internal/entity/mcp.go @@ -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"` diff --git a/internal/entity/memory.go b/internal/entity/memory.go index 4ed2594c7a..0155459894 100644 --- a/internal/entity/memory.go +++ b/internal/entity/memory.go @@ -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"` diff --git a/internal/entity/search.go b/internal/entity/search.go index 2b2ab53869..ddfe6f50d4 100644 --- a/internal/entity/search.go +++ b/internal/entity/search.go @@ -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"` diff --git a/internal/entity/skill_space.go b/internal/entity/skill_space.go index 935def9929..4abe0597c6 100644 --- a/internal/entity/skill_space.go +++ b/internal/entity/skill_space.go @@ -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"` diff --git a/internal/service/chat.go b/internal/service/chat.go index f91f619a02..2b54c9381c 100644 --- a/internal/service/chat.go +++ b/internal/service/chat.go @@ -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 diff --git a/internal/service/chat_channel.go b/internal/service/chat_channel.go index 623dbb619c..dff05c4c15 100644 --- a/internal/service/chat_channel.go +++ b/internal/service/chat_channel.go @@ -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, diff --git a/internal/service/compilation_template_group_service.go b/internal/service/compilation_template_group_service.go index d0b16d72be..41441a078c 100644 --- a/internal/service/compilation_template_group_service.go +++ b/internal/service/compilation_template_group_service.go @@ -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, diff --git a/internal/service/connector.go b/internal/service/connector.go index 87b81ea95f..c1c703ddad 100644 --- a/internal/service/connector.go +++ b/internal/service/connector.go @@ -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, diff --git a/internal/service/dataset/crud.go b/internal/service/dataset/crud.go index 002f11bd15..fe73774f99 100644 --- a/internal/service/dataset/crud.go +++ b/internal/service/dataset/crud.go @@ -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, diff --git a/internal/service/document/document_crud.go b/internal/service/document/document_crud.go index 96812af2d4..53a165e6f5 100644 --- a/internal/service/document/document_crud.go +++ b/internal/service/document/document_crud.go @@ -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) diff --git a/internal/service/document/document_upload.go b/internal/service/document/document_upload.go index 6b9e58a9e4..9c96ec74e5 100644 --- a/internal/service/document/document_upload.go +++ b/internal/service/document/document_upload.go @@ -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, diff --git a/internal/service/document/file2document.go b/internal/service/document/file2document.go index 541bb4d379..81702e54cb 100644 --- a/internal/service/document/file2document.go +++ b/internal/service/document/file2document.go @@ -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, diff --git a/internal/service/document/sync_document.go b/internal/service/document/sync_document.go index 5a532aa53c..b44c1ac29f 100644 --- a/internal/service/document/sync_document.go +++ b/internal/service/document/sync_document.go @@ -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. diff --git a/internal/service/file/file_folder.go b/internal/service/file/file_folder.go index f409a962cd..db2b36381e 100644 --- a/internal/service/file/file_folder.go +++ b/internal/service/file/file_folder.go @@ -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, diff --git a/internal/service/file/file_upload.go b/internal/service/file/file_upload.go index 1d6f1ca235..e18a810b76 100644 --- a/internal/service/file/file_upload.go +++ b/internal/service/file/file_upload.go @@ -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, diff --git a/internal/service/mcp.go b/internal/service/mcp.go index c338084d2a..44f65b21f4 100644 --- a/internal/service/mcp.go +++ b/internal/service/mcp.go @@ -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, diff --git a/internal/service/memory.go b/internal/service/memory.go index 276c1a2737..b190a4cd0d 100644 --- a/internal/service/memory.go +++ b/internal/service/memory.go @@ -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", diff --git a/internal/service/search.go b/internal/service/search.go index 1e10fd120a..af55c4791c 100644 --- a/internal/service/search.go +++ b/internal/service/search.go @@ -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, diff --git a/internal/service/skill_space.go b/internal/service/skill_space.go index e0c1e44f59..319ee01269 100644 --- a/internal/service/skill_space.go +++ b/internal/service/skill_space.go @@ -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, diff --git a/internal/service/user.go b/internal/service/user.go index 463b91d10d..0276391429 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -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: "/",