294 lines
8.0 KiB
Go
294 lines
8.0 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"kra/internal/config"
|
|
dataintegration "kra/internal/data/integration"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
integrationKindStorage = "storage"
|
|
integrationKindEmail = "email"
|
|
)
|
|
|
|
var storageProviderNames = []string{
|
|
"local",
|
|
"qiniu",
|
|
"aliyun-oss",
|
|
"huawei-obs",
|
|
"tencent-cos",
|
|
"aws-s3",
|
|
"cloudflare-r2",
|
|
"minio",
|
|
}
|
|
|
|
func normalizeStorageType(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if value == "" {
|
|
return "local"
|
|
}
|
|
return value
|
|
}
|
|
|
|
func storageProviderValue(storage *config.Storage, provider string) any {
|
|
if storage == nil {
|
|
storage = &config.Storage{}
|
|
}
|
|
switch provider {
|
|
case "qiniu":
|
|
if storage.Qiniu == nil {
|
|
storage.Qiniu = &config.Qiniu{}
|
|
}
|
|
return storage.Qiniu
|
|
case "aliyun-oss":
|
|
return ensureObjectStore(&storage.AliyunOSS)
|
|
case "huawei-obs":
|
|
return ensureObjectStore(&storage.HuaweiOBS)
|
|
case "tencent-cos":
|
|
return ensureObjectStore(&storage.TencentCOS)
|
|
case "aws-s3":
|
|
return ensureObjectStore(&storage.AWSS3)
|
|
case "cloudflare-r2":
|
|
return ensureObjectStore(&storage.CloudflareR2)
|
|
case "minio":
|
|
return ensureObjectStore(&storage.Minio)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func ensureObjectStore(value **config.ObjectStore) *config.ObjectStore {
|
|
if *value == nil {
|
|
*value = &config.ObjectStore{}
|
|
}
|
|
return *value
|
|
}
|
|
|
|
func marshalStorageProvider(storage *config.Storage, provider string) (string, error) {
|
|
value := storageProviderValue(storage, provider)
|
|
if value == nil {
|
|
return "{}", nil
|
|
}
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(raw), nil
|
|
}
|
|
|
|
func unmarshalStorageProvider(storage *config.Storage, provider, value string) error {
|
|
if provider == "local" || strings.TrimSpace(value) == "" {
|
|
return 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 := 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 *config.Storage) error {
|
|
if storage == nil {
|
|
storage = &config.Storage{}
|
|
}
|
|
active := normalizeStorageType(storage.Type)
|
|
known := false
|
|
for _, provider := range storageProviderNames {
|
|
if provider == active {
|
|
known = true
|
|
break
|
|
}
|
|
}
|
|
if !known {
|
|
return fmt.Errorf("unsupported storage type %q", active)
|
|
}
|
|
|
|
return db.Session(&gorm.Session{NewDB: true}).Transaction(func(tx *gorm.DB) error {
|
|
for _, provider := range storageProviderNames {
|
|
value, err := marshalStorageProvider(storage, provider)
|
|
if err != nil {
|
|
return fmt.Errorf("encode %s integration configuration: %w", provider, err)
|
|
}
|
|
var current dataintegration.ConfigPO
|
|
err = tx.Where("kind = ? AND provider = ?", integrationKindStorage, provider).First(¤t).Error
|
|
switch {
|
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
|
current = dataintegration.ConfigPO{Kind: integrationKindStorage, Provider: provider}
|
|
current.Enabled, current.Config = provider == active, value
|
|
if err = tx.Create(¤t).Error; err != nil {
|
|
return err
|
|
}
|
|
case err != nil:
|
|
return err
|
|
default:
|
|
if err = tx.Model(¤t).Updates(map[string]any{"enabled": provider == active, "config": value}).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func loadStorageIntegrationConfig(db *gorm.DB) (*config.Storage, bool, error) {
|
|
var rows []dataintegration.ConfigPO
|
|
err := db.Session(&gorm.Session{NewDB: true}).
|
|
Where("kind = ?", integrationKindStorage).
|
|
Order("id ASC").
|
|
Find(&rows).Error
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
|
|
storage := &config.Storage{Type: "local"}
|
|
for _, row := range rows {
|
|
if err = unmarshalStorageProvider(storage, row.Provider, row.Config); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if row.Enabled {
|
|
storage.Type = row.Provider
|
|
}
|
|
}
|
|
return storage, true, nil
|
|
}
|
|
|
|
// 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 *config.Storage) (*config.Storage, error) {
|
|
clean := db.Session(&gorm.Session{NewDB: true})
|
|
if !clean.Migrator().HasTable(&dataintegration.ConfigPO{}) {
|
|
if legacy == nil {
|
|
return &config.Storage{Type: "local"}, nil
|
|
}
|
|
return config.CloneStorage(legacy), nil
|
|
}
|
|
storage, found, err := loadStorageIntegrationConfig(clean)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if found {
|
|
return storage, nil
|
|
}
|
|
if legacy == nil {
|
|
legacy = &config.Storage{Type: "local"}
|
|
}
|
|
if err = saveStorageIntegrationConfig(clean, legacy); err != nil {
|
|
return nil, err
|
|
}
|
|
storage, _, err = loadStorageIntegrationConfig(clean)
|
|
return storage, err
|
|
}
|
|
|
|
func (d *Data) persistStorageIntegrationConfig(ctx context.Context, storage *config.Storage) error {
|
|
if !d.databaseReady.Load() {
|
|
return errors.New("database is not initialized")
|
|
}
|
|
db := d.gormDB.WithContext(ctx)
|
|
if !db.Migrator().HasTable(&dataintegration.ConfigPO{}) {
|
|
return errors.New("integration configuration table does not exist")
|
|
}
|
|
return saveStorageIntegrationConfig(db, storage)
|
|
}
|
|
|
|
func defaultEmailIntegrationConfig() *config.Email {
|
|
return &config.Email{Port: 465, IsSSL: true}
|
|
}
|
|
|
|
func saveEmailIntegrationConfig(db *gorm.DB, email *config.Email) error {
|
|
if email == nil {
|
|
email = defaultEmailIntegrationConfig()
|
|
}
|
|
raw, err := json.Marshal(email)
|
|
if err != nil {
|
|
return fmt.Errorf("encode smtp integration configuration: %w", err)
|
|
}
|
|
enabled := email.Host != "" && email.From != "" && email.Secret != "" && email.Port > 0
|
|
clean := db.Session(&gorm.Session{NewDB: true})
|
|
var current dataintegration.ConfigPO
|
|
err = clean.Where("kind = ? AND provider = ?", integrationKindEmail, "smtp").First(¤t).Error
|
|
switch {
|
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
|
return clean.Create(&dataintegration.ConfigPO{
|
|
Kind: integrationKindEmail, Provider: "smtp", Enabled: enabled, Config: string(raw),
|
|
}).Error
|
|
case err != nil:
|
|
return err
|
|
default:
|
|
return clean.Model(¤t).Updates(map[string]any{"enabled": enabled, "config": string(raw)}).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").
|
|
First(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, false, nil
|
|
}
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !json.Valid([]byte(row.Config)) {
|
|
return nil, false, errors.New("invalid smtp integration configuration")
|
|
}
|
|
email := defaultEmailIntegrationConfig()
|
|
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 *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 config.CloneEmail(legacy), nil
|
|
}
|
|
email, found, err := loadEmailIntegrationConfig(clean)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if found {
|
|
return email, nil
|
|
}
|
|
if legacy == nil {
|
|
legacy = defaultEmailIntegrationConfig()
|
|
}
|
|
if err = saveEmailIntegrationConfig(clean, legacy); err != nil {
|
|
return nil, err
|
|
}
|
|
email, _, err = loadEmailIntegrationConfig(clean)
|
|
return email, err
|
|
}
|
|
|
|
func (d *Data) persistEmailIntegrationConfig(ctx context.Context, email *config.Email) error {
|
|
if !d.databaseReady.Load() {
|
|
return errors.New("database is not initialized")
|
|
}
|
|
db := d.gormDB.WithContext(ctx)
|
|
if !db.Migrator().HasTable(&dataintegration.ConfigPO{}) {
|
|
return errors.New("integration configuration table does not exist")
|
|
}
|
|
return saveEmailIntegrationConfig(db, email)
|
|
}
|