217 lines
5.8 KiB
Go
217 lines
5.8 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"kra/internal/biz/system"
|
|
configpkg "kra/internal/config"
|
|
"kra/internal/integration/storage"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func (d *Data) Config() *configpkg.Config {
|
|
if d == nil || d.runtime == nil {
|
|
return nil
|
|
}
|
|
return d.runtime.Snapshot()
|
|
}
|
|
|
|
func refreshDatabaseSources(value *configpkg.Data) error {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
if err := refreshDatabaseSource(value.Database); err != nil {
|
|
return err
|
|
}
|
|
for _, database := range value.DatabaseList {
|
|
if database == nil || database.Disable {
|
|
continue
|
|
}
|
|
if err := refreshDatabaseSource(database); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func refreshDatabaseSource(database *configpkg.Database) error {
|
|
if database == nil {
|
|
return nil
|
|
}
|
|
hasStructuredConfig := database.Host != "" || database.Port != "" || database.User != "" || database.Password != "" || database.Name != "" || database.Config != "" || database.Path != ""
|
|
if !hasStructuredConfig {
|
|
return nil
|
|
}
|
|
previousSource := database.Source
|
|
database.Source = ""
|
|
source, err := databaseDSN(database, "")
|
|
if err != nil {
|
|
database.Source = previousSource
|
|
return err
|
|
}
|
|
database.Source = source
|
|
return nil
|
|
}
|
|
|
|
func (d *Data) PersistConfig(context.Context) error { return d.persistConfig() }
|
|
func (d *Data) PersistRuntimeConfig(ctx context.Context, value *configpkg.Config) error {
|
|
current := d.Config()
|
|
if current == nil {
|
|
current = &configpkg.Config{}
|
|
}
|
|
next := configpkg.Clone(value)
|
|
if next == nil {
|
|
next = configpkg.Clone(current)
|
|
}
|
|
if next.Data == nil {
|
|
next.Data = configpkg.CloneData(current.Data)
|
|
}
|
|
if next.Admin == nil {
|
|
next.Admin = configpkg.CloneAdmin(current.Admin)
|
|
}
|
|
if next.Admin != nil && current.Admin != nil {
|
|
if next.Admin.Storage == nil {
|
|
next.Admin.Storage = configpkg.CloneStorage(current.Admin.Storage)
|
|
}
|
|
if next.Admin.Email == nil {
|
|
next.Admin.Email = configpkg.CloneEmail(current.Admin.Email)
|
|
}
|
|
next.Admin.ConfigPath = current.Admin.ConfigPath
|
|
}
|
|
if next.Data == nil {
|
|
next.Data = &configpkg.Data{}
|
|
}
|
|
if err := refreshDatabaseSources(next.Data); err != nil {
|
|
return err
|
|
}
|
|
candidateStorage, err := storage.New(next.Admin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := d.persistStorageIntegrationConfig(ctx, next.Admin.Storage); err != nil {
|
|
return err
|
|
}
|
|
if err := d.persistEmailIntegrationConfig(ctx, next.Admin.Email); err != nil {
|
|
return err
|
|
}
|
|
if err := d.persistConfigValues(next.Data, next.Admin); err != nil {
|
|
return err
|
|
}
|
|
d.runtime.Replace(next)
|
|
if d.storage != nil {
|
|
d.storage.Replace(candidateStorage)
|
|
}
|
|
return nil
|
|
}
|
|
func (d *Data) ReloadConfig(ctx context.Context) error {
|
|
return d.reloadConfig(ctx)
|
|
}
|
|
|
|
func (d *Data) IsInitialized(context.Context) (bool, error) {
|
|
return d.databaseReady.Load(), nil
|
|
}
|
|
|
|
// InitializeDatabase opens and activates the configured database. The callback
|
|
// is the application-level first-install hook; data owns only lifecycle and
|
|
// schema migration, while initialize owns system seed orchestration.
|
|
func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseConfig, seed func(context.Context, *gorm.DB) error) error {
|
|
database := &configpkg.Database{}
|
|
if current := d.runtime.Data(); current != nil && current.Database != nil {
|
|
database = configpkg.CloneDatabase(current.Database)
|
|
}
|
|
database.Driver = input.Driver
|
|
database.Host = input.Host
|
|
database.Port = input.Port
|
|
database.User = input.User
|
|
database.Password = input.Password
|
|
database.Name = input.Name
|
|
database.Path = input.Path
|
|
database.Config = input.Config
|
|
database.Source = ""
|
|
source, err := databaseDSN(database, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
database.Source = source
|
|
d.initMu.Lock()
|
|
defer d.initMu.Unlock()
|
|
initialized, err := d.IsInitialized(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if initialized {
|
|
return errors.New("数据库已初始化,无需重复初始化")
|
|
}
|
|
candidate, err := openDatabase(database, true, input.Template, d.logger())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
activated := false
|
|
defer func() {
|
|
if !activated {
|
|
if sqlDB, closeErr := candidate.DB(); closeErr == nil {
|
|
_ = sqlDB.Close()
|
|
}
|
|
}
|
|
}()
|
|
db := candidate.WithContext(ctx)
|
|
if err := migrateAll(db, d.catalog); err != nil {
|
|
return err
|
|
}
|
|
if seed != nil {
|
|
if err := seed(ctx, db); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
currentAdmin := d.runtime.Admin()
|
|
var legacyStorage *configpkg.Storage
|
|
if currentAdmin != nil {
|
|
legacyStorage = currentAdmin.Storage
|
|
}
|
|
storageConfig, err := resolveStorageIntegrationConfig(candidate.WithContext(ctx), legacyStorage)
|
|
if err != nil {
|
|
return fmt.Errorf("initialize storage integration configuration: %w", err)
|
|
}
|
|
var legacyEmail *configpkg.Email
|
|
if currentAdmin != nil {
|
|
legacyEmail = currentAdmin.Email
|
|
}
|
|
emailConfig, err := resolveEmailIntegrationConfig(candidate.WithContext(ctx), legacyEmail)
|
|
if err != nil {
|
|
return fmt.Errorf("initialize email integration configuration: %w", err)
|
|
}
|
|
signingKey := uuid.NewString()
|
|
if err := d.persistDatabaseConfig(database, signingKey); err != nil {
|
|
return fmt.Errorf("persist database configuration: %w", err)
|
|
}
|
|
integrationConfigs, err := readIntegrationRuntime(candidate)
|
|
if err != nil {
|
|
return fmt.Errorf("initialize integration runtime: %w", err)
|
|
}
|
|
d.activateDatabase(candidate, database)
|
|
activated = true
|
|
current := d.Config()
|
|
if current == nil {
|
|
current = &configpkg.Config{}
|
|
}
|
|
currentData, currentAdmin := current.Data, current.Admin
|
|
if currentAdmin == nil {
|
|
currentAdmin = &configpkg.Admin{}
|
|
}
|
|
if currentAdmin.JWT == nil {
|
|
currentAdmin.JWT = &configpkg.JWT{}
|
|
}
|
|
currentAdmin.JWT.SigningKey = signingKey
|
|
currentAdmin.Storage = storageConfig
|
|
currentAdmin.Email = emailConfig
|
|
d.runtime.Replace(&configpkg.Config{Server: current.Server, Data: currentData, Admin: currentAdmin})
|
|
if d.integrations != nil {
|
|
d.integrations.Replace(integrationConfigs)
|
|
}
|
|
return nil
|
|
}
|