package integration import ( "encoding/json" "errors" "fmt" "strings" "kra/internal/config" "gorm.io/gorm" ) const ( kindStorage = "storage" kindEmail = "email" providerSMTP = "smtp" ) var storageProviders = []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 SaveStorageConfig(db *gorm.DB, storage *config.Storage) error { if storage == nil { storage = &config.Storage{} } active := normalizeStorageType(storage.Type) known := false for _, provider := range storageProviders { if provider == active { known = true break } } if !known { return fmt.Errorf("unsupported storage type %q", active) } rows := make([]ConfigRow, 0, len(storageProviders)) for _, provider := range storageProviders { value, err := marshalStorageProvider(storage, provider) if err != nil { return fmt.Errorf("encode %s integration configuration: %w", provider, err) } rows = append(rows, ConfigRow{Provider: provider, Enabled: provider == active, Config: value}) } return UpsertConfigs(db, kindStorage, rows) } func LoadStorageConfig(db *gorm.DB) (*config.Storage, bool, error) { rows, err := ListConfigs(db, kindStorage) 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 } // ResolveStorageConfig imports a legacy YAML value only when the database has // no storage configuration yet. The database is authoritative afterwards. func ResolveStorageConfig(db *gorm.DB, legacy *config.Storage) (*config.Storage, error) { if !HasConfigTable(db) { if legacy == nil { return &config.Storage{Type: "local"}, nil } return config.CloneStorage(legacy), nil } storage, found, err := LoadStorageConfig(db) if err != nil { return nil, err } if found { return storage, nil } if legacy == nil { legacy = &config.Storage{Type: "local"} } if err = SaveStorageConfig(db, legacy); err != nil { return nil, err } storage, _, err = LoadStorageConfig(db) return storage, err } func defaultEmailConfig() *config.Email { return &config.Email{Port: 465, IsSSL: true} } func SaveEmailConfig(db *gorm.DB, email *config.Email) error { if email == nil { email = defaultEmailConfig() } 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 return UpsertConfigs(db, kindEmail, []ConfigRow{{Provider: providerSMTP, Enabled: enabled, Config: string(raw)}}) } func LoadEmailConfig(db *gorm.DB) (*config.Email, bool, error) { row, found, err := FindConfig(db, kindEmail, providerSMTP) if err != nil || !found { return nil, false, err } if !json.Valid([]byte(row.Config)) { return nil, false, errors.New("invalid smtp integration configuration") } email := defaultEmailConfig() 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 ResolveEmailConfig(db *gorm.DB, legacy *config.Email) (*config.Email, error) { if !HasConfigTable(db) { if legacy == nil { return defaultEmailConfig(), nil } return config.CloneEmail(legacy), nil } email, found, err := LoadEmailConfig(db) if err != nil { return nil, err } if found { return email, nil } if legacy == nil { legacy = defaultEmailConfig() } if err = SaveEmailConfig(db, legacy); err != nil { return nil, err } email, _, err = LoadEmailConfig(db) return email, err }