package service import ( "fmt" "log" "mime/multipart" "os" "reflect" "github.com/stonith404/pocket-id/backend/internal/common" "github.com/stonith404/pocket-id/backend/internal/dto" "github.com/stonith404/pocket-id/backend/internal/model" "github.com/stonith404/pocket-id/backend/internal/utils" "gorm.io/gorm" ) type AppConfigService struct { DbConfig *model.AppConfig db *gorm.DB } func NewAppConfigService(db *gorm.DB) *AppConfigService { service := &AppConfigService{ DbConfig: &defaultDbConfig, db: db, } if err := service.InitDbConfig(); err != nil { log.Fatalf("Failed to initialize app config service: %v", err) } return service } var defaultDbConfig = model.AppConfig{ // General AppName: model.AppConfigVariable{ Key: "appName", Type: "string", IsPublic: true, DefaultValue: "Pocket ID", }, SessionDuration: model.AppConfigVariable{ Key: "sessionDuration", Type: "number", DefaultValue: "60", }, EmailsVerified: model.AppConfigVariable{ Key: "emailsVerified", Type: "bool", DefaultValue: "false", }, AllowOwnAccountEdit: model.AppConfigVariable{ Key: "allowOwnAccountEdit", Type: "bool", IsPublic: true, DefaultValue: "true", }, // Internal BackgroundImageType: model.AppConfigVariable{ Key: "backgroundImageType", Type: "string", IsInternal: true, DefaultValue: "jpg", }, LogoLightImageType: model.AppConfigVariable{ Key: "logoLightImageType", Type: "string", IsInternal: true, DefaultValue: "svg", }, LogoDarkImageType: model.AppConfigVariable{ Key: "logoDarkImageType", Type: "string", IsInternal: true, DefaultValue: "svg", }, // Email SmtpHost: model.AppConfigVariable{ Key: "smtpHost", Type: "string", }, SmtpPort: model.AppConfigVariable{ Key: "smtpPort", Type: "number", }, SmtpFrom: model.AppConfigVariable{ Key: "smtpFrom", Type: "string", }, SmtpUser: model.AppConfigVariable{ Key: "smtpUser", Type: "string", }, SmtpPassword: model.AppConfigVariable{ Key: "smtpPassword", Type: "string", }, SmtpTls: model.AppConfigVariable{ Key: "smtpTls", Type: "bool", DefaultValue: "true", }, SmtpSkipCertVerify: model.AppConfigVariable{ Key: "smtpSkipCertVerify", Type: "bool", DefaultValue: "false", }, EmailLoginNotificationEnabled: model.AppConfigVariable{ Key: "emailLoginNotificationEnabled", Type: "bool", DefaultValue: "false", }, EmailOneTimeAccessEnabled: model.AppConfigVariable{ Key: "emailOneTimeAccessEnabled", Type: "bool", IsPublic: true, DefaultValue: "false", }, // LDAP LdapEnabled: model.AppConfigVariable{ Key: "ldapEnabled", Type: "bool", DefaultValue: "false", }, LdapUrl: model.AppConfigVariable{ Key: "ldapUrl", Type: "string", }, LdapBindDn: model.AppConfigVariable{ Key: "ldapBindDn", Type: "string", }, LdapBindPassword: model.AppConfigVariable{ Key: "ldapBindPassword", Type: "string", }, LdapBase: model.AppConfigVariable{ Key: "ldapBase", Type: "string", }, LdapSkipCertVerify: model.AppConfigVariable{ Key: "ldapSkipCertVerify", Type: "bool", DefaultValue: "false", }, LdapAttributeUserUniqueIdentifier: model.AppConfigVariable{ Key: "ldapAttributeUserUniqueIdentifier", Type: "string", }, LdapAttributeUserUsername: model.AppConfigVariable{ Key: "ldapAttributeUserUsername", Type: "string", }, LdapAttributeUserEmail: model.AppConfigVariable{ Key: "ldapAttributeUserEmail", Type: "string", }, LdapAttributeUserFirstName: model.AppConfigVariable{ Key: "ldapAttributeUserFirstName", Type: "string", }, LdapAttributeUserLastName: model.AppConfigVariable{ Key: "ldapAttributeUserLastName", Type: "string", }, LdapAttributeGroupUniqueIdentifier: model.AppConfigVariable{ Key: "ldapAttributeGroupUniqueIdentifier", Type: "string", }, LdapAttributeGroupName: model.AppConfigVariable{ Key: "ldapAttributeGroupName", Type: "string", }, LdapAttributeAdminGroup: model.AppConfigVariable{ Key: "ldapAttributeAdminGroup", Type: "string", }, } func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) { var savedConfigVariables []model.AppConfigVariable tx := s.db.Begin() rt := reflect.ValueOf(input).Type() rv := reflect.ValueOf(input) for i := 0; i < rt.NumField(); i++ { field := rt.Field(i) key := field.Tag.Get("json") value := rv.FieldByName(field.Name).String() // If the emailEnabled is set to false, disable the emailOneTimeAccessEnabled if key == s.DbConfig.EmailOneTimeAccessEnabled.Key { if rv.FieldByName("EmailEnabled").String() == "false" { value = "false" } } var appConfigVariable model.AppConfigVariable if err := tx.First(&appConfigVariable, "key = ? AND is_internal = false", key).Error; err != nil { tx.Rollback() return nil, err } appConfigVariable.Value = value if err := tx.Save(&appConfigVariable).Error; err != nil { tx.Rollback() return nil, err } savedConfigVariables = append(savedConfigVariables, appConfigVariable) } tx.Commit() if err := s.LoadDbConfigFromDb(); err != nil { return nil, err } return savedConfigVariables, nil } func (s *AppConfigService) UpdateImageType(imageName string, fileType string) error { key := fmt.Sprintf("%sImageType", imageName) err := s.db.Model(&model.AppConfigVariable{}).Where("key = ?", key).Update("value", fileType).Error if err != nil { return err } return s.LoadDbConfigFromDb() } func (s *AppConfigService) ListAppConfig(showAll bool) ([]model.AppConfigVariable, error) { var configuration []model.AppConfigVariable var err error if showAll { err = s.db.Find(&configuration).Error } else { err = s.db.Find(&configuration, "is_public = true").Error } if err != nil { return nil, err } // Set the value to the default value if it is empty for i := range configuration { if configuration[i].Value == "" && configuration[i].DefaultValue != "" { configuration[i].Value = configuration[i].DefaultValue } } return configuration, nil } func (s *AppConfigService) UpdateImage(uploadedFile *multipart.FileHeader, imageName string, oldImageType string) error { fileType := utils.GetFileExtension(uploadedFile.Filename) mimeType := utils.GetImageMimeType(fileType) if mimeType == "" { return &common.FileTypeNotSupportedError{} } // Delete the old image if it has a different file type if fileType != oldImageType { oldImagePath := fmt.Sprintf("%s/application-images/%s.%s", common.EnvConfig.UploadPath, imageName, oldImageType) if err := os.Remove(oldImagePath); err != nil { return err } } imagePath := fmt.Sprintf("%s/application-images/%s.%s", common.EnvConfig.UploadPath, imageName, fileType) if err := utils.SaveFile(uploadedFile, imagePath); err != nil { return err } // Update the file type in the database if err := s.UpdateImageType(imageName, fileType); err != nil { return err } return nil } // InitDbConfig creates the default configuration values in the database if they do not exist, // updates existing configurations if they differ from the default, and deletes any configurations // that are not in the default configuration. func (s *AppConfigService) InitDbConfig() error { // Reflect to get the underlying value of DbConfig and its default configuration defaultConfigReflectValue := reflect.ValueOf(defaultDbConfig) defaultKeys := make(map[string]struct{}) // Iterate over the fields of DbConfig for i := 0; i < defaultConfigReflectValue.NumField(); i++ { defaultConfigVar := defaultConfigReflectValue.Field(i).Interface().(model.AppConfigVariable) defaultKeys[defaultConfigVar.Key] = struct{}{} var storedConfigVar model.AppConfigVariable if err := s.db.First(&storedConfigVar, "key = ?", defaultConfigVar.Key).Error; err != nil { // If the configuration does not exist, create it if err := s.db.Create(&defaultConfigVar).Error; err != nil { return err } continue } // Update existing configuration if it differs from the default if storedConfigVar.Type != defaultConfigVar.Type || storedConfigVar.IsPublic != defaultConfigVar.IsPublic || storedConfigVar.IsInternal != defaultConfigVar.IsInternal || storedConfigVar.DefaultValue != defaultConfigVar.DefaultValue { storedConfigVar.Type = defaultConfigVar.Type storedConfigVar.IsPublic = defaultConfigVar.IsPublic storedConfigVar.IsInternal = defaultConfigVar.IsInternal storedConfigVar.DefaultValue = defaultConfigVar.DefaultValue if err := s.db.Save(&storedConfigVar).Error; err != nil { return err } } } // Delete any configurations not in the default keys var allConfigVars []model.AppConfigVariable if err := s.db.Find(&allConfigVars).Error; err != nil { return err } for _, config := range allConfigVars { if _, exists := defaultKeys[config.Key]; !exists { if err := s.db.Delete(&config).Error; err != nil { return err } } } return s.LoadDbConfigFromDb() } // LoadDbConfigFromDb loads the configuration values from the database into the DbConfig struct. func (s *AppConfigService) LoadDbConfigFromDb() error { dbConfigReflectValue := reflect.ValueOf(s.DbConfig).Elem() for i := 0; i < dbConfigReflectValue.NumField(); i++ { dbConfigField := dbConfigReflectValue.Field(i) currentConfigVar := dbConfigField.Interface().(model.AppConfigVariable) var storedConfigVar model.AppConfigVariable if err := s.db.First(&storedConfigVar, "key = ?", currentConfigVar.Key).Error; err != nil { return err } if storedConfigVar.Value == "" && storedConfigVar.DefaultValue != "" { storedConfigVar.Value = storedConfigVar.DefaultValue } dbConfigField.Set(reflect.ValueOf(storedConfigVar)) } return nil }