Skip to content

[WIP] chore(api): create modular config interface #1352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

pujitm
Copy link
Member

@pujitm pujitm commented Apr 10, 2025

Summary by CodeRabbit

  • New Features
    • Introduced a new configuration option for module paths with environment-specific settings.
    • Enhanced system reliability by automatically ensuring necessary directory structures exist.
    • Rolled out a robust configuration management framework that streamlines state persistence and improves error handling.

Copy link
Contributor

coderabbitai bot commented Apr 10, 2025

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Walkthrough

This pull request introduces a new configuration variable, CONFIG_MODULES_HOME, in several environment files with environment-specific paths. In addition, a constant is exposed in the application for using this variable, and the code now ensures the requisite directory is created at startup. New configuration management files have been added under the unraid-api directory to handle state configuration, registration, persistence, and dependency injection.

Changes

File(s) Change Summary
api/.env.development, api/.env.production, api/.env.staging, api/.env.test Added new variable CONFIG_MODULES_HOME with different path values per environment
api/src/environment.ts Added constant CONFIG_MODULES_HOME retrieving value from process.env
api/src/index.ts Added mkdirSync call using CONFIG_MODULES_HOME to ensure directory existence
api/src/unraid-api/config/api-state.model.ts Added ApiStateConfig class and ApiStateConfigOptions interface with methods: persist, parseConfig, load, update
api/src/unraid-api/config/api-state.register.ts Added ApiStateConfigModule class with static async register method setting up providers (ConfigProvider and PersistenceProvider)
api/src/unraid-api/config/api-state.service.ts Added ScheduledConfigPersistence class and ApiStateConfigPersistenceOptions interface to manage scheduled persistence with error handling
api/src/unraid-api/config/config.injection.ts Added custom decorator function InjectConfig for dependency injection of configuration settings
api/src/unraid-api/config/config.interface.ts Added interfaces ConfigFeatures and ConfigMetadata to define configuration structure
api/src/unraid-api/config/config.registry.ts Added makeConfigToken function and ConfigRegistry class with methods to register and generate tokens for configuration types

Sequence Diagram(s)

sequenceDiagram
    participant App as Application Startup
    participant FS as File System (mkdirSync)
    participant Env as Environment Module

    App->>Env: Retrieve CONFIG_MODULES_HOME value
    Env-->>App: Returns path value
    App->>FS: Call mkdirSync(<CONFIG_MODULES_HOME>, {recursive: true})
    FS-->>App: Directory created (if not exists)
Loading
sequenceDiagram
    participant Main as Main App
    participant Reg as ApiStateConfigModule.register()
    participant Conf as ApiStateConfig Instance
    participant Prov as Providers (ConfigProvider & PersistenceProvider)
    participant Scheduler as ScheduledConfigPersistence

    Main->>Reg: Call register(options)
    Reg->>Conf: Instantiate ApiStateConfig with default settings
    Reg->>Prov: Setup ConfigProvider and PersistenceProvider
    Prov->>Scheduler: Initialize scheduled persistence
    Scheduler-->>Prov: Scheduled task ready
    Reg-->>Main: Return DynamicModule with providers
Loading

Suggested reviewers

  • mdatelle
  • zackspear

Poem

In code we trust, our paths align,
CONFIG_MODULES_HOME lights the design.
Directories rise, from fs’s art,
Configs register, playing their part.
Bugs run away, as systems sing—
New features bloom, and joy they bring!
🚀🎉


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@pujitm
Copy link
Member Author

pujitm commented Apr 10, 2025

@coderabbitai pause

Copy link
Contributor

@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 (9)
api/src/index.ts (2)

6-6: Fix import order according to linting rules

The static analysis is flagging an ordering issue with the imports.

-import { unlinkSync, mkdirSync } from 'fs';
+import { mkdirSync, unlinkSync } from 'fs';
🧰 Tools
🪛 GitHub Check: Build API

[failure] 6-6:
Replace unlinkSync,·mkdir with mkdirSync,·unlink

🪛 GitHub Check: Test API

[failure] 6-6:
Replace unlinkSync,·mkdir with mkdirSync,·unlink


17-17: Fix import order according to linting rules

The static analysis is flagging an ordering issue with the environment imports.

-import { environment, PORT, CONFIG_MODULES_HOME } from '@app/environment.js';
+import { CONFIG_MODULES_HOME, environment, PORT } from '@app/environment.js';
🧰 Tools
🪛 GitHub Check: Build API

[failure] 17-17:
Replace environment,·PORT,·CONFIG_MODULES_HOME with CONFIG_MODULES_HOME,·environment,·PORT

🪛 GitHub Check: Test API

[failure] 17-17:
Replace environment,·PORT,·CONFIG_MODULES_HOME with CONFIG_MODULES_HOME,·environment,·PORT

api/src/unraid-api/config/config.interface.ts (1)

1-9: Consider the static analysis suggestion about empty interface

While the empty ConfigFeatures interface serves as a container for type extensions, the static analyzer flags it as suspicious. Since it's meant to be extended, keeping it as an interface is justified, but you could add documentation to clarify this for static analyzers.

/**
 * Container record of config names to their types. Used for type completion on registered configs.
 * Config authors should redeclare/merge this interface with their config names as the keys 
 * and implementation models as the types.
+ *
+ * @remarks This is intentionally an empty interface to be extended by other modules.
 */
export interface ConfigFeatures {};
🧰 Tools
🪛 Biome (1.9.4)

[error] 8-8: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

api/src/unraid-api/config/config.registry.ts (1)

14-30: Consider refactoring the static-only class

The static analysis tool flags this class for having only static members. While functionally correct, consider these alternatives:

  1. Use the class name instead of this in static methods
  2. Convert to a namespace or module-level object with exported functions

Option 1: Use class name instead of this:

export class ConfigRegistry {
    /** A map of config names to their implementation models. */
    private static configTypes = new Map<string, string>();

    static register(configName: string, configType: string) {
-        this.configTypes.set(configName, configType);
+        ConfigRegistry.configTypes.set(configName, configType);
    }

    static getConfigType(configName: string) {
-        return this.configTypes.get(configName);
+        return ConfigRegistry.configTypes.get(configName);
    }

    static getConfigToken(configName: string) {
        const configType = ConfigRegistry.getConfigType(configName) ?? '';
        return makeConfigToken(configName, configType);
    }
}

Option 2: Convert to module-level functions:

/** A map of config names to their implementation models. */
const configTypes = new Map<string, string>();

export function registerConfig(configName: string, configType: string) {
    configTypes.set(configName, configType);
}

export function getConfigType(configName: string) {
    return configTypes.get(configName);
}

export function getConfigToken(configName: string) {
    const configType = getConfigType(configName) ?? '';
    return makeConfigToken(configName, configType);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 14-30: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 19-19: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 23-23: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

api/src/unraid-api/config/api-state.service.ts (2)

4-5: Consider using absolute import paths.

Static analysis flags these relative imports. If your style guide requires absolute paths, consider updating these imports accordingly.

- import type { ApiStateConfig } from './api-state.model.js';
- import { makeConfigToken } from './config.registry.js';
+ import type { ApiStateConfig } from '@app/unraid-api/config/api-state.model.js';
+ import { makeConfigToken } from '@app/unraid-api/config/config.registry.js';
🧰 Tools
🪛 GitHub Check: Build API

[failure] 4-4:
import statements should have an absolute path

🪛 GitHub Check: Test API

[failure] 4-4:
import statements should have an absolute path


57-70: Consider handling multiple invocations of setup().

If this method is called more than once, multiple intervals could be registered for the same token, leading to unexpected behavior. An optional improvement is to guard against re-initializing.

setup() {
    if (this.schedulerRegistry.doesExists('interval', this.token)) {
+       this.logger.warn(`Persistence already set up for token ${this.token}. Skipping re-initialization.`);
        return;
    }
    ...
}
api/src/unraid-api/config/api-state.register.ts (2)

4-7: Consider using absolute import paths.

Your build pipeline reports warnings for relative paths. Converting them to absolute paths may align with your import style guidelines.

- import type { ApiStateConfigOptions } from './api-state.model.js';
- import type { ApiStateConfigPersistenceOptions } from './api-state.service.js';
- import { ApiStateConfig } from './api-state.model.js';
- import { ScheduledConfigPersistence } from './api-state.service.js';
+ import type { ApiStateConfigOptions } from '@app/unraid-api/config/api-state.model.js';
+ import type { ApiStateConfigPersistenceOptions } from '@app/unraid-api/config/api-state.service.js';
+ import { ApiStateConfig } from '@app/unraid-api/config/api-state.model.js';
+ import { ScheduledConfigPersistence } from '@app/unraid-api/config/api-state.service.js';
🧰 Tools
🪛 GitHub Check: Build API

[failure] 4-4:
import statements should have an absolute path


[failure] 5-5:
import statements should have an absolute path


[failure] 6-6:
import statements should have an absolute path


[failure] 7-7:
import statements should have an absolute path

🪛 GitHub Check: Test API

[failure] 4-4:
import statements should have an absolute path


[failure] 5-5:
import statements should have an absolute path


[failure] 6-6:
import statements should have an absolute path


[failure] 7-7:
import statements should have an absolute path


13-45: Consider refactoring to a plain function rather than a static-only class.

A lint rule warns about classes containing only static methods. If instance state is unnecessary, a simpler function can suffice. Alternatively, keep the class if you anticipate adding instance functionality later.

-export class ApiStateConfigModule {
-    private static readonly logger = new Logger(ApiStateConfigModule.name);
-
-    static async register<ConfigType>(
-        options: ApiStateRegisterOptions<ConfigType>
-    ): Promise<DynamicModule> {
-        ...
-        return {
-            module: ApiStateConfigModule,
-            providers: [ConfigProvider, PersistenceProvider],
-            exports: [ConfigProvider],
-        };
-    }
-}
+
+export async function registerApiStateConfigModule<ConfigType>(
+  options: ApiStateRegisterOptions<ConfigType>
+): Promise<DynamicModule> {
+  const logger = new Logger('ApiStateConfigModule');
+  ...
+  return {
+    module: class {},
+    providers: [ConfigProvider, PersistenceProvider],
+    exports: [ConfigProvider],
+  };
+}
🧰 Tools
🪛 Biome (1.9.4)

[error] 13-45: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

api/src/unraid-api/config/api-state.model.ts (1)

10-10: Consider using absolute import paths.

Static analysis warns about these imports. Converting them to absolute references could satisfy your project’s build checks.

- import { ConfigRegistry } from './config.registry.js';
+ import { ConfigRegistry } from '@app/unraid-api/config/config.registry.js';
🧰 Tools
🪛 GitHub Check: Build API

[failure] 10-10:
import statements should have an absolute path

🪛 GitHub Check: Test API

[failure] 10-10:
import statements should have an absolute path

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bb9efc and 69e1b71.

📒 Files selected for processing (12)
  • api/.env.development (1 hunks)
  • api/.env.production (1 hunks)
  • api/.env.staging (1 hunks)
  • api/.env.test (1 hunks)
  • api/src/environment.ts (1 hunks)
  • api/src/index.ts (3 hunks)
  • api/src/unraid-api/config/api-state.model.ts (1 hunks)
  • api/src/unraid-api/config/api-state.register.ts (1 hunks)
  • api/src/unraid-api/config/api-state.service.ts (1 hunks)
  • api/src/unraid-api/config/config.injection.ts (1 hunks)
  • api/src/unraid-api/config/config.interface.ts (1 hunks)
  • api/src/unraid-api/config/config.registry.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
api/src/unraid-api/config/config.injection.ts (2)
api/src/unraid-api/config/config.interface.ts (1)
  • ConfigFeatures (8-8)
api/src/unraid-api/config/config.registry.ts (1)
  • ConfigRegistry (14-30)
api/src/index.ts (1)
api/src/environment.ts (1)
  • CONFIG_MODULES_HOME (89-89)
api/src/unraid-api/config/api-state.register.ts (2)
api/src/unraid-api/config/api-state.model.ts (2)
  • ApiStateConfigOptions (12-17)
  • ApiStateConfig (19-103)
api/src/unraid-api/config/api-state.service.ts (2)
  • ApiStateConfigPersistenceOptions (7-16)
  • ScheduledConfigPersistence (18-74)
api/src/unraid-api/config/api-state.service.ts (2)
api/src/unraid-api/config/api-state.model.ts (1)
  • ApiStateConfig (19-103)
api/src/unraid-api/config/config.registry.ts (1)
  • makeConfigToken (10-12)
api/src/unraid-api/config/api-state.model.ts (2)
api/src/unraid-api/config/config.registry.ts (1)
  • ConfigRegistry (14-30)
api/src/environment.ts (1)
  • CONFIG_MODULES_HOME (89-89)
api/src/unraid-api/config/config.registry.ts (1)
api/src/unraid-api/config/api-state.service.ts (1)
  • configName (34-36)
🪛 GitHub Check: Build API
api/src/environment.ts

[failure] 89-89:
Insert


[failure] 89-89:
Newline required at end of file but not found

api/src/index.ts

[failure] 6-6:
Replace unlinkSync,·mkdir with mkdirSync,·unlink


[failure] 17-17:
Replace environment,·PORT,·CONFIG_MODULES_HOME with CONFIG_MODULES_HOME,·environment,·PORT

api/src/unraid-api/config/api-state.register.ts

[failure] 4-4:
import statements should have an absolute path


[failure] 5-5:
import statements should have an absolute path


[failure] 6-6:
import statements should have an absolute path


[failure] 7-7:
import statements should have an absolute path

api/src/unraid-api/config/api-state.service.ts

[failure] 4-4:
import statements should have an absolute path

api/src/unraid-api/config/api-state.model.ts

[failure] 10-10:
import statements should have an absolute path

🪛 GitHub Check: Test API
api/src/environment.ts

[failure] 89-89:
Insert


[failure] 89-89:
Newline required at end of file but not found

api/src/index.ts

[failure] 6-6:
Replace unlinkSync,·mkdir with mkdirSync,·unlink


[failure] 17-17:
Replace environment,·PORT,·CONFIG_MODULES_HOME with CONFIG_MODULES_HOME,·environment,·PORT

api/src/unraid-api/config/api-state.register.ts

[failure] 4-4:
import statements should have an absolute path


[failure] 5-5:
import statements should have an absolute path


[failure] 6-6:
import statements should have an absolute path


[failure] 7-7:
import statements should have an absolute path

api/src/unraid-api/config/api-state.service.ts

[failure] 4-4:
import statements should have an absolute path

api/src/unraid-api/config/api-state.model.ts

[failure] 10-10:
import statements should have an absolute path

🪛 Biome (1.9.4)
api/src/unraid-api/config/config.interface.ts

[error] 8-8: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

api/src/unraid-api/config/api-state.register.ts

[error] 13-45: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

api/src/unraid-api/config/config.registry.ts

[error] 14-30: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 19-19: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 23-23: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build Web App
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
api/.env.test (1)

12-12: Looks good - new config variable added consistently.

The CONFIG_MODULES_HOME variable has been added with a development-appropriate relative path that matches the pattern of other configuration paths in this file.

api/.env.development (1)

12-12: Looks good - new config variable added consistently.

The CONFIG_MODULES_HOME variable has been added with a development-appropriate relative path that matches the pattern of other configuration paths in this file.

api/.env.production (1)

5-5: Looks good - new config variable added with production path.

The CONFIG_MODULES_HOME variable has been added with an appropriate absolute path for the production environment.

api/.env.staging (1)

5-5: Looks good - new config variable added with staging path.

The CONFIG_MODULES_HOME variable has been added with an appropriate absolute path for the staging environment, matching the production path for environment parity.

api/src/unraid-api/config/config.injection.ts (1)

1-13: Well-designed custom decorator for configuration injection

The InjectConfig decorator provides a clean abstraction over NestJS's dependency injection system for configuration settings. Good use of generics to ensure type safety by constraining the feature parameter to keys of the ConfigFeatures interface.

api/src/index.ts (1)

47-48: Good practice ensuring the config directory exists

Creating the config modules directory at startup with recursive option is a good practice to prevent file operation errors later in the application lifecycle.

api/src/unraid-api/config/config.interface.ts (1)

11-20: Good interface design for configuration metadata

The ConfigMetadata interface provides a clear structure for configuration metadata with well-documented properties. The use of Zod for schema validation is a good practice for runtime type checking.

api/src/unraid-api/config/config.registry.ts (1)

10-12: Clean implementation of token creation

The makeConfigToken function is a simple and effective way to generate unique tokens for configurations.

api/src/unraid-api/config/api-state.model.ts (1)

19-29: Nice design for managing default config immutability and logging.

Registering the config before assigning a new logger ensures the token is always recognized, and the structuredClone usage effectively prevents accidental mutations to options.defaultConfig.

@@ -86,3 +86,4 @@
: 'https://mothership.unraid.net/ws';

export const PM2_HOME = process.env.PM2_HOME ?? join(homedir(), '.pm2');
export const CONFIG_MODULES_HOME = process.env.CONFIG_MODULES_HOME!;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add fallback value and fix missing newline.

Two issues to address:

  1. The non-null assertion (!) assumes CONFIG_MODULES_HOME will always be defined. Consider adding a fallback value to prevent potential runtime errors.
  2. Static analysis indicates a missing newline at the end of the file.
-export const CONFIG_MODULES_HOME = process.env.CONFIG_MODULES_HOME!;
+export const CONFIG_MODULES_HOME = process.env.CONFIG_MODULES_HOME ?? './configs';
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const CONFIG_MODULES_HOME = process.env.CONFIG_MODULES_HOME!;
export const CONFIG_MODULES_HOME = process.env.CONFIG_MODULES_HOME ?? './configs';
🧰 Tools
🪛 GitHub Check: Build API

[failure] 89-89:
Insert


[failure] 89-89:
Newline required at end of file but not found

🪛 GitHub Check: Test API

[failure] 89-89:
Insert


[failure] 89-89:
Newline required at end of file but not found

Copy link
Contributor

coderabbitai bot commented Apr 10, 2025

✅ Actions performed

Reviews paused.

@pujitm pujitm force-pushed the feat/config-module branch from 7c6525e to 86cd4b4 Compare April 14, 2025 13:39
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1352/dynamix.unraid.net.plg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant