Compare commits

...

4 Commits

Author SHA1 Message Date
Rostislav Dugin
2815cc3752 Merge pull request #481 from databasus/develop
FIX (storages): Validat only single rclone storage is passed
2026-03-31 10:37:54 +03:00
Rostislav Dugin
189573fa1b FIX (storages): Validat only single rclone storage is passed 2026-03-31 10:37:13 +03:00
Rostislav Dugin
81f77760c9 Merge pull request #479 from databasus/develop
FEATURE (navbar): Update navbar link color
2026-03-30 13:16:07 +03:00
Rostislav Dugin
63e23b2489 FEATURE (navbar): Update navbar link color 2026-03-30 13:15:03 +03:00
4 changed files with 148 additions and 2 deletions

View File

@@ -144,6 +144,27 @@ func (r *RcloneStorage) Validate(encryptor encryption.FieldEncryptor) error {
return errors.New("rclone config content is required")
}
configContent, err := encryptor.Decrypt(r.StorageID, r.ConfigContent)
if err != nil {
return fmt.Errorf("failed to decrypt rclone config content: %w", err)
}
parsedConfig, err := parseConfigContent(configContent)
if err != nil {
return fmt.Errorf("failed to parse rclone config: %w", err)
}
if len(parsedConfig) == 0 {
return errors.New("rclone config must contain at least one remote section")
}
if len(parsedConfig) > 1 {
return fmt.Errorf(
"rclone config must contain exactly one remote section, but found %d; create a separate storage for each remote",
len(parsedConfig),
)
}
return nil
}
@@ -230,6 +251,13 @@ func (r *RcloneStorage) getFs(
return nil, errors.New("rclone config must contain at least one remote section")
}
if len(parsedConfig) > 1 {
return nil, fmt.Errorf(
"rclone config must contain exactly one remote section, but found %d; create a separate storage for each remote",
len(parsedConfig),
)
}
var remoteName string
for section, values := range parsedConfig {
remoteName = section

View File

@@ -0,0 +1,118 @@
package rclone_storage
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ParseConfigContent_SingleRemote_ParsedCorrectly(t *testing.T) {
content := `[myremote]
type = s3
provider = AWS
access_key_id = AKIAIOSFODNN7EXAMPLE
secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region = us-east-1`
sections, err := parseConfigContent(content)
require.NoError(t, err)
require.Len(t, sections, 1)
assert.Equal(t, "s3", sections["myremote"]["type"])
assert.Equal(t, "AWS", sections["myremote"]["provider"])
assert.Equal(t, "AKIAIOSFODNN7EXAMPLE", sections["myremote"]["access_key_id"])
assert.Equal(t, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", sections["myremote"]["secret_access_key"])
assert.Equal(t, "us-east-1", sections["myremote"]["region"])
}
func Test_ParseConfigContent_MultipleRemotes_AllParsed(t *testing.T) {
content := `[remote1]
type = s3
region = us-east-1
[remote2]
type = drive
client_id = abc123`
sections, err := parseConfigContent(content)
require.NoError(t, err)
assert.Len(t, sections, 2)
assert.Equal(t, "s3", sections["remote1"]["type"])
assert.Equal(t, "us-east-1", sections["remote1"]["region"])
assert.Equal(t, "drive", sections["remote2"]["type"])
assert.Equal(t, "abc123", sections["remote2"]["client_id"])
}
func Test_ParseConfigContent_EmptyContent_ReturnsEmptyMap(t *testing.T) {
sections, err := parseConfigContent("")
require.NoError(t, err)
assert.Empty(t, sections)
}
func Test_ParseConfigContent_CommentsAndBlankLines_Ignored(t *testing.T) {
content := `# This is a comment
; Another comment
[myremote]
type = s3
# inline comment line
region = eu-west-1`
sections, err := parseConfigContent(content)
require.NoError(t, err)
require.Len(t, sections, 1)
assert.Equal(t, "s3", sections["myremote"]["type"])
assert.Equal(t, "eu-west-1", sections["myremote"]["region"])
}
func Test_ParseConfigContent_ValueWithEqualsSign_PreservesFullValue(t *testing.T) {
content := `[myremote]
type = s3
secret_access_key = abc=def=ghi`
sections, err := parseConfigContent(content)
require.NoError(t, err)
assert.Equal(t, "abc=def=ghi", sections["myremote"]["secret_access_key"])
}
func Test_ParseConfigContent_KeyWithoutValue_EmptyString(t *testing.T) {
content := `[myremote]
type =
provider = AWS`
sections, err := parseConfigContent(content)
require.NoError(t, err)
assert.Equal(t, "", sections["myremote"]["type"])
assert.Equal(t, "AWS", sections["myremote"]["provider"])
}
func Test_ParseConfigContent_KeyValueOutsideSection_Ignored(t *testing.T) {
content := `orphan_key = orphan_value
[myremote]
type = s3`
sections, err := parseConfigContent(content)
require.NoError(t, err)
assert.Len(t, sections, 1)
assert.Equal(t, "s3", sections["myremote"]["type"])
}
func Test_ParseConfigContent_WhitespaceAroundKeysAndValues_Trimmed(t *testing.T) {
content := `[myremote]
type = s3
region = us-west-2 `
sections, err := parseConfigContent(content)
require.NoError(t, err)
assert.Equal(t, "s3", sections["myremote"]["type"])
assert.Equal(t, "us-west-2", sections["myremote"]["region"])
}

View File

@@ -34,7 +34,7 @@ export function AuthNavbarComponent() {
{!IS_CLOUD && (
<a
className="!text-black hover:opacity-80 dark:!text-gray-200"
className="!text-black !underline !decoration-blue-600 !decoration-2 underline-offset-2 hover:opacity-80 dark:!text-gray-200"
href="https://databasus.com/cloud"
target="_blank"
rel="noreferrer"

View File

@@ -232,7 +232,7 @@ export const MainScreenComponent = () => {
{!IS_CLOUD && (
<a
className="!text-black hover:opacity-80 dark:!text-gray-200"
className="!text-black !underline !decoration-blue-600 !decoration-2 underline-offset-2 hover:opacity-80 dark:!text-gray-200"
href="https://databasus.com/cloud"
target="_blank"
rel="noreferrer"