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 }