init push
This commit is contained in:
89
backend/migration/fns/0001_migrate_node_version.go
Normal file
89
backend/migration/fns/0001_migrate_node_version.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package fns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/repo/mq"
|
||||
"github.com/chaitin/panda-wiki/usecase"
|
||||
)
|
||||
|
||||
type MigrationNodeVersion struct {
|
||||
Name string
|
||||
logger *log.Logger
|
||||
nodeUsecase *usecase.NodeUsecase
|
||||
kbUsecase *usecase.KnowledgeBaseUsecase
|
||||
ragRepo *mq.RAGRepository
|
||||
}
|
||||
|
||||
func NewMigrationNodeVersion(logger *log.Logger, nodeUsecase *usecase.NodeUsecase, kbUsecase *usecase.KnowledgeBaseUsecase, ragRepo *mq.RAGRepository) *MigrationNodeVersion {
|
||||
return &MigrationNodeVersion{
|
||||
Name: "0001_migrate_node_version",
|
||||
logger: logger,
|
||||
nodeUsecase: nodeUsecase,
|
||||
kbUsecase: kbUsecase,
|
||||
ragRepo: ragRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MigrationNodeVersion) Execute(tx *gorm.DB) error {
|
||||
ctx := context.Background()
|
||||
// 1. create kb release for all kb
|
||||
kbList, err := m.kbUsecase.GetKnowledgeBaseList(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get kb list failed: %w", err)
|
||||
}
|
||||
for _, kb := range kbList {
|
||||
nodes, err := m.nodeUsecase.GetList(ctx, &domain.GetNodeListReq{
|
||||
KBID: kb.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get node list failed: %w", err)
|
||||
}
|
||||
nodeIDs := lo.Map(nodes, func(node *domain.NodeListItemResp, _ int) string {
|
||||
return node.ID
|
||||
})
|
||||
releaseID, err := m.kbUsecase.CreateKBRelease(ctx, &domain.CreateKBReleaseReq{
|
||||
KBID: kb.ID,
|
||||
Message: "release all old nodes",
|
||||
Tag: "init",
|
||||
NodeIDs: nodeIDs,
|
||||
}, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create kb release failed: %w", err)
|
||||
}
|
||||
m.logger.Info("create kb release success", log.String("kb_id", kb.ID), log.String("release_id", releaseID))
|
||||
}
|
||||
// 2. get all old node doc ids and delete in rag service
|
||||
var nodes []domain.Node
|
||||
if err := tx.Model(&domain.Node{}).
|
||||
Select("id", "kb_id", "doc_id").
|
||||
Find(&nodes).Error; err != nil {
|
||||
return fmt.Errorf("get node doc ids failed: %w", err)
|
||||
}
|
||||
if len(nodes) > 0 {
|
||||
nodeReleaseVectorRequests := make([]*domain.NodeReleaseVectorRequest, 0)
|
||||
for _, node := range nodes {
|
||||
if node.DocID == "" {
|
||||
continue
|
||||
}
|
||||
nodeReleaseVectorRequests = append(nodeReleaseVectorRequests, &domain.NodeReleaseVectorRequest{
|
||||
KBID: node.KBID,
|
||||
DocID: node.DocID,
|
||||
Action: "delete",
|
||||
})
|
||||
}
|
||||
if len(nodeReleaseVectorRequests) > 0 {
|
||||
if err := m.ragRepo.AsyncUpdateNodeReleaseVector(ctx, nodeReleaseVectorRequests); err != nil {
|
||||
return fmt.Errorf("delete node release vector failed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
115
backend/migration/fns/0002_create_bot_auth.go
Normal file
115
backend/migration/fns/0002_create_bot_auth.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package fns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
)
|
||||
|
||||
type MigrationCreateBotAuth struct {
|
||||
Name string
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewMigrationCreateBotAuth(logger *log.Logger) *MigrationCreateBotAuth {
|
||||
return &MigrationCreateBotAuth{
|
||||
Name: "0002_create_bot_auth",
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MigrationCreateBotAuth) Execute(tx *gorm.DB) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 获取所有机器人类型的应用
|
||||
var apps []domain.App
|
||||
if err := tx.WithContext(ctx).Where("type IN ?", []domain.AppType{
|
||||
domain.AppTypeWidget,
|
||||
domain.AppTypeDingTalkBot,
|
||||
domain.AppTypeFeishuBot,
|
||||
domain.AppTypeWechatBot,
|
||||
domain.AppTypeWechatServiceBot,
|
||||
domain.AppTypeDisCordBot,
|
||||
domain.AppTypeWechatOfficialAccount,
|
||||
}).Find(&apps).Error; err != nil {
|
||||
return fmt.Errorf("failed to get apps: %w", err)
|
||||
}
|
||||
|
||||
m.logger.Info("found apps for bot auth creation", log.Int("count", len(apps)))
|
||||
|
||||
for _, app := range apps {
|
||||
sourceType := app.Type.ToSourceType()
|
||||
if sourceType == "" {
|
||||
m.logger.Warn("app type has no corresponding source type", log.String("app_id", app.ID), log.Any("app_type", uint8(app.Type)))
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否需要创建认证记录(检查应用是否启用)
|
||||
shouldCreateAuth := false
|
||||
|
||||
switch app.Type {
|
||||
case domain.AppTypeWidget:
|
||||
shouldCreateAuth = app.Settings.WidgetBotSettings.IsOpen
|
||||
case domain.AppTypeDingTalkBot:
|
||||
shouldCreateAuth = app.Settings.DingTalkBotIsEnabled != nil && *app.Settings.DingTalkBotIsEnabled
|
||||
case domain.AppTypeFeishuBot:
|
||||
shouldCreateAuth = app.Settings.FeishuBotIsEnabled != nil && *app.Settings.FeishuBotIsEnabled
|
||||
case domain.AppTypeWechatBot:
|
||||
shouldCreateAuth = app.Settings.WeChatAppIsEnabled != nil && *app.Settings.WeChatAppIsEnabled
|
||||
case domain.AppTypeWechatServiceBot:
|
||||
shouldCreateAuth = app.Settings.WeChatServiceIsEnabled != nil && *app.Settings.WeChatServiceIsEnabled
|
||||
case domain.AppTypeDisCordBot:
|
||||
shouldCreateAuth = app.Settings.DiscordBotIsEnabled != nil && *app.Settings.DiscordBotIsEnabled
|
||||
case domain.AppTypeWechatOfficialAccount:
|
||||
shouldCreateAuth = app.Settings.WechatOfficialAccountIsEnabled != nil && *app.Settings.WechatOfficialAccountIsEnabled
|
||||
}
|
||||
|
||||
if !shouldCreateAuth {
|
||||
m.logger.Debug("app is not enabled, skipping auth creation", log.String("app_id", app.ID), log.String("source_type", string(sourceType)))
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否已存在该类型的认证记录
|
||||
var existingAuthCount int64
|
||||
if err := tx.WithContext(ctx).Model(&domain.Auth{}).
|
||||
Where("kb_id = ? AND source_type = ?", app.KBID, string(sourceType)).
|
||||
Count(&existingAuthCount).Error; err != nil {
|
||||
return fmt.Errorf("failed to check existing auth for kb_id %s, source_type %s: %w", app.KBID, sourceType, err)
|
||||
}
|
||||
|
||||
if existingAuthCount > 0 {
|
||||
m.logger.Debug("auth already exists, skipping", log.String("kb_id", app.KBID), log.String("source_type", string(sourceType)))
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建新的认证记录
|
||||
auth := &domain.Auth{
|
||||
KBID: app.KBID,
|
||||
UnionID: fmt.Sprintf("bot_%s_%s", app.ID, sourceType),
|
||||
SourceType: sourceType,
|
||||
LastLoginTime: time.Now(),
|
||||
UserInfo: domain.AuthUserInfo{
|
||||
Username: sourceType.Name(),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tx.WithContext(ctx).Create(auth).Error; err != nil {
|
||||
return fmt.Errorf("failed to create auth for kb_id %s, source_type %s: %w", app.KBID, sourceType, err)
|
||||
}
|
||||
|
||||
m.logger.Info("created bot auth",
|
||||
log.String("kb_id", app.KBID),
|
||||
log.String("app_id", app.ID),
|
||||
log.String("source_type", string(sourceType)),
|
||||
log.String("union_id", auth.UnionID),
|
||||
log.Any("auth_id", auth.ID))
|
||||
}
|
||||
|
||||
m.logger.Info("bot auth migration completed successfully")
|
||||
return nil
|
||||
}
|
||||
73
backend/migration/fns/0003_fix_group_ids.go
Normal file
73
backend/migration/fns/0003_fix_group_ids.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package fns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/repo/mq"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MigrationFixGroupIds struct {
|
||||
Name string
|
||||
logger *log.Logger
|
||||
ragRepo *mq.RAGRepository
|
||||
}
|
||||
|
||||
func NewMigrationFixGroupIds(logger *log.Logger, ragRepo *mq.RAGRepository) *MigrationFixGroupIds {
|
||||
return &MigrationFixGroupIds{
|
||||
Name: "0003_fix_group_ids",
|
||||
logger: logger,
|
||||
ragRepo: ragRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MigrationFixGroupIds) Execute(tx *gorm.DB) error {
|
||||
var nodes []domain.Node
|
||||
if err := tx.Model(&domain.Node{}).
|
||||
Select("id", "kb_id", "doc_id").
|
||||
Where("permissions->>'answerable' = ?", consts.NodeAccessPermClosed).
|
||||
Find(&nodes).Error; err != nil {
|
||||
return fmt.Errorf("get node list failed: %w", err)
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
nodeIds := make([]string, 0, len(nodes))
|
||||
for _, node := range nodes {
|
||||
nodeIds = append(nodeIds, node.ID)
|
||||
}
|
||||
|
||||
var nodeReleases []domain.NodeRelease
|
||||
if err := tx.Model(&domain.NodeRelease{}).
|
||||
Where("node_id IN (?)", nodeIds).
|
||||
Select("DISTINCT ON (node_id) id, node_id, kb_id, doc_id").
|
||||
Order("node_id, updated_at DESC").
|
||||
Find(&nodeReleases).Error; err != nil {
|
||||
return fmt.Errorf("get node release list failed: %w", err)
|
||||
}
|
||||
|
||||
var nodeVectorContentRequests []*domain.NodeReleaseVectorRequest
|
||||
for _, nodeRelease := range nodeReleases {
|
||||
if nodeRelease.DocID == "" {
|
||||
continue
|
||||
}
|
||||
nodeVectorContentRequests = append(nodeVectorContentRequests, &domain.NodeReleaseVectorRequest{
|
||||
KBID: nodeRelease.KBID,
|
||||
DocID: nodeRelease.DocID,
|
||||
Action: "update_group_ids",
|
||||
GroupIds: []int{},
|
||||
})
|
||||
}
|
||||
|
||||
if len(nodeVectorContentRequests) > 0 {
|
||||
if err := m.ragRepo.AsyncUpdateNodeReleaseVector(context.Background(), nodeVectorContentRequests); err != nil {
|
||||
return fmt.Errorf("async update node release vector failed: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
backend/migration/fns/0004_update_node_status_unreleased.go
Normal file
36
backend/migration/fns/0004_update_node_status_unreleased.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package fns
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
)
|
||||
|
||||
type MigrationUpdateNodeStatusUnreleased struct {
|
||||
Name string
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewMigrationUpdateNodeStatusUnreleased(logger *log.Logger) *MigrationUpdateNodeStatusUnreleased {
|
||||
return &MigrationUpdateNodeStatusUnreleased{
|
||||
Name: "0004_update_node_status_unreleased",
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MigrationUpdateNodeStatusUnreleased) Execute(tx *gorm.DB) error {
|
||||
// 将所有 status=1 (Draft) 且从未发布过的节点更新为 status=0 (Unreleased)
|
||||
// 判断条件:node_releases 表中不存在该 node_id 的记录
|
||||
result := tx.Model(&domain.Node{}).
|
||||
Where("status = ?", domain.NodeStatusDraft).
|
||||
Where("id NOT IN (SELECT DISTINCT node_id FROM node_releases)").
|
||||
Update("status", domain.NodeStatusUnreleased)
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
m.logger.Info("migration update node status unreleased", log.Int64("affected_rows", result.RowsAffected))
|
||||
return nil
|
||||
}
|
||||
86
backend/migration/fns/0005_create_first_nav_tabs.go
Normal file
86
backend/migration/fns/0005_create_first_nav_tabs.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package fns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
)
|
||||
|
||||
type MigrationCreateFirstNavs struct {
|
||||
Name string
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewMigrationCreateFirstNavs(logger *log.Logger) *MigrationCreateFirstNavs {
|
||||
return &MigrationCreateFirstNavs{
|
||||
Name: "0005_create_first_navs",
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MigrationCreateFirstNavs) Execute(tx *gorm.DB) error {
|
||||
|
||||
var kbs []*domain.KnowledgeBaseListItem
|
||||
if err := tx.Model(&domain.KnowledgeBase{}).
|
||||
Order("created_at ASC").
|
||||
Find(&kbs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, kb := range kbs {
|
||||
|
||||
nav := &domain.Nav{
|
||||
ID: uuid.New().String(),
|
||||
Name: kb.Name,
|
||||
KbID: kb.ID,
|
||||
}
|
||||
|
||||
if err := tx.Model(&domain.Nav{}).Create(nav).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&domain.Node{}).
|
||||
Where("kb_id = ?", kb.ID).
|
||||
Update("nav_id", nav.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var release domain.KBRelease
|
||||
err := tx.Model(&domain.KBRelease{}).
|
||||
Where("kb_id = ?", kb.ID).
|
||||
Order("created_at DESC").
|
||||
First(&release).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
navRelease := &domain.NavRelease{
|
||||
ID: uuid.New().String(),
|
||||
NavID: nav.ID,
|
||||
ReleaseID: release.ID,
|
||||
KbID: release.KBID,
|
||||
Name: nav.Name,
|
||||
Position: nav.Position,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if err := tx.Model(&domain.NavRelease{}).Create(navRelease).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&domain.KBReleaseNodeRelease{}).
|
||||
Where("kb_id = ? AND release_id = ?", kb.ID, release.ID).
|
||||
Update("nav_id", nav.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
13
backend/migration/fns/provider.go
Normal file
13
backend/migration/fns/provider.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package fns
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
var ProviderSet = wire.NewSet(
|
||||
NewMigrationNodeVersion,
|
||||
NewMigrationCreateBotAuth,
|
||||
NewMigrationFixGroupIds,
|
||||
NewMigrationUpdateNodeStatusUnreleased,
|
||||
NewMigrationCreateFirstNavs,
|
||||
)
|
||||
Reference in New Issue
Block a user