优化结构
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# Initialization
|
||||
|
||||
`internal/initialize` owns application bootstrap orchestration. It implements
|
||||
`biz.InitializationRepo`, coordinates the explicit first-install workflow, and
|
||||
formats the compatible runtime configuration API.
|
||||
|
||||
The package depends on a narrow `Backend` interface. `*data.Data` implements
|
||||
that interface and remains responsible for opening, migrating, activating,
|
||||
reloading, and persisting database-backed runtime infrastructure.
|
||||
|
||||
The first-install order is:
|
||||
|
||||
1. `data` opens the candidate database.
|
||||
2. The single root migration runner applies infrastructure, system, and
|
||||
payment schema steps.
|
||||
3. `initialize` invokes `system.SeedSystem` for administrator, menu, API,
|
||||
department, policy, and module administration-surface records.
|
||||
4. `data` persists configuration and activates the candidate clients.
|
||||
|
||||
Schema migration and business seed data are deliberately separate. Normal
|
||||
startup and configuration reload may run migrations, but only the explicit
|
||||
first-install endpoint runs seed data.
|
||||
@@ -0,0 +1,24 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Backend is the infrastructure boundary required by application
|
||||
// initialization and runtime configuration management.
|
||||
type Backend interface {
|
||||
IsInitialized(context.Context) (bool, error)
|
||||
InitializeDatabase(context.Context, *biz.DatabaseConfig, func(context.Context, *gorm.DB) error) error
|
||||
PersistConfig(context.Context) error
|
||||
PersistAdminConfig(context.Context, []byte) error
|
||||
PersistRuntimeConfig(context.Context, []byte, []byte) error
|
||||
ReloadConfig(context.Context) error
|
||||
RuntimeValues() (*conf.Data, *conf.AdminBackend)
|
||||
RuntimeAdmin() *conf.AdminBackend
|
||||
RefreshDatabaseSources(*conf.Data) error
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func compatibleDatabase(value *conf.Data_Database) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
path := value.Host
|
||||
if value.Driver == "sqlite" {
|
||||
path = value.Path
|
||||
}
|
||||
return map[string]any{
|
||||
"prefix": value.Prefix, "port": value.Port, "config": value.Config,
|
||||
"db-name": value.Name, "username": value.User, "password": value.Password,
|
||||
"path": path, "engine": value.Engine, "log-mode": value.LogMode,
|
||||
"max-idle-conns": value.MaxIdleConns, "max-open-conns": value.MaxOpenConns,
|
||||
"conn-max-lifetime": value.ConnMaxLifetime, "singular": value.Singular,
|
||||
}
|
||||
}
|
||||
|
||||
func compatibleRedis(value *conf.Data_Redis) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{"name": value.Name, "addr": value.Addr, "password": value.Password, "db": value.Db, "useCluster": value.UseCluster, "clusterAddrs": value.ClusterAddrs}
|
||||
}
|
||||
|
||||
func compatibleObjectStore(value *conf.AdminBackend_ObjectStore) map[string]any {
|
||||
if value == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
"endpoint": value.Endpoint, "region": value.Region, "bucket": value.Bucket,
|
||||
"access-key": value.AccessKey, "secret-key": value.SecretKey, "base-url": value.BaseUrl,
|
||||
"path-prefix": value.PathPrefix, "use-ssl": value.UseSsl,
|
||||
"s3-force-path-style": value.ForcePathStyle, "account-id": value.AccountId,
|
||||
}
|
||||
}
|
||||
|
||||
func compatibleObjectStores(storage *conf.AdminBackend_Storage) map[string]map[string]any {
|
||||
if storage == nil {
|
||||
storage = &conf.AdminBackend_Storage{}
|
||||
}
|
||||
aliyun, huawei := storage.AliyunOss, storage.HuaweiObs
|
||||
tencent, aws := storage.TencentCos, storage.AwsS3
|
||||
cloudflare, minio := storage.CloudflareR2, storage.Minio
|
||||
if aliyun == nil {
|
||||
aliyun = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if huawei == nil {
|
||||
huawei = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if tencent == nil {
|
||||
tencent = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if aws == nil {
|
||||
aws = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if cloudflare == nil {
|
||||
cloudflare = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
if minio == nil {
|
||||
minio = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
return map[string]map[string]any{
|
||||
"aliyun-oss": {"endpoint": aliyun.Endpoint, "access-key-id": aliyun.AccessKey, "access-key-secret": aliyun.SecretKey, "bucket-name": aliyun.Bucket, "bucket-url": aliyun.BaseUrl, "base-path": aliyun.PathPrefix},
|
||||
"hua-wei-obs": {"path": huawei.PathPrefix, "bucket": huawei.Bucket, "endpoint": huawei.Endpoint, "access-key": huawei.AccessKey, "secret-key": huawei.SecretKey},
|
||||
"tencent-cos": {"bucket": tencent.Bucket, "region": tencent.Region, "secret-id": tencent.AccessKey, "secret-key": tencent.SecretKey, "base-url": tencent.BaseUrl, "path-prefix": tencent.PathPrefix},
|
||||
"aws-s3": {"bucket": aws.Bucket, "region": aws.Region, "endpoint": aws.Endpoint, "secret-id": aws.AccessKey, "secret-key": aws.SecretKey, "base-url": aws.BaseUrl, "path-prefix": aws.PathPrefix, "s3-force-path-style": aws.ForcePathStyle, "disable-ssl": !aws.UseSsl},
|
||||
"cloudflare-r2": {"bucket": cloudflare.Bucket, "base-url": cloudflare.BaseUrl, "path": cloudflare.PathPrefix, "account-id": cloudflare.AccountId, "access-key-id": cloudflare.AccessKey, "secret-access-key": cloudflare.SecretKey},
|
||||
"minio": {"endpoint": minio.Endpoint, "access-key-id": minio.AccessKey, "access-key-secret": minio.SecretKey, "bucket-name": minio.Bucket, "use-ssl": minio.UseSsl, "base-path": minio.PathPrefix, "bucket-url": minio.BaseUrl},
|
||||
}
|
||||
}
|
||||
|
||||
func compatibleConfiguration(data *conf.Data, admin *conf.AdminBackend) map[string]any {
|
||||
result := map[string]any{}
|
||||
if data == nil {
|
||||
data = &conf.Data{}
|
||||
}
|
||||
if admin == nil {
|
||||
admin = &conf.AdminBackend{}
|
||||
}
|
||||
jwt := admin.Jwt
|
||||
if jwt == nil {
|
||||
jwt = &conf.AdminBackend_JWT{}
|
||||
}
|
||||
result["jwt"] = map[string]any{"signing-key": jwt.SigningKey, "expires-time": durationString(jwt.ExpiresTime), "buffer-time": durationString(jwt.BufferTime), "issuer": jwt.Issuer}
|
||||
zap := admin.Zap
|
||||
if zap == nil {
|
||||
zap = &conf.AdminBackend_Zap{}
|
||||
}
|
||||
result["zap"] = map[string]any{
|
||||
"level": zap.Level, "prefix": zap.Prefix, "format": zap.Format, "director": zap.Director,
|
||||
"encode-level": zap.EncodeLevel, "stacktrace-key": zap.StacktraceKey, "show-line": zap.ShowLine,
|
||||
"log-in-console": zap.LogInConsole, "retention-day": zap.RetentionDay,
|
||||
"access-req-body": zap.AccessReqBody, "access-resp-data": zap.AccessRespData,
|
||||
"access-req-headers": zap.AccessReqHeaders, "access-log-max-bytes": zap.AccessLogMaxBytes,
|
||||
"file-only-modules": zap.FileOnlyModules,
|
||||
}
|
||||
result["redis"] = compatibleRedis(data.Redis)
|
||||
redisList := make([]map[string]any, 0, len(data.RedisList))
|
||||
for _, item := range data.RedisList {
|
||||
redisList = append(redisList, compatibleRedis(item))
|
||||
}
|
||||
result["redis-list"] = redisList
|
||||
mongo := data.Mongo
|
||||
if mongo == nil {
|
||||
mongo = &conf.Data_Mongo{}
|
||||
}
|
||||
hosts := make([]map[string]any, 0, len(mongo.Hosts))
|
||||
for _, host := range mongo.Hosts {
|
||||
if host != nil {
|
||||
hosts = append(hosts, map[string]any{"host": host.Host, "port": host.Port})
|
||||
}
|
||||
}
|
||||
result["mongo"] = map[string]any{"coll": mongo.Coll, "options": mongo.Options, "database": mongo.Database, "username": mongo.Username, "password": mongo.Password, "auth-source": mongo.AuthSource, "min-pool-size": mongo.MinPoolSize, "max-pool-size": mongo.MaxPoolSize, "socket-timeout-ms": mongo.SocketTimeoutMs, "connect-timeout-ms": mongo.ConnectTimeoutMs, "is-zap": mongo.IsZap, "hosts": hosts}
|
||||
email := admin.Email
|
||||
if email == nil {
|
||||
email = &conf.AdminBackend_Email{}
|
||||
}
|
||||
result["email"] = map[string]any{"to": email.To, "from": email.From, "host": email.Host, "secret": email.Secret, "nickname": email.Nickname, "port": email.Port, "is-ssl": email.IsSsl, "is-loginauth": email.IsLoginAuth}
|
||||
system := admin.System
|
||||
if system == nil {
|
||||
system = &conf.AdminBackend_System{}
|
||||
}
|
||||
storageType := "local"
|
||||
if admin.Storage != nil && admin.Storage.Type != "" {
|
||||
storageType = admin.Storage.Type
|
||||
}
|
||||
driver := ""
|
||||
if data.Database != nil {
|
||||
driver = data.Database.Driver
|
||||
}
|
||||
result["system"] = map[string]any{"db-type": driver, "oss-type": storageType, "router-prefix": admin.RouterPrefix, "addr": system.Addr, "iplimit-count": system.IplimitCount, "iplimit-time": system.IplimitTime, "use-multipoint": system.UseMultipoint, "use-redis": system.UseRedis, "use-mongo": system.UseMongo, "use-strict-auth": system.UseStrictAuth, "disable-auto-migrate": system.DisableAutoMigrate}
|
||||
for _, name := range []string{"mysql", "mssql", "pgsql", "oracle", "sqlite"} {
|
||||
result[name] = map[string]any{}
|
||||
}
|
||||
if driver != "" {
|
||||
result[driver] = compatibleDatabase(data.Database)
|
||||
}
|
||||
dbList := make([]map[string]any, 0, len(data.DatabaseList))
|
||||
for _, item := range data.DatabaseList {
|
||||
row := compatibleDatabase(item)
|
||||
if item != nil {
|
||||
row["type"], row["alias-name"], row["disable"] = item.Driver, item.AliasName, item.Disable
|
||||
}
|
||||
dbList = append(dbList, row)
|
||||
}
|
||||
result["db-list"] = dbList
|
||||
local := admin.Local
|
||||
if local == nil {
|
||||
local = &conf.AdminBackend_Local{}
|
||||
}
|
||||
result["local"] = map[string]any{"path": local.PathPrefix, "store-path": local.StorePath}
|
||||
media := admin.Media
|
||||
if media == nil {
|
||||
media = &conf.AdminBackend_Media{}
|
||||
}
|
||||
chunkDir := media.ChunkDir
|
||||
if chunkDir == "" {
|
||||
chunkDir = "uploads/chunks"
|
||||
}
|
||||
result["media"] = map[string]any{"session-ttl": media.SessionTtl, "max-file-size": media.MaxFileSize, "chunk-dir": chunkDir}
|
||||
if admin.Storage == nil {
|
||||
admin.Storage = &conf.AdminBackend_Storage{}
|
||||
}
|
||||
qiniu := admin.Storage.Qiniu
|
||||
if qiniu == nil {
|
||||
qiniu = &conf.AdminBackend_Qiniu{}
|
||||
}
|
||||
result["qiniu"] = map[string]any{"zone": qiniu.Zone, "bucket": qiniu.Bucket, "img-path": qiniu.BaseUrl, "access-key": qiniu.AccessKey, "secret-key": qiniu.SecretKey, "use-https": qiniu.UseHttps, "use-cdn-domains": qiniu.UseCdnDomains}
|
||||
for key, value := range compatibleObjectStores(admin.Storage) {
|
||||
result[key] = value
|
||||
}
|
||||
disks := make([]map[string]any, 0, len(admin.DiskList))
|
||||
for _, disk := range admin.DiskList {
|
||||
if disk != nil {
|
||||
disks = append(disks, map[string]any{"mount-point": disk.MountPoint})
|
||||
}
|
||||
}
|
||||
result["disk-list"] = disks
|
||||
cors := admin.Cors
|
||||
if cors == nil {
|
||||
cors = &conf.AdminBackend_CORS{}
|
||||
}
|
||||
whitelist := make([]map[string]any, 0, len(cors.Whitelist))
|
||||
for _, rule := range cors.Whitelist {
|
||||
if rule != nil {
|
||||
whitelist = append(whitelist, map[string]any{"allow-origin": rule.AllowOrigin, "allow-methods": rule.AllowMethods, "allow-headers": rule.AllowHeaders, "expose-headers": rule.ExposeHeaders, "allow-credentials": rule.AllowCredentials})
|
||||
}
|
||||
}
|
||||
result["cors"] = map[string]any{"mode": cors.Mode, "whitelist": whitelist}
|
||||
result["app"] = map[string]any{"node": admin.GetApp().GetNode(), "app-id": admin.GetApp().GetAppId(), "env": admin.GetApp().GetEnv()}
|
||||
return result
|
||||
}
|
||||
|
||||
func objectMap(value any) map[string]any {
|
||||
result, _ := value.(map[string]any)
|
||||
return result
|
||||
}
|
||||
|
||||
func stringValue(values map[string]any, key string) string {
|
||||
value, _ := values[key].(string)
|
||||
return value
|
||||
}
|
||||
|
||||
func boolValue(values map[string]any, key string, fallback bool) bool {
|
||||
value, ok := values[key].(bool)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func int64Value(values map[string]any, key string, fallback int64) int64 {
|
||||
value, ok := values[key].(float64)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
return int64(value)
|
||||
}
|
||||
|
||||
func stringSliceValue(values map[string]any, key string, fallback []string) []string {
|
||||
items, ok := values[key].([]any)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
result := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
if value, ok := item.(string); ok {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func applyCompatibleRedis(target *conf.Data_Redis, values map[string]any) {
|
||||
if target == nil || values == nil {
|
||||
return
|
||||
}
|
||||
target.Name, target.Addr = stringValue(values, "name"), stringValue(values, "addr")
|
||||
if password := stringValue(values, "password"); password != "" && password != "******" {
|
||||
target.Password = password
|
||||
}
|
||||
target.Db = int32(int64Value(values, "db", int64(target.Db)))
|
||||
target.UseCluster = boolValue(values, "useCluster", target.UseCluster)
|
||||
target.ClusterAddrs = stringSliceValue(values, "clusterAddrs", target.ClusterAddrs)
|
||||
}
|
||||
|
||||
func durationValue(raw string, fallback *durationpb.Duration) *durationpb.Duration {
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
value, err := parseCompatibleDuration(raw)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return durationpb.New(value)
|
||||
}
|
||||
|
||||
func parseCompatibleDuration(raw string) (time.Duration, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if value, err := time.ParseDuration(raw); err == nil {
|
||||
return value, nil
|
||||
}
|
||||
if index := strings.Index(raw, "d"); index >= 0 {
|
||||
days, _ := strconv.Atoi(raw[:index])
|
||||
value := time.Duration(days) * 24 * time.Hour
|
||||
remainder, err := time.ParseDuration(raw[index+1:])
|
||||
if err != nil {
|
||||
return value, nil
|
||||
}
|
||||
return value + remainder, nil
|
||||
}
|
||||
nanoseconds, err := strconv.ParseInt(raw, 10, 64)
|
||||
return time.Duration(nanoseconds), err
|
||||
}
|
||||
|
||||
func applyCompatibleDatabase(target *conf.Data_Database, values map[string]any, driver string) {
|
||||
if target == nil || values == nil {
|
||||
return
|
||||
}
|
||||
target.Driver = driver
|
||||
target.Prefix, target.Port, target.Config = stringValue(values, "prefix"), stringValue(values, "port"), stringValue(values, "config")
|
||||
target.Name, target.User = stringValue(values, "db-name"), stringValue(values, "username")
|
||||
if password := stringValue(values, "password"); password != "" && password != "******" {
|
||||
target.Password = password
|
||||
}
|
||||
if driver == "sqlite" {
|
||||
target.Path = stringValue(values, "path")
|
||||
} else {
|
||||
target.Host = stringValue(values, "path")
|
||||
}
|
||||
target.Engine, target.LogMode = stringValue(values, "engine"), stringValue(values, "log-mode")
|
||||
target.MaxIdleConns = int32(int64Value(values, "max-idle-conns", int64(target.MaxIdleConns)))
|
||||
target.MaxOpenConns = int32(int64Value(values, "max-open-conns", int64(target.MaxOpenConns)))
|
||||
target.ConnMaxLifetime = int32(int64Value(values, "conn-max-lifetime", int64(target.ConnMaxLifetime)))
|
||||
target.Singular = boolValue(values, "singular", target.Singular)
|
||||
}
|
||||
|
||||
func applyCompatibleObjectStore(target *conf.AdminBackend_ObjectStore, values map[string]any) {
|
||||
if target == nil || values == nil {
|
||||
return
|
||||
}
|
||||
target.Endpoint, target.Region, target.Bucket = stringValue(values, "endpoint"), stringValue(values, "region"), stringValue(values, "bucket")
|
||||
if target.Bucket == "" {
|
||||
target.Bucket = stringValue(values, "bucket-name")
|
||||
}
|
||||
target.AccessKey = stringValue(values, "access-key")
|
||||
if target.AccessKey == "" {
|
||||
target.AccessKey = stringValue(values, "access-key-id")
|
||||
}
|
||||
if target.AccessKey == "" {
|
||||
target.AccessKey = stringValue(values, "secret-id")
|
||||
}
|
||||
secret := stringValue(values, "secret-key")
|
||||
if secret == "" {
|
||||
secret = stringValue(values, "access-key-secret")
|
||||
}
|
||||
if secret == "" {
|
||||
secret = stringValue(values, "secret-access-key")
|
||||
}
|
||||
if secret != "" && secret != "******" {
|
||||
target.SecretKey = secret
|
||||
}
|
||||
target.BaseUrl = stringValue(values, "base-url")
|
||||
if target.BaseUrl == "" {
|
||||
target.BaseUrl = stringValue(values, "bucket-url")
|
||||
}
|
||||
target.PathPrefix = stringValue(values, "path-prefix")
|
||||
if target.PathPrefix == "" {
|
||||
target.PathPrefix = stringValue(values, "base-path")
|
||||
}
|
||||
if target.PathPrefix == "" {
|
||||
target.PathPrefix = stringValue(values, "path")
|
||||
}
|
||||
target.UseSsl = boolValue(values, "use-ssl", !boolValue(values, "disable-ssl", !target.UseSsl))
|
||||
target.ForcePathStyle = boolValue(values, "s3-force-path-style", target.ForcePathStyle)
|
||||
target.AccountId = stringValue(values, "account-id")
|
||||
}
|
||||
|
||||
func applyCompatibleConfiguration(raw json.RawMessage, data *conf.Data, admin *conf.AdminBackend) error {
|
||||
var values map[string]any
|
||||
if err := json.Unmarshal(raw, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, compatibleShape := values["system"]; !compatibleShape {
|
||||
return nil
|
||||
}
|
||||
if data.Database == nil {
|
||||
data.Database = &conf.Data_Database{}
|
||||
}
|
||||
if admin.System == nil {
|
||||
admin.System = &conf.AdminBackend_System{}
|
||||
}
|
||||
if admin.Storage == nil {
|
||||
admin.Storage = &conf.AdminBackend_Storage{}
|
||||
}
|
||||
system := objectMap(values["system"])
|
||||
driver := stringValue(system, "db-type")
|
||||
if driver == "" {
|
||||
driver = data.Database.Driver
|
||||
}
|
||||
applyCompatibleDatabase(data.Database, objectMap(values[driver]), driver)
|
||||
admin.Storage.Type = stringValue(system, "oss-type")
|
||||
admin.RouterPrefix = stringValue(system, "router-prefix")
|
||||
admin.System.UseMultipoint = boolValue(system, "use-multipoint", admin.System.UseMultipoint)
|
||||
admin.System.UseRedis = boolValue(system, "use-redis", admin.System.UseRedis)
|
||||
admin.System.UseMongo = boolValue(system, "use-mongo", admin.System.UseMongo)
|
||||
admin.System.UseStrictAuth = boolValue(system, "use-strict-auth", admin.System.UseStrictAuth)
|
||||
admin.System.DisableAutoMigrate = boolValue(system, "disable-auto-migrate", admin.System.DisableAutoMigrate)
|
||||
admin.System.Addr = int32(int64Value(system, "addr", int64(admin.System.Addr)))
|
||||
admin.System.IplimitCount = int32(int64Value(system, "iplimit-count", int64(admin.System.IplimitCount)))
|
||||
admin.System.IplimitTime = int32(int64Value(system, "iplimit-time", int64(admin.System.IplimitTime)))
|
||||
if admin.Jwt == nil {
|
||||
admin.Jwt = &conf.AdminBackend_JWT{}
|
||||
}
|
||||
jwt := objectMap(values["jwt"])
|
||||
if secret := stringValue(jwt, "signing-key"); secret != "" && secret != "******" {
|
||||
admin.Jwt.SigningKey = secret
|
||||
}
|
||||
admin.Jwt.ExpiresTime = durationValue(stringValue(jwt, "expires-time"), admin.Jwt.ExpiresTime)
|
||||
admin.Jwt.BufferTime = durationValue(stringValue(jwt, "buffer-time"), admin.Jwt.BufferTime)
|
||||
admin.Jwt.Issuer = stringValue(jwt, "issuer")
|
||||
if admin.Zap == nil {
|
||||
admin.Zap = &conf.AdminBackend_Zap{}
|
||||
}
|
||||
zap := objectMap(values["zap"])
|
||||
admin.Zap.Level, admin.Zap.Prefix, admin.Zap.Format, admin.Zap.Director = stringValue(zap, "level"), stringValue(zap, "prefix"), stringValue(zap, "format"), stringValue(zap, "director")
|
||||
admin.Zap.EncodeLevel, admin.Zap.StacktraceKey = stringValue(zap, "encode-level"), stringValue(zap, "stacktrace-key")
|
||||
admin.Zap.ShowLine, admin.Zap.LogInConsole = boolValue(zap, "show-line", admin.Zap.ShowLine), boolValue(zap, "log-in-console", admin.Zap.LogInConsole)
|
||||
admin.Zap.RetentionDay = int32(int64Value(zap, "retention-day", int64(admin.Zap.RetentionDay)))
|
||||
admin.Zap.AccessReqBody = boolValue(zap, "access-req-body", admin.Zap.AccessReqBody)
|
||||
admin.Zap.AccessRespData = boolValue(zap, "access-resp-data", admin.Zap.AccessRespData)
|
||||
admin.Zap.AccessReqHeaders = boolValue(zap, "access-req-headers", admin.Zap.AccessReqHeaders)
|
||||
admin.Zap.AccessLogMaxBytes = int32(int64Value(zap, "access-log-max-bytes", int64(admin.Zap.AccessLogMaxBytes)))
|
||||
if list, ok := zap["file-only-modules"].([]any); ok {
|
||||
admin.Zap.FileOnlyModules = admin.Zap.FileOnlyModules[:0]
|
||||
for _, item := range list {
|
||||
if value, ok := item.(string); ok {
|
||||
admin.Zap.FileOnlyModules = append(admin.Zap.FileOnlyModules, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if data.Redis == nil {
|
||||
data.Redis = &conf.Data_Redis{}
|
||||
}
|
||||
applyCompatibleRedis(data.Redis, objectMap(values["redis"]))
|
||||
if items, ok := values["redis-list"].([]any); ok {
|
||||
current := make(map[string]*conf.Data_Redis, len(data.RedisList))
|
||||
for _, item := range data.RedisList {
|
||||
if item != nil {
|
||||
current[item.Name] = item
|
||||
}
|
||||
}
|
||||
data.RedisList = make([]*conf.Data_Redis, 0, len(items))
|
||||
for _, item := range items {
|
||||
values := objectMap(item)
|
||||
target := current[stringValue(values, "name")]
|
||||
if target == nil {
|
||||
target = &conf.Data_Redis{}
|
||||
}
|
||||
applyCompatibleRedis(target, values)
|
||||
data.RedisList = append(data.RedisList, target)
|
||||
}
|
||||
}
|
||||
if mongo := objectMap(values["mongo"]); mongo != nil {
|
||||
if data.Mongo == nil {
|
||||
data.Mongo = &conf.Data_Mongo{}
|
||||
}
|
||||
data.Mongo.Coll, data.Mongo.Options, data.Mongo.Database = stringValue(mongo, "coll"), stringValue(mongo, "options"), stringValue(mongo, "database")
|
||||
data.Mongo.Username, data.Mongo.AuthSource = stringValue(mongo, "username"), stringValue(mongo, "auth-source")
|
||||
if password := stringValue(mongo, "password"); password != "" && password != "******" {
|
||||
data.Mongo.Password = password
|
||||
}
|
||||
data.Mongo.MinPoolSize = uint64(int64Value(mongo, "min-pool-size", int64(data.Mongo.MinPoolSize)))
|
||||
data.Mongo.MaxPoolSize = uint64(int64Value(mongo, "max-pool-size", int64(data.Mongo.MaxPoolSize)))
|
||||
data.Mongo.SocketTimeoutMs = int64Value(mongo, "socket-timeout-ms", data.Mongo.SocketTimeoutMs)
|
||||
data.Mongo.ConnectTimeoutMs = int64Value(mongo, "connect-timeout-ms", data.Mongo.ConnectTimeoutMs)
|
||||
data.Mongo.IsZap = boolValue(mongo, "is-zap", data.Mongo.IsZap)
|
||||
if hosts, ok := mongo["hosts"].([]any); ok {
|
||||
data.Mongo.Hosts = make([]*conf.Data_MongoHost, 0, len(hosts))
|
||||
for _, item := range hosts {
|
||||
host := objectMap(item)
|
||||
data.Mongo.Hosts = append(data.Mongo.Hosts, &conf.Data_MongoHost{Host: stringValue(host, "host"), Port: stringValue(host, "port")})
|
||||
}
|
||||
}
|
||||
}
|
||||
if items, ok := values["db-list"].([]any); ok {
|
||||
current := make(map[string]*conf.Data_Database, len(data.DatabaseList))
|
||||
for _, item := range data.DatabaseList {
|
||||
if item != nil {
|
||||
current[item.AliasName] = item
|
||||
}
|
||||
}
|
||||
data.DatabaseList = make([]*conf.Data_Database, 0, len(items))
|
||||
for _, item := range items {
|
||||
row := objectMap(item)
|
||||
alias := stringValue(row, "alias-name")
|
||||
target := current[alias]
|
||||
if target == nil {
|
||||
target = &conf.Data_Database{}
|
||||
}
|
||||
applyCompatibleDatabase(target, row, stringValue(row, "type"))
|
||||
target.AliasName, target.Disable = alias, boolValue(row, "disable", target.Disable)
|
||||
data.DatabaseList = append(data.DatabaseList, target)
|
||||
}
|
||||
}
|
||||
if admin.Email == nil {
|
||||
admin.Email = &conf.AdminBackend_Email{}
|
||||
}
|
||||
email := objectMap(values["email"])
|
||||
admin.Email.To, admin.Email.From, admin.Email.Host = stringValue(email, "to"), stringValue(email, "from"), stringValue(email, "host")
|
||||
admin.Email.Nickname = stringValue(email, "nickname")
|
||||
admin.Email.Port = int32(int64Value(email, "port", int64(admin.Email.Port)))
|
||||
admin.Email.IsSsl, admin.Email.IsLoginAuth = boolValue(email, "is-ssl", admin.Email.IsSsl), boolValue(email, "is-loginauth", admin.Email.IsLoginAuth)
|
||||
if secret := stringValue(email, "secret"); secret != "" && secret != "******" {
|
||||
admin.Email.Secret = secret
|
||||
}
|
||||
if admin.Local == nil {
|
||||
admin.Local = &conf.AdminBackend_Local{}
|
||||
}
|
||||
local := objectMap(values["local"])
|
||||
admin.Local.PathPrefix, admin.Local.StorePath = stringValue(local, "path"), stringValue(local, "store-path")
|
||||
if admin.Media == nil {
|
||||
admin.Media = &conf.AdminBackend_Media{}
|
||||
}
|
||||
media := objectMap(values["media"])
|
||||
admin.Media.SessionTtl = int32(int64Value(media, "session-ttl", int64(admin.Media.SessionTtl)))
|
||||
admin.Media.MaxFileSize = int64Value(media, "max-file-size", admin.Media.MaxFileSize)
|
||||
admin.Media.ChunkDir = stringValue(media, "chunk-dir")
|
||||
if admin.Storage.Qiniu == nil {
|
||||
admin.Storage.Qiniu = &conf.AdminBackend_Qiniu{}
|
||||
}
|
||||
qiniu := objectMap(values["qiniu"])
|
||||
admin.Storage.Qiniu.Zone, admin.Storage.Qiniu.Bucket, admin.Storage.Qiniu.BaseUrl = stringValue(qiniu, "zone"), stringValue(qiniu, "bucket"), stringValue(qiniu, "img-path")
|
||||
admin.Storage.Qiniu.AccessKey = stringValue(qiniu, "access-key")
|
||||
if secret := stringValue(qiniu, "secret-key"); secret != "" && secret != "******" {
|
||||
admin.Storage.Qiniu.SecretKey = secret
|
||||
}
|
||||
admin.Storage.Qiniu.UseHttps, admin.Storage.Qiniu.UseCdnDomains = boolValue(qiniu, "use-https", admin.Storage.Qiniu.UseHttps), boolValue(qiniu, "use-cdn-domains", admin.Storage.Qiniu.UseCdnDomains)
|
||||
stores := []struct {
|
||||
key string
|
||||
target **conf.AdminBackend_ObjectStore
|
||||
}{{"aliyun-oss", &admin.Storage.AliyunOss}, {"hua-wei-obs", &admin.Storage.HuaweiObs}, {"tencent-cos", &admin.Storage.TencentCos}, {"aws-s3", &admin.Storage.AwsS3}, {"cloudflare-r2", &admin.Storage.CloudflareR2}, {"minio", &admin.Storage.Minio}}
|
||||
for _, store := range stores {
|
||||
if *store.target == nil {
|
||||
*store.target = &conf.AdminBackend_ObjectStore{}
|
||||
}
|
||||
applyCompatibleObjectStore(*store.target, objectMap(values[store.key]))
|
||||
}
|
||||
if disks, ok := values["disk-list"].([]any); ok {
|
||||
admin.DiskList = make([]*conf.AdminBackend_Disk, 0, len(disks))
|
||||
for _, item := range disks {
|
||||
disk := objectMap(item)
|
||||
admin.DiskList = append(admin.DiskList, &conf.AdminBackend_Disk{MountPoint: stringValue(disk, "mount-point")})
|
||||
}
|
||||
}
|
||||
if cors := objectMap(values["cors"]); cors != nil {
|
||||
if admin.Cors == nil {
|
||||
admin.Cors = &conf.AdminBackend_CORS{}
|
||||
}
|
||||
admin.Cors.Mode = stringValue(cors, "mode")
|
||||
if rules, ok := cors["whitelist"].([]any); ok {
|
||||
admin.Cors.Whitelist = make([]*conf.AdminBackend_CORSRule, 0, len(rules))
|
||||
for _, item := range rules {
|
||||
rule := objectMap(item)
|
||||
admin.Cors.Whitelist = append(admin.Cors.Whitelist, &conf.AdminBackend_CORSRule{AllowOrigin: stringValue(rule, "allow-origin"), AllowMethods: stringValue(rule, "allow-methods"), AllowHeaders: stringValue(rule, "allow-headers"), ExposeHeaders: stringValue(rule, "expose-headers"), AllowCredentials: boolValue(rule, "allow-credentials", false)})
|
||||
}
|
||||
}
|
||||
}
|
||||
if app := objectMap(values["app"]); app != nil {
|
||||
if admin.App == nil {
|
||||
admin.App = &conf.AdminBackend_App{}
|
||||
}
|
||||
admin.App.Node, admin.App.AppId, admin.App.Env = stringValue(app, "node"), stringValue(app, "app-id"), stringValue(app, "env")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/utils/configutil"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
type configurationEnvelope struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
Admin json.RawMessage `json:"admin"`
|
||||
Email *struct {
|
||||
To string `json:"to"`
|
||||
From string `json:"from"`
|
||||
Host string `json:"host"`
|
||||
Secret string `json:"secret"`
|
||||
Nickname string `json:"nickname"`
|
||||
Port int32 `json:"port"`
|
||||
IsSSL bool `json:"is-ssl"`
|
||||
IsLoginAuth bool `json:"is-loginauth"`
|
||||
} `json:"email"`
|
||||
}
|
||||
|
||||
func (r *Repo) ConfigurationJSON() (json.RawMessage, error) {
|
||||
dataConfig, adminConfig := r.backend.RuntimeValues()
|
||||
admin := map[string]any{"routerPrefix": ""}
|
||||
email := map[string]any{}
|
||||
if adminConfig != nil {
|
||||
admin["routerPrefix"] = adminConfig.RouterPrefix
|
||||
if adminConfig.System != nil {
|
||||
admin["system"] = map[string]any{
|
||||
"useRedis": adminConfig.System.UseRedis,
|
||||
"useMultipoint": adminConfig.System.UseMultipoint,
|
||||
"useStrictAuth": adminConfig.System.UseStrictAuth,
|
||||
"disableAutoMigrate": adminConfig.System.DisableAutoMigrate,
|
||||
"useMongo": adminConfig.System.UseMongo,
|
||||
"addr": adminConfig.System.Addr,
|
||||
"iplimitCount": adminConfig.System.IplimitCount,
|
||||
"iplimitTime": adminConfig.System.IplimitTime,
|
||||
}
|
||||
}
|
||||
if adminConfig.Jwt != nil {
|
||||
admin["jwt"] = map[string]any{"signingKey": "******", "expiresTime": durationString(adminConfig.Jwt.ExpiresTime), "bufferTime": durationString(adminConfig.Jwt.BufferTime), "issuer": adminConfig.Jwt.Issuer}
|
||||
}
|
||||
if adminConfig.Captcha != nil {
|
||||
admin["captcha"] = map[string]any{"keyLong": adminConfig.Captcha.KeyLong, "imgWidth": adminConfig.Captcha.ImgWidth, "imgHeight": adminConfig.Captcha.ImgHeight, "storeExpiration": durationString(adminConfig.Captcha.StoreExpiration)}
|
||||
}
|
||||
if adminConfig.Local != nil {
|
||||
admin["local"] = map[string]any{"storePath": adminConfig.Local.StorePath, "pathPrefix": adminConfig.Local.PathPrefix}
|
||||
}
|
||||
if adminConfig.Media != nil {
|
||||
chunkDir := adminConfig.Media.ChunkDir
|
||||
if chunkDir == "" {
|
||||
chunkDir = "uploads/chunks"
|
||||
}
|
||||
admin["media"] = map[string]any{"sessionTtl": adminConfig.Media.SessionTtl, "maxFileSize": adminConfig.Media.MaxFileSize, "chunkDir": chunkDir}
|
||||
}
|
||||
if adminConfig.Email != nil {
|
||||
secret := ""
|
||||
if adminConfig.Email.Secret != "" {
|
||||
secret = "******"
|
||||
}
|
||||
email = map[string]any{"to": adminConfig.Email.To, "from": adminConfig.Email.From, "host": adminConfig.Email.Host, "secret": secret, "nickname": adminConfig.Email.Nickname, "port": adminConfig.Email.Port, "is-ssl": adminConfig.Email.IsSsl, "is-loginauth": adminConfig.Email.IsLoginAuth}
|
||||
}
|
||||
if adminConfig.Storage != nil {
|
||||
storage := proto.Clone(adminConfig.Storage).(*conf.AdminBackend_Storage)
|
||||
maskStorageSecrets(storage)
|
||||
admin["storage"] = storage
|
||||
}
|
||||
disks := make([]map[string]any, 0, len(adminConfig.DiskList))
|
||||
for _, disk := range adminConfig.DiskList {
|
||||
if disk != nil {
|
||||
disks = append(disks, map[string]any{"mount_point": disk.MountPoint})
|
||||
}
|
||||
}
|
||||
admin["disk_list"] = disks
|
||||
if adminConfig.Zap != nil {
|
||||
admin["zap"] = adminConfig.Zap
|
||||
}
|
||||
if adminConfig.Cors != nil {
|
||||
admin["cors"] = adminConfig.Cors
|
||||
}
|
||||
if adminConfig.App != nil {
|
||||
admin["app"] = adminConfig.App
|
||||
}
|
||||
}
|
||||
// Never mask secrets on the live runtime object. ConfigurationJSON is a
|
||||
// read-only operation; mutating dataConfig here would replace the actual
|
||||
// database/Redis/Mongo credentials with "******" and make the next reload
|
||||
// fail. Work on a clone, just as we already do for adminConfig.
|
||||
safeData := cloneDataConfig(dataConfig)
|
||||
maskDataSecrets(safeData)
|
||||
safeAdmin := cloneAdminConfig(adminConfig)
|
||||
if safeAdmin != nil {
|
||||
if safeAdmin.Jwt != nil {
|
||||
safeAdmin.Jwt.SigningKey = "******"
|
||||
}
|
||||
if safeAdmin.Email != nil && safeAdmin.Email.Secret != "" {
|
||||
safeAdmin.Email.Secret = "******"
|
||||
}
|
||||
maskStorageSecrets(safeAdmin.Storage)
|
||||
}
|
||||
dataMap := map[string]any{}
|
||||
if safeData != nil {
|
||||
raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(safeData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(raw, &dataMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
config := compatibleConfiguration(safeData, safeAdmin)
|
||||
config["admin"], config["email"], config["data"] = admin, email, dataMap
|
||||
return json.Marshal(map[string]any{"config": config})
|
||||
}
|
||||
|
||||
func (r *Repo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error {
|
||||
currentData, currentAdmin := r.backend.RuntimeValues()
|
||||
if currentAdmin == nil {
|
||||
return nil
|
||||
}
|
||||
nextData := cloneDataConfig(currentData)
|
||||
nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
||||
if err := applyCompatibleConfiguration(raw, nextData, nextAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
var value configurationEnvelope
|
||||
if err := json.Unmarshal(raw, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
options := protojson.UnmarshalOptions{DiscardUnknown: true}
|
||||
if len(value.Data) > 0 && string(value.Data) != "null" {
|
||||
if err := configutil.MergeProtoJSON(nextData, value.Data, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(value.Admin) > 0 && string(value.Admin) != "null" {
|
||||
if err := configutil.MergeProtoJSON(nextAdmin, value.Admin, options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
preserveDataSecrets(nextData, currentData)
|
||||
preserveAdminSecrets(nextAdmin, currentAdmin)
|
||||
if value.Email != nil {
|
||||
if nextAdmin.Email == nil {
|
||||
nextAdmin.Email = &conf.AdminBackend_Email{}
|
||||
}
|
||||
nextAdmin.Email.To, nextAdmin.Email.From, nextAdmin.Email.Host = value.Email.To, value.Email.From, value.Email.Host
|
||||
nextAdmin.Email.Nickname, nextAdmin.Email.Port = value.Email.Nickname, value.Email.Port
|
||||
nextAdmin.Email.IsSsl, nextAdmin.Email.IsLoginAuth = value.Email.IsSSL, value.Email.IsLoginAuth
|
||||
if value.Email.Secret != "" && value.Email.Secret != "******" {
|
||||
nextAdmin.Email.Secret = value.Email.Secret
|
||||
}
|
||||
}
|
||||
if err := r.backend.RefreshDatabaseSources(nextData); err != nil {
|
||||
return err
|
||||
}
|
||||
nextAdmin.ConfigPath = currentAdmin.ConfigPath
|
||||
dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(nextData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adminRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(nextAdmin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.PersistRuntimeConfig(ctx, dataRaw, adminRaw)
|
||||
}
|
||||
|
||||
func (r *Repo) DiskMountPoints() []string {
|
||||
config := r.backend.RuntimeAdmin()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
points := make([]string, 0, len(config.DiskList))
|
||||
for _, item := range config.DiskList {
|
||||
if item != nil && item.MountPoint != "" {
|
||||
points = append(points, item.MountPoint)
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func cloneDataConfig(value *conf.Data) *conf.Data {
|
||||
if value == nil {
|
||||
return &conf.Data{}
|
||||
}
|
||||
return proto.Clone(value).(*conf.Data)
|
||||
}
|
||||
|
||||
func cloneAdminConfig(value *conf.AdminBackend) *conf.AdminBackend {
|
||||
if value == nil {
|
||||
return &conf.AdminBackend{}
|
||||
}
|
||||
return proto.Clone(value).(*conf.AdminBackend)
|
||||
}
|
||||
|
||||
func durationString(value *durationpb.Duration) string {
|
||||
if value == nil {
|
||||
return "0s"
|
||||
}
|
||||
return value.AsDuration().String()
|
||||
}
|
||||
|
||||
func maskDataSecrets(value *conf.Data) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
if value.Database != nil {
|
||||
value.Database.Password = "******"
|
||||
// Source may contain an embedded password (for example a MySQL DSN).
|
||||
// The management response must never expose it. Structured database
|
||||
// fields are retained and refreshDatabaseSources rebuilds Source when
|
||||
// the configuration is saved.
|
||||
value.Database.Source = ""
|
||||
}
|
||||
if value.Redis != nil {
|
||||
value.Redis.Password = "******"
|
||||
}
|
||||
if value.Mongo != nil {
|
||||
value.Mongo.Password = "******"
|
||||
}
|
||||
for _, item := range value.DatabaseList {
|
||||
if item != nil {
|
||||
item.Password = "******"
|
||||
item.Source = ""
|
||||
}
|
||||
}
|
||||
for _, item := range value.RedisList {
|
||||
if item != nil {
|
||||
item.Password = "******"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func preserveDataSecrets(next, current *conf.Data) {
|
||||
if next == nil || current == nil {
|
||||
return
|
||||
}
|
||||
if next.Database != nil && current.Database != nil && maskedSecret(next.Database.Password) {
|
||||
next.Database.Password = current.Database.Password
|
||||
}
|
||||
if next.Database != nil && current.Database != nil && next.Database.Source == "" {
|
||||
next.Database.Source = current.Database.Source
|
||||
}
|
||||
if next.Redis != nil && current.Redis != nil && maskedSecret(next.Redis.Password) {
|
||||
next.Redis.Password = current.Redis.Password
|
||||
}
|
||||
if next.Mongo != nil && current.Mongo != nil && maskedSecret(next.Mongo.Password) {
|
||||
next.Mongo.Password = current.Mongo.Password
|
||||
}
|
||||
currentDatabases := make(map[string]*conf.Data_Database, len(current.DatabaseList))
|
||||
for _, item := range current.DatabaseList {
|
||||
if item != nil && item.AliasName != "" {
|
||||
currentDatabases[item.AliasName] = item
|
||||
}
|
||||
}
|
||||
for i, item := range next.DatabaseList {
|
||||
var previous *conf.Data_Database
|
||||
if item != nil && item.AliasName != "" {
|
||||
previous = currentDatabases[item.AliasName]
|
||||
}
|
||||
if previous == nil && i < len(current.DatabaseList) {
|
||||
previous = current.DatabaseList[i]
|
||||
}
|
||||
if item != nil && previous != nil {
|
||||
if maskedSecret(item.Password) {
|
||||
item.Password = previous.Password
|
||||
}
|
||||
if item.Source == "" {
|
||||
item.Source = previous.Source
|
||||
}
|
||||
}
|
||||
}
|
||||
currentRedis := make(map[string]*conf.Data_Redis, len(current.RedisList))
|
||||
for _, item := range current.RedisList {
|
||||
if item != nil && item.Name != "" {
|
||||
currentRedis[item.Name] = item
|
||||
}
|
||||
}
|
||||
for i, item := range next.RedisList {
|
||||
var previous *conf.Data_Redis
|
||||
if item != nil && item.Name != "" {
|
||||
previous = currentRedis[item.Name]
|
||||
}
|
||||
if previous == nil && i < len(current.RedisList) {
|
||||
previous = current.RedisList[i]
|
||||
}
|
||||
if item != nil && previous != nil && maskedSecret(item.Password) {
|
||||
item.Password = previous.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func preserveAdminSecrets(next, current *conf.AdminBackend) {
|
||||
if next.Jwt != nil && current.Jwt != nil && maskedSecret(next.Jwt.SigningKey) {
|
||||
next.Jwt.SigningKey = current.Jwt.SigningKey
|
||||
}
|
||||
if next.Email != nil && current.Email != nil && maskedSecret(next.Email.Secret) {
|
||||
next.Email.Secret = current.Email.Secret
|
||||
}
|
||||
preserveStorageSecrets(next.Storage, current.Storage)
|
||||
}
|
||||
|
||||
func maskedSecret(value string) bool { return value == "" || value == "******" }
|
||||
|
||||
func objectStores(storage *conf.AdminBackend_Storage) []*conf.AdminBackend_ObjectStore {
|
||||
if storage == nil {
|
||||
return nil
|
||||
}
|
||||
return []*conf.AdminBackend_ObjectStore{storage.AliyunOss, storage.HuaweiObs, storage.TencentCos, storage.AwsS3, storage.CloudflareR2, storage.Minio}
|
||||
}
|
||||
|
||||
func maskStorageSecrets(storage *conf.AdminBackend_Storage) {
|
||||
if storage == nil {
|
||||
return
|
||||
}
|
||||
if storage.Qiniu != nil && storage.Qiniu.SecretKey != "" {
|
||||
storage.Qiniu.SecretKey = "******"
|
||||
}
|
||||
for _, item := range objectStores(storage) {
|
||||
if item != nil && item.SecretKey != "" {
|
||||
item.SecretKey = "******"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func preserveStorageSecrets(next, current *conf.AdminBackend_Storage) {
|
||||
if next == nil || current == nil {
|
||||
return
|
||||
}
|
||||
if next.Qiniu != nil && current.Qiniu != nil && maskedSecret(next.Qiniu.SecretKey) {
|
||||
next.Qiniu.SecretKey = current.Qiniu.SecretKey
|
||||
}
|
||||
nextItems, currentItems := objectStores(next), objectStores(current)
|
||||
for i := range nextItems {
|
||||
if nextItems[i] != nil && currentItems[i] != nil && maskedSecret(nextItems[i].SecretKey) {
|
||||
nextItems[i].SecretKey = currentItems[i].SecretKey
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/biz"
|
||||
datapayment "kra/internal/data/payment"
|
||||
datasystem "kra/internal/data/system"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repo struct{ backend Backend }
|
||||
|
||||
func NewRepo(backend Backend) biz.InitializationRepo {
|
||||
return &Repo{backend: backend}
|
||||
}
|
||||
|
||||
func (r *Repo) IsInitialized(ctx context.Context) (bool, error) {
|
||||
return r.backend.IsInitialized(ctx)
|
||||
}
|
||||
|
||||
func (r *Repo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error {
|
||||
return r.backend.InitializeDatabase(ctx, input, func(ctx context.Context, db *gorm.DB) error {
|
||||
return datasystem.SeedSystem(ctx, db, input, datapayment.AdminSurface())
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repo) PersistConfig(ctx context.Context) error {
|
||||
return r.backend.PersistConfig(ctx)
|
||||
}
|
||||
|
||||
func (r *Repo) PersistAdminConfig(ctx context.Context, value []byte) error {
|
||||
return r.backend.PersistAdminConfig(ctx, value)
|
||||
}
|
||||
|
||||
func (r *Repo) PersistRuntimeConfig(ctx context.Context, data, admin []byte) error {
|
||||
return r.backend.PersistRuntimeConfig(ctx, data, admin)
|
||||
}
|
||||
|
||||
func (r *Repo) ReloadConfig(ctx context.Context) error {
|
||||
return r.backend.ReloadConfig(ctx)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package initialize
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
var ProviderSet = wire.NewSet(NewRepo)
|
||||
Reference in New Issue
Block a user