优化结构

This commit is contained in:
Yvan
2026-08-24 13:25:16 +08:00
parent 7a9a738c6a
commit 0b6b8ddf1a
72 changed files with 2475 additions and 1443 deletions
+10 -12
View File
@@ -1,30 +1,28 @@
package data
import (
"kra/internal/conf"
"google.golang.org/protobuf/proto"
"kra/internal/config"
)
func cloneAdminConfig(value *conf.AdminBackend) *conf.AdminBackend {
func cloneAdminConfig(value *config.Admin) *config.Admin {
if value == nil {
return &conf.AdminBackend{}
return &config.Admin{}
}
return proto.Clone(value).(*conf.AdminBackend)
return config.CloneAdmin(value)
}
func maskStorageSecrets(storage *conf.AdminBackend_Storage) {
func maskStorageSecrets(storage *config.Storage) {
if storage == nil {
return
}
if storage.Qiniu != nil && storage.Qiniu.SecretKey != "" {
storage.Qiniu.SecretKey = "******"
}
for _, item := range []*conf.AdminBackend_ObjectStore{
storage.AliyunOss,
storage.HuaweiObs,
storage.TencentCos,
storage.AwsS3,
for _, item := range []*config.ObjectStore{
storage.AliyunOSS,
storage.HuaweiOBS,
storage.TencentCOS,
storage.AWSS3,
storage.CloudflareR2,
storage.Minio,
} {
+19 -45
View File
@@ -9,29 +9,23 @@ import (
"path/filepath"
"strconv"
"kra/internal/conf"
"kra/internal/config"
"kra/internal/integration/storage"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
)
func protoMap(message proto.Message) (map[string]any, error) {
raw, err := (protojson.MarshalOptions{
UseProtoNames: true,
EmitDefaultValues: true,
}).Marshal(message)
func configMap(input any) (map[string]any, error) {
raw, err := yaml.Marshal(input)
if err != nil {
return nil, err
}
var value map[string]any
if err = json.Unmarshal(raw, &value); err != nil {
var output map[string]any
if err = yaml.Unmarshal(raw, &output); err != nil {
return nil, err
}
delete(value, "config_path")
return value, nil
return output, nil
}
func setYAMLMapping(node *yaml.Node, key string, value any) error {
@@ -60,9 +54,8 @@ func setYAMLMapping(node *yaml.Node, key string, value any) error {
}
// mergeYAMLNode updates values produced from the runtime configuration while
// retaining keys that are not represented by the protobuf and comments that
// were already present in the user's config file. Replacing an entire mapping
// with protojson output would discard configuration hints and extension keys.
// retaining keys that are not represented by the typed configuration and
// comments that were already present in the user's config file.
func mergeYAMLNode(dst, src *yaml.Node) {
if dst.Kind == yaml.MappingNode && src.Kind == yaml.MappingNode {
for i := 0; i+1 < len(src.Content); i += 2 {
@@ -138,7 +131,7 @@ func deleteYAMLMapping(node *yaml.Node, keys ...string) {
}
}
func setServerHTTPPort(document *yaml.Node, port int32) error {
func setServerHTTPPort(document *yaml.Node, port int) error {
if port <= 0 {
return nil
}
@@ -156,13 +149,13 @@ func (d *Data) persistConfig() error {
return d.persistConfigValues(dataConfig, adminConfig)
}
func (d *Data) persistConfigValues(dataConfig *conf.Data, adminConfig *conf.AdminBackend) error {
func (d *Data) persistConfigValues(dataConfig *config.Data, adminConfig *config.Admin) error {
d.configMu.Lock()
defer d.configMu.Unlock()
return d.persistConfigValuesLocked(dataConfig, adminConfig)
}
func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *conf.AdminBackend) error {
func (d *Data) persistConfigValuesLocked(dataConfig *config.Data, adminConfig *config.Admin) error {
if adminConfig == nil || adminConfig.ConfigPath == "" {
return nil
}
@@ -175,14 +168,14 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
if err = yaml.Unmarshal(raw, &document); err != nil {
return err
}
dataValue, err := protoMap(dataConfig)
dataValue, err := configMap(dataConfig)
if err != nil {
return err
}
fileAdmin := cloneAdminConfig(adminConfig)
fileAdmin.Storage = nil
fileAdmin.Email = nil
adminValue, err := protoMap(fileAdmin)
adminValue, err := configMap(fileAdmin)
if err != nil {
return err
}
@@ -207,7 +200,7 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
// partially populated runtime config back over the template: doing so removes
// all omitted/default settings from data and admin and leaves the next startup
// with no visible configuration.
func (d *Data) persistDatabaseConfig(database *conf.Data_Database, signingKey string) error {
func (d *Data) persistDatabaseConfig(database *config.Database, signingKey string) error {
d.configMu.Lock()
defer d.configMu.Unlock()
@@ -227,11 +220,8 @@ func (d *Data) persistDatabaseConfig(database *conf.Data_Database, signingKey st
if err != nil {
return err
}
// Do not marshal the init request through protojson here. Proto3 omits
// empty and zero values, which makes a merge retain stale values from the
// template (an empty database password is the common example). Update the
// fields owned by the init page explicitly and leave advanced database,
// Redis, Mongo, admin and server settings untouched.
// Update the fields owned by the init page explicitly and leave advanced
// database, Redis, Mongo, admin and server settings untouched.
databaseValue := map[string]any{
"driver": database.Driver,
"source": source,
@@ -428,7 +418,7 @@ func (d *Data) reloadConfig(ctx context.Context) error {
closeCandidate = false
candidateDBListAccepted = true
candidateRedisAccepted = true
d.runtime.Replace(next.Data, next.Admin)
d.runtime.Replace(next)
if d.integrations != nil {
d.integrations.Replace(integrationConfigs)
}
@@ -438,22 +428,6 @@ func (d *Data) reloadConfig(ctx context.Context) error {
return nil
}
func readBootstrap(configPath string) (*conf.Bootstrap, error) {
raw, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
var plain map[string]any
if err = yaml.Unmarshal(raw, &plain); err != nil {
return nil, err
}
encoded, err := json.Marshal(plain)
if err != nil {
return nil, err
}
var next conf.Bootstrap
if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(encoded, &next); err != nil {
return nil, err
}
return &next, nil
func readBootstrap(configPath string) (*config.Config, error) {
return config.Load(configPath)
}
+1 -1
View File
@@ -88,7 +88,7 @@ func (d *Data) watchConfig() func() {
next.Admin.Email = current.Email
}
next.Admin.ConfigPath = absolute
d.runtime.Replace(next.Data, next.Admin)
d.runtime.Replace(next)
logger.Info("config file changed", "mod", "system", "path", absolute)
case watchErr, ok := <-watchErrors:
if !ok {
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"kra/internal/conf"
"kra/internal/config"
)
func TestConfigWatcherStopWaitsForDebouncedReload(t *testing.T) {
@@ -17,11 +17,11 @@ func TestConfigWatcherStopWaitsForDebouncedReload(t *testing.T) {
if err := os.WriteFile(configPath, config, 0o600); err != nil {
t.Fatal(err)
}
runtime := conf.NewRuntime(&conf.Data{}, &conf.AdminBackend{ConfigPath: configPath})
runtime := config.NewStore(&config.Config{Data: &config.Data{}, Admin: &config.Admin{ConfigPath: configPath}})
started := make(chan struct{})
release := make(chan struct{})
var startOnce sync.Once
stopListener := runtime.Subscribe(func(*conf.Data, *conf.AdminBackend) {
stopListener := runtime.Subscribe(func(*config.Data, *config.Admin) {
startOnce.Do(func() { close(started) })
<-release
})
+16 -16
View File
@@ -11,7 +11,7 @@ import (
"github.com/google/wire"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
"kra/internal/conf"
"kra/internal/config"
dataintegration "kra/internal/data/integration"
datapayment "kra/internal/data/payment"
datasystem "kra/internal/data/system"
@@ -49,7 +49,7 @@ type Data struct {
gormDB *reloadableDB
redis *reloadableRedis
mongo *reloadableMongo
runtime *conf.Runtime
runtime *config.Store
integrations *runtimeconfig.Store
storage *storage.Reloadable
dbListMu sync.RWMutex
@@ -76,7 +76,7 @@ func (d *Data) DatabaseReady() bool {
// Runtime exposes the immutable runtime configuration snapshot to data
// submodules that need system settings while keeping Data itself private.
func (d *Data) Runtime() *conf.Runtime {
func (d *Data) Runtime() *config.Store {
if d == nil {
return nil
}
@@ -112,7 +112,7 @@ func (d *Data) logger() *slog.Logger {
return slog.Default()
}
func openDatabaseList(configs []*conf.Data_Database, appLogger ...*slog.Logger) (map[string]*gorm.DB, error) {
func openDatabaseList(configs []*config.Database, appLogger ...*slog.Logger) (map[string]*gorm.DB, error) {
items := make(map[string]*gorm.DB)
for _, config := range configs {
if config == nil || config.Disable || config.AliasName == "" {
@@ -161,19 +161,19 @@ func (d *Data) database(name string) (*gorm.DB, error) {
return db, nil
}
func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *storage.Reloadable, catalog module.Catalog) (*Data, func(), error) {
func NewData(runtime *config.Store, appLogger *slog.Logger, storageManager *storage.Reloadable, catalog module.Catalog) (*Data, func(), error) {
if appLogger == nil {
appLogger = slog.Default()
}
c := runtime.Data()
if c == nil {
c = &conf.Data{}
c = &config.Data{}
}
if c.Database == nil {
// An empty database block starts the service on the bootstrap database
// so /init/checkdb
// and /init/initdb remain available.
c.Database = &conf.Data_Database{}
c.Database = &config.Database{}
}
d := &Data{runtime: runtime, integrations: runtimeconfig.NewStore(), appLogger: appLogger, storage: storageManager, catalog: catalog}
var stopConfigWatcher func()
@@ -234,7 +234,7 @@ func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *stor
}
admin := runtime.Admin()
if admin == nil {
admin = &conf.AdminBackend{}
admin = &config.Admin{}
}
disableAutoMigrate := admin.System != nil && admin.System.DisableAutoMigrate
if !usingFallback && !disableAutoMigrate {
@@ -253,7 +253,7 @@ func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *stor
}
admin.Storage = storageConfig
admin.Email = emailConfig
runtime.Replace(c, admin)
runtime.Replace(&config.Config{Server: runtime.Server(), Data: c, Admin: admin})
if err = d.loadIntegrationRuntime(db); err != nil {
return nil, nil, fmt.Errorf("load integration runtime: %w", err)
}
@@ -287,7 +287,7 @@ func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *stor
return d, cleanup, nil
}
func openRedis(config *conf.Data_Redis, enabled bool, appLogger ...*slog.Logger) redis.UniversalClient {
func openRedis(config *config.Redis, enabled bool, appLogger ...*slog.Logger) redis.UniversalClient {
if !enabled || config == nil || (config.Addr == "" && len(config.ClusterAddrs) == 0) {
return nil
}
@@ -299,12 +299,12 @@ func openRedis(config *conf.Data_Redis, enabled bool, appLogger ...*slog.Logger)
}
candidate = redis.NewClusterClient(&redis.ClusterOptions{Addrs: addresses, Password: config.Password})
} else {
options := &redis.Options{Addr: config.Addr, Network: config.Network, Password: config.Password, DB: int(config.Db)}
if config.ReadTimeout != nil {
options.ReadTimeout = config.ReadTimeout.AsDuration()
options := &redis.Options{Addr: config.Addr, Network: config.Network, Password: config.Password, DB: int(config.DB)}
if config.ReadTimeout > 0 {
options.ReadTimeout = config.ReadTimeout
}
if config.WriteTimeout != nil {
options.WriteTimeout = config.WriteTimeout.AsDuration()
if config.WriteTimeout > 0 {
options.WriteTimeout = config.WriteTimeout
}
candidate = redis.NewClient(options)
}
@@ -322,7 +322,7 @@ func openRedis(config *conf.Data_Redis, enabled bool, appLogger ...*slog.Logger)
return candidate
}
func (d *Data) activateDatabase(db *gorm.DB, config *conf.Data_Database) {
func (d *Data) activateDatabase(db *gorm.DB, config *config.Database) {
d.gormDB.replace(db, d.enqueueDataScopeAudit)
d.runtime.UpdateDatabase(config)
d.databaseReady.Store(true)
+7 -7
View File
@@ -19,7 +19,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"kra/internal/conf"
"kra/internal/config"
"kra/pkg/database/gormkit"
)
@@ -29,7 +29,7 @@ var databaseNamePattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_$-]*$`)
// administration backend: an explicitly selected database (or a standalone
// DSN) means a real database connection is expected to exist. The internal
// bootstrap database is deliberately excluded from this state.
func databaseConnectionConfigured(c *conf.Data_Database) bool {
func databaseConnectionConfigured(c *config.Database) bool {
if c == nil {
return false
}
@@ -56,7 +56,7 @@ func normalizedDriver(driver string) string {
}
}
func databaseDSN(c *conf.Data_Database, name string) (string, error) {
func databaseDSN(c *config.Database, name string) (string, error) {
if c.Source != "" && c.Host == "" && c.Path == "" {
return c.Source, nil
}
@@ -118,7 +118,7 @@ func databaseDSN(c *conf.Data_Database, name string) (string, error) {
return "", fmt.Errorf("unsupported database driver %q", c.Driver)
}
func gormConfig(config *conf.Data_Database, appLogger ...*slog.Logger) *gorm.Config {
func gormConfig(config *config.Database, appLogger ...*slog.Logger) *gorm.Config {
level := logger.Info
switch strings.ToLower(config.LogMode) {
case "silent":
@@ -136,10 +136,10 @@ func gormConfig(config *conf.Data_Database, appLogger ...*slog.Logger) *gorm.Con
}
func openWithDriver(driver, dsn string, appLogger ...*slog.Logger) (*gorm.DB, error) {
return openWithDriverConfig(driver, dsn, &conf.Data_Database{Driver: driver}, appLogger...)
return openWithDriverConfig(driver, dsn, &config.Database{Driver: driver}, appLogger...)
}
func openWithDriverConfig(driver, dsn string, config *conf.Data_Database, appLogger ...*slog.Logger) (*gorm.DB, error) {
func openWithDriverConfig(driver, dsn string, config *config.Database, appLogger ...*slog.Logger) (*gorm.DB, error) {
gormConfig := gormConfig(config, appLogger...)
var db *gorm.DB
var err error
@@ -179,7 +179,7 @@ func openWithDriverConfig(driver, dsn string, config *conf.Data_Database, appLog
return db, nil
}
func openDatabase(c *conf.Data_Database, create bool, template string, appLogger ...*slog.Logger) (*gorm.DB, error) {
func openDatabase(c *config.Database, create bool, template string, appLogger ...*slog.Logger) (*gorm.DB, error) {
driver := normalizedDriver(c.Driver)
if driver == "" {
return nil, fmt.Errorf("unsupported database driver %q", c.Driver)
+71 -88
View File
@@ -4,22 +4,23 @@ import (
"context"
"errors"
"fmt"
"kra/internal/biz/system"
"kra/internal/conf"
"kra/internal/biz/system"
configpkg "kra/internal/config"
"kra/internal/integration/storage"
"github.com/google/uuid"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gorm.io/gorm"
)
func (d *Data) RuntimeValues() (*conf.Data, *conf.AdminBackend) { return d.runtime.Values() }
func (d *Data) RuntimeAdmin() *conf.AdminBackend { return d.runtime.Admin() }
func (d *Data) RefreshDatabaseSources(value *conf.Data) error { return refreshDatabaseSources(value) }
func (d *Data) Config() *configpkg.Config {
if d == nil || d.runtime == nil {
return nil
}
return d.runtime.Snapshot()
}
func refreshDatabaseSources(value *conf.Data) error {
func refreshDatabaseSources(value *configpkg.Data) error {
if value == nil {
return nil
}
@@ -37,7 +38,7 @@ func refreshDatabaseSources(value *conf.Data) error {
return nil
}
func refreshDatabaseSource(database *conf.Data_Database) error {
func refreshDatabaseSource(database *configpkg.Database) error {
if database == nil {
return nil
}
@@ -57,72 +58,50 @@ func refreshDatabaseSource(database *conf.Data_Database) error {
}
func (d *Data) PersistConfig(context.Context) error { return d.persistConfig() }
func (d *Data) PersistAdminConfig(ctx context.Context, raw []byte) error {
currentData, currentAdmin := d.runtime.Values()
next := proto.Clone(currentAdmin).(*conf.AdminBackend)
if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(raw, next); err != nil {
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
}
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)
candidateStorage, err := storage.New(next.Admin)
if err != nil {
return err
}
if err := d.persistStorageIntegrationConfig(ctx, next.Storage); err != nil {
if err := d.persistStorageIntegrationConfig(ctx, next.Admin.Storage); err != nil {
return err
}
if err := d.persistEmailIntegrationConfig(ctx, next.Email); err != nil {
if err := d.persistEmailIntegrationConfig(ctx, next.Admin.Email); err != nil {
return err
}
if err := d.persistConfigValues(currentData, next); err != nil {
if err := d.persistConfigValues(next.Data, next.Admin); 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.
d.runtime.Replace(currentData, next)
if d.storage != nil {
d.storage.Replace(candidateStorage)
}
return nil
}
func (d *Data) PersistRuntimeConfig(ctx context.Context, dataRaw, adminRaw []byte) error {
currentData, currentAdmin := d.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 := d.persistStorageIntegrationConfig(ctx, nextAdmin.Storage); err != nil {
return err
}
if err := d.persistEmailIntegrationConfig(ctx, nextAdmin.Email); err != nil {
return err
}
if err := d.persistConfigValues(nextData, nextAdmin); err != nil {
return err
}
d.runtime.Replace(nextData, nextAdmin)
d.runtime.Replace(next)
if d.storage != nil {
d.storage.Replace(candidateStorage)
}
@@ -140,24 +119,24 @@ func (d *Data) IsInitialized(context.Context) (bool, error) {
// 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 {
config := &conf.Data_Database{}
database := &configpkg.Database{}
if current := d.runtime.Data(); current != nil && current.Database != nil {
config = proto.Clone(current.Database).(*conf.Data_Database)
database = configpkg.CloneDatabase(current.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, "")
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
}
config.Source = source
database.Source = source
d.initMu.Lock()
defer d.initMu.Unlock()
initialized, err := d.IsInitialized(ctx)
@@ -167,7 +146,7 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseCon
if initialized {
return errors.New("数据库已初始化,无需重复初始化")
}
candidate, err := openDatabase(config, true, input.Template, d.logger())
candidate, err := openDatabase(database, true, input.Template, d.logger())
if err != nil {
return err
}
@@ -189,7 +168,7 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseCon
}
}
currentAdmin := d.runtime.Admin()
var legacyStorage *conf.AdminBackend_Storage
var legacyStorage *configpkg.Storage
if currentAdmin != nil {
legacyStorage = currentAdmin.Storage
}
@@ -197,7 +176,7 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseCon
if err != nil {
return fmt.Errorf("initialize storage integration configuration: %w", err)
}
var legacyEmail *conf.AdminBackend_Email
var legacyEmail *configpkg.Email
if currentAdmin != nil {
legacyEmail = currentAdmin.Email
}
@@ -206,26 +185,30 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseCon
return fmt.Errorf("initialize email integration configuration: %w", err)
}
signingKey := uuid.NewString()
if err := d.persistDatabaseConfig(config, signingKey); err != nil {
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, config)
d.activateDatabase(candidate, database)
activated = true
currentData, currentAdmin := d.runtime.Values()
current := d.Config()
if current == nil {
current = &configpkg.Config{}
}
currentData, currentAdmin := current.Data, current.Admin
if currentAdmin == nil {
currentAdmin = &conf.AdminBackend{}
currentAdmin = &configpkg.Admin{}
}
if currentAdmin.Jwt == nil {
currentAdmin.Jwt = &conf.AdminBackend_JWT{}
if currentAdmin.JWT == nil {
currentAdmin.JWT = &configpkg.JWT{}
}
currentAdmin.Jwt.SigningKey = signingKey
currentAdmin.JWT.SigningKey = signingKey
currentAdmin.Storage = storageConfig
currentAdmin.Email = emailConfig
d.runtime.Replace(currentData, currentAdmin)
d.runtime.Replace(&configpkg.Config{Server: current.Server, Data: currentData, Admin: currentAdmin})
if d.integrations != nil {
d.integrations.Replace(integrationConfigs)
}
+8 -8
View File
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
"kra/internal/conf"
"kra/internal/config"
"gopkg.in/yaml.v3"
)
@@ -15,14 +15,14 @@ import (
func TestDatabaseConnectionConfigured(t *testing.T) {
tests := []struct {
name string
config *conf.Data_Database
config *config.Database
want bool
}{
{name: "missing config", config: nil, want: false},
{name: "empty database", config: &conf.Data_Database{Driver: "mysql"}, want: false},
{name: "named database", config: &conf.Data_Database{Driver: "mysql", Name: "kra"}, want: true},
{name: "standalone DSN", config: &conf.Data_Database{Driver: "sqlite", Source: "file:kra.db"}, want: true},
{name: "generated DSN still requires a name", config: &conf.Data_Database{Driver: "mysql", Source: "root@tcp(localhost)/", Host: "localhost"}, want: false},
{name: "empty database", config: &config.Database{Driver: "mysql"}, want: false},
{name: "named database", config: &config.Database{Driver: "mysql", Name: "kra"}, want: true},
{name: "standalone DSN", config: &config.Database{Driver: "sqlite", Source: "file:kra.db"}, want: true},
{name: "generated DSN still requires a name", config: &config.Database{Driver: "mysql", Source: "root@tcp(localhost)/", Host: "localhost"}, want: false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -78,9 +78,9 @@ extension:
if err := os.WriteFile(configPath, []byte(original), 0o600); err != nil {
t.Fatal(err)
}
runtime := conf.NewRuntime(&conf.Data{}, &conf.AdminBackend{ConfigPath: configPath})
runtime := config.NewStore(&config.Data{}, &config.Admin{ConfigPath: configPath})
data := &Data{runtime: runtime}
if err := data.persistDatabaseConfig(&conf.Data_Database{Driver: "mysql", Host: "db", Port: "3306", User: "root", Password: "secret", Name: "kra", Config: "parseTime=True"}, "new-key"); err != nil {
if err := data.persistDatabaseConfig(&config.Database{Driver: "mysql", Host: "db", Port: "3306", User: "root", Password: "secret", Name: "kra", Config: "parseTime=True"}, "new-key"); err != nil {
t.Fatal(err)
}
raw, err := os.ReadFile(configPath)
+36 -38
View File
@@ -7,11 +7,9 @@ import (
"fmt"
"strings"
"kra/internal/conf"
"kra/internal/config"
dataintegration "kra/internal/data/integration"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gorm.io/gorm"
)
@@ -39,24 +37,24 @@ func normalizeStorageType(value string) string {
return value
}
func storageProviderMessage(storage *conf.AdminBackend_Storage, provider string) proto.Message {
func storageProviderValue(storage *config.Storage, provider string) any {
if storage == nil {
storage = &conf.AdminBackend_Storage{}
storage = &config.Storage{}
}
switch provider {
case "qiniu":
if storage.Qiniu == nil {
storage.Qiniu = &conf.AdminBackend_Qiniu{}
storage.Qiniu = &config.Qiniu{}
}
return storage.Qiniu
case "aliyun-oss":
return ensureObjectStore(&storage.AliyunOss)
return ensureObjectStore(&storage.AliyunOSS)
case "huawei-obs":
return ensureObjectStore(&storage.HuaweiObs)
return ensureObjectStore(&storage.HuaweiOBS)
case "tencent-cos":
return ensureObjectStore(&storage.TencentCos)
return ensureObjectStore(&storage.TencentCOS)
case "aws-s3":
return ensureObjectStore(&storage.AwsS3)
return ensureObjectStore(&storage.AWSS3)
case "cloudflare-r2":
return ensureObjectStore(&storage.CloudflareR2)
case "minio":
@@ -66,45 +64,45 @@ func storageProviderMessage(storage *conf.AdminBackend_Storage, provider string)
}
}
func ensureObjectStore(value **conf.AdminBackend_ObjectStore) proto.Message {
func ensureObjectStore(value **config.ObjectStore) *config.ObjectStore {
if *value == nil {
*value = &conf.AdminBackend_ObjectStore{}
*value = &config.ObjectStore{}
}
return *value
}
func marshalStorageProvider(storage *conf.AdminBackend_Storage, provider string) (string, error) {
message := storageProviderMessage(storage, provider)
if message == nil {
func marshalStorageProvider(storage *config.Storage, provider string) (string, error) {
value := storageProviderValue(storage, provider)
if value == nil {
return "{}", nil
}
raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(message)
raw, err := json.Marshal(value)
if err != nil {
return "", err
}
return string(raw), nil
}
func unmarshalStorageProvider(storage *conf.AdminBackend_Storage, provider, value string) error {
func unmarshalStorageProvider(storage *config.Storage, provider, value string) error {
if provider == "local" || strings.TrimSpace(value) == "" {
return nil
}
message := storageProviderMessage(storage, provider)
if message == nil {
target := storageProviderValue(storage, provider)
if target == nil {
return nil
}
if !json.Valid([]byte(value)) {
return fmt.Errorf("invalid %s integration configuration", provider)
}
if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(value), message); err != nil {
if err := json.Unmarshal([]byte(value), target); err != nil {
return fmt.Errorf("decode %s integration configuration: %w", provider, err)
}
return nil
}
func saveStorageIntegrationConfig(db *gorm.DB, storage *conf.AdminBackend_Storage) error {
func saveStorageIntegrationConfig(db *gorm.DB, storage *config.Storage) error {
if storage == nil {
storage = &conf.AdminBackend_Storage{}
storage = &config.Storage{}
}
active := normalizeStorageType(storage.Type)
known := false
@@ -145,7 +143,7 @@ func saveStorageIntegrationConfig(db *gorm.DB, storage *conf.AdminBackend_Storag
})
}
func loadStorageIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_Storage, bool, error) {
func loadStorageIntegrationConfig(db *gorm.DB) (*config.Storage, bool, error) {
var rows []dataintegration.ConfigPO
err := db.Session(&gorm.Session{NewDB: true}).
Where("kind = ?", integrationKindStorage).
@@ -158,7 +156,7 @@ func loadStorageIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_Storage, bool
return nil, false, nil
}
storage := &conf.AdminBackend_Storage{Type: "local"}
storage := &config.Storage{Type: "local"}
for _, row := range rows {
if err = unmarshalStorageProvider(storage, row.Provider, row.Config); err != nil {
return nil, false, err
@@ -173,13 +171,13 @@ func loadStorageIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_Storage, bool
// resolveStorageIntegrationConfig upgrades a legacy YAML configuration only
// when the database has no storage rows yet. From then on the database is the
// sole source of truth.
func resolveStorageIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_Storage) (*conf.AdminBackend_Storage, error) {
func resolveStorageIntegrationConfig(db *gorm.DB, legacy *config.Storage) (*config.Storage, error) {
clean := db.Session(&gorm.Session{NewDB: true})
if !clean.Migrator().HasTable(&dataintegration.ConfigPO{}) {
if legacy == nil {
return &conf.AdminBackend_Storage{Type: "local"}, nil
return &config.Storage{Type: "local"}, nil
}
return proto.Clone(legacy).(*conf.AdminBackend_Storage), nil
return config.CloneStorage(legacy), nil
}
storage, found, err := loadStorageIntegrationConfig(clean)
if err != nil {
@@ -189,7 +187,7 @@ func resolveStorageIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_Stor
return storage, nil
}
if legacy == nil {
legacy = &conf.AdminBackend_Storage{Type: "local"}
legacy = &config.Storage{Type: "local"}
}
if err = saveStorageIntegrationConfig(clean, legacy); err != nil {
return nil, err
@@ -198,7 +196,7 @@ func resolveStorageIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_Stor
return storage, err
}
func (d *Data) persistStorageIntegrationConfig(ctx context.Context, storage *conf.AdminBackend_Storage) error {
func (d *Data) persistStorageIntegrationConfig(ctx context.Context, storage *config.Storage) error {
if !d.databaseReady.Load() {
return errors.New("database is not initialized")
}
@@ -209,15 +207,15 @@ func (d *Data) persistStorageIntegrationConfig(ctx context.Context, storage *con
return saveStorageIntegrationConfig(db, storage)
}
func defaultEmailIntegrationConfig() *conf.AdminBackend_Email {
return &conf.AdminBackend_Email{Port: 465, IsSsl: true}
func defaultEmailIntegrationConfig() *config.Email {
return &config.Email{Port: 465, IsSSL: true}
}
func saveEmailIntegrationConfig(db *gorm.DB, email *conf.AdminBackend_Email) error {
func saveEmailIntegrationConfig(db *gorm.DB, email *config.Email) error {
if email == nil {
email = defaultEmailIntegrationConfig()
}
raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(email)
raw, err := json.Marshal(email)
if err != nil {
return fmt.Errorf("encode smtp integration configuration: %w", err)
}
@@ -237,7 +235,7 @@ func saveEmailIntegrationConfig(db *gorm.DB, email *conf.AdminBackend_Email) err
}
}
func loadEmailIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_Email, bool, error) {
func loadEmailIntegrationConfig(db *gorm.DB) (*config.Email, bool, error) {
var row dataintegration.ConfigPO
err := db.Session(&gorm.Session{NewDB: true}).
Where("kind = ? AND provider = ?", integrationKindEmail, "smtp").
@@ -252,19 +250,19 @@ func loadEmailIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_Email, bool, er
return nil, false, errors.New("invalid smtp integration configuration")
}
email := defaultEmailIntegrationConfig()
if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(row.Config), email); err != nil {
if err = json.Unmarshal([]byte(row.Config), email); err != nil {
return nil, false, fmt.Errorf("decode smtp integration configuration: %w", err)
}
return email, true, nil
}
func resolveEmailIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_Email) (*conf.AdminBackend_Email, error) {
func resolveEmailIntegrationConfig(db *gorm.DB, legacy *config.Email) (*config.Email, error) {
clean := db.Session(&gorm.Session{NewDB: true})
if !clean.Migrator().HasTable(&dataintegration.ConfigPO{}) {
if legacy == nil {
return defaultEmailIntegrationConfig(), nil
}
return proto.Clone(legacy).(*conf.AdminBackend_Email), nil
return config.CloneEmail(legacy), nil
}
email, found, err := loadEmailIntegrationConfig(clean)
if err != nil {
@@ -283,7 +281,7 @@ func resolveEmailIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_Email)
return email, err
}
func (d *Data) persistEmailIntegrationConfig(ctx context.Context, email *conf.AdminBackend_Email) error {
func (d *Data) persistEmailIntegrationConfig(ctx context.Context, email *config.Email) error {
if !d.databaseReady.Load() {
return errors.New("database is not initialized")
}
+32 -32
View File
@@ -8,7 +8,7 @@ import (
"testing"
integrationbiz "kra/internal/biz/integration"
"kra/internal/conf"
"kra/internal/config"
dataintegration "kra/internal/data/integration"
"kra/internal/integration/storage"
@@ -54,21 +54,21 @@ func TestMigrateAllCreatesIntegrationConfigTable(t *testing.T) {
func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
db := openIntegrationConfigTestDB(t)
storage := &conf.AdminBackend_Storage{
storage := &config.Storage{
Type: "aliyun-oss",
Qiniu: &conf.AdminBackend_Qiniu{
Qiniu: &config.Qiniu{
Zone: "ZoneHuadong", Bucket: "qiniu-bucket", AccessKey: "qiniu-key", SecretKey: "qiniu-secret",
},
AliyunOss: &conf.AdminBackend_ObjectStore{
AliyunOss: &config.ObjectStore{
Endpoint: "oss-cn-hangzhou.aliyuncs.com", Region: "cn-hangzhou", Bucket: "assets",
AccessKey: "aliyun-key", SecretKey: "aliyun-secret", BaseUrl: "https://cdn.example.com", PathPrefix: "uploads",
},
Minio: &conf.AdminBackend_ObjectStore{Endpoint: "127.0.0.1:9000", Bucket: "local", ForcePathStyle: true},
Minio: &config.ObjectStore{Endpoint: "127.0.0.1:9000", Bucket: "local", ForcePathStyle: true},
}
if err := saveStorageIntegrationConfig(db, storage); err != nil {
t.Fatal(err)
}
email := &conf.AdminBackend_Email{
email := &config.Email{
To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
Secret: "smtp-secret", Nickname: "Kra", Port: 465, IsSsl: true,
}
@@ -89,8 +89,8 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
if loaded.Type != "aliyun-oss" {
t.Fatalf("storage type = %q, want aliyun-oss", loaded.Type)
}
if loaded.AliyunOss == nil || loaded.AliyunOss.SecretKey != "aliyun-secret" || loaded.AliyunOss.PathPrefix != "uploads" {
t.Fatalf("aliyun configuration = %#v", loaded.AliyunOss)
if loaded.AliyunOSS == nil || loaded.AliyunOSS.SecretKey != "aliyun-secret" || loaded.AliyunOSS.PathPrefix != "uploads" {
t.Fatalf("aliyun configuration = %#v", loaded.AliyunOSS)
}
if loaded.Qiniu == nil || loaded.Qiniu.SecretKey != "qiniu-secret" {
t.Fatalf("qiniu configuration = %#v", loaded.Qiniu)
@@ -127,9 +127,9 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
db := openIntegrationConfigTestDB(t)
legacy := &conf.AdminBackend_Storage{
legacy := &config.Storage{
Type: "qiniu",
Qiniu: &conf.AdminBackend_Qiniu{Bucket: "legacy", SecretKey: "legacy-secret"},
Qiniu: &config.Qiniu{Bucket: "legacy", SecretKey: "legacy-secret"},
}
loaded, err := resolveStorageIntegrationConfig(db, legacy)
if err != nil {
@@ -139,9 +139,9 @@ func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
t.Fatalf("migrated storage = %#v", loaded)
}
other := &conf.AdminBackend_Storage{
other := &config.Storage{
Type: "minio",
Minio: &conf.AdminBackend_ObjectStore{Bucket: "must-not-replace-database"},
Minio: &config.ObjectStore{Bucket: "must-not-replace-database"},
}
loaded, err = resolveStorageIntegrationConfig(db, other)
if err != nil {
@@ -153,23 +153,23 @@ func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
}
func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) {
storage := &conf.AdminBackend_Storage{
Qiniu: &conf.AdminBackend_Qiniu{},
AliyunOss: &conf.AdminBackend_ObjectStore{SecretKey: "configured-secret"},
Minio: &conf.AdminBackend_ObjectStore{},
storage := &config.Storage{
Qiniu: &config.Qiniu{},
AliyunOss: &config.ObjectStore{SecretKey: "configured-secret"},
Minio: &config.ObjectStore{},
}
maskStorageSecrets(storage)
if storage.Qiniu.SecretKey != "" || storage.Minio.SecretKey != "" {
t.Fatalf("empty provider secrets were masked: %#v", storage)
}
if storage.AliyunOss.SecretKey != "******" {
t.Fatalf("configured secret was not masked: %q", storage.AliyunOss.SecretKey)
if storage.AliyunOSS.SecretKey != "******" {
t.Fatalf("configured secret was not masked: %q", storage.AliyunOSS.SecretKey)
}
}
func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
db := openIntegrationConfigTestDB(t)
legacy := &conf.AdminBackend_Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSsl: true}
legacy := &config.Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSsl: true}
loaded, err := resolveEmailIntegrationConfig(db, legacy)
if err != nil {
t.Fatal(err)
@@ -177,7 +177,7 @@ func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
if loaded.Host != legacy.Host || loaded.Secret != legacy.Secret {
t.Fatalf("migrated email = %#v", loaded)
}
loaded, err = resolveEmailIntegrationConfig(db, &conf.AdminBackend_Email{Host: "must-not-replace.example.com"})
loaded, err = resolveEmailIntegrationConfig(db, &config.Email{Host: "must-not-replace.example.com"})
if err != nil {
t.Fatal(err)
}
@@ -193,16 +193,16 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
t.Fatal(err)
}
d := &Data{}
admin := &conf.AdminBackend{
admin := &config.Admin{
ConfigPath: path,
RouterPrefix: "/api",
Storage: &conf.AdminBackend_Storage{
Storage: &config.Storage{
Type: "qiniu",
Qiniu: &conf.AdminBackend_Qiniu{SecretKey: "database-only-secret"},
Qiniu: &config.Qiniu{SecretKey: "database-only-secret"},
},
Email: &conf.AdminBackend_Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"},
Email: &config.Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"},
}
if err := d.persistConfigValues(&conf.Data{}, admin); err != nil {
if err := d.persistConfigValues(&config.Data{}, admin); err != nil {
t.Fatal(err)
}
@@ -237,10 +237,10 @@ func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
}
oldRoot := filepath.Join(t.TempDir(), "old")
newRoot := filepath.Join(t.TempDir(), "new")
currentAdmin := &conf.AdminBackend{
currentAdmin := &config.Admin{
ConfigPath: configPath,
Local: &conf.AdminBackend_Local{StorePath: oldRoot, PathPrefix: "old-files"},
Storage: &conf.AdminBackend_Storage{Type: "local"},
Local: &config.Local{StorePath: oldRoot, PathPrefix: "old-files"},
Storage: &config.Storage{Type: "local"},
}
currentStorage, err := storage.NewReloadable(currentAdmin)
if err != nil {
@@ -249,19 +249,19 @@ func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
reloadableDB := &reloadableDB{}
reloadableDB.current.Store(db)
d := &Data{
runtime: conf.NewRuntime(&conf.Data{}, currentAdmin),
runtime: config.NewStore(&config.Config{Data: &config.Data{}, Admin: currentAdmin}),
gormDB: reloadableDB,
storage: currentStorage,
}
d.databaseReady.Store(true)
nextAdmin := cloneAdminConfig(currentAdmin)
nextAdmin.Local = &conf.AdminBackend_Local{StorePath: newRoot, PathPrefix: "new-files"}
nextAdmin.Email = &conf.AdminBackend_Email{
nextAdmin.Local = &config.Local{StorePath: newRoot, PathPrefix: "new-files"}
nextAdmin.Email = &config.Email{
To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
Secret: "runtime-secret", Port: 465, IsSsl: true,
}
dataRaw, err := protojson.Marshal(&conf.Data{})
dataRaw, err := protojson.Marshal(&config.Data{})
if err != nil {
t.Fatal(err)
}
+3 -3
View File
@@ -6,13 +6,13 @@ import (
"strings"
"time"
"kra/internal/conf"
"kra/internal/config"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func mongoURI(config *conf.Data_Mongo) string {
func mongoURI(config *config.Mongo) string {
hosts := make([]string, 0, len(config.Hosts))
for _, host := range config.Hosts {
if host != nil && host.Host != "" && host.Port != "" {
@@ -26,7 +26,7 @@ func mongoURI(config *conf.Data_Mongo) string {
return uri
}
func openMongo(config *conf.Data_Mongo, enabled bool) (*mongo.Client, error) {
func openMongo(config *config.Mongo, enabled bool) (*mongo.Client, error) {
if !enabled {
return nil, nil
}
+6 -4
View File
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
"kra/internal/conf"
"kra/internal/config"
)
func newPolicyTestData(t *testing.T) *Data {
@@ -20,9 +20,11 @@ func newPolicyTestData(t *testing.T) *Data {
}
data := &Data{
gormDB: newReloadableDB(db, nil),
runtime: conf.NewRuntime(
&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}},
&conf.AdminBackend{System: &conf.AdminBackend_System{UseStrictAuth: true}},
runtime: config.NewStore(
&config.Config{
Data: &config.Data{Database: &config.Database{Driver: "sqlite"}},
Admin: &config.Admin{System: &config.System{UseStrictAuth: true}},
},
),
}
t.Cleanup(func() { data.gormDB.close() })
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"kra/internal/biz/system"
"testing"
"kra/internal/conf"
"kra/internal/config"
"gorm.io/gorm"
)
@@ -15,7 +15,7 @@ func authorityUintPointer(value uint) *uint { return &value }
func enableStrictAuthorityTestMode(data *Data) {
currentData, _ := data.runtime.Values()
data.runtime.Replace(currentData, &conf.AdminBackend{System: &conf.AdminBackend_System{UseStrictAuth: true}})
data.runtime.Replace(&config.Config{Data: currentData, Admin: &config.Admin{System: &config.System{UseStrictAuth: true}}})
}
func TestCopyAuthorityStrictPolicyValidationMatchesAdministrationContract(t *testing.T) {
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"path/filepath"
"testing"
"kra/internal/conf"
"kra/internal/config"
)
func TestLogViewerReadsNestedCategoryFiles(t *testing.T) {
@@ -22,7 +22,7 @@ func TestLogViewerReadsNestedCategoryFiles(t *testing.T) {
t.Fatal(err)
}
runtime := conf.NewRuntime(nil, &conf.AdminBackend{Zap: &conf.AdminBackend_Zap{Director: root}})
runtime := config.NewStore(&config.Config{Admin: &config.Admin{Zap: &config.Zap{Director: root}}})
repo := &logFileRepo{data: &Data{runtime: runtime}}
files, err := repo.LogFiles(context.Background(), date)
if err != nil {
@@ -41,7 +41,7 @@ func TestLogViewerReadsNestedCategoryFiles(t *testing.T) {
}
func TestLogViewerRejectsPathTraversal(t *testing.T) {
runtime := conf.NewRuntime(nil, &conf.AdminBackend{Zap: &conf.AdminBackend_Zap{Director: t.TempDir()}})
runtime := config.NewStore(&config.Config{Admin: &config.Admin{Zap: &config.Zap{Director: t.TempDir()}}})
repo := &logFileRepo{data: &Data{runtime: runtime}}
_, err := repo.LogContent(context.Background(), "2026-08-16", "../application.log", nil)
if !errors.Is(err, system.ErrInvalidLogPath) {
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"kra/internal/conf"
"kra/internal/config"
)
func newMenuTestData(t *testing.T) *Data {
@@ -20,7 +20,7 @@ func newMenuTestData(t *testing.T) *Data {
}
data := &Data{
gormDB: newReloadableDB(db, nil),
runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{}),
runtime: config.NewStore(&config.Config{Data: &config.Data{Database: &config.Database{Driver: "sqlite"}}, Admin: &config.Admin{}}),
}
t.Cleanup(func() { data.gormDB.close() })
return data
+4 -4
View File
@@ -5,7 +5,7 @@ import (
"kra/internal/biz/system"
"testing"
"kra/internal/conf"
"kra/internal/config"
)
func TestPermissionSelectedButtonsEmptyShape(t *testing.T) {
@@ -16,7 +16,7 @@ func TestPermissionSelectedButtonsEmptyShape(t *testing.T) {
if err := db.AutoMigrate(&authorityButtonPO{}); err != nil {
t.Fatal(err)
}
data := &Data{gormDB: newReloadableDB(db, nil), runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{})}
data := &Data{gormDB: newReloadableDB(db, nil), runtime: config.NewStore(&config.Config{Data: &config.Data{Database: &config.Database{Driver: "sqlite"}}, Admin: &config.Admin{}})}
t.Cleanup(func() { data.gormDB.close() })
ids, err := (&permissionRepo{data: data}).SelectedButtons(context.Background(), 1, 2)
@@ -36,7 +36,7 @@ func TestPermissionCanRemoveButtonUsesRawQueryID(t *testing.T) {
if err := db.AutoMigrate(&authorityButtonPO{}); err != nil {
t.Fatal(err)
}
data := &Data{gormDB: newReloadableDB(db, nil), runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{})}
data := &Data{gormDB: newReloadableDB(db, nil), runtime: config.NewStore(&config.Config{Data: &config.Data{Database: &config.Database{Driver: "sqlite"}}, Admin: &config.Admin{}})}
t.Cleanup(func() { data.gormDB.close() })
repo := &permissionRepo{data: data}
if err := data.gormDB.WithContext(context.Background()).Create(&authorityButtonPO{AuthorityID: 888, MenuID: 10, ButtonID: 31}).Error; err != nil {
@@ -67,7 +67,7 @@ func TestPermissionSetSelectedButtonsPreservesDuplicates(t *testing.T) {
if err := db.AutoMigrate(&authorityButtonPO{}); err != nil {
t.Fatal(err)
}
data := &Data{gormDB: newReloadableDB(db, nil), runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{})}
data := &Data{gormDB: newReloadableDB(db, nil), runtime: config.NewStore(&config.Config{Data: &config.Data{Database: &config.Database{Driver: "sqlite"}}, Admin: &config.Admin{}})}
t.Cleanup(func() { data.gormDB.close() })
repo := &permissionRepo{data: data}
+2 -2
View File
@@ -1,7 +1,7 @@
package system
import (
"kra/internal/conf"
"kra/internal/config"
"gorm.io/gorm"
)
@@ -12,5 +12,5 @@ type Provider interface {
DB() *gorm.DB
Database(name string) (*gorm.DB, error)
DatabaseReady() bool
Runtime() *conf.Runtime
Runtime() *config.Store
}
+17 -17
View File
@@ -7,15 +7,15 @@ import (
"kra/internal/biz/system"
"time"
"kra/internal/conf"
"kra/internal/config"
"kra/internal/security"
jwt "github.com/golang-jwt/jwt/v5"
)
type runtimeSettings struct{ runtime *conf.Runtime }
type runtimeSettings struct{ runtime *config.Store }
func NewRuntimeSettings(runtime *conf.Runtime) system.RuntimeSettings {
func NewRuntimeSettings(runtime *config.Store) system.RuntimeSettings {
return &runtimeSettings{runtime: runtime}
}
@@ -30,18 +30,18 @@ func (s *runtimeSettings) RouterPrefix() string {
func (s *runtimeSettings) JWTSettings() system.JWTSettings {
value := system.JWTSettings{Issuer: "kra", Expires: 7 * 24 * time.Hour, Buffer: 24 * time.Hour}
config := s.runtime.Admin()
if config == nil || config.Jwt == nil {
if config == nil || config.JWT == nil {
return value
}
value.SigningKey = config.Jwt.SigningKey
if config.Jwt.Issuer != "" {
value.Issuer = config.Jwt.Issuer
value.SigningKey = config.JWT.SigningKey
if config.JWT.Issuer != "" {
value.Issuer = config.JWT.Issuer
}
if config.Jwt.ExpiresTime != nil {
value.Expires = config.Jwt.ExpiresTime.AsDuration()
if config.JWT.ExpiresTime > 0 {
value.Expires = config.JWT.ExpiresTime
}
if config.Jwt.BufferTime != nil {
value.Buffer = config.Jwt.BufferTime.AsDuration()
if config.JWT.BufferTime > 0 {
value.Buffer = config.JWT.BufferTime
}
return value
}
@@ -53,16 +53,16 @@ func (s *runtimeSettings) CaptchaSettings() system.CaptchaSettings {
return value
}
if config.Captcha.KeyLong > 0 {
value.KeyLong = int(config.Captcha.KeyLong)
value.KeyLong = config.Captcha.KeyLong
}
if config.Captcha.ImgWidth > 0 {
value.ImageWidth = int(config.Captcha.ImgWidth)
value.ImageWidth = config.Captcha.ImgWidth
}
if config.Captcha.ImgHeight > 0 {
value.ImageHeight = int(config.Captcha.ImgHeight)
value.ImageHeight = config.Captcha.ImgHeight
}
if config.Captcha.StoreExpiration != nil {
value.StoreExpiration = config.Captcha.StoreExpiration.AsDuration()
if config.Captcha.StoreExpiration > 0 {
value.StoreExpiration = config.Captcha.StoreExpiration
}
return value
}
@@ -72,7 +72,7 @@ func (s *runtimeSettings) MediaSettings() system.MediaSettings {
if config == nil || config.Media == nil {
return system.MediaSettings{}
}
return system.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize, ChunkDir: config.Media.ChunkDir}
return system.MediaSettings{SessionTTL: config.Media.SessionTTL, MaxFileSize: config.Media.MaxFileSize, ChunkDir: config.Media.ChunkDir}
}
func (s *runtimeSettings) UseMultipoint() bool {
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"kra/internal/conf"
"kra/internal/config"
)
// Data is a small in-package test harness. Production repositories depend on
@@ -16,7 +16,7 @@ import (
type Data struct {
gormDB *reloadableDB
redis *reloadableRedis
runtime *conf.Runtime
runtime *config.Store
databaseReady atomic.Bool
}
@@ -29,7 +29,7 @@ func (d *Data) DB() *gorm.DB {
func (d *Data) Database(string) (*gorm.DB, error) { return d.DB(), nil }
func (d *Data) DatabaseReady() bool { return d != nil && d.databaseReady.Load() }
func (d *Data) Runtime() *conf.Runtime {
func (d *Data) Runtime() *config.Store {
if d == nil {
return nil
}
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
"kra/internal/conf"
"kra/internal/config"
)
func newTransactionTestData(t *testing.T) *Data {
@@ -19,7 +19,7 @@ func newTransactionTestData(t *testing.T) *Data {
if err = migrateAll(db); err != nil {
t.Fatal(err)
}
data := &Data{gormDB: newReloadableDB(db, nil), redis: newReloadableRedis(nil), runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{})}
data := &Data{gormDB: newReloadableDB(db, nil), redis: newReloadableRedis(nil), runtime: config.NewStore(&config.Config{Data: &config.Data{Database: &config.Database{Driver: "sqlite"}}, Admin: &config.Admin{}})}
t.Cleanup(func() { data.gormDB.close() })
return data
}