181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"kra/internal/biz"
|
|
"kra/internal/conf"
|
|
datasystem "kra/internal/data/system"
|
|
"kra/internal/integration/storage"
|
|
|
|
"github.com/google/uuid"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
func (r *initializationRepo) PersistConfig(context.Context) error { return r.data.persistConfig() }
|
|
func (r *initializationRepo) PersistAdminConfig(ctx context.Context, raw []byte) error {
|
|
currentData, currentAdmin := r.data.runtime.Values()
|
|
next := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
|
if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(raw, next); err != nil {
|
|
return err
|
|
}
|
|
if next.Storage == nil {
|
|
next.Storage = currentAdmin.Storage
|
|
}
|
|
if next.Email == nil {
|
|
next.Email = currentAdmin.Email
|
|
}
|
|
next.ConfigPath = currentAdmin.ConfigPath
|
|
candidateStorage, err := storage.New(next)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := r.data.persistStorageIntegrationConfig(ctx, next.Storage); err != nil {
|
|
return err
|
|
}
|
|
if err := r.data.persistEmailIntegrationConfig(ctx, next.Email); err != nil {
|
|
return err
|
|
}
|
|
if err := r.data.persistConfigValues(currentData, next); err != nil {
|
|
return err
|
|
}
|
|
// Writing through the management API updates the same in-memory values
|
|
// immediately; the file watcher remains the fallback for external edits.
|
|
r.data.runtime.Replace(currentData, next)
|
|
if r.data.storage != nil {
|
|
r.data.storage.Replace(candidateStorage)
|
|
}
|
|
return nil
|
|
}
|
|
func (r *initializationRepo) PersistRuntimeConfig(ctx context.Context, dataRaw, adminRaw []byte) error {
|
|
currentData, currentAdmin := r.data.runtime.Values()
|
|
nextData := proto.Clone(currentData).(*conf.Data)
|
|
nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
|
options := protojson.UnmarshalOptions{DiscardUnknown: true}
|
|
if err := options.Unmarshal(dataRaw, nextData); err != nil {
|
|
return err
|
|
}
|
|
if err := options.Unmarshal(adminRaw, nextAdmin); err != nil {
|
|
return err
|
|
}
|
|
if nextAdmin.Storage == nil {
|
|
nextAdmin.Storage = currentAdmin.Storage
|
|
}
|
|
if nextAdmin.Email == nil {
|
|
nextAdmin.Email = currentAdmin.Email
|
|
}
|
|
nextAdmin.ConfigPath = currentAdmin.ConfigPath
|
|
candidateStorage, err := storage.New(nextAdmin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := r.data.persistStorageIntegrationConfig(ctx, nextAdmin.Storage); err != nil {
|
|
return err
|
|
}
|
|
if err := r.data.persistEmailIntegrationConfig(ctx, nextAdmin.Email); err != nil {
|
|
return err
|
|
}
|
|
if err := r.data.persistConfigValues(nextData, nextAdmin); err != nil {
|
|
return err
|
|
}
|
|
r.data.runtime.Replace(nextData, nextAdmin)
|
|
if r.data.storage != nil {
|
|
r.data.storage.Replace(candidateStorage)
|
|
}
|
|
return nil
|
|
}
|
|
func (r *initializationRepo) ReloadConfig(ctx context.Context) error {
|
|
return r.data.reloadConfig(ctx)
|
|
}
|
|
|
|
func (r *initializationRepo) IsInitialized(ctx context.Context) (bool, error) {
|
|
return r.data.databaseReady.Load(), nil
|
|
}
|
|
|
|
func (r *initializationRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error {
|
|
config := &conf.Data_Database{}
|
|
if current := r.data.runtime.Data(); current != nil && current.Database != nil {
|
|
config = proto.Clone(current.Database).(*conf.Data_Database)
|
|
}
|
|
config.Driver = input.Driver
|
|
config.Host = input.Host
|
|
config.Port = input.Port
|
|
config.User = input.User
|
|
config.Password = input.Password
|
|
config.Name = input.Name
|
|
config.Path = input.Path
|
|
config.Config = input.Config
|
|
config.Source = ""
|
|
source, err := databaseDSN(config, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.Source = source
|
|
r.data.initMu.Lock()
|
|
defer r.data.initMu.Unlock()
|
|
initialized, err := r.IsInitialized(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if initialized {
|
|
return errors.New("数据库已初始化,无需重复初始化")
|
|
}
|
|
candidate, err := openDatabase(config, true, input.Template, r.data.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); err != nil {
|
|
return err
|
|
}
|
|
if err := datasystem.SeedSystem(ctx, db, input); err != nil {
|
|
return err
|
|
}
|
|
currentAdmin := r.data.runtime.Admin()
|
|
var legacyStorage *conf.AdminBackend_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 *conf.AdminBackend_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 := r.data.persistDatabaseConfig(config, signingKey); err != nil {
|
|
return fmt.Errorf("persist database configuration: %w", err)
|
|
}
|
|
r.data.activateDatabase(candidate, config)
|
|
currentData, currentAdmin := r.data.runtime.Values()
|
|
if currentAdmin == nil {
|
|
currentAdmin = &conf.AdminBackend{}
|
|
}
|
|
if currentAdmin.Jwt == nil {
|
|
currentAdmin.Jwt = &conf.AdminBackend_JWT{}
|
|
}
|
|
currentAdmin.Jwt.SigningKey = signingKey
|
|
currentAdmin.Storage = storageConfig
|
|
currentAdmin.Email = emailConfig
|
|
r.data.runtime.Replace(currentData, currentAdmin)
|
|
activated = true
|
|
return nil
|
|
}
|