From f5bb164170460b1020bfe6bce8e8abb3315e32e3 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Thu, 20 Nov 2025 11:35:12 -0800 Subject: [PATCH 1/5] Add a `ApiKeyFile` option If given, `ApiKeyFile` points at a file that ImmichFrame will read to get the api key. `ApiKeyFile` is mutually exclusive with `ApiKey`. This fixes https://github.com/immichFrame/ImmichFrame/issues/510 --- .../Interfaces/IServerSettings.cs | 9 +++++- .../Helpers/Config/ConfigLoaderTest.cs | 29 ++++++++++------- .../Resources/TestV1.json | 2 +- .../Resources/TestV2.json | 4 ++- ImmichFrame.WebApi.Tests/Resources/TestV2.yml | 4 ++- .../Helpers/Config/ConfigLoader.cs | 8 ++++- .../Helpers/Config/ServerSettingsV1.cs | 8 ++++- ImmichFrame.WebApi/Models/ServerSettings.cs | 32 ++++++++++++++++++- docker/Settings.example.json | 5 +-- docker/Settings.example.yml | 5 ++- docker/example.env | 6 +++- docs/docs/getting-started/configuration.md | 6 ++-- 12 files changed, 94 insertions(+), 24 deletions(-) diff --git a/ImmichFrame.Core/Interfaces/IServerSettings.cs b/ImmichFrame.Core/Interfaces/IServerSettings.cs index 630218c1..51d12ba2 100644 --- a/ImmichFrame.Core/Interfaces/IServerSettings.cs +++ b/ImmichFrame.Core/Interfaces/IServerSettings.cs @@ -4,12 +4,15 @@ public interface IServerSettings { public IEnumerable Accounts { get; } public IGeneralSettings GeneralSettings { get; } + + public void validate(); } public interface IAccountSettings { public string ImmichServerUrl { get; } public string ApiKey { get; } + public string? ApiKeyFile { get; } public bool ShowMemories { get; } public bool ShowFavorites { get; } public bool ShowArchived { get; } @@ -20,6 +23,8 @@ public interface IAccountSettings public List ExcludedAlbums { get; } public List People { get; } public int? Rating { get; } + + public void validate(); } public interface IGeneralSettings @@ -57,5 +62,7 @@ public interface IGeneralSettings public bool ImageFill { get; } public string Layout { get; } public string Language { get; } + + public void validate(); } -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs index 7d51774d..15c3254b 100644 --- a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs +++ b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs @@ -29,7 +29,7 @@ public void TestLoadConfigV1Json() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV1.json")); - VerifyConfig(new ServerSettingsV1Adapter(config), false); + VerifyConfig(new ServerSettingsV1Adapter(config), false, true); } [Test] @@ -39,7 +39,7 @@ public void TestLoadConfigEnv() TestContext.CurrentContext.TestDirectory, "Resources/TestV1.json")); var config = _configLoader.LoadConfigFromDictionary(ToDictionary(jsonConfig)); - VerifyConfig(new ServerSettingsV1Adapter(config), false); + VerifyConfig(new ServerSettingsV1Adapter(config), false, true); } [Test] @@ -47,7 +47,7 @@ public void TestLoadConfigV2Json() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2.json")); - VerifyConfig(config, true); + VerifyConfig(config, true, false); } [Test] @@ -65,26 +65,26 @@ public void TestLoadConfigV2Yaml() { var config = _configLoader.LoadConfigYaml(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2.yml")); - VerifyConfig(config, true); + VerifyConfig(config, true, false); } - private void VerifyConfig(IServerSettings serverSettings, bool usePrefix) + private void VerifyConfig(IServerSettings serverSettings, bool usePrefix, bool expectNullApiKeyFile) { VerifyProperties(serverSettings.GeneralSettings); - VerifyAccounts(serverSettings.Accounts, usePrefix); + VerifyAccounts(serverSettings.Accounts, usePrefix, expectNullApiKeyFile); } - private void VerifyAccounts(IEnumerable accounts, bool usePrefix) + private void VerifyAccounts(IEnumerable accounts, bool usePrefix, bool expectNullApiKeyFile) { var idx = 1; foreach (var account in accounts) { - VerifyProperties(account, usePrefix ? "Account" + idx + "." : ""); + VerifyProperties(account, usePrefix ? "Account" + idx + "." : "", expectNullApiKeyFile); idx++; } } - private void VerifyProperties(object o, string? prefix = "") + private void VerifyProperties(object o, string? prefix = "", bool expectNullApiKeyFile = false) { foreach (var prop in o.GetType().GetProperties()) { @@ -107,7 +107,14 @@ private void VerifyProperties(object o, string? prefix = "") switch (type) { case var t when t == typeof(string): - Assert.That(value, Is.EqualTo(prefix + prop.Name + "_TEST"), prop.Name); + if (prop.Name.Equals("ApiKeyFile") && expectNullApiKeyFile) + { + Assert.That(value, Is.EqualTo(null), prop.Name); + } + else + { + Assert.That(value, Is.EqualTo(prefix + prop.Name + "_TEST"), prop.Name); + } break; case var t when t == typeof(Boolean): Assert.That(value, Is.EqualTo(true), prop.Name); @@ -171,4 +178,4 @@ public static IDictionary ToDictionary(object obj, bool ignoreNullValues = false return dictionary; } -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV1.json b/ImmichFrame.WebApi.Tests/Resources/TestV1.json index 1ce862ed..71b938f1 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV1.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV1.json @@ -54,4 +54,4 @@ "Account2.ImmichServerUrl": "Account2.ImmichServerUrl_TEST", "Account2.ApiKey": "Account2.ApiKey_TEST", "Account2.ImagesFromDate": "Account2.ImagesFromDate_TEST" -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.json b/ImmichFrame.WebApi.Tests/Resources/TestV2.json index 9c9b1d43..ce64ebee 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.json @@ -40,6 +40,7 @@ { "ImmichServerUrl": "Account1.ImmichServerUrl_TEST", "ApiKey": "Account1.ApiKey_TEST", + "ApiKeyFile": "Account1.ApiKeyFile_TEST", "ImagesFromDate": "2020-01-02", "ShowMemories": true, "ShowFavorites": true, @@ -60,6 +61,7 @@ { "ImmichServerUrl": "Account2.ImmichServerUrl_TEST", "ApiKey": "Account2.ApiKey_TEST", + "ApiKeyFile": "Account2.ApiKeyFile_TEST", "ImagesFromDate": "2020-01-02", "ShowMemories": true, "ShowFavorites": true, @@ -78,4 +80,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml index 71f7b13c..f4072cd0 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV2.yml +++ b/ImmichFrame.WebApi.Tests/Resources/TestV2.yml @@ -37,6 +37,7 @@ General: Accounts: - ImmichServerUrl: Account1.ImmichServerUrl_TEST ApiKey: Account1.ApiKey_TEST + ApiKeyFile: Account1.ApiKeyFile_TEST ImagesFromDate: '2020-01-02' ShowMemories: true ShowFavorites: true @@ -52,6 +53,7 @@ Accounts: - 00000000-0000-0000-0000-000000000001 - ImmichServerUrl: Account2.ImmichServerUrl_TEST ApiKey: Account2.ApiKey_TEST + ApiKeyFile: Account2.ApiKeyFile_TEST ImagesFromDate: '2020-01-02' ShowMemories: true ShowFavorites: true @@ -64,4 +66,4 @@ Accounts: ExcludedAlbums: - 00000000-0000-0000-0000-000000000001 People: - - 00000000-0000-0000-0000-000000000001 \ No newline at end of file + - 00000000-0000-0000-0000-000000000001 diff --git a/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs b/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs index 7dcc61ec..ce9e01f4 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs @@ -21,6 +21,12 @@ private string FindConfigFile(string dir, params string[] fileNames) ?? Path.Combine(dir, fileNames.First()); } public IServerSettings LoadConfig(string configPath) + { + var config = LoadConfigRaw(configPath); + config.validate(); + return config; + } + private IServerSettings LoadConfigRaw(string configPath) { var jsonConfigPath = FindConfigFile(configPath, "Settings.json"); if (File.Exists(jsonConfigPath)) @@ -145,4 +151,4 @@ public IServerSettings LoadConfig(string configPath) throw new SettingsNotValidException($"Problem with parsing the settings: {ex.Message}", ex); } } -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index 7bd9bd34..d9392de3 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -63,11 +63,13 @@ public class ServerSettingsV1Adapter(ServerSettingsV1 _delegate) : IServerSettin public IEnumerable Accounts => new List { new(_delegate) }; public IGeneralSettings GeneralSettings => new GeneralSettingsV1Adapter(_delegate); + public void validate() {} class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings { public string ImmichServerUrl => _delegate.ImmichServerUrl; public string ApiKey => _delegate.ApiKey; + public string? ApiKeyFile => null; // V1 settings didn't support paths to api keys. public bool ShowMemories => _delegate.ShowMemories; public bool ShowFavorites => _delegate.ShowFavorites; public bool ShowArchived => _delegate.ShowArchived; @@ -78,6 +80,8 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings public List ExcludedAlbums => _delegate.ExcludedAlbums; public List People => _delegate.People; public int? Rating => _delegate.Rating; + + public void validate() {} } class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings @@ -115,5 +119,7 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings public bool ImageFill => _delegate.ImageFill; public string Layout => _delegate.Layout; public string Language => _delegate.Language; + + public void validate() {} } -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index 24e4633c..bae01a4d 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -23,6 +23,16 @@ public class ServerSettings : IServerSettings, IConfigSettable [JsonIgnore] [YamlIgnore] public IEnumerable Accounts => AccountsImpl; + + public void validate() + { + GeneralSettings.validate(); + + foreach (var account in Accounts) + { + account.validate(); + } + } } public class GeneralSettings : IGeneralSettings, IConfigSettable @@ -60,12 +70,15 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public string? WeatherLatLong { get; set; } = "40.7128,74.0060"; public string? Webhook { get; set; } public string? AuthenticationSecret { get; set; } + + public void validate() {} } public class ServerAccountSettings : IAccountSettings, IConfigSettable { public string ImmichServerUrl { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty; + public string? ApiKeyFile { get; set; } = null; public bool ShowMemories { get; set; } = false; public bool ShowFavorites { get; set; } = false; public bool ShowArchived { get; set; } = false; @@ -77,4 +90,21 @@ public class ServerAccountSettings : IAccountSettings, IConfigSettable public List ExcludedAlbums { get; set; } = new(); public List People { get; set; } = new(); public int? Rating { get; set; } -} \ No newline at end of file + + public void validate() + { + if (!string.IsNullOrWhiteSpace(ApiKeyFile)) + { + if (!string.IsNullOrWhiteSpace(ApiKey)) + { + throw new Exception("Cannot specify both ApiKey and ApiKeyFile. Please provide only one."); + } + ApiKey = File.ReadAllText(ApiKeyFile).Trim(); + } + + if (string.IsNullOrWhiteSpace(ApiKey)) + { + throw new InvalidOperationException("Either ApiKey or ApiKeyFile must be provided."); + } + } +} diff --git a/docker/Settings.example.json b/docker/Settings.example.json index 118ce629..fe37b5a5 100644 --- a/docker/Settings.example.json +++ b/docker/Settings.example.json @@ -39,7 +39,8 @@ "Accounts": [ { "ImmichServerUrl": "REQUIRED", - "ApiKey": "REQUIRED", + "ApiKey": "super-secret-api-key", + "ApiKeyFile": "/path/to/api.key", "ImagesFromDate": null, "ShowMemories": false, "ShowFavorites": false, @@ -58,4 +59,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/docker/Settings.example.yml b/docker/Settings.example.yml index 41fea573..5662b364 100644 --- a/docker/Settings.example.yml +++ b/docker/Settings.example.yml @@ -35,7 +35,10 @@ General: Layout: splitview Accounts: - ImmichServerUrl: REQUIRED - ApiKey: REQUIRED + # Exactly one of ApiKey or ApiKeyFile must be set. + ApiKey: "super-secret-api-key" + # ApiKeyFile: "/path/to/api.key" + ImagesFromDate: null ShowMemories: false ShowFavorites: false diff --git a/docker/example.env b/docker/example.env index e0044d1e..75e875fd 100644 --- a/docker/example.env +++ b/docker/example.env @@ -1,5 +1,9 @@ ImmichServerUrl=URL + +# Either ApiKey or ApiKeyFile must be specified. ApiKey=KEY +# ApiKeyFile=/path/to/key + # AuthenticationSecret= # Interval=10 # TransitionDuration=2 @@ -41,4 +45,4 @@ ApiKey=KEY # UnitSystem=imperial # WeatherLatLong= # Language=en -# Webhook= \ No newline at end of file +# Webhook= diff --git a/docs/docs/getting-started/configuration.md b/docs/docs/getting-started/configuration.md index 9a7ccd59..cd276dff 100644 --- a/docs/docs/getting-started/configuration.md +++ b/docs/docs/getting-started/configuration.md @@ -27,7 +27,7 @@ Only override settings you intend to change. Defaults might change between versions, so keeping your config minimal helps future upgrades. ::: -Defaults are below, only one account with `ImmichServerUrl` and `ApiKey` are required. +Defaults are below, only one account with `ImmichServerUrl` and `ApiKey`|`ApiKeyFile` are required. ```yaml # settings applicable to the web client - when viewing with a browser or webview @@ -104,7 +104,9 @@ Accounts: - # The URL of your Immich server e.g. `http://photos.yourdomain.com` / `http://192.168.0.100:2283`. ImmichServerUrl: 'REQUIRED' # string, required, no default # Read more about how to obtain an Immich API key: https://immich.app/docs/features/command-line-interface#obtain-the-api-key - ApiKey: 'REQUIRED' # string, required, no default + # Exactly one of ApiKey or ApiKeyFile must be set. + ApiKey: "super-secret-api-key" + # ApiKeyFile: "/path/to/api.key" # Show images after date. Overwrites the `ImagesFromDays`-Setting ImagesFromDate: null # Date # If this is set, memories are displayed. From c07378f89d31c02d1bc92f55be7fe4496e2ec69f Mon Sep 17 00:00:00 2001 From: JW-CH <17313367+JW-CH@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:22:53 +0100 Subject: [PATCH 2/5] fix method naming --- ImmichFrame.Core/Interfaces/IServerSettings.cs | 6 +++--- ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs | 2 +- ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs | 6 +++--- ImmichFrame.WebApi/Models/ServerSettings.cs | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ImmichFrame.Core/Interfaces/IServerSettings.cs b/ImmichFrame.Core/Interfaces/IServerSettings.cs index 51d12ba2..fcbff7c0 100644 --- a/ImmichFrame.Core/Interfaces/IServerSettings.cs +++ b/ImmichFrame.Core/Interfaces/IServerSettings.cs @@ -5,7 +5,7 @@ public interface IServerSettings public IEnumerable Accounts { get; } public IGeneralSettings GeneralSettings { get; } - public void validate(); + public void Validate(); } public interface IAccountSettings @@ -24,7 +24,7 @@ public interface IAccountSettings public List People { get; } public int? Rating { get; } - public void validate(); + public void Validate(); } public interface IGeneralSettings @@ -63,6 +63,6 @@ public interface IGeneralSettings public string Layout { get; } public string Language { get; } - public void validate(); + public void Validate(); } } diff --git a/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs b/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs index ce9e01f4..58782cba 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs @@ -23,7 +23,7 @@ private string FindConfigFile(string dir, params string[] fileNames) public IServerSettings LoadConfig(string configPath) { var config = LoadConfigRaw(configPath); - config.validate(); + config.Validate(); return config; } private IServerSettings LoadConfigRaw(string configPath) diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index d9392de3..4973ae9d 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -63,7 +63,7 @@ public class ServerSettingsV1Adapter(ServerSettingsV1 _delegate) : IServerSettin public IEnumerable Accounts => new List { new(_delegate) }; public IGeneralSettings GeneralSettings => new GeneralSettingsV1Adapter(_delegate); - public void validate() {} + public void Validate() { } class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings { @@ -81,7 +81,7 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings public List People => _delegate.People; public int? Rating => _delegate.Rating; - public void validate() {} + public void Validate() { } } class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings @@ -120,6 +120,6 @@ class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings public string Layout => _delegate.Layout; public string Language => _delegate.Language; - public void validate() {} + public void Validate() { } } } diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index bae01a4d..8c2ce019 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -24,13 +24,13 @@ public class ServerSettings : IServerSettings, IConfigSettable [YamlIgnore] public IEnumerable Accounts => AccountsImpl; - public void validate() + public void Validate() { - GeneralSettings.validate(); + GeneralSettings.Validate(); foreach (var account in Accounts) { - account.validate(); + account.Validate(); } } } @@ -71,7 +71,7 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public string? Webhook { get; set; } public string? AuthenticationSecret { get; set; } - public void validate() {} + public void Validate() { } } public class ServerAccountSettings : IAccountSettings, IConfigSettable @@ -91,7 +91,7 @@ public class ServerAccountSettings : IAccountSettings, IConfigSettable public List People { get; set; } = new(); public int? Rating { get; set; } - public void validate() + public void Validate() { if (!string.IsNullOrWhiteSpace(ApiKeyFile)) { From 11374c87a99f2df2eaffe33676cbe26b13c95fd0 Mon Sep 17 00:00:00 2001 From: JW-CH <17313367+JW-CH@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:35:51 +0100 Subject: [PATCH 3/5] support apikeyfile for v1, simplify tests --- .../Helpers/Config/ConfigLoaderTest.cs | 33 ++++++++----------- .../Resources/TestV1.json | 3 +- .../Helpers/Config/ServerSettingsV1.cs | 3 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs index 15c3254b..ca3744c6 100644 --- a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs +++ b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs @@ -29,7 +29,7 @@ public void TestLoadConfigV1Json() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV1.json")); - VerifyConfig(new ServerSettingsV1Adapter(config), false, true); + VerifyConfig(new ServerSettingsV1Adapter(config), false); } [Test] @@ -39,7 +39,7 @@ public void TestLoadConfigEnv() TestContext.CurrentContext.TestDirectory, "Resources/TestV1.json")); var config = _configLoader.LoadConfigFromDictionary(ToDictionary(jsonConfig)); - VerifyConfig(new ServerSettingsV1Adapter(config), false, true); + VerifyConfig(new ServerSettingsV1Adapter(config), false); } [Test] @@ -47,15 +47,15 @@ public void TestLoadConfigV2Json() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2.json")); - VerifyConfig(config, true, false); + VerifyConfig(config, true); } - + [Test] public void TestLoadConfigV2Json_NoGeneral() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2_NoGeneral.json")); - + Assert.That(config.GeneralSettings, Is.Not.Null); config.GeneralSettings.Should().BeEquivalentTo(new GeneralSettings()); } @@ -65,26 +65,26 @@ public void TestLoadConfigV2Yaml() { var config = _configLoader.LoadConfigYaml(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2.yml")); - VerifyConfig(config, true, false); + VerifyConfig(config, true); } - private void VerifyConfig(IServerSettings serverSettings, bool usePrefix, bool expectNullApiKeyFile) + private void VerifyConfig(IServerSettings serverSettings, bool usePrefix) { VerifyProperties(serverSettings.GeneralSettings); - VerifyAccounts(serverSettings.Accounts, usePrefix, expectNullApiKeyFile); + VerifyAccounts(serverSettings.Accounts, usePrefix); } - private void VerifyAccounts(IEnumerable accounts, bool usePrefix, bool expectNullApiKeyFile) + private void VerifyAccounts(IEnumerable accounts, bool usePrefix) { var idx = 1; foreach (var account in accounts) { - VerifyProperties(account, usePrefix ? "Account" + idx + "." : "", expectNullApiKeyFile); + VerifyProperties(account, usePrefix ? "Account" + idx + "." : ""); idx++; } } - private void VerifyProperties(object o, string? prefix = "", bool expectNullApiKeyFile = false) + private void VerifyProperties(object o, string? prefix = "") { foreach (var prop in o.GetType().GetProperties()) { @@ -107,14 +107,7 @@ private void VerifyProperties(object o, string? prefix = "", bool expectNullApiK switch (type) { case var t when t == typeof(string): - if (prop.Name.Equals("ApiKeyFile") && expectNullApiKeyFile) - { - Assert.That(value, Is.EqualTo(null), prop.Name); - } - else - { - Assert.That(value, Is.EqualTo(prefix + prop.Name + "_TEST"), prop.Name); - } + Assert.That(value, Is.EqualTo(prefix + prop.Name + "_TEST"), prop.Name); break; case var t when t == typeof(Boolean): Assert.That(value, Is.EqualTo(true), prop.Name); @@ -178,4 +171,4 @@ public static IDictionary ToDictionary(object obj, bool ignoreNullValues = false return dictionary; } -} +} \ No newline at end of file diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV1.json b/ImmichFrame.WebApi.Tests/Resources/TestV1.json index 71b938f1..9a0ac704 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV1.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV1.json @@ -1,6 +1,7 @@ { "ImmichServerUrl": "ImmichServerUrl_TEST", "ApiKey": "ApiKey_TEST", + "ApiKeyFile": "ApiKeyFile_TEST", "AuthenticationSecret": "AuthenticationSecret_TEST", "Interval": 7, "TransitionDuration": 7.7, @@ -54,4 +55,4 @@ "Account2.ImmichServerUrl": "Account2.ImmichServerUrl_TEST", "Account2.ApiKey": "Account2.ApiKey_TEST", "Account2.ImagesFromDate": "Account2.ImagesFromDate_TEST" -} +} \ No newline at end of file diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index 4973ae9d..f4014cb9 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -9,6 +9,7 @@ public class ServerSettingsV1 : IConfigSettable { public string ImmichServerUrl { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty; + public string? ApiKeyFile { get; set; } = null; public bool ShowMemories { get; set; } = false; public bool ShowFavorites { get; set; } = false; public bool ShowArchived { get; set; } = false; @@ -69,7 +70,7 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings { public string ImmichServerUrl => _delegate.ImmichServerUrl; public string ApiKey => _delegate.ApiKey; - public string? ApiKeyFile => null; // V1 settings didn't support paths to api keys. + public string? ApiKeyFile => _delegate.ApiKeyFile; public bool ShowMemories => _delegate.ShowMemories; public bool ShowFavorites => _delegate.ShowFavorites; public bool ShowArchived => _delegate.ShowArchived; From 85108a4adf702eca6c45e28510ddd8c164e90681 Mon Sep 17 00:00:00 2001 From: JW-CH <17313367+JW-CH@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:48:54 +0100 Subject: [PATCH 4/5] Revert "support apikeyfile for v1, simplify tests" This reverts commit 11374c87a99f2df2eaffe33676cbe26b13c95fd0. --- .../Helpers/Config/ConfigLoaderTest.cs | 33 +++++++++++-------- .../Resources/TestV1.json | 3 +- .../Helpers/Config/ServerSettingsV1.cs | 3 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs index ca3744c6..15c3254b 100644 --- a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs +++ b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs @@ -29,7 +29,7 @@ public void TestLoadConfigV1Json() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV1.json")); - VerifyConfig(new ServerSettingsV1Adapter(config), false); + VerifyConfig(new ServerSettingsV1Adapter(config), false, true); } [Test] @@ -39,7 +39,7 @@ public void TestLoadConfigEnv() TestContext.CurrentContext.TestDirectory, "Resources/TestV1.json")); var config = _configLoader.LoadConfigFromDictionary(ToDictionary(jsonConfig)); - VerifyConfig(new ServerSettingsV1Adapter(config), false); + VerifyConfig(new ServerSettingsV1Adapter(config), false, true); } [Test] @@ -47,15 +47,15 @@ public void TestLoadConfigV2Json() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2.json")); - VerifyConfig(config, true); + VerifyConfig(config, true, false); } - + [Test] public void TestLoadConfigV2Json_NoGeneral() { var config = _configLoader.LoadConfigJson(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2_NoGeneral.json")); - + Assert.That(config.GeneralSettings, Is.Not.Null); config.GeneralSettings.Should().BeEquivalentTo(new GeneralSettings()); } @@ -65,26 +65,26 @@ public void TestLoadConfigV2Yaml() { var config = _configLoader.LoadConfigYaml(Path.Combine( TestContext.CurrentContext.TestDirectory, "Resources/TestV2.yml")); - VerifyConfig(config, true); + VerifyConfig(config, true, false); } - private void VerifyConfig(IServerSettings serverSettings, bool usePrefix) + private void VerifyConfig(IServerSettings serverSettings, bool usePrefix, bool expectNullApiKeyFile) { VerifyProperties(serverSettings.GeneralSettings); - VerifyAccounts(serverSettings.Accounts, usePrefix); + VerifyAccounts(serverSettings.Accounts, usePrefix, expectNullApiKeyFile); } - private void VerifyAccounts(IEnumerable accounts, bool usePrefix) + private void VerifyAccounts(IEnumerable accounts, bool usePrefix, bool expectNullApiKeyFile) { var idx = 1; foreach (var account in accounts) { - VerifyProperties(account, usePrefix ? "Account" + idx + "." : ""); + VerifyProperties(account, usePrefix ? "Account" + idx + "." : "", expectNullApiKeyFile); idx++; } } - private void VerifyProperties(object o, string? prefix = "") + private void VerifyProperties(object o, string? prefix = "", bool expectNullApiKeyFile = false) { foreach (var prop in o.GetType().GetProperties()) { @@ -107,7 +107,14 @@ private void VerifyProperties(object o, string? prefix = "") switch (type) { case var t when t == typeof(string): - Assert.That(value, Is.EqualTo(prefix + prop.Name + "_TEST"), prop.Name); + if (prop.Name.Equals("ApiKeyFile") && expectNullApiKeyFile) + { + Assert.That(value, Is.EqualTo(null), prop.Name); + } + else + { + Assert.That(value, Is.EqualTo(prefix + prop.Name + "_TEST"), prop.Name); + } break; case var t when t == typeof(Boolean): Assert.That(value, Is.EqualTo(true), prop.Name); @@ -171,4 +178,4 @@ public static IDictionary ToDictionary(object obj, bool ignoreNullValues = false return dictionary; } -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi.Tests/Resources/TestV1.json b/ImmichFrame.WebApi.Tests/Resources/TestV1.json index 9a0ac704..71b938f1 100644 --- a/ImmichFrame.WebApi.Tests/Resources/TestV1.json +++ b/ImmichFrame.WebApi.Tests/Resources/TestV1.json @@ -1,7 +1,6 @@ { "ImmichServerUrl": "ImmichServerUrl_TEST", "ApiKey": "ApiKey_TEST", - "ApiKeyFile": "ApiKeyFile_TEST", "AuthenticationSecret": "AuthenticationSecret_TEST", "Interval": 7, "TransitionDuration": 7.7, @@ -55,4 +54,4 @@ "Account2.ImmichServerUrl": "Account2.ImmichServerUrl_TEST", "Account2.ApiKey": "Account2.ApiKey_TEST", "Account2.ImagesFromDate": "Account2.ImagesFromDate_TEST" -} \ No newline at end of file +} diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index f4014cb9..4973ae9d 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -9,7 +9,6 @@ public class ServerSettingsV1 : IConfigSettable { public string ImmichServerUrl { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty; - public string? ApiKeyFile { get; set; } = null; public bool ShowMemories { get; set; } = false; public bool ShowFavorites { get; set; } = false; public bool ShowArchived { get; set; } = false; @@ -70,7 +69,7 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings { public string ImmichServerUrl => _delegate.ImmichServerUrl; public string ApiKey => _delegate.ApiKey; - public string? ApiKeyFile => _delegate.ApiKeyFile; + public string? ApiKeyFile => null; // V1 settings didn't support paths to api keys. public bool ShowMemories => _delegate.ShowMemories; public bool ShowFavorites => _delegate.ShowFavorites; public bool ShowArchived => _delegate.ShowArchived; From fc4fb845ad599a83e22d91bff74e28d375919be2 Mon Sep 17 00:00:00 2001 From: JW-CH <17313367+JW-CH@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:59:40 +0100 Subject: [PATCH 5/5] Method renaming again & Implement for v1 --- ImmichFrame.Core/Interfaces/IServerSettings.cs | 2 +- ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs | 11 +++++++++-- ImmichFrame.WebApi/Models/ServerSettings.cs | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ImmichFrame.Core/Interfaces/IServerSettings.cs b/ImmichFrame.Core/Interfaces/IServerSettings.cs index fcbff7c0..0141af37 100644 --- a/ImmichFrame.Core/Interfaces/IServerSettings.cs +++ b/ImmichFrame.Core/Interfaces/IServerSettings.cs @@ -24,7 +24,7 @@ public interface IAccountSettings public List People { get; } public int? Rating { get; } - public void Validate(); + public void ValidateAndInitialize(); } public interface IGeneralSettings diff --git a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs index 4973ae9d..1e6ae02b 100644 --- a/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs +++ b/ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs @@ -63,7 +63,14 @@ public class ServerSettingsV1Adapter(ServerSettingsV1 _delegate) : IServerSettin public IEnumerable Accounts => new List { new(_delegate) }; public IGeneralSettings GeneralSettings => new GeneralSettingsV1Adapter(_delegate); - public void Validate() { } + public void Validate() + { + GeneralSettings.Validate(); + foreach (var account in Accounts) + { + account.ValidateAndInitialize(); + } + } class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings { @@ -81,7 +88,7 @@ class AccountSettingsV1Adapter(ServerSettingsV1 _delegate) : IAccountSettings public List People => _delegate.People; public int? Rating => _delegate.Rating; - public void Validate() { } + public void ValidateAndInitialize() { } } class GeneralSettingsV1Adapter(ServerSettingsV1 _delegate) : IGeneralSettings diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index 8c2ce019..6f2ce348 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -30,7 +30,7 @@ public void Validate() foreach (var account in Accounts) { - account.Validate(); + account.ValidateAndInitialize(); } } } @@ -91,7 +91,7 @@ public class ServerAccountSettings : IAccountSettings, IConfigSettable public List People { get; set; } = new(); public int? Rating { get; set; } - public void Validate() + public void ValidateAndInitialize() { if (!string.IsNullOrWhiteSpace(ApiKeyFile)) {