added backup apis and tests

This commit is contained in:
Gani Georgiev
2023-05-13 22:10:14 +03:00
parent 3b0f60fe15
commit e8b4a7eb26
104 changed files with 3192 additions and 1017 deletions
+40 -18
View File
@@ -10,6 +10,7 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/tools/auth"
"github.com/pocketbase/pocketbase/tools/cron"
"github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/rest"
"github.com/pocketbase/pocketbase/tools/security"
@@ -23,12 +24,10 @@ const SecretMask string = "******"
type Settings struct {
mux sync.RWMutex
Meta MetaConfig `form:"meta" json:"meta"`
Logs LogsConfig `form:"logs" json:"logs"`
Smtp SmtpConfig `form:"smtp" json:"smtp"`
S3 S3Config `form:"s3" json:"s3"`
// @todo update tests
Meta MetaConfig `form:"meta" json:"meta"`
Logs LogsConfig `form:"logs" json:"logs"`
Smtp SmtpConfig `form:"smtp" json:"smtp"`
S3 S3Config `form:"s3" json:"s3"`
Backups BackupsConfig `form:"backups" json:"backups"`
AdminAuthToken TokenConfig `form:"adminAuthToken" json:"adminAuthToken"`
@@ -87,6 +86,9 @@ func New() *Settings {
Password: "",
Tls: false,
},
Backups: BackupsConfig{
CronMaxKeep: 3,
},
AdminAuthToken: TokenConfig{
Secret: security.RandomString(50),
Duration: 1209600, // 14 days
@@ -194,6 +196,7 @@ func (s *Settings) Validate() error {
validation.Field(&s.RecordFileToken),
validation.Field(&s.Smtp),
validation.Field(&s.S3),
validation.Field(&s.Backups),
validation.Field(&s.GoogleAuth),
validation.Field(&s.FacebookAuth),
validation.Field(&s.GithubAuth),
@@ -248,6 +251,7 @@ func (s *Settings) RedactClone() (*Settings, error) {
sensitiveFields := []*string{
&clone.Smtp.Password,
&clone.S3.Secret,
&clone.Backups.S3.Secret,
&clone.AdminAuthToken.Secret,
&clone.AdminPasswordResetToken.Secret,
&clone.AdminFileToken.Secret,
@@ -397,28 +401,46 @@ func (c S3Config) Validate() error {
// -------------------------------------------------------------------
type BackupsConfig struct {
AutoInterval BackupInterval `form:"autoInterval" json:"autoInterval"`
AutoMaxRetention int `form:"autoMaxRetention" json:"autoMaxRetention"`
S3 S3Config `form:"s3" json:"s3"`
// Cron is a cron expression to schedule auto backups, eg. "* * * * *".
//
// Leave it empty to disable the auto backups functionality.
Cron string `form:"cron" json:"cron"`
// CronMaxKeep is the the max number of cron generated backups to
// keep before removing older entries.
//
// This field works only when the cron config has valid cron expression.
CronMaxKeep int `form:"cronMaxKeep" json:"cronMaxKeep"`
// S3 is an optional S3 storage config specifying where to store the app backups.
S3 S3Config `form:"s3" json:"s3"`
}
// Validate makes BackupsConfig validatable by implementing [validation.Validatable] interface.
func (c BackupsConfig) Validate() error {
return validation.ValidateStruct(&c,
validation.Field(&c.S3),
validation.Field(&c.Cron, validation.By(checkCronExpression)),
validation.Field(
&c.CronMaxKeep,
validation.When(c.Cron != "", validation.Required),
validation.Min(1),
),
)
}
// @todo
type BackupInterval struct {
Day int
}
func checkCronExpression(value any) error {
v, _ := value.(string)
if v == "" {
return nil // nothing to check
}
// Validate makes BackupInterval validatable by implementing [validation.Validatable] interface.
func (c BackupInterval) Validate() error {
return validation.ValidateStruct(&c,
validation.Field(&c.Day),
)
_, err := cron.NewSchedule(v)
if err != nil {
return validation.NewError("validation_invalid_cron", err.Error())
}
return nil
}
// -------------------------------------------------------------------
+70
View File
@@ -127,6 +127,7 @@ func TestSettingsMerge(t *testing.T) {
s2.Smtp.Enabled = true
s2.S3.Enabled = true
s2.S3.Endpoint = "test"
s2.Backups.Cron = "* * * * *"
s2.AdminAuthToken.Duration = 1
s2.AdminPasswordResetToken.Duration = 2
s2.AdminFileToken.Duration = 2
@@ -231,6 +232,7 @@ func TestSettingsRedactClone(t *testing.T) {
// secrets
s1.Smtp.Password = testSecret
s1.S3.Secret = testSecret
s1.Backups.S3.Secret = testSecret
s1.AdminAuthToken.Secret = testSecret
s1.AdminPasswordResetToken.Secret = testSecret
s1.AdminFileToken.Secret = testSecret
@@ -610,6 +612,74 @@ func TestMetaConfigValidate(t *testing.T) {
}
}
func TestBackupsConfigValidate(t *testing.T) {
scenarios := []struct {
name string
config settings.BackupsConfig
expectedErrors []string
}{
{
"zero value",
settings.BackupsConfig{},
[]string{},
},
{
"invalid cron",
settings.BackupsConfig{
Cron: "invalid",
CronMaxKeep: 0,
},
[]string{"cron", "cronMaxKeep"},
},
{
"invalid enabled S3",
settings.BackupsConfig{
S3: settings.S3Config{
Enabled: true,
},
},
[]string{"s3"},
},
{
"valid data",
settings.BackupsConfig{
S3: settings.S3Config{
Enabled: true,
Endpoint: "example.com",
Bucket: "test",
Region: "test",
AccessKey: "test",
Secret: "test",
},
Cron: "*/10 * * * *",
CronMaxKeep: 1,
},
[]string{},
},
}
for _, s := range scenarios {
result := s.config.Validate()
// parse errors
errs, ok := result.(validation.Errors)
if !ok && result != nil {
t.Errorf("[%s] Failed to parse errors %v", s.name, result)
continue
}
// check errors
if len(errs) > len(s.expectedErrors) {
t.Errorf("[%s] Expected error keys %v, got %v", s.name, s.expectedErrors, errs)
}
for _, k := range s.expectedErrors {
if _, ok := errs[k]; !ok {
t.Errorf("[%s] Missing expected error key %q in %v", s.name, k, errs)
}
}
}
}
func TestEmailTemplateValidate(t *testing.T) {
scenarios := []struct {
emailTemplate settings.EmailTemplate