Files
GoMFT/internal/db/storage_provider_validator.go
StarFleetCPTN b819162732 feat: Update storage provider form and validation enhancements
- Added new HTML elements and JavaScript functionality to improve the user experience for WebDAV and Hetzner storage providers.
- Updated form field descriptions and placeholders for clarity, ensuring users understand the required input.
- Enhanced validation logic for WebDAV to ensure the host includes the protocol.
- Improved logging for debugging during form submissions, particularly for WebDAV credentials.
- Removed the deprecated provider form component to streamline the codebase.
2025-04-18 03:54:48 -07:00

263 lines
8.3 KiB
Go

package db
import (
"errors"
"fmt"
"log"
"strings"
"gorm.io/gorm"
)
// ValidateStorageProvider validates a storage provider based on its type
func (sp *StorageProvider) Validate() error {
// Special case - if we have an empty struct or just an ID (could happen during GORM operations like foreign key checks)
if (sp.ID > 0 && sp.Name == "" && sp.Type == "") || (sp.ID == 0 && sp.Name == "" && sp.Type == "") {
log.Printf("Skipping validation for StorageProvider: ID=%d without other data (likely a reference check)", sp.ID)
return nil
}
// Common validations
if strings.TrimSpace(sp.Name) == "" {
return errors.New("provider name cannot be empty")
}
// Type-specific validations
switch sp.Type {
case ProviderTypeSFTP, ProviderTypeHetzner:
return sp.validateSFTP()
case ProviderTypeS3:
return sp.validateS3()
case ProviderTypeFTP:
return sp.validateFTP()
case ProviderTypeWebDAV, ProviderTypeNextcloud:
return sp.validateWebDAV()
case ProviderTypeSMB:
return sp.validateSMB()
case ProviderTypeOneDrive:
return sp.validateOneDrive()
case ProviderTypeGoogleDrive:
return sp.validateGoogleDrive()
case ProviderTypeGooglePhoto:
return sp.validateGooglePhoto()
case ProviderTypeLocal:
return sp.validateLocal()
default:
return fmt.Errorf("unsupported provider type: %s", sp.Type)
}
}
// validateSFTP validates SFTP-specific fields
func (sp *StorageProvider) validateSFTP() error {
if strings.TrimSpace(sp.Host) == "" {
return errors.New("host is required for SFTP provider")
}
if sp.Port <= 0 {
return errors.New("invalid port for SFTP provider")
}
if strings.TrimSpace(sp.Username) == "" {
return errors.New("username is required for SFTP provider")
}
// Either password or key file must be provided
if strings.TrimSpace(sp.Password) == "" && strings.TrimSpace(sp.EncryptedPassword) == "" && strings.TrimSpace(sp.KeyFile) == "" {
return errors.New("either password or key file is required for SFTP provider")
}
return nil
}
// validateWebDAV validates WebDAV-specific fields
func (sp *StorageProvider) validateWebDAV() error {
if strings.TrimSpace(sp.Host) == "" {
return errors.New("host is required for WebDAV provider")
}
// Host must include the protocol
if !strings.HasPrefix(sp.Host, "http://") && !strings.HasPrefix(sp.Host, "https://") {
return errors.New("host must include the protocol (http:// or https://)")
}
if strings.TrimSpace(sp.Username) == "" {
return errors.New("username is required for WebDAV provider")
}
// Either password or encrypted password must be provided
if strings.TrimSpace(sp.Password) == "" && strings.TrimSpace(sp.EncryptedPassword) == "" {
return errors.New("password is required for WebDAV provider")
}
return nil
}
// validateS3 validates S3-specific fields
func (sp *StorageProvider) validateS3() error {
// For S3, either AccessKey or Username is used
if strings.TrimSpace(sp.AccessKey) == "" && strings.TrimSpace(sp.Username) == "" {
return errors.New("access key is required for S3 provider")
}
// Either SecretKey or EncryptedSecretKey must be provided
if strings.TrimSpace(sp.SecretKey) == "" && strings.TrimSpace(sp.EncryptedSecretKey) == "" {
return errors.New("secret key is required for S3 provider")
}
// Region is required for most S3 providers
if strings.TrimSpace(sp.Region) == "" {
return errors.New("region is required for S3 provider")
}
return nil
}
// validateFTP validates FTP-specific fields
func (sp *StorageProvider) validateFTP() error {
if strings.TrimSpace(sp.Host) == "" {
return errors.New("host is required for FTP provider")
}
if sp.Port <= 0 {
return errors.New("invalid port for FTP provider")
}
if strings.TrimSpace(sp.Username) == "" {
return errors.New("username is required for FTP provider")
}
// Either password or encrypted password must be provided
if strings.TrimSpace(sp.Password) == "" && strings.TrimSpace(sp.EncryptedPassword) == "" {
return errors.New("password is required for FTP provider")
}
return nil
}
// validateSMB validates SMB-specific fields
func (sp *StorageProvider) validateSMB() error {
if strings.TrimSpace(sp.Host) == "" {
return errors.New("host is required for SMB provider")
}
if strings.TrimSpace(sp.Username) == "" {
return errors.New("username is required for SMB provider")
}
// Either password or encrypted password must be provided
if strings.TrimSpace(sp.Password) == "" && strings.TrimSpace(sp.EncryptedPassword) == "" {
return errors.New("password is required for SMB provider")
}
return nil
}
// validateOneDrive validates OneDrive-specific fields
func (sp *StorageProvider) validateOneDrive() error {
if strings.TrimSpace(sp.ClientID) == "" {
return errors.New("client ID is required for OneDrive provider")
}
// Either ClientSecret or EncryptedClientSecret must be provided
if strings.TrimSpace(sp.ClientSecret) == "" && strings.TrimSpace(sp.EncryptedClientSecret) == "" {
return errors.New("client secret is required for OneDrive provider")
}
// For authenticated providers, RefreshToken must be set
if sp.GetAuthenticated() && strings.TrimSpace(sp.EncryptedRefreshToken) == "" && strings.TrimSpace(sp.RefreshToken) == "" {
return errors.New("refresh token is required for authenticated OneDrive provider")
}
return nil
}
// validateGoogleDrive validates Google Drive-specific fields
func (sp *StorageProvider) validateGoogleDrive() error {
// If not using builtin auth, ClientID and ClientSecret are required
if !sp.GetUseBuiltinAuth() {
if strings.TrimSpace(sp.ClientID) == "" {
return errors.New("client ID is required for Google Drive provider when not using builtin auth")
}
// Either ClientSecret or EncryptedClientSecret must be provided
if strings.TrimSpace(sp.ClientSecret) == "" && strings.TrimSpace(sp.EncryptedClientSecret) == "" {
return errors.New("client secret is required for Google Drive provider when not using builtin auth")
}
}
// For authenticated providers, RefreshToken must be set
if sp.GetAuthenticated() && strings.TrimSpace(sp.EncryptedRefreshToken) == "" && strings.TrimSpace(sp.RefreshToken) == "" {
return errors.New("refresh token is required for authenticated Google Drive provider")
}
return nil
}
// validateGooglePhoto validates Google Photos-specific fields
func (sp *StorageProvider) validateGooglePhoto() error {
// Similar to Google Drive
return sp.validateGoogleDrive()
}
// validateLocal validates Local-specific fields
func (sp *StorageProvider) validateLocal() error {
// Local providers don't need additional validation
return nil
}
// BeforeSave is a GORM hook that runs before saving the provider
func (sp *StorageProvider) BeforeSave(tx *gorm.DB) error {
// Check if this is a reference check by examining the GORM operation
if tx.Statement.SQL.String() == "" {
// No explicit SQL means this might be part of a preload or association check
// Case 1: Empty struct (as you already have)
if sp.ID == 0 && sp.Name == "" && sp.Type == "" {
log.Printf("BeforeSave: Skipping validation for empty StorageProvider")
return nil
}
// Case 2: ID-only struct (foreign key reference check)
if sp.ID > 0 && sp.Name == "" && sp.Type == "" {
log.Printf("BeforeSave: Skipping validation for StorageProvider ID=%d (reference check)", sp.ID)
return nil
}
// Case 3: Minimal data loaded from database for relationship check
// Check if only a few fields are populated (typically ID and maybe a couple others)
populatedFields := 0
if sp.ID > 0 {
populatedFields++
}
if sp.Name != "" {
populatedFields++
}
if string(sp.Type) != "" {
populatedFields++
}
if sp.Host != "" {
populatedFields++
}
if sp.Username != "" {
populatedFields++
}
// If we have just a few populated fields, it's likely a reference check
if populatedFields <= 3 {
log.Printf("BeforeSave: Skipping validation for partially loaded StorageProvider ID=%d (likely reference check)", sp.ID)
return nil
}
}
// Check if this is called from a foreign key operation on another model
stmt := tx.Statement
if stmt.Schema != nil && stmt.Schema.Table != "storage_providers" {
log.Printf("BeforeSave: Skipping validation for StorageProvider ID=%d (called from %s table operation)",
sp.ID, stmt.Schema.Table)
return nil
}
log.Printf("BeforeSave: Validating StorageProvider: ID=%d, Name=%s, Type=%s", sp.ID, sp.Name, sp.Type)
return sp.Validate()
}