Files
oneterm/backend/internal/service/config.go
2025-07-16 18:11:04 +08:00

217 lines
6.6 KiB
Go

package service
import (
"context"
"time"
"github.com/veops/oneterm/internal/model"
"github.com/veops/oneterm/internal/repository"
"github.com/veops/oneterm/pkg/cache"
dbpkg "github.com/veops/oneterm/pkg/db"
"github.com/veops/oneterm/pkg/logger"
"go.uber.org/zap"
"gorm.io/gorm"
)
// ConfigService handles configuration business logic
type ConfigService struct {
repo repository.ConfigRepository
}
// NewConfigService creates a new config service
func NewConfigService() *ConfigService {
return &ConfigService{
repo: repository.NewConfigRepository(),
}
}
// SaveConfig saves a configuration
func (s *ConfigService) SaveConfig(ctx context.Context, cfg *model.Config) error {
cfg.Id = 0 // Ensure we're creating a new config
if err := dbpkg.DB.Model(cfg).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("deleted_at = 0").Delete(&model.Config{}).Error; err != nil {
return err
}
return tx.Create(cfg).Error
}); err != nil {
return err
}
// Update global config and cache
model.GlobalConfig.Store(cfg)
cache.SetEx(ctx, "config", cfg, time.Hour)
return nil
}
// GetConfig retrieves the current configuration
func (s *ConfigService) GetConfig(ctx context.Context) (*model.Config, error) {
return s.repo.GetConfig(ctx)
}
// MigrateConfigStructure migrates the config table structure from protocol-specific to unified permissions
func (s *ConfigService) MigrateConfigStructure(ctx context.Context) error {
logger.L().Info("Starting config table structure migration")
// Check if migration is needed by looking for old columns
var columnExists bool
if err := dbpkg.DB.Raw("SELECT COUNT(*) > 0 FROM information_schema.columns WHERE table_name = 'config' AND column_name = 'ssh_copy'").Scan(&columnExists).Error; err != nil {
logger.L().Error("Failed to check for old columns", zap.Error(err))
return err
}
if !columnExists {
logger.L().Info("Config table already migrated, skipping")
return nil
}
logger.L().Info("Config table migration needed, starting migration")
// Migrate existing config data
if err := dbpkg.DB.Transaction(func(tx *gorm.DB) error {
// Read existing config with old structure
var oldConfig struct {
ID int `gorm:"column:id"`
Timeout int `gorm:"column:timeout"`
SSHCopy bool `gorm:"column:ssh_copy"`
SSHPaste bool `gorm:"column:ssh_paste"`
RDPCopy bool `gorm:"column:rdp_copy"`
RDPPaste bool `gorm:"column:rdp_paste"`
VNCCopy bool `gorm:"column:vnc_copy"`
VNCPaste bool `gorm:"column:vnc_paste"`
CreatorId int `gorm:"column:creator_id"`
UpdaterId int `gorm:"column:updater_id"`
}
if err := tx.Table("config").Where("deleted_at = 0").First(&oldConfig).Error; err != nil {
if err == gorm.ErrRecordNotFound {
// No existing config, create default
logger.L().Info("No existing config found, will create default after migration")
return nil
}
return err
}
// Convert old config to new structure
// Use logical OR for permissions - if any protocol allowed it, then allow it
newConfig := &model.Config{
Id: oldConfig.ID,
Timeout: oldConfig.Timeout,
DefaultPermissions: model.DefaultPermissions{
Connect: true, // Always allow connect
FileUpload: true, // Default to allow file operations
FileDownload: true,
Copy: oldConfig.SSHCopy || oldConfig.RDPCopy || oldConfig.VNCCopy,
Paste: oldConfig.SSHPaste || oldConfig.RDPPaste || oldConfig.VNCPaste,
Share: false, // Default to deny share for security
},
CreatorId: oldConfig.CreatorId,
UpdaterId: oldConfig.UpdaterId,
}
// Store converted config data temporarily
if err := tx.Exec("CREATE TEMPORARY TABLE temp_config AS SELECT * FROM config WHERE deleted_at = 0").Error; err != nil {
return err
}
logger.L().Info("Migrated config permissions",
zap.Bool("copy", newConfig.DefaultPermissions.Copy),
zap.Bool("paste", newConfig.DefaultPermissions.Paste))
return nil
}); err != nil {
logger.L().Error("Failed to migrate config data", zap.Error(err))
return err
}
// Drop old columns and add new ones
if err := dbpkg.DB.Transaction(func(tx *gorm.DB) error {
// Add new columns
alterSQL := `
ALTER TABLE config
ADD COLUMN default_connect BOOLEAN DEFAULT TRUE,
ADD COLUMN default_file_upload BOOLEAN DEFAULT TRUE,
ADD COLUMN default_file_download BOOLEAN DEFAULT TRUE,
ADD COLUMN default_copy BOOLEAN DEFAULT TRUE,
ADD COLUMN default_paste BOOLEAN DEFAULT TRUE,
ADD COLUMN default_share BOOLEAN DEFAULT FALSE
`
if err := tx.Exec(alterSQL).Error; err != nil {
logger.L().Error("Failed to add new columns", zap.Error(err))
return err
}
// Update data from temporary table if it exists
updateSQL := `
UPDATE config SET
default_connect = TRUE,
default_file_upload = TRUE,
default_file_download = TRUE,
default_copy = COALESCE((SELECT (ssh_copy OR rdp_copy OR vnc_copy) FROM temp_config WHERE temp_config.id = config.id), TRUE),
default_paste = COALESCE((SELECT (ssh_paste OR rdp_paste OR vnc_paste) FROM temp_config WHERE temp_config.id = config.id), TRUE),
default_share = FALSE
WHERE deleted_at = 0
`
if err := tx.Exec(updateSQL).Error; err != nil {
logger.L().Error("Failed to update config data", zap.Error(err))
return err
}
// Drop old columns
dropSQL := `
ALTER TABLE config
DROP COLUMN ssh_copy,
DROP COLUMN ssh_paste,
DROP COLUMN rdp_copy,
DROP COLUMN rdp_paste,
DROP COLUMN rdp_enable_drive,
DROP COLUMN rdp_drive_path,
DROP COLUMN rdp_create_drive_path,
DROP COLUMN rdp_disable_upload,
DROP COLUMN rdp_disable_download,
DROP COLUMN vnc_copy,
DROP COLUMN vnc_paste
`
if err := tx.Exec(dropSQL).Error; err != nil {
logger.L().Error("Failed to drop old columns", zap.Error(err))
return err
}
// Drop temporary table
if err := tx.Exec("DROP TEMPORARY TABLE IF EXISTS temp_config").Error; err != nil {
logger.L().Warn("Failed to drop temporary table", zap.Error(err))
}
return nil
}); err != nil {
return err
}
logger.L().Info("Config table structure migration completed successfully")
return nil
}
// EnsureDefaultConfig ensures there's a default config in the database
func (s *ConfigService) EnsureDefaultConfig(ctx context.Context) error {
cfg, err := s.GetConfig(ctx)
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
if cfg == nil {
// Create default config
defaultCfg := model.GetDefaultConfig()
defaultCfg.CreatorId = 1 // System user
defaultCfg.UpdaterId = 1
if err := s.SaveConfig(ctx, defaultCfg); err != nil {
return err
}
logger.L().Info("Created default config")
}
return nil
}