Skip to content

Conversation

@jfly
Copy link
Contributor

@jfly jfly commented Nov 20, 2025

If given, ApiKeyFile points at a file that ImmichFrame will read to get the api key. ApiKeyFile is mutually exclusive with ApiKey.

This fixes #510

Summary by CodeRabbit

  • New Features

    • Add ApiKeyFile option to allow providing API keys via a file; file contents will be used when supplied.
    • Configuration loading now performs explicit post-load validation.
  • Bug Fixes / Validation

    • Enforce exactly one of ApiKey or ApiKeyFile; reject invalid combinations and initialize keys from files.
  • Documentation

    • Update examples and docs to show ApiKeyFile and mutual-exclusivity.
  • Tests

    • Test fixtures updated to cover ApiKeyFile scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

Adds ApiKeyFile support and validation across interfaces, models, V1 adapters, config loader, examples, docs, and tests. LoadConfig now delegates parsing to a new LoadConfigRaw and invokes Validate() on the parsed settings; account validation enforces mutual-exclusivity, can read ApiKey from a file, and requires one credential per account.

Changes

Cohort / File(s) Summary
Core interfaces
ImmichFrame.Core/Interfaces/IServerSettings.cs
Added void Validate() to IServerSettings, void ValidateAndInitialize() to IAccountSettings, void Validate() to IGeneralSettings, and string? ApiKeyFile { get; } to IAccountSettings.
Config loader
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs
Added private LoadConfigRaw(string) helper; LoadConfig(string) now calls it and invokes Validate() on the returned IServerSettings before returning.
V1 adapters
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs
Added Validate() implementations to ServerSettingsV1Adapter and GeneralSettingsV1Adapter; added string? ApiKeyFile => null and ValidateAndInitialize() to AccountSettingsV1Adapter (V1 has no file-key support).
Server settings models
ImmichFrame.WebApi/Models/ServerSettings.cs
Added ServerSettings.Validate() (delegates to General and accounts) and GeneralSettings.Validate() (no-op); added string? ApiKeyFile { get; set; } to ServerAccountSettings and implemented ValidateAndInitialize() enforcing mutual-exclusion, reading and trimming ApiKey from ApiKeyFile when provided, and requiring one credential.
Examples
docker/Settings.example.json, docker/Settings.example.yml
Added ApiKeyFile field to account examples; updated ApiKey example and comments to indicate mutual-exclusion with ApiKeyFile.
Documentation & fixtures
docs/docs/getting-started/configuration.md, ImmichFrame.WebApi.Tests/Resources/TestV2.json, ImmichFrame.WebApi.Tests/Resources/TestV2.yml
Documented ApiKeyFile option and clarified mutual-exclusion; test fixtures updated to include ApiKeyFile.
Test helpers
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs
Tests updated to accept and assert an expectNullApiKeyFile flag; VerifyConfig/VerifyAccounts/VerifyProperties signatures changed to propagate the new flag and validate ApiKeyFile expectations.
Other test resources
ImmichFrame.WebApi.Tests/Resources/TestV1.json
Trailing newline/end-of-file formatting change only.
Misc (env/example)
docker/example.env
Minor formatting/comment adjustments (blank line added and comment character change).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ConfigLoader
    participant Parser
    participant ServerSettings
    participant Account
    participant FileSystem

    Client->>ConfigLoader: LoadConfig(path)
    ConfigLoader->>Parser: LoadConfigRaw(path)
    Parser-->>ConfigLoader: IServerSettings (parsed)
    ConfigLoader->>ServerSettings: Validate()
    ServerSettings->>ServerSettings: GeneralSettings.Validate()
    loop for each Account
        ServerSettings->>Account: ValidateAndInitialize()
        alt ApiKeyFile provided
            Account->>FileSystem: read(ApiKeyFile)
            FileSystem-->>Account: file contents
            Account->>Account: ApiKey = trimmed(contents)
        else Both ApiKey and ApiKeyFile provided
            Account-->>ServerSettings: throw validation error
        else No credential provided
            Account-->>ServerSettings: throw validation error
        end
    end
    ServerSettings-->>ConfigLoader: validated settings
    ConfigLoader-->>Client: IServerSettings (validated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • Mutual-exclusivity logic and exception message clarity in ServerAccountSettings.ValidateAndInitialize().
    • File reading: path validation, encoding assumptions, error propagation, and trimming behavior.
    • LoadConfigRaw preserving parsing behavior for JSON/YAML and not bypassing previous parsing semantics.
    • Test changes in ConfigLoaderTest.cs to ensure V1 adapters still surface null ApiKeyFile while V2 fixtures provide file paths.

"I nibble lines of config bright,
found keys asleep in file-light.
One check, one hop, I tidy code—
secrets safe along the road.
🐇🔑"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding an ApiKeyFile configuration option, which is the primary focus of this PR.
Linked Issues check ✅ Passed All objectives from issue #510 are met: ApiKeyFile option added to configuration, mutual exclusivity with ApiKey enforced via ValidateAndInitialize(), and existing ApiKey behavior preserved.
Out of Scope Changes check ✅ Passed All changes are directly related to adding the ApiKeyFile feature. Test updates, documentation updates, example configurations, and interface/implementation changes all support the stated objective.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ab6554 and 3f2e266.

📒 Files selected for processing (7)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
  • docker/Settings.example.json (2 hunks)
  • docker/Settings.example.yml (1 hunks)
  • docs/docs/getting-started/configuration.md (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)
  • VerifyConfig (71-75)
ImmichFrame.Core/Interfaces/IServerSettings.cs (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-105)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-105)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-105)
🔇 Additional comments (6)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (1)

23-28: LGTM! Clean separation of loading and validation.

The introduction of a public LoadConfig method that wraps LoadConfigRaw and ensures validation is performed is a clean design pattern. This guarantees that all loaded configurations are validated before use.

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (2)

66-66: LGTM! Appropriate no-op validation for V1 compatibility.

The no-op validate() implementations in the V1 adapters appropriately satisfy the interface contracts without changing V1 behavior, maintaining backward compatibility.


72-72: LGTM! Correct default for unsupported V1 feature.

Returning an empty string for ApiKeyFile correctly indicates that V1 settings didn't support this feature.

ImmichFrame.Core/Interfaces/IServerSettings.cs (2)

8-8: LGTM! Interface extensions support validation flow.

The addition of validate() methods to the interfaces enables consistent post-load validation across all configuration layers.


15-15: LGTM! ApiKeyFile property added to support the new feature.

The ApiKeyFile property extension to IAccountSettings cleanly supports the new API key file configuration option.

ImmichFrame.WebApi/Models/ServerSettings.cs (1)

27-35: LGTM! Validation properly cascades through the configuration hierarchy.

The validate() implementation correctly propagates validation to both GeneralSettings and all account configurations, ensuring comprehensive validation.

@jfly jfly force-pushed the issue-510-add-ApiKeyFile branch 2 times, most recently from 06b6ced to 82e6324 Compare November 20, 2025 22:34
@JW-CH JW-CH added the enhancement New feature or request label Nov 21, 2025
@JW-CH
Copy link
Collaborator

JW-CH commented Nov 28, 2025

Tests are failing for this. Could you fix the tests (add your new option to the base test files)? Also adding docs for this would be great!

@jfly jfly force-pushed the issue-510-add-ApiKeyFile branch from 82e6324 to 2594818 Compare December 2, 2025 06:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ImmichFrame.WebApi.Tests/Resources/TestV2.yml (1)

38-53: TestV2.yml sets both ApiKey and ApiKeyFile, conflicting with validation rules

ServerAccountSettings.validate() enforces that exactly one of ApiKey or ApiKeyFile is provided and, when ApiKeyFile is set, attempts to read the key from that file. In this fixture the first account has both fields populated, and ApiKeyFile points to a test string that is unlikely to exist as a real file. Any test or code path that loads this via ConfigLoader.LoadConfig (which now always calls config.validate()) will see validation/file‑IO failures, and even when validate() isn’t called this fixture no longer models a valid configuration.

If the intent is for this file to represent a valid V2 config, consider making the first account purely file‑based and relying on the second account to cover inline keys, e.g.:

- - ImmichServerUrl: Account1.ImmichServerUrl_TEST
-   ApiKey: Account1.ApiKey_TEST
-   ApiKeyFile: Account1.ApiKeyFile_TEST
+ - ImmichServerUrl: Account1.ImmichServerUrl_TEST
+   # Account1 uses file-based API key to exercise ApiKeyFile handling
+   ApiKeyFile: Account1.ApiKeyFile_TEST

This keeps coverage for both ApiKeyFile (account 1) and ApiKey (account 2) while aligning with the validation contract.

Also applies to: 68-68

ImmichFrame.WebApi.Tests/Resources/TestV2.json (1)

40-60: TestV2.json first account also violates ApiKey vs ApiKeyFile mutual exclusivity

As with the YAML fixture, the first account here sets both "ApiKey" and "ApiKeyFile", which contradicts the runtime contract enforced by ServerAccountSettings.validate(). Given ConfigLoader.LoadConfig now always invokes config.validate(), this JSON fixture can’t safely represent a “valid” V2 configuration.

To align it with the model while still covering both scenarios, you can mirror the YAML change and make account 1 file‑based and account 2 inline:

-    {
-      "ImmichServerUrl": "Account1.ImmichServerUrl_TEST",
-      "ApiKey": "Account1.ApiKey_TEST",
-      "ApiKeyFile": "Account1.ApiKeyFile_TEST",
+    {
+      "ImmichServerUrl": "Account1.ImmichServerUrl_TEST",
+      // Account1 uses file-based API key to exercise ApiKeyFile handling
+      "ApiKeyFile": "Account1.ApiKeyFile_TEST",

This keeps tests exercising ApiKeyFile without conflicting with the validation behavior.

🧹 Nitpick comments (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

3-9: Clarify and align the new validate() surface across settings interfaces

Centralizing validation via validate() on IServerSettings, IAccountSettings, and IGeneralSettings plus adding ApiKeyFile to IAccountSettings makes the contracts explicit. Two optional improvements to consider:

  • Use PascalCase Validate() to match usual .NET naming conventions for methods on interfaces.
  • Add XML doc comments explaining when callers are expected to invoke validation and what invariants (e.g., exactly one of ApiKey/ApiKeyFile) are guaranteed afterward, since the interface itself can’t enforce that validate() is called.

These are polish items; the current shape is functionally sound.

Also applies to: 11-28, 30-67

ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (1)

23-29: Nice separation of loading vs. validation; consider normalizing validation errors

Refactoring the existing logic into LoadConfigRaw and having LoadConfig call config.validate() centralizes validation nicely. For consistency with LoadConfigJson/LoadConfigYaml, you might want to catch exceptions from config.validate() here and wrap them in a SettingsNotValidException so callers see a uniform exception type for “config invalid” conditions.

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)

61-67: V1 adapters correctly implement the new surface; optional: add minimal validation

Returning ApiKeyFile => null for AccountSettingsV1Adapter is a good way to satisfy the new interface without changing legacy semantics, and the empty validate() implementations ensure older configs still load. If you want legacy/ENV configs to get some of the same safety as V2 (e.g., non-empty ImmichServerUrl and ApiKey, positive intervals), you could add lightweight checks inside these validate() methods, but it’s reasonable to leave them as no-ops if V1 is truly “compatibility only.”

Also applies to: 68-85, 87-124

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82e6324 and 2594818.

📒 Files selected for processing (9)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json (2 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
  • docker/Settings.example.json (2 hunks)
  • docker/Settings.example.yml (1 hunks)
  • docs/docs/getting-started/configuration.md (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • docker/Settings.example.json
  • ImmichFrame.WebApi/Models/ServerSettings.cs
🧰 Additional context used
🧬 Code graph analysis (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
ImmichFrame.WebApi/Helpers/SettingsExtensonMethods.cs (1)
  • IConfigSettable (6-9)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
🔇 Additional comments (3)
docs/docs/getting-started/configuration.md (2)

87-95: ApiKey / ApiKeyFile example now correctly reflects mutual exclusivity

The note plus using ApiKey as the active value and ApiKeyFile as a commented alternative matches the runtime validation rules and avoids showing an invalid config.


166-178: Custom CSS example and fenced block look correct

The volume mapping example and closing fence/newline are well‑formed; no further changes needed.

docker/Settings.example.yml (1)

36-40: Example config now demonstrates a valid ApiKey vs ApiKeyFile setup

Having ApiKey active with ApiKeyFile commented, plus the “Exactly one” comment, cleanly aligns the example with the validation contract.

@jfly jfly force-pushed the issue-510-add-ApiKeyFile branch 2 times, most recently from a6c6295 to c72de4d Compare December 2, 2025 07:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

8-8: Consider using PascalCase for method names.

C# conventions typically use PascalCase for public method names (Validate() rather than validate()). While this is functional, it deviates from standard .NET naming conventions.

-        public void validate();
+        public void Validate();

Apply the same change to all three validate() declarations (lines 8, 27, and 66).

Also applies to: 27-27, 66-66

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

110-117: Consider adding validation tests for mutual exclusivity.

The property verification logic correctly handles ApiKeyFile assertions. However, the test suite currently only tests config loading, not config validation.

Consider adding tests that verify:

  1. validate() succeeds when only ApiKey is provided
  2. validate() succeeds when only ApiKeyFile is provided (with a valid file)
  3. validate() throws when both are provided
  4. validate() throws when neither is provided

Would you like me to generate test cases for the validation logic?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2594818 and a6c6295.

📒 Files selected for processing (11)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (5 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json (1 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json (3 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml (3 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
  • docker/Settings.example.json (2 hunks)
  • docker/Settings.example.yml (1 hunks)
  • docs/docs/getting-started/configuration.md (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json
  • docker/Settings.example.json
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs
  • docker/Settings.example.yml
🧰 Additional context used
🧬 Code graph analysis (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
🔇 Additional comments (9)
ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

14-15: Note: ApiKey getter allows mutation from validate().

The ApiKey property is defined with only a getter in the interface, but the implementation in ServerAccountSettings.validate() assigns to it via ApiKey = File.ReadAllText(ApiKeyFile).Trim(). This works because the concrete class has a setter, but it's worth noting that the interface contract doesn't expose this mutability.

If you want the interface to explicitly reflect that ApiKey can be set during validation, consider adding a setter or documenting this behavior. Otherwise, this is acceptable as-is since the implementation handles it correctly.

ImmichFrame.WebApi.Tests/Resources/TestV2.yml (1)

38-56: Test data contains both ApiKey and ApiKeyFile, which would fail validation.

Both accounts specify ApiKey and ApiKeyFile together. Per the validation logic in ServerAccountSettings.validate(), this configuration throws: "Cannot specify both ApiKey and ApiKeyFile."

This works because LoadConfigJson/LoadConfigYaml don't invoke validate(), but it may cause confusion. Consider either:

  1. Removing ApiKeyFile from test accounts (keeping only ApiKey), or
  2. Adding a separate test file that exercises the mutual exclusivity validation

Are there separate tests that verify the mutual exclusivity validation throws appropriately when both fields are provided?

ImmichFrame.WebApi.Tests/Resources/TestV1.json (1)

57-57: LGTM!

Trailing newline added - good file formatting practice.

docs/docs/getting-started/configuration.md (1)

92-94: LGTM!

The documentation correctly demonstrates the mutual exclusivity between ApiKey and ApiKeyFile by showing one option active and the alternative commented out. The explanatory comment on line 92 is clear.

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

71-75: LGTM!

The test helper correctly propagates the expectNullApiKeyFile flag through the verification chain to handle both V1 (null ApiKeyFile) and V2 (populated ApiKeyFile) configurations.

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (4)

66-66: LGTM: No-op validation appropriate for V1 adapter.

The empty validate() implementation correctly satisfies the IServerSettings interface requirement while maintaining backward compatibility for V1 configs. Validation logic for V2 configs is handled in the actual models (e.g., ServerAccountSettings.validate()).


72-72: LGTM: Clear explanation for null ApiKeyFile.

Returning null for ApiKeyFile is correct since V1 configuration format didn't support file-based API keys. The inline comment clearly documents this design decision.


83-84: LGTM: Consistent with adapter pattern.

The no-op validate() implementation is appropriate for the V1 adapter, following the same pattern as the other adapter classes in this file.


122-123: LGTM: Interface requirement satisfied.

The no-op validate() implementation correctly implements the IGeneralSettings interface requirement while maintaining V1 backward compatibility.

@jfly
Copy link
Contributor Author

jfly commented Dec 2, 2025

Could you fix the tests (add your new option to the base test files)?

Done. This unfortunately got pretty janky. The tests as written are a little too generic (assuming every single string setting's value is a straightforward modification of the setting name).

Also adding docs for this would be great!

I made a very minor tweak to /docs/getting-started/configuration. Let me know if there's anything else you'd like!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

110-117: Conditional assertion correctly handles ApiKeyFile property.

The logic properly asserts that ApiKeyFile is null for V1 configs and has the expected test value for V2 configs. This maintains test coverage across config versions.

Consider adding dedicated tests for the validation logic (in a separate test file for ServerSettings.validate()) to cover:

  • Both ApiKey and ApiKeyFile set → exception
  • Neither ApiKey nor ApiKeyFile set → exception
  • ApiKeyFile points to valid file → ApiKey populated from file content
  • ApiKeyFile points to missing file → helpful error message

These validation scenarios would complement the config loading tests here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6c6295 and c72de4d.

📒 Files selected for processing (11)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (5 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json (1 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json (3 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml (3 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
  • docker/Settings.example.json (2 hunks)
  • docker/Settings.example.yml (1 hunks)
  • docs/docs/getting-started/configuration.md (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • docker/Settings.example.json
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml
🧰 Additional context used
🧬 Code graph analysis (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
ImmichFrame.WebApi/Helpers/SettingsExtensonMethods.cs (1)
  • IConfigSettable (6-9)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
🔇 Additional comments (6)
docker/Settings.example.yml (1)

38-40: LGTM! Example now demonstrates valid configuration.

The mutual exclusivity between ApiKey and ApiKeyFile is clearly documented, and the example shows a valid configuration with ApiKey set and ApiKeyFile commented out. This properly addresses the previous review feedback.

ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

8-8: LGTM! Clean and consistent interface extensions.

The addition of validate() methods across all three interfaces (IServerSettings, IAccountSettings, IGeneralSettings) and the nullable ApiKeyFile property to IAccountSettings are well-designed. The consistency across interfaces and the nullable type for the optional ApiKeyFile field follow good practices.

Also applies to: 15-15, 27-27, 66-66

docs/docs/getting-started/configuration.md (1)

15-15: LGTM! Documentation clearly explains the new option.

The documentation updates accurately reflect the ApiKeyFile feature, clearly indicating that exactly one of ApiKey or ApiKeyFile is required. The example configuration properly demonstrates mutual exclusivity by commenting out the ApiKeyFile option.

Also applies to: 92-94

ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2)

29-88: LGTM! LoadConfigRaw preserves existing loading logic.

The refactoring to extract the raw loading logic into a private method is clean and maintains all existing behavior (JSON current/legacy, YAML current/legacy, environment variables).


23-28: Good separation of loading and validation logic.

The new LoadConfig wrapper that calls LoadConfigRaw and then config.validate() is a clean architectural improvement. This ensures that configuration validation (including ApiKey/ApiKeyFile mutual exclusivity and file reading) is consistently enforced.

However, verification of the validate() implementation in ServerSettings.cs could not be completed due to repository access limitations. Ensure that the validate() method properly handles file I/O errors when ApiKeyFile points to a non-existent or unreadable file—specifically, the File.ReadAllText call should catch IOException and provide context like "ApiKeyFile '{path}' not found or could not be read."

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

32-32: LGTM! Tests properly handle V1 vs V2 ApiKeyFile expectations.

The updated test calls correctly pass expectNullApiKeyFile=true for V1 configs (which don't support ApiKeyFile) and expectNullApiKeyFile=false for V2 configs (which include ApiKeyFile). This ensures backward compatibility.

Also applies to: 42-42, 50-50, 68-68

Copy link
Collaborator

@3rob3 3rob3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to submit this a few weeks ago, sorry.

@jfly jfly force-pushed the issue-510-add-ApiKeyFile branch from c72de4d to 77453aa Compare December 2, 2025 19:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

11-28: Make account-level validation idempotent when using ApiKeyFile

The addition of ApiKeyFile and validate() on IAccountSettings is the right abstraction. One concern from the ServerAccountSettings.validate() implementation (in ImmichFrame.WebApi/Models/ServerSettings.cs) is that it reads the API key from ApiKeyFile into ApiKey but leaves ApiKeyFile non‑null and then rejects configurations where both are set. This makes validate() unsafe to call more than once: a second call after a successful first call will see both ApiKey and ApiKeyFile populated and throw.

Consider making the account-level validation idempotent, for example by clearing ApiKeyFile after a successful file read or restructuring the logic so that a second call with an already-loaded key does not fail. For instance:

public void validate()
{
    if (!string.IsNullOrWhiteSpace(ApiKeyFile) && !string.IsNullOrWhiteSpace(ApiKey))
    {
        throw new Exception("Cannot specify both ApiKey and ApiKeyFile. Please provide only one.");
    }

    if (!string.IsNullOrWhiteSpace(ApiKeyFile) && string.IsNullOrWhiteSpace(ApiKey))
    {
        ApiKey = File.ReadAllText(ApiKeyFile).Trim();
        ApiKeyFile = null; // optional but makes repeated validate() calls safe
    }

    if (string.IsNullOrWhiteSpace(ApiKey))
    {
        throw new InvalidOperationException("Either ApiKey or ApiKeyFile must be provided.");
    }
}

This keeps the mutual-exclusion semantics while avoiding surprises if validate() is ever called more than once on the same instance.

♻️ Duplicate comments (1)
docker/Settings.example.json (1)

42-43: Configuration example violates mutual exclusivity (duplicate concern).

This example still includes both ApiKey and ApiKeyFile, which will fail validation since the codebase enforces that only one can be set. This was flagged in a previous review. Since you and others preferred not to use JSON comments, consider either:

  • Remove ApiKeyFile to show the traditional approach, or
  • Remove ApiKey to showcase the new feature.

A working example is more valuable to users than demonstrating both options that cannot coexist.

🧹 Nitpick comments (4)
ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

30-67: No-op IGeneralSettings.validate() is fine for now, but document/extend later if needed

Adding validate() to IGeneralSettings aligns with the pattern used for IServerSettings and IAccountSettings, and the current no-op implementation is acceptable. If you later add invariants for general settings (e.g., validating Interval, Language, or weather settings), you already have a hook to put them in.

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (2)

46-51: Consider avoiding “both ApiKey and ApiKeyFile set” in V2 sample configs

For V2 JSON/YAML, VerifyConfig(config, true, false) still expects both ApiKey and ApiKeyFile string properties to be populated with the ..._TEST pattern. That works for the reflection-based shape tests, but it conflicts with the runtime validation rule that you can't specify both an inline API key and ApiKeyFile.

It may be cleaner (and less confusing for future readers) to adjust these tests so that V2 resources model one of the valid configurations (either inline key or file-based key), and add a separate, focused test that asserts the mutual-exclusion behavior via validate(), instead of encoding “both set” into the generic sample config.

Also applies to: 64-69


71-88: Targeted ApiKeyFile handling in Verify* helpers is reasonable

Threading expectNullApiKeyFile through VerifyConfigVerifyAccountsVerifyProperties and special-casing prop.Name == "ApiKeyFile" keeps the existing generic “property name → value pattern” assertions intact without over-complicating the test harness.

The conditional:

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);
}

matches the current adapter behavior for V1 vs V2 and keeps the change localized.

Also applies to: 107-117

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)

61-67: V1 adapters correctly expose ApiKeyFile as unsupported and provide no-op validation

Having AccountSettingsV1Adapter.ApiKeyFile => null and implementing empty validate() methods on ServerSettingsV1Adapter, AccountSettingsV1Adapter, and GeneralSettingsV1Adapter is an appropriate way to satisfy the new interface contracts without altering legacy V1 behavior. This also aligns with the tests that expect ApiKeyFile to be null for V1.

If you expect to add more cross-version validation in the future, a brief comment on ServerSettingsV1Adapter.validate() noting that V1 validation is intentionally a no-op would make the intent explicit.

Also applies to: 68-85, 87-124

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c72de4d and 77453aa.

📒 Files selected for processing (12)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (5 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json (1 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json (3 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml (3 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
  • docker/Settings.example.json (2 hunks)
  • docker/Settings.example.yml (1 hunks)
  • docker/example.env (2 hunks)
  • docs/docs/getting-started/configuration.md (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • docker/example.env
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml
  • ImmichFrame.WebApi/Models/ServerSettings.cs
  • docs/docs/getting-started/configuration.md
  • docker/Settings.example.yml
🧰 Additional context used
🧬 Code graph analysis (3)
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)
  • ServerSettingsV1Adapter (61-125)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2)
  • IServerSettings (23-28)
  • IServerSettings (29-88)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
ImmichFrame.Core/Interfaces/IServerSettings.cs (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
🔇 Additional comments (3)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (1)

23-29: Centralizing validation in LoadConfig looks good

Wrapping the existing load logic in LoadConfigRaw and calling config.validate() in LoadConfig is a clean separation of concerns and makes it clear where validation happens. This will ensure ApiKey/ApiKeyFile rules are enforced consistently for all config sources that go through LoadConfig.

ImmichFrame.Core/Interfaces/IServerSettings.cs (1)

3-9: Interface-level validate() makes sense for a single validation entry point

Adding validate() to IServerSettings provides a clear contract for running configuration validation after loading and matches how ConfigLoader.LoadConfig is now structured.

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

28-43: V1 tests correctly assert ApiKeyFile is unavailable

Passing expectNullApiKeyFile: true for V1 JSON and env paths and having the adapter expose ApiKeyFile => null is a good way to keep the generic property assertions while signaling that V1 configs don't support file-based API keys.

If given, `ApiKeyFile` points at a file that ImmichFrame will read to
get the api key. `ApiKeyFile` is mutually exclusive with `ApiKey`.

This fixes immichFrame#510
@jfly jfly force-pushed the issue-510-add-ApiKeyFile branch from 77453aa to f5bb164 Compare December 3, 2025 18:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
docker/Settings.example.json (1)

41-43: JSON example still invalid: sets both ApiKey and ApiKeyFile

This example config will fail validation because ServerAccountSettings.validate() throws when both ApiKey and ApiKeyFile are provided. Users copying this file will get an immediate error.

Since JSON can’t express “either/or” with comments, the example should keep only one of the two fields so it’s a valid, runnable config.

One simple fix is to drop ApiKeyFile here and rely on the YAML/docs to showcase that option:

   "Accounts": [
     {
       "ImmichServerUrl": "REQUIRED",
-      "ApiKey": "super-secret-api-key",
-      "ApiKeyFile": "/path/to/api.key",
+      "ApiKey": "super-secret-api-key",
       "ImagesFromDate": null,

Alternatively, you could remove ApiKey and keep only ApiKeyFile, but only one should remain in this JSON example.

Also applies to: 62-62

🧹 Nitpick comments (1)
ImmichFrame.WebApi/Models/ServerSettings.cs (1)

27-35: Validation flow and ApiKeyFile handling look correct; consider tightening exception type

  • ServerSettings.validate() correctly walks GeneralSettings and each account, wiring validation into the load flow.
  • ServerAccountSettings.validate() enforces:
    • ApiKey/ApiKeyFile mutual exclusivity,
    • reading the key from ApiKeyFile when present,
    • and requiring an effective API key at the end.

To keep exceptions consistent and more idiomatic, you might want the mutual-exclusivity branch to throw InvalidOperationException (like the “either ApiKey or ApiKeyFile must be provided” case) instead of a bare Exception, e.g.:

-    if (!string.IsNullOrWhiteSpace(ApiKeyFile))
+    if (!string.IsNullOrWhiteSpace(ApiKeyFile))
     {
         if (!string.IsNullOrWhiteSpace(ApiKey))
         {
-            throw new Exception("Cannot specify both ApiKey and ApiKeyFile. Please provide only one.");
+            throw new InvalidOperationException("Cannot specify both ApiKey and ApiKeyFile. Please provide only one.");
         }
         ApiKey = File.ReadAllText(ApiKeyFile).Trim();
     }

Functionality is fine as-is; this is just a small polish.

Also applies to: 74-75, 81-81, 94-109

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77453aa and f5bb164.

📒 Files selected for processing (12)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (5 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json (1 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json (3 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.yml (3 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
  • docker/Settings.example.json (2 hunks)
  • docker/Settings.example.yml (1 hunks)
  • docker/example.env (2 hunks)
  • docs/docs/getting-started/configuration.md (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docker/example.env
🚧 Files skipped from review as they are similar to previous changes (4)
  • ImmichFrame.WebApi.Tests/Resources/TestV2.json
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json
  • ImmichFrame.Core/Interfaces/IServerSettings.cs
  • docker/Settings.example.yml
🧰 Additional context used
🧬 Code graph analysis (4)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
ImmichFrame.WebApi/Helpers/SettingsExtensonMethods.cs (1)
  • IConfigSettable (6-9)
ImmichFrame.WebApi/Models/ServerSettings.cs (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • validate (8-8)
  • validate (27-27)
  • validate (66-66)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • validate (66-66)
  • validate (84-84)
  • validate (123-123)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • validate (27-35)
  • validate (74-74)
  • validate (94-109)
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)
  • ServerSettingsV1Adapter (61-125)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2)
  • IServerSettings (23-28)
  • IServerSettings (29-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (5)
docs/docs/getting-started/configuration.md (1)

30-30: ApiKey / ApiKeyFile docs and example look consistent with validation rules

The text and YAML example now clearly express mutual exclusivity and show only one of ApiKey / ApiKeyFile active, which matches the runtime validation behavior.

Also applies to: 107-109

ImmichFrame.WebApi.Tests/Resources/TestV2.yml (1)

40-40: Test fixture updates align with reflection-based verifier

The added ApiKeyFile entries and People indentation match the VerifyProperties pattern (prefix + PropName + "_TEST"), so these resources should integrate cleanly with the updated tests.

Also applies to: 56-56, 69-69

ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (1)

23-29: Centralizing validation in LoadConfig is a good step

Refactoring the existing logic into LoadConfigRaw and having LoadConfig call config.validate() before returning gives you a single, consistent validation point without changing the loading strategy.

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

32-32: Tests correctly distinguish V1 (no ApiKeyFile) from V2 (with ApiKeyFile)

The added expectNullApiKeyFile flag and its use in VerifyConfig / VerifyAccounts / VerifyProperties align with the adapters:

  • V1 adapters surface ApiKeyFile as null, and tests now assert that.
  • V2 JSON/YAML fixtures set Account{n}.ApiKeyFile_TEST, and the prefix-based assertion covers that.

This gives good coverage of the new property without disturbing existing reflection-based checks.

Also applies to: 42-42, 50-50, 69-69, 71-75, 77-83, 87-118, 181-181

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)

66-66: V1 adapter changes cleanly satisfy new interfaces without altering behavior

Adding no-op validate() methods and exposing ApiKeyFile as null on the V1 adapters keeps legacy configs working while allowing ConfigLoader.LoadConfig to call validate() uniformly across versions.

Also applies to: 72-72, 84-84, 123-123

@jfly jfly requested a review from 3rob3 December 3, 2025 19:13
@JW-CH JW-CH self-assigned this Dec 4, 2025
@JW-CH
Copy link
Collaborator

JW-CH commented Dec 8, 2025

Thank you for the changes @jfly! I added 'support' (was not much) for the ApiKeyFile in the v1-Settings and simplified the test. I hope you don't mind :P

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)

67-67: V1 adapters bypass validation.

The no-op Validate() implementations in V1 adapters mean that V1 configurations skip mutual exclusivity checks and file reading logic. This allows V1 configs to have both ApiKey and ApiKeyFile set without errors.

If this is intentional for backward compatibility, consider adding a comment explaining the design decision. Otherwise, consider implementing at least basic validation:

-public void Validate() { }
+public void Validate() 
+{
+    // V1 configs bypass validation for backward compatibility.
+    // Users should migrate to V2 format for validation support.
+}

Alternatively, if V1 configs should support ApiKeyFile functionality, implement file reading and mutual exclusivity checks similar to V2.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5bb164 and 11374c8.

📒 Files selected for processing (6)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json (1 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (2 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (4 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ImmichFrame.WebApi/Models/ServerSettings.cs
🧰 Additional context used
🧬 Code graph analysis (3)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • Validate (8-8)
  • Validate (27-27)
  • Validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • Validate (27-35)
  • Validate (74-74)
  • Validate (94-109)
ImmichFrame.WebApi/Helpers/SettingsExtensonMethods.cs (1)
  • IConfigSettable (6-9)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • Validate (67-67)
  • Validate (85-85)
  • Validate (124-124)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • Validate (27-35)
  • Validate (74-74)
  • Validate (94-109)
ImmichFrame.WebApi/Helpers/SettingsExtensonMethods.cs (1)
  • IConfigSettable (6-9)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (3)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • Validate (67-67)
  • Validate (85-85)
  • Validate (124-124)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • Validate (8-8)
  • Validate (27-27)
  • Validate (66-66)
ImmichFrame.WebApi/Models/ServerSettings.cs (3)
  • Validate (27-35)
  • Validate (74-74)
  • Validate (94-109)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (10)
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)

52-58: LGTM!

The whitespace adjustments have no functional impact.

ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (1)

23-28: Clean separation of loading and validation.

The refactoring separates configuration loading from validation, improving the design. The public LoadConfig method now explicitly validates after loading, ensuring configuration errors (including mutual exclusivity violations and missing files) are caught early.

ImmichFrame.Core/Interfaces/IServerSettings.cs (4)

8-8: LGTM!

The addition of Validate() to IServerSettings establishes a consistent validation contract across configuration types.


15-15: LGTM!

The ApiKeyFile property enables file-based API key storage as intended by the PR objectives.


27-27: LGTM!

The addition of Validate() to IAccountSettings aligns with the validation contract established across all settings interfaces.


66-66: LGTM!

The addition of Validate() to IGeneralSettings completes the validation contract across all settings interfaces.

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (4)

12-12: LGTM!

Adding ApiKeyFile to the V1 model maintains backward compatibility while supporting the new file-based configuration option.


73-73: LGTM!

Exposing ApiKeyFile through the adapter maintains interface compliance with IAccountSettings.


85-85: Consistent with adapter design.

The no-op Validate() in AccountSettingsV1Adapter is consistent with the overall V1 adapter pattern of bypassing validation. See the comment on line 67 for discussion of this design decision.


124-124: Consistent with adapter design.

The no-op Validate() in GeneralSettingsV1Adapter completes the consistent V1 adapter pattern of bypassing validation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)

66-66: No-op Validate() in V1 adapters may deserve either delegation or an explicit comment

Right now all three V1 adapter Validate() methods are empty, while the v2 ServerSettings implementation delegates to GeneralSettings.Validate() and each account.Validate(). That means any new validation logic (like enforcing API key presence or ApiKey/ApiKeyFile semantics) will not run for legacy V1 configs.

If this is intentional for backward compatibility, consider adding a brief comment like:

public void Validate() { } // V1 had no validation; keep as no-op to preserve behavior.

If you eventually want consistent validation across versions, you could instead mirror the v2 pattern:

public void Validate()
{
    GeneralSettings.Validate();
    foreach (var account in Accounts)
    {
        account.Validate();
    }
}

and then add minimal checks in the nested adapters as needed (e.g., ensure ApiKey is non-empty).

Also applies to: 84-84, 123-123

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11374c8 and 85108a4.

📒 Files selected for processing (3)
  • ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (5 hunks)
  • ImmichFrame.WebApi.Tests/Resources/TestV1.json (1 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
ImmichFrame.Core/Interfaces/IServerSettings.cs (4)
  • Validate (8-8)
  • Validate (27-27)
  • Validate (66-66)
  • IServerSettings (3-7)
ImmichFrame.WebApi/Models/ServerSettings.cs (5)
  • Validate (27-35)
  • Validate (74-74)
  • Validate (94-109)
  • ServerSettings (8-26)
  • GeneralSettings (28-63)
ImmichFrame.WebApi/Helpers/Config/ConfigLoader.cs (1)
  • ConfigLoader (10-148)
ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (1)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)
  • ServerSettingsV1Adapter (61-125)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (5)
ImmichFrame.WebApi.Tests/Resources/TestV1.json (1)

1-57: Previous mutual-exclusivity concern has been properly resolved.

The prior review flagged that this test resource contained both ApiKey and ApiKeyFile, violating the mutual-exclusivity constraint. The current state correctly contains only ApiKey (line 3) with no ApiKeyFile field, modeling a valid V1 configuration. This separation—where V1 tests use ApiKey alone and V2 tests handle ApiKeyFile support—is a clean design that avoids modeling invalid configurations in baseline test data.

The trailing newline addition is a minor formatting improvement with no functional impact.

ImmichFrame.WebApi.Tests/Helpers/Config/ConfigLoaderTest.cs (3)

28-68: LGTM! V1/V2 config handling correctly distinguishes ApiKeyFile expectations.

The test invocations correctly pass expectNullApiKeyFile = true for V1 configs (which don't support file-based API keys per the V1 adapter) and false for V2 configs (which do support the new option).


71-87: LGTM! Clean parameter propagation through the verification chain.

The new expectNullApiKeyFile parameter flows logically from VerifyConfig through VerifyAccounts to VerifyProperties. The default value of false in VerifyProperties is sensible since most configurations should have a non-null ApiKeyFile.


110-117: LGTM! ApiKeyFile validation logic correctly handles both V1 and V2 scenarios.

The conditional check appropriately expects null for V1 configs (which don't support file-based API keys) and the standard test pattern for V2 configs. The implementation is consistent with the existing generic property verification approach.

Verify that file reading functionality and mutual exclusivity validation for ApiKeyFile vs ApiKey are tested elsewhere, as this test focuses on config loading/parsing rather than business logic validation.

ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)

72-72: ApiKeyFile placeholder returning null for V1 is reasonable

Mapping ApiKeyFile to null here cleanly expresses that V1 configs cannot opt into file-based API keys while still satisfying the IAccountSettings contract. Given that the validation logic in the v2 model only acts when ApiKeyFile is non-empty, this avoids accidental file reads for legacy settings and keeps behavior unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
ImmichFrame.WebApi/Models/ServerSettings.cs (2)

27-35: Validation flow is consistent; consider clearer handling of missing accounts

The Validate method correctly mirrors the adapter pattern by validating general settings and then each account. If AccountsImpl can ever be null (e.g., Accounts omitted in config), this will currently surface as a NullReferenceException when iterating. If you prefer a clearer config error, you could optionally add an explicit check and throw a more descriptive InvalidOperationException when no accounts are configured.


94-109: ValidateAndInitialize logic is sound; watch out for non-idempotence

The method enforces the intended rules well:

  • Disallows specifying both ApiKey and ApiKeyFile.
  • Reads and trims the key from ApiKeyFile when provided.
  • Ensures that after initialization, an API key is always present (from either source), rejecting empty/whitespace-only values from the file.

One subtle edge case: after the first successful run where ApiKey is populated from ApiKeyFile, any subsequent call on the same ServerAccountSettings instance will see both ApiKey and ApiKeyFile as non-empty and throw the “Cannot specify both” exception, even though the state is actually valid. If ValidateAndInitialize is ever called more than once per instance (now or in future refactors), this ceases to be idempotent.

If you want to future-proof this, consider one of:

  • Tracking a “loaded from file” flag and skipping the mutual-exclusivity check when ApiKey was previously empty and just set from ApiKeyFile.
  • Or, documenting and enforcing that ValidateAndInitialize is single-shot and only called internally via ServerSettings.Validate.

Also, for consistency with the final check, you may optionally switch the first throw new Exception(...) to InvalidOperationException.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85108a4 and fc4fb84.

📒 Files selected for processing (3)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs (3 hunks)
  • ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3 hunks)
  • ImmichFrame.WebApi/Models/ServerSettings.cs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ImmichFrame.Core/Interfaces/IServerSettings.cs
🧰 Additional context used
🧬 Code graph analysis (2)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (1)
ImmichFrame.WebApi/Models/ServerSettings.cs (4)
  • Validate (27-35)
  • Validate (74-74)
  • GeneralSettings (38-75)
  • ValidateAndInitialize (94-109)
ImmichFrame.WebApi/Models/ServerSettings.cs (2)
ImmichFrame.Core/Interfaces/IServerSettings.cs (3)
  • Validate (8-8)
  • Validate (66-66)
  • ValidateAndInitialize (27-27)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (3)
  • Validate (66-73)
  • Validate (130-130)
  • ValidateAndInitialize (91-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (6)
ImmichFrame.WebApi/Helpers/Config/ServerSettingsV1.cs (4)

66-73: Adapter-level validation wiring looks correct

ServerSettingsV1Adapter.Validate mirrors the main model’s flow by delegating to GeneralSettings.Validate() and then ValidateAndInitialize() on each account. This keeps V1 configs participating in the common validation pipeline without changing their semantics.


79-79: Explicitly returning null for ApiKeyFile is appropriate for V1

Hard-coding ApiKeyFile to null clearly documents that V1 configs don’t support file-based API keys while still satisfying the updated interface.


91-92: No-op ValidateAndInitialize keeps V1 accounts compatible

An empty ValidateAndInitialize is a reasonable choice here: V1 accounts don’t need extra initialization, but the method being present lets shared code treat all account settings uniformly.


129-131: No-op general settings validation is fine for now

Providing a Validate stub on GeneralSettingsV1Adapter satisfies the interface and keeps room for future checks without impacting existing behavior.

ImmichFrame.WebApi/Models/ServerSettings.cs (2)

74-74: No-op GeneralSettings.Validate is acceptable

Adding a stub Validate on GeneralSettings keeps the interface consistent and allows shared validation calls, without changing existing behavior.


81-81: ApiKeyFile shape and default look good

Introducing nullable ApiKeyFile with a null default cleanly models the optional file-based credential and works well with the subsequent validation logic.

@JW-CH
Copy link
Collaborator

JW-CH commented Dec 8, 2025

Sorry for the back and forth. Usually I don't like to commit to PRs directly, but I thought I'd just rename one method... I have renamed the validate-Method (twice) and also implemented the method on the IServerSettings implementations (v1 adapter will just run the empty methods).

I'll leave the PR open until you guys get a chance to comment, after I will merge it :)

Copy link
Contributor Author

@jfly jfly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your latest changes LGTM, @JW-CH! Thanks!

@3rob3
Copy link
Collaborator

3rob3 commented Dec 8, 2025

LGTM 👍

@JW-CH JW-CH merged commit 55dfcf3 into immichFrame:main Dec 8, 2025
7 checks passed
@jfly jfly deleted the issue-510-add-ApiKeyFile branch December 8, 2025 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: configurable ApiKeyFile in addition to existing ApiKey

3 participants