diff --git a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs index 15c3254b..e9d37dfd 100644 --- a/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs +++ b/ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using System.Collections; using System.Reflection; using ImmichFrame.Core.Interfaces; @@ -68,6 +69,56 @@ public void TestLoadConfigV2Yaml() VerifyConfig(config, true, false); } + [Test] + public void TestLoadConfigV2Json_WeatherApiKeyFile() + { + var configDir = Path.Combine(Path.GetTempPath(), $"immichframe-weather-{Guid.NewGuid():N}"); + Directory.CreateDirectory(configDir); + var keyFile = Path.Combine(configDir, "weather-api-key"); + + try + { + File.WriteAllText(keyFile, " weather-api-key \n"); + var settings = new + { + General = new { WeatherApiKey = "", WeatherApiKeyFile = keyFile }, + Accounts = new[] + { + new { ImmichServerUrl = "https://immich.example", ApiKey = "account-api-key" } + } + }; + File.WriteAllText( + Path.Combine(configDir, "Settings.json"), + JsonSerializer.Serialize(settings)); + + var config = _configLoader.LoadConfig(configDir); + + Assert.That(config.GeneralSettings.WeatherApiKey, Is.EqualTo("weather-api-key")); + } + finally + { + Directory.Delete(configDir, recursive: true); + } + } + + [Test] + public void TestLoadConfigV2Json_WeatherApiKeyFile_ConflictsWithInlineKey() + { + var settings = new GeneralSettings + { + WeatherApiKey = "inline-weather-api-key", + WeatherApiKeyFile = "weather-api-key-file" + }; + + var exception = Assert.Throws(() => settings.Validate()); + + Assert.That( + exception!.Message, + Is.EqualTo("Cannot specify both WeatherApiKey and WeatherApiKeyFile. Please provide only one.")); + } + + + private void VerifyConfig(IServerSettings serverSettings, bool usePrefix, bool expectNullApiKeyFile) { VerifyProperties(serverSettings.GeneralSettings); @@ -107,7 +158,8 @@ private void VerifyProperties(object o, string? prefix = "", bool expectNullApiK switch (type) { case var t when t == typeof(string): - if (prop.Name.Equals("ApiKeyFile") && expectNullApiKeyFile) + if (prop.Name.Equals("ApiKeyFile") && expectNullApiKeyFile || + prop.Name.Equals("WeatherApiKeyFile") && value is null) { Assert.That(value, Is.EqualTo(null), prop.Name); } diff --git a/ImmichFrame.WebApi/Models/ServerSettings.cs b/ImmichFrame.WebApi/Models/ServerSettings.cs index 74d0fb8e..6e9a134b 100644 --- a/ImmichFrame.WebApi/Models/ServerSettings.cs +++ b/ImmichFrame.WebApi/Models/ServerSettings.cs @@ -68,12 +68,24 @@ public class GeneralSettings : IGeneralSettings, IConfigSettable public List Webcalendars { get; set; } = new(); public int RefreshAlbumPeopleInterval { get; set; } = 12; public string? WeatherApiKey { get; set; } = string.Empty; + public string? WeatherApiKeyFile { get; set; } = null; public string? UnitSystem { get; set; } = "imperial"; public string? WeatherLatLong { get; set; } = "40.7128,74.0060"; public string? Webhook { get; set; } public string? AuthenticationSecret { get; set; } - public void Validate() { } + public void Validate() + { + if (!string.IsNullOrWhiteSpace(WeatherApiKeyFile)) + { + if (!string.IsNullOrWhiteSpace(WeatherApiKey)) + { + throw new Exception("Cannot specify both WeatherApiKey and WeatherApiKeyFile. Please provide only one."); + } + + WeatherApiKey = File.ReadAllText(WeatherApiKeyFile).Trim(); + } + } } public class ServerAccountSettings : IAccountSettings, IConfigSettable diff --git a/docker/Settings.example.json b/docker/Settings.example.json index a86a4d00..42d9c186 100644 --- a/docker/Settings.example.json +++ b/docker/Settings.example.json @@ -10,6 +10,7 @@ "PhotoDateFormat": "MM/dd/yyyy", "ImageLocationFormat": "City,State,Country", "WeatherApiKey": "", + "WeatherApiKeyFile": null, "UnitSystem": "imperial", "WeatherLatLong": "40.730610,-73.935242", "Webhook": null, diff --git a/docker/Settings.example.yml b/docker/Settings.example.yml index 173b31a5..27119303 100644 --- a/docker/Settings.example.yml +++ b/docker/Settings.example.yml @@ -7,7 +7,9 @@ General: RefreshAlbumPeopleInterval: 12 PhotoDateFormat: MM/dd/yyyy ImageLocationFormat: 'City,State,Country' + # Set either WeatherApiKey or WeatherApiKeyFile; leave both empty to disable weather. WeatherApiKey: '' + # WeatherApiKeyFile: '/path/to/weather-api.key' UnitSystem: imperial WeatherLatLong: '40.730610,-73.935242' Webhook: null diff --git a/docs/docs/getting-started/configuration.md b/docs/docs/getting-started/configuration.md index 7378c7d1..52d003a3 100644 --- a/docs/docs/getting-started/configuration.md +++ b/docs/docs/getting-started/configuration.md @@ -49,7 +49,9 @@ General: PhotoDateFormat: 'MM/dd/yyyy' # string ImageLocationFormat: 'City,State,Country' # Get an API key from OpenWeatherMap: https://openweathermap.org/appid + # Set either WeatherApiKey or WeatherApiKeyFile; leave both empty to disable weather. WeatherApiKey: '' # string + # WeatherApiKeyFile: '/path/to/weather-api.key' # Imperial or metric system (Fahrenheit or Celsius) UnitSystem: 'imperial' # 'imperial' | 'metric' # Set the weather location with lat/lon.