Skip to content

Conversation

elibosley
Copy link
Member

@elibosley elibosley commented Jul 31, 2025

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive UPS (Uninterruptible Power Supply) management, including real-time monitoring, configuration, and subscription updates.
    • Added a robust WebSocket client for improved server connectivity and GraphQL request handling.
    • Added initial connection status tracking and internal API key management for Unraid Connect.
  • Refactor

    • Replaced legacy GraphQL client and remote access types with new UPS-focused schema and services.
    • Simplified and updated subscription management and connection logic to use the new WebSocket client.
    • Updated mothership connection handling to use WebSocket-based client and local GraphQL executor.
    • Enhanced connection status file handling with ensured directory creation before writing.
  • Chores

    • Updated development environment and configuration files for new endpoints and settings.
    • Removed obsolete scripts and configuration related to the previous GraphQL client.
    • Disabled file modifications in development mode to prevent unintended changes during development.

Copy link
Contributor

coderabbitai bot commented Jul 31, 2025

Walkthrough

This pull request introduces a major refactor of the Unraid Connect plugin’s backend, shifting from a GraphQL-over-HTTP approach to a WebSocket-based architecture for mothership communication. It adds a new WebSocket client, updates environment/configuration handling, removes obsolete GraphQL code generation, and extensively revises the GraphQL schema and related services.

Changes

Cohort / File(s) Change Summary
Environment & Config Updates
api/.env.development, api/dev/configs/api.json, api/dev/configs/connect.json
Updated environment variables (renamed MOTHERSHIP_GRAPHQL_LINK to MOTHERSHIP_BASE_URL, added new paths), incremented API version, and expanded connect config with new fields.
API Key Management
api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json, api/dev/keys/b5b4aa3d-8e40-4c92-bc40-d50182071886.json
Added a new internal API key JSON file and removed an obsolete one.
GraphQL Schema Overhaul
api/generated-schema.graphql
Extended GraphQL schema with detailed UPS management types, queries, mutations, subscriptions, and enums.
Environment Variable Usage
api/src/environment.ts, packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts
Renamed environment variable usage throughout codebase and updated assignment logic.
Connect Status Writer Improvements
packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts
Refactored to fetch status file path from config service and ensure directory existence before writing.
Code Generation Removal
packages/unraid-api-plugin-connect/codegen.ts, packages/unraid-api-plugin-connect/package.json
Removed Mothership GraphQL client type generation and updated npm scripts accordingly.
Mothership Proxy Refactor
packages/unraid-api-plugin-connect/src/mothership-proxy/local-graphql-executor.service.ts, packages/unraid-api-plugin-connect/src/mothership-proxy/mothership-subscription.handler.ts, packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.controller.ts, packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.module.ts
Introduced a local GraphQL executor, replaced GraphQL client with UnraidServerClient, simplified subscription handling, and updated module providers.
WebSocket Client Addition
packages/unraid-api-plugin-connect/src/mothership-proxy/unraid-server-client.service.ts
Added robust WebSocket client/service for mothership communication, handling GraphQL requests and subscriptions with reconnection and keepalive logic.
Connect Status JSON State
api/dev/states/connectStatus.json
Added initial JSON file representing connection status state.
File Modification Behavior in Dev
api/src/unraid-api/unraid-file-modifier/file-modification.ts, api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
Disabled file modifications during development environment by early return in shouldApply and skipping initialization in service.

Sequence Diagram(s)

WebSocket-based Mothership Communication Flow

sequenceDiagram
    participant Plugin as Unraid Connect Plugin
    participant UnraidServerClient as WebSocket Client
    participant Mothership as Mothership Server
    participant LocalExecutor as LocalGraphQLExecutor

    Plugin->>UnraidServerClient: initializeClient(apiKey, mothershipUrl)
    UnraidServerClient->>Mothership: Open WebSocket connection
    Mothership-->>UnraidServerClient: Sends GraphQL request (JSON)
    UnraidServerClient->>LocalExecutor: execute({query, variables, ...})
    LocalExecutor-->>UnraidServerClient: GraphQL response
    UnraidServerClient->>Mothership: Send response (JSON)
    loop Every 30s
        UnraidServerClient->>Mothership: Send plaintext ping
        Mothership-->>UnraidServerClient: pong
    end
    Plugin->>UnraidServerClient: disconnect()
    UnraidServerClient->>Mothership: Close WebSocket
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mdatelle
  • pujitm
  • zackspear

Poem

A WebSocket tunnel, a rabbit’s delight,
Now mothership talks run smooth through the night.
Old GraphQL scripts are swept from the den,
UPS types march in, again and again.
With pings and with pongs, the server’s alive—
This code-hop ensures our carrots will thrive! 🥕


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5caeb09 and 4dd3718.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • api/dev/configs/api.json (1 hunks)
  • api/dev/states/connectStatus.json (1 hunks)
  • api/generated-schema.graphql (5 hunks)
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts (2 hunks)
  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts (2 hunks)
  • packages/unraid-api-plugin-connect/package.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • api/dev/states/connectStatus.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/dev/configs/api.json
  • packages/unraid-api-plugin-connect/package.json
  • api/generated-schema.graphql
🧰 Additional context used
📓 Path-based instructions (2)
api/**/*

📄 CodeRabbit Inference Engine (.cursor/rules/api-rules.mdc)

api/**/*: Use pnpm as the only package manager
Always run scripts from api/package.json unless specifically requested otherwise

Files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
api/src/unraid-api/**/*

📄 CodeRabbit Inference Engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the NestJS repository located at api/src/unraid-api/ instead of the legacy code

Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
🧠 Learnings (17)
📓 Common learnings
Learnt from: mdatelle
PR: unraid/api#1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.
Learnt from: pujitm
PR: unraid/api#1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.
Learnt from: elibosley
PR: unraid/api#969
File: web/justfile:7-9
Timestamp: 2024-11-27T15:30:02.252Z
Learning: In the Unraid Connect project, the different implementations of the `setup` commands in `web/justfile` and `api/justfile` are intentional and correct behavior.
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:24-27
Timestamp: 2025-01-31T22:01:22.708Z
Learning: The Unraid UI uses a modern notification system with a custom `unraid-toaster` component replacing the legacy jGrowl notifications. The system is backed by a comprehensive GraphQL API with real-time subscription support for notification updates.
Learnt from: pujitm
PR: unraid/api#1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.
📚 Learning: the unraidfilemodifierservice in the unraid api provides comprehensive error handling for all filemo...
Learnt from: elibosley
PR: unraid/api#1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:39-41
Timestamp: 2025-01-29T16:35:43.699Z
Learning: The UnraidFileModifierService in the Unraid API provides comprehensive error handling for all FileModification implementations, including detailed error logging with stack traces and modification IDs. Individual FileModification implementations should focus on their core functionality without duplicating error handling.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: the unraidfilemodifierservice in the unraid api provides comprehensive error handling for all filemo...
Learnt from: elibosley
PR: unraid/api#1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:33-37
Timestamp: 2025-01-29T16:36:04.777Z
Learning: The UnraidFileModifierService in the Unraid API provides comprehensive error handling for all FileModification implementations. It includes try/catch blocks, detailed error logging, and safe rollback mechanisms. Individual FileModification implementations (like LogRotateModification) should allow errors to propagate to this service layer rather than handling them internally.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: in the unraid api, filemodification implementations (like logrotatemodification) don't need to handl...
Learnt from: elibosley
PR: unraid/api#1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:33-37
Timestamp: 2025-01-29T16:36:04.777Z
Learning: In the Unraid API, FileModification implementations (like LogRotateModification) don't need to handle errors internally as error handling is managed at the UnraidFileModifierService level.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: in the unraid api, filemodification implementations (apply/rollback methods) don't need to implement...
Learnt from: elibosley
PR: unraid/api#1082
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:39-41
Timestamp: 2025-01-29T16:35:43.699Z
Learning: In the Unraid API, FileModification implementations (apply/rollback methods) don't need to implement their own error handling as it's handled by the UnraidFileModifierService caller.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: applies to api/src/unraid-api/**/* : prefer adding new files to the nestjs repository located at api...
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-07-21T14:00:06.258Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repository located at api/src/unraid-api/ instead of the legacy code

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: applies to api/src/unraid-api/**/* : prefer adding new files to the nestjs repo located at api/src/u...
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: when modifying `apikey.roles` in `removerolefromapikey` and `addroletoapikey` within `api/src/unraid...
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:44:46.432Z
Learning: When modifying `apiKey.roles` in `removeRoleFromApiKey` and `addRoleToApiKey` within `api/src/unraid-api/auth/auth.service.ts`, concurrency issues are not a concern because the keys are stored in the file system.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
📚 Learning: when reviewing typescript files containing php code snippets intended for file modifications (like i...
Learnt from: elibosley
PR: unraid/api#1078
File: api/src/unraid-api/unraid-file-modifier/modifications/sso.modification.ts:35-36
Timestamp: 2025-01-28T16:39:50.278Z
Learning: When reviewing TypeScript files containing PHP code snippets intended for file modifications (like in `SSOFileModification`), PHP functions that appear undefined should not be flagged as issues since they will be available in the target PHP environment where the code is injected.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: the build script in web/package.json should include type-check command (`npm run type-check`) even w...
Learnt from: mdatelle
PR: unraid/api#1122
File: web/package.json:16-16
Timestamp: 2025-02-06T17:25:45.397Z
Learning: The build script in web/package.json should include type-check command (`npm run type-check`) even when NODE_ENV is production, as it provides value for local builds by catching type errors before pushing to CI/CD.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: in dockereventservice, the chokidar file watcher is configured with `ignoreinitial: false` to ensure...
Learnt from: elibosley
PR: unraid/api#1334
File: api/src/unraid-api/graph/resolvers/docker/docker-event.service.ts:63-66
Timestamp: 2025-04-07T14:34:47.255Z
Learning: In DockerEventService, the chokidar file watcher is configured with `ignoreInitial: false` to ensure that existing files (like the Docker socket) are detected and processed at application startup, not just when files change after the watcher is initialized.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
📚 Learning: the project uses a custom or extended implementation of nestjs configservice that includes a `set()`...
Learnt from: pujitm
PR: unraid/api#1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard @nestjs/config package which only provides getter methods.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
📚 Learning: the project uses a custom configservice implementation that includes a `set()` method for runtime co...
Learnt from: pujitm
PR: unraid/api#1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom ConfigService implementation that includes a `set()` method for runtime configuration mutation, unlike the standard @nestjs/config package which only provides getter methods.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
📚 Learning: this codebase uses esm (ecmascript modules) and requires .js extensions in import statements, even w...
Learnt from: elibosley
PR: unraid/api#1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts
📚 Learning: in the filemodification class's patch handling: - `results === false` indicates patch application fa...
Learnt from: elibosley
PR: unraid/api#1111
File: api/src/unraid-api/unraid-file-modifier/file-modification.ts:182-187
Timestamp: 2025-02-04T18:45:23.106Z
Learning: In the FileModification class's patch handling:
- `results === false` indicates patch application failure
- Empty string (`results === ''`) is a valid patch result indicating the file should be deleted
- These are distinct conditions and should be handled differently

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: the base `filemodification` class's `shouldapply` method should include validation for file existenc...
Learnt from: pujitm
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/file-modification.ts:149-154
Timestamp: 2025-02-04T13:45:07.161Z
Learning: The base `FileModification` class's `shouldApply` method should include validation for file existence and permissions using `access` with `constants.R_OK | constants.W_OK`, proper error handling with logger.error, and configuration validation for filePath and id.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
📚 Learning: for critical components in the unraid api, such as retrieving version information from package.json,...
Learnt from: pujitm
PR: unraid/api#1252
File: api/src/environment.ts:56-56
Timestamp: 2025-03-27T13:34:53.438Z
Learning: For critical components in the Unraid API, such as retrieving version information from package.json, failing fast (allowing crashes) is preferred over graceful degradation with fallback values.

Applied to files:

  • api/src/unraid-api/unraid-file-modifier/file-modification.ts
🪛 GitHub Actions: CI - Main (API)
api/src/unraid-api/unraid-file-modifier/file-modification.ts

[error] 114-153: Failed to apply patch due to invalid patch format: No hunks found for /usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php.


[error] 114-153: Failed to apply patch due to invalid patch format: No hunks found for /usr/local/emhttp/plugins/dynamix/Notifications.page.


[error] Failed to check if files should be applied due to missing 'constants' export on 'node:fs' mock in vitest tests.

🔇 Additional comments (4)
api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts (2)

11-11: LGTM!

The import of NODE_ENV is appropriately placed and follows the existing import pattern.


33-36: LGTM!

The development mode check is well-implemented with clear logging and appropriate early return. This provides the first line of defense against file modifications in development environments.

api/src/unraid-api/unraid-file-modifier/file-modification.ts (2)

11-11: LGTM!

The import of NODE_ENV is appropriately placed and follows the existing import pattern.


222-228: LGTM!

The development mode check provides excellent defense-in-depth alongside the service-level check. The early return is well-positioned before file checks, and the return value correctly implements the ShouldApplyWithReason interface with a clear reason string.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mothership-v2

🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.

@coderabbitai coderabbitai bot requested review from mdatelle, pujitm and zackspear July 31, 2025 01:16
…ient

- Updated package.json scripts to remove MOTHERSHIP_GRAPHQL_LINK environment variable.
- Changed MOTHERSHIP_GRAPHQL_LINK to MOTHERSHIP_BASE_URL in environment.ts.
- Removed GraphQL code generation for Mothership types in codegen.ts.
- Updated connection status services to use MOTHERSHIP_BASE_URL.
- Refactored MothershipSubscriptionHandler to utilize UnraidServerClient instead of GraphQL client.
- Implemented UnraidServerClient for WebSocket communication with Mothership.
- Enhanced MothershipController to manage UnraidServerClient lifecycle.
- Added reconnection logic and ping/pong handling in UnraidServerClient.
- Simplified GraphQL execution logic in UnraidServerClient.
@elibosley elibosley force-pushed the feat/mothership-v2 branch from 9ab5977 to 5caeb09 Compare July 31, 2025 01:19
Copy link

claude bot commented Jul 31, 2025

Claude finished @elibosley's task —— View job


PR Review: Mothership v2

I've completed a comprehensive review of this PR. Here's my detailed feedback:

Review Tasks

  • Analyze project structure and dependencies
  • Review new services (local-graphql-executor, unraid-server-client)
  • Examine refactored mothership components
  • Check configuration and environment changes
  • Assess security and performance implications
  • Evaluate test coverage
  • Provide comprehensive feedback

🔍 Code Quality & Architecture

Strengths:

  • Clean separation of concerns with LocalGraphQLExecutor and UnraidServerClient
  • Good use of NestJS patterns with proper dependency injection
  • Comprehensive WebSocket message handling with multiple format support
  • Proper cleanup in OnModuleDestroy lifecycle hooks

Areas for Improvement:

1. Error Handling & Logging 🚨

  • UnraidServerClient (unraid-server-client.service.ts:64, 76, 86): Uses console.log/error instead of proper logger
    // Instead of console.log
    console.log('Connected to mothership')
    // Use:
    this.logger.log('Connected to mothership')

2. Hardcoded Values & Magic Numbers

  • Ping interval: 30000 ms hardcoded (unraid-server-client.service.ts:200)
  • Max reconnect delay: 30 * 60 * 1000 hardcoded (unraid-server-client.service.ts:40)
  • Should be configurable via environment/config service

3. Code Duplication

  • Three similar handleRequestMessage, handleRouteMessage, and handleNewFormatGraphQLRequest methods with nearly identical logic (unraid-server-client.service.ts:314-400)
  • Consider extracting common GraphQL execution logic

🔒 Security Concerns

1. API Key Exposure ⚠️

  • API keys passed in WebSocket headers (unraid-server-client.service.ts:59) - consider if this is secure over the wire
  • Ensure mothership connection uses TLS in production

2. Input Validation

  • LocalGraphQLExecutor.transformRemoteQueryToLocal() (local-graphql-executor.service.ts:68) parses user-provided GraphQL without validation
  • Should validate/sanitize incoming queries before transformation

3. Message Size Limits

  • No limits on WebSocket message sizes could lead to DoS attacks
  • Consider implementing message size validation

Performance Considerations

1. Memory Management

  • WebSocket reconnection uses exponential backoff (good!)
  • But no cleanup of failed connection objects could lead to memory leaks
  • Consider implementing connection pool limits

2. GraphQL Query Transformation

  • transformRemoteQueryToLocal() parses and transforms every query (local-graphql-executor.service.ts:68-109)
  • Consider caching transformed queries for frequently used operations

3. Subscription Management

  • MothershipSubscriptionHandler has TODO comments for incomplete subscription cleanup (mothership-subscription.handler.ts:34, 51)
  • Incomplete subscription handling could lead to resource leaks

🧪 Test Coverage

Critical Gap:

  • No tests for the core new services:
    • LocalGraphQLExecutor - critical GraphQL transformation logic untested
    • UnraidServerClient - complex WebSocket handling untested
    • UnraidServerClientService - NestJS service wrapper untested

Existing Test:

  • Only mothership.events.test.ts exists, but it's for the event handler, not the core new functionality

Recommendations:

  • Add unit tests for GraphQL query transformation logic
  • Add integration tests for WebSocket connection/reconnection
  • Add tests for error handling scenarios
  • Mock the WebSocket dependency properly for testing

🐛 Potential Bugs

1. Race Conditions

  • shouldReconnect flag and WebSocket state management could have race conditions (unraid-server-client.service.ts:43, 52, 403)
  • Multiple rapid connect/disconnect calls might cause issues

2. Error Response Format

  • Inconsistent error response formats across different message handlers
  • Some return { errors: [...] }, others might return different formats

3. Incomplete Implementation

  • executeQuery() method in MothershipSubscriptionHandler returns hardcoded success response (mothership-subscription.handler.ts:127)
  • Should implement actual query execution

📝 Additional Recommendations

1. Configuration Management

  • Move hardcoded values to configuration:
    // Add to environment.ts
    export const WEBSOCKET_PING_INTERVAL = process.env.WS_PING_INTERVAL ? parseInt(process.env.WS_PING_INTERVAL) : 30000;
    export const MAX_RECONNECT_DELAY = process.env.MAX_RECONNECT_DELAY ? parseInt(process.env.MAX_RECONNECT_DELAY) : 30 * 60 * 1000;

2. Documentation

  • Add JSDoc comments to complex methods like transformRemoteQueryToLocal()
  • Document the WebSocket message protocol and expected formats

3. Monitoring & Observability

  • Add metrics for connection health, message counts, error rates
  • Add structured logging for better debugging

🎯 Priority Actions

  1. High Priority:

    • Add comprehensive tests for new services
    • Fix console.log usage (replace with proper logger)
    • Complete subscription cleanup implementation
  2. Medium Priority:

    • Refactor duplicate GraphQL execution logic
    • Add input validation for GraphQL queries
    • Make configuration values configurable
  3. Low Priority:

    • Add JSDoc documentation
    • Implement monitoring/metrics
    • Consider caching for query transformations

Overall Assessment

This is a solid architectural improvement moving to WebSocket-based communication. The code structure is clean and follows good patterns. However, test coverage is critically missing and there are several security and performance considerations that should be addressed before production deployment.

The refactoring successfully centralizes the mothership communication logic, which should improve maintainability and reliability compared to the previous implementation.


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: 5

🧹 Nitpick comments (5)
api/.env.development (1)

20-20: LGTM: New environment variable supports centralized configuration.

The addition of PATHS_CONNECT_STATUS aligns with the shift toward centralized configuration management via ConfigService.

Consider adding quotes around the value to match the project's .env formatting conventions:

-PATHS_CONNECT_STATUS=./dev/states/connectStatus.json # Connect status file for development
+PATHS_CONNECT_STATUS="./dev/states/connectStatus.json" # Connect status file for development
packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.controller.ts (1)

39-41: Simplify the disconnect logic.

The null check is redundant since the optional chaining operator already handles null/undefined cases safely.

-        if (this.clientService.getClient()) {
-            this.clientService.getClient()?.disconnect();
-        }
+        this.clientService.getClient()?.disconnect();
packages/unraid-api-plugin-connect/src/mothership-proxy/local-graphql-executor.service.ts (2)

125-150: Consider making field name mappings configurable.

The hardcoded field name mappings in transformRemoteFieldName could become a maintenance burden as the API evolves. Consider extracting these mappings to a configuration object.

+private readonly fieldNameMappings: Record<string, string> = {
+  'remoteGetDockerInfo': 'docker',
+  'remoteGetVms': 'vms',
+  'remoteGetSystemInfo': 'system',
+};
+
 private transformRemoteFieldName(fieldName: string): string {
-    // Handle common patterns
-    if (fieldName === 'remoteGetDockerInfo') {
-      return 'docker';
-    }
-    if (fieldName === 'remoteGetVms') {
-      return 'vms';
-    }
-    if (fieldName === 'remoteGetSystemInfo') {
-      return 'system';
-    }
+    // Check configured mappings first
+    if (this.fieldNameMappings[fieldName]) {
+      return this.fieldNameMappings[fieldName];
+    }
     
     // Generic transformation: remove "remoteGet" and convert to camelCase
     if (fieldName.startsWith('remoteGet')) {

152-155: Implement the stopSubscription method.

The stopSubscription method is currently just a stub. This could lead to memory leaks if subscriptions are not properly cleaned up.

Do you want me to help implement the subscription cleanup logic or open an issue to track this task?

packages/unraid-api-plugin-connect/src/mothership-proxy/unraid-server-client.service.ts (1)

56-56: Validate WebSocket URL construction.

The WebSocket URL is constructed by appending to the base URL. Ensure the base URL doesn't already end with a slash to avoid double slashes.

-        const wsUrl = `${this.mothershipUrl}/ws/server`
+        const wsUrl = `${this.mothershipUrl.replace(/\/$/, '')}/ws/server`
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 52f2267 and 5caeb09.

📒 Files selected for processing (16)
  • api/.env.development (1 hunks)
  • api/dev/configs/api.json (1 hunks)
  • api/dev/configs/connect.json (1 hunks)
  • api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json (1 hunks)
  • api/dev/keys/b5b4aa3d-8e40-4c92-bc40-d50182071886.json (0 hunks)
  • api/generated-schema.graphql (5 hunks)
  • api/src/environment.ts (1 hunks)
  • packages/unraid-api-plugin-connect/codegen.ts (1 hunks)
  • packages/unraid-api-plugin-connect/package.json (1 hunks)
  • packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts (2 hunks)
  • packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts (3 hunks)
  • packages/unraid-api-plugin-connect/src/mothership-proxy/local-graphql-executor.service.ts (1 hunks)
  • packages/unraid-api-plugin-connect/src/mothership-proxy/mothership-subscription.handler.ts (1 hunks)
  • packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.controller.ts (4 hunks)
  • packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.module.ts (1 hunks)
  • packages/unraid-api-plugin-connect/src/mothership-proxy/unraid-server-client.service.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • api/dev/keys/b5b4aa3d-8e40-4c92-bc40-d50182071886.json
🧰 Additional context used
📓 Path-based instructions (1)
api/**/*

📄 CodeRabbit Inference Engine (.cursor/rules/api-rules.mdc)

api/**/*: Use pnpm as the only package manager
Always run scripts from api/package.json unless specifically requested otherwise

Files:

  • api/dev/configs/api.json
  • api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json
  • api/dev/configs/connect.json
  • api/src/environment.ts
  • api/generated-schema.graphql
🧠 Learnings (15)
📓 Common learnings
Learnt from: mdatelle
PR: unraid/api#1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.
Learnt from: pujitm
PR: unraid/api#1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.
Learnt from: elibosley
PR: unraid/api#969
File: web/justfile:7-9
Timestamp: 2024-11-27T15:30:02.252Z
Learning: In the Unraid Connect project, the different implementations of the `setup` commands in `web/justfile` and `api/justfile` are intentional and correct behavior.
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:24-27
Timestamp: 2025-01-31T22:01:22.708Z
Learning: The Unraid UI uses a modern notification system with a custom `unraid-toaster` component replacing the legacy jGrowl notifications. The system is backed by a comprehensive GraphQL API with real-time subscription support for notification updates.
Learnt from: pujitm
PR: unraid/api#1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php:203-207
Timestamp: 2025-02-03T17:21:26.738Z
Learning: The project uses patches to override existing Unraid pages rather than modifying them directly.
api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json (3)

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:41:22.303Z
Learning: In api/src/unraid-api/auth/auth.service.ts, the addRoleToApiKey function operates on API keys stored as JSON files in a directory, not a database, so concurrency is not a concern for modifying apiKey.roles.

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:44:46.432Z
Learning: When modifying apiKey.roles in removeRoleFromApiKey and addRoleToApiKey within api/src/unraid-api/auth/auth.service.ts, concurrency issues are not a concern because the keys are stored in the file system.

Learnt from: elibosley
PR: #1425
File: api/dev/configs/connect.json:5-13
Timestamp: 2025-06-20T20:50:14.050Z
Learning: Connect configuration files like api/dev/configs/connect.json use their own naming conventions (e.g., apikey, accesstoken, idtoken, refreshtoken) as implementation details, and these should not be changed to follow general camelCase patterns.

api/dev/configs/connect.json (2)

Learnt from: elibosley
PR: #1425
File: api/dev/configs/connect.json:5-13
Timestamp: 2025-06-20T20:50:14.050Z
Learning: Connect configuration files like api/dev/configs/connect.json use their own naming conventions (e.g., apikey, accesstoken, idtoken, refreshtoken) as implementation details, and these should not be changed to follow general camelCase patterns.

Learnt from: pujitm
PR: #1211
File: web/codegen.ts:14-14
Timestamp: 2025-03-12T13:35:43.900Z
Learning: The JSON scalar type in web/codegen.ts was temporarily changed from 'string' to 'any' for compatibility with JsonForms integration. This change facilitates the implementation of the Connect settings web component.

packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts (6)

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: pujitm
PR: #1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the @UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

Learnt from: pujitm
PR: #1211
File: web/codegen.ts:14-14
Timestamp: 2025-03-12T13:35:43.900Z
Learning: The JSON scalar type in web/codegen.ts was temporarily changed from 'string' to 'any' for compatibility with JsonForms integration. This change facilitates the implementation of the Connect settings web component.

Learnt from: elibosley
PR: #1381
File: plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh:11-16
Timestamp: 2025-05-08T19:31:52.417Z
Learning: The dynamix.my.servers namespace is still valid and should not be changed to dynamix.unraid.net in file paths, as both namespaces coexist in the codebase.

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.module.ts (9)

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: mdatelle
PR: #1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-07-21T14:00:06.258Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repository located at api/src/unraid-api/ instead of the legacy code

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Learnt from: elibosley
PR: #1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Learnt from: elibosley
PR: #1308
File: unraid-ui/src/components/common/loading/Error.vue:2-2
Timestamp: 2025-04-02T21:21:29.168Z
Learning: Components in the unraid-ui folder require explicit imports and are not autoloaded, unlike other parts of the project that may use Nuxt.js autoloading features.

Learnt from: elibosley
PR: #1211
File: api/src/unraid-api/graph/connect/connect-settings.service.ts:48-53
Timestamp: 2025-03-13T16:17:55.820Z
Learning: In NestJS services, dynamic imports should be wrapped in try/catch blocks with proper error logging and fallback handling to prevent unhandled rejections from crashing the application.

packages/unraid-api-plugin-connect/package.json (14)

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: pujitm
PR: #1211
File: web/codegen.ts:14-14
Timestamp: 2025-03-12T13:35:43.900Z
Learning: The JSON scalar type in web/codegen.ts was temporarily changed from 'string' to 'any' for compatibility with JsonForms integration. This change facilitates the implementation of the Connect settings web component.

Learnt from: mdatelle
PR: #1122
File: web/package.json:16-16
Timestamp: 2025-02-06T17:25:45.397Z
Learning: The build script in web/package.json should include type-check command (npm run type-check) even when NODE_ENV is production, as it provides value for local builds by catching type errors before pushing to CI/CD.

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Always run scripts from api/package.json unless requested

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-07-21T14:00:06.258Z
Learning: Applies to api/**/* : Always run scripts from api/package.json unless specifically requested otherwise

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: pujitm
PR: #1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the @UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

Learnt from: pujitm
PR: #1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script /etc/rc.d/rc.unraid-api is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Learnt from: elibosley
PR: #1120
File: plugin/package.json:0-0
Timestamp: 2025-02-05T14:43:48.568Z
Learning: In Node.js projects, npm scripts should be organized with clear namespacing (e.g., build:, docker:, env:*) and include proper environment validation and error handling. Each script should follow the single responsibility principle.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention

Learnt from: elibosley
PR: #1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Learnt from: elibosley
PR: #1120
File: plugin/Dockerfile:1-3
Timestamp: 2025-02-05T14:25:37.316Z
Learning: The Dockerfile in the plugin directory is specifically for building artifacts during CI and is not used for production deployments. It creates a build environment with Node.js and necessary dependencies to generate plugin files.

packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts (12)

Learnt from: elibosley
PR: #1151
File: plugin/builder/utils/consts.ts:6-6
Timestamp: 2025-03-04T14:55:00.903Z
Learning: The startingDir constant in plugin/builder/utils/consts.ts is defined using process.cwd(), which can cause issues if directory changes occur after importing this constant. Using __dirname.split('/builder')[0] would make it more reliable by making it relative to the file location rather than the current working directory.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-07-21T14:00:06.258Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repository located at api/src/unraid-api/ instead of the legacy code

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:44:46.432Z
Learning: When modifying apiKey.roles in removeRoleFromApiKey and addRoleToApiKey within api/src/unraid-api/auth/auth.service.ts, concurrency issues are not a concern because the keys are stored in the file system.

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: pujitm
PR: #1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the @UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

Learnt from: pujitm
PR: #1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a set() method for runtime configuration mutation, unlike the standard @nestjs/config package which only provides getter methods.

Learnt from: pujitm
PR: #1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom ConfigService implementation that includes a set() method for runtime configuration mutation, unlike the standard @nestjs/config package which only provides getter methods.

Learnt from: pujitm
PR: #1367
File: packages/unraid-api-plugin-connect/src/event-handler/mothership.handler.ts:50-57
Timestamp: 2025-06-05T14:27:57.441Z
Learning: For NestJS applications with async event handlers using @onevent decorator, implement generic error handling using a custom decorator or base class rather than individual try-catch blocks in each handler method to avoid application crashes from unhandled promise rejections.

Learnt from: pujitm
PR: #1367
File: packages/unraid-api-plugin-connect/src/event-handler/mothership.handler.ts:50-57
Timestamp: 2025-06-05T14:27:57.441Z
Learning: NestJS async event handlers using @onevent decorator can crash the entire application if exceptions are not caught. Industry best practices recommend generic error handling approaches like custom decorators, Sentry's automatic wrapping, or global event exception services rather than individual try-catch blocks in each handler.

Learnt from: elibosley
PR: #1211
File: api/src/unraid-api/graph/connect/connect-settings.service.ts:48-53
Timestamp: 2025-03-13T16:17:55.820Z
Learning: In NestJS services, dynamic imports should be wrapped in try/catch blocks with proper error logging and fallback handling to prevent unhandled rejections from crashing the application.

Learnt from: elibosley
PR: #1352
File: packages/unraid-api-plugin-connect/src/config.persistence.ts:0-0
Timestamp: 2025-04-21T18:44:39.643Z
Learning: Use Promise-based file writing instead of callback-based approaches for better error handling and to allow proper propagation of success/failure states to callers.

api/.env.development (5)

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: pujitm
PR: #1047
File: api/src/unraid-api/graph/sandbox-plugin.ts:57-57
Timestamp: 2025-01-15T21:34:00.006Z
Learning: In the GraphQL sandbox (api/src/unraid-api/graph/sandbox-plugin.ts), CSRF token validation should fail silently with a fallback value to maintain sandbox accessibility, as it's a development tool where strict security measures aren't required.

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: elibosley
PR: #1063
File: web/_data/serverState.ts:137-147
Timestamp: 2025-01-27T14:57:46.617Z
Learning: The values in web/_data/serverState.ts are used for testing purposes and should remain as hardcoded mock data to facilitate testing different scenarios.

api/src/environment.ts (11)

Learnt from: elibosley
PR: #1381
File: plugin/builder/cli/common-environment.ts:18-22
Timestamp: 2025-05-08T19:28:37.034Z
Learning: In plugin/builder/cli/common-environment.ts, the getDefaultBaseUrl() function intentionally returns an invalid URL when running in CI environments to force explicit specification of the base URL parameter rather than relying on defaults.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.mutation.ts : GraphQL mutations must be placed in .mutation.ts files

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention (.query.ts for queries, .mutation.ts for mutations)

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.mutation.ts : GraphQL mutations must be placed in .mutation.ts files under web/

Learnt from: elibosley
PR: #1063
File: web/_data/serverState.ts:137-147
Timestamp: 2025-01-27T14:57:46.617Z
Learning: The values in web/_data/serverState.ts are used for testing purposes and should remain as hardcoded mock data to facilitate testing different scenarios.

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Always run scripts from api/package.json unless requested

Learnt from: elibosley
PR: #1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

packages/unraid-api-plugin-connect/codegen.ts (13)

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:41-47
Timestamp: 2024-11-05T14:48:56.949Z
Learning: Input types for GraphQL mutations can be pulled from the GraphQL codegen types, as they are generated into the API files. Therefore, explicit typing in the resolver files may be unnecessary.

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: pujitm
PR: #1211
File: web/codegen.ts:14-14
Timestamp: 2025-03-12T13:35:43.900Z
Learning: The JSON scalar type in web/codegen.ts was temporarily changed from 'string' to 'any' for compatibility with JsonForms integration. This change facilitates the implementation of the Connect settings web component.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Always run pnpm codegen for GraphQL code generation in the web directory

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: pujitm
PR: #1047
File: api/src/unraid-api/graph/sandbox-plugin.ts:57-57
Timestamp: 2025-01-15T21:34:00.006Z
Learning: In the GraphQL sandbox (api/src/unraid-api/graph/sandbox-plugin.ts), CSRF token validation should fail silently with a fallback value to maintain sandbox accessibility, as it's a development tool where strict security measures aren't required.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention (.query.ts for queries, .mutation.ts for mutations)

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.query.ts : GraphQL queries must be placed in .query.ts files

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.query.ts : GraphQL queries must be placed in .query.ts files under web/

Learnt from: elibosley
PR: #1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.mutation.ts : GraphQL mutations must be placed in .mutation.ts files under web/

packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.controller.ts (5)

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: mdatelle
PR: #1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: pujitm
PR: #1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the @UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

packages/unraid-api-plugin-connect/src/mothership-proxy/local-graphql-executor.service.ts (7)

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.{query,mutation}.ts : All GraphQL under web/ must follow this naming convention (.query.ts for queries, .mutation.ts for mutations)

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Applies to web/**/*.query.ts : GraphQL queries must be placed in .query.ts files

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to web/**/*.query.ts : GraphQL queries must be placed in .query.ts files under web/

packages/unraid-api-plugin-connect/src/mothership-proxy/mothership-subscription.handler.ts (9)

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: pujitm
PR: #1075
File: web/components/Notifications/Sidebar.vue:52-57
Timestamp: 2025-01-30T19:56:42.055Z
Learning: Notification links in the Unraid API are validated at creation time on the server side, making additional client-side validation unnecessary when receiving notifications through GraphQL subscriptions.

Learnt from: elibosley
PR: #1526
File: api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts:82-85
Timestamp: 2025-07-22T13:29:32.175Z
Learning: In GraphQL subscriptions using the graphql-subscriptions PubSub, the correct method is asyncIterableIterator(), not asyncIterator(). The asyncIterableIterator method returns an AsyncIterableIterator that conforms to both AsyncIterable and AsyncIterator interfaces, which is required for GraphQL subscription resolvers. Using asyncIterator() would cause TypeScript type errors as it only returns an AsyncIterator that doesn't satisfy the AsyncIterable interface requirement.

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: elibosley
PR: #1222
File: api/src/unraid-api/graph/resolvers/logs/logs.resolver.ts:37-65
Timestamp: 2025-03-17T13:41:52.496Z
Learning: The LogsService in Unraid API uses a reference counting mechanism to track log file subscriptions, ensuring file watchers remain active until all subscribers have unsubscribed.

Learnt from: pujitm
PR: #978
File: web/helpers/apollo-cache/index.ts:133-157
Timestamp: 2024-12-11T17:20:09.604Z
Learning: In web/helpers/apollo-cache/index.ts, when updating the cache in the cache.updateQuery function, it's sufficient to check if data is present, as type checking inside data is handled at the GraphQL layer.

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: pujitm
PR: #1148
File: web/composables/gql/graphql.ts:1812-1821
Timestamp: 2025-02-19T17:30:55.418Z
Learning: When using Apollo Vue's subscribeToMore, throttling can be implemented by wrapping the update function with lodash's throttle or using RxJS's throttleTime operator to prevent flooding the client with updates.

Learnt from: elibosley
PR: #1526
File: api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts:82-85
Timestamp: 2025-07-22T13:29:32.175Z
Learning: In GraphQL subscriptions using the graphql-subscriptions PubSub, the correct method to create an async iterator is asyncIterableIterator(), not asyncIterator(). This is the modern/current method used in the graphql-subscriptions library.

packages/unraid-api-plugin-connect/src/mothership-proxy/unraid-server-client.service.ts (7)

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In api/src/unraid-api/auth/api-key.service.ts, when handling read errors in the findById method, throw a GraphQLError instead of an InternalServerErrorException.

Learnt from: mdatelle
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts, methods like removeRoleFromApiKey rely on service-level error handling.

Learnt from: pujitm
PR: #1211
File: web/composables/gql/gql.ts:17-18
Timestamp: 2025-03-12T13:48:14.850Z
Learning: In the Unraid API project, the duplicate GraphQL query and mutation strings in gql.ts files are intentionally generated by GraphQL CodeGen tool and are necessary for the type system to function properly.

Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/api-rules.mdc:0-0
Timestamp: 2025-07-21T14:00:06.258Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repository located at api/src/unraid-api/ instead of the legacy code

Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-28T15:34:24.715Z
Learning: Applies to api/src/unraid-api/**/* : Prefer adding new files to the NestJS repo located at api/src/unraid-api/ instead of the legacy code

Learnt from: mdatelle
PR: #1106
File: unraid-ui/src/components/index.ts:2-2
Timestamp: 2025-02-04T17:21:39.710Z
Learning: The unraid-ui package is undergoing a major refactoring process, and breaking changes are expected during this transition period.

Learnt from: pujitm
PR: #1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a set() method for runtime configuration mutation, unlike the standard @nestjs/config package which only provides getter methods.

api/generated-schema.graphql (1)

Learnt from: elibosley
PR: #942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:41-47
Timestamp: 2024-11-05T14:48:56.949Z
Learning: Input types for GraphQL mutations can be pulled from the GraphQL codegen types, as they are generated into the API files. Therefore, explicit typing in the resolver files may be unnecessary.

🪛 Gitleaks (8.27.2)
api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json

5-5: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 GitHub Actions: CI - Main (API)
packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts

[error] 64-64: TypeError: The "path" argument must be of type string. Received an instance of Object. This error occurs multiple times during writeStatus calls.

🪛 dotenv-linter (3.3.0)
api/.env.development

[warning] 20-20: [UnorderedKey] The PATHS_CONNECT_STATUS key should go before the PATHS_CONNECT_STATUS_FILE_PATH key


[warning] 20-20: [ValueWithoutQuotes] This value needs to be surrounded in quotes


[warning] 21-21: [QuoteCharacter] The value has quote characters (', ")


[warning] 21-21: [UnorderedKey] The ENVIRONMENT key should go before the PATHS_ACTIVATION_BASE key


[warning] 22-22: [QuoteCharacter] The value has quote characters (', ")


[warning] 22-22: [UnorderedKey] The NODE_ENV key should go before the PATHS_ACTIVATION_BASE key


[warning] 23-23: [QuoteCharacter] The value has quote characters (', ")


[warning] 24-24: [UnorderedKey] The PLAYGROUND key should go before the PORT key


[warning] 25-25: [UnorderedKey] The INTROSPECTION key should go before the NODE_ENV key


[warning] 26-26: [QuoteCharacter] The value has quote characters (', ")


[warning] 26-26: [UnorderedKey] The MOTHERSHIP_BASE_URL key should go before the NODE_ENV key

🔇 Additional comments (20)
api/src/environment.ts (1)

96-100: LGTM! Clean environment variable refactor.

The renaming from MOTHERSHIP_GRAPHQL_LINK to MOTHERSHIP_BASE_URL and removal of the /ws suffix aligns well with the architectural shift from GraphQL-over-HTTP to WebSocket-based communication. The conditional logic and fallback behavior are preserved correctly.

api/dev/configs/api.json (1)

2-2: Appropriate version bump for the architectural changes.

The minor version increment from "4.11.0" to "4.12.0" correctly reflects the introduction of new WebSocket-based mothership communication features while maintaining backward compatibility.

packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts (2)

96-96: LGTM! Consistent environment variable update.

The change from MOTHERSHIP_GRAPHQL_LINK to MOTHERSHIP_BASE_URL aligns with the environment configuration updates and maintains the existing connectivity check functionality.


207-207: LGTM! Consistent environment variable update.

The change from MOTHERSHIP_GRAPHQL_LINK to MOTHERSHIP_BASE_URL aligns with the environment configuration updates and maintains the existing DNS check functionality.

api/dev/keys/7789353b-40f4-4f3b-a230-b1f22909abff.json (1)

1-11: Development API key correctly scoped to development environment

No references to this key or its secret were found in production code. Keeping the JSON file under api/dev/keys/ is appropriate for development/testing purposes.

packages/unraid-api-plugin-connect/package.json (1)

16-16: LGTM! Script cleanup aligns with architectural changes.

Removing the inline MOTHERSHIP_GRAPHQL_LINK environment variable from the codegen script is consistent with the transition to WebSocket-based communication and the renaming of environment variables throughout the codebase.

packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.module.ts (2)

11-11: LGTM: Clean architectural transition to WebSocket-based services.

The addition of LocalGraphQLExecutor and UnraidServerClientService imports aligns well with the broader shift from HTTP GraphQL to WebSocket-based mothership communication.

Also applies to: 15-15


23-25: LGTM: Provider registration looks correct.

The replacement of MothershipGraphqlClientService with UnraidServerClientService and LocalGraphQLExecutor in the providers array is properly structured and consistent with the architectural changes.

api/.env.development (1)

26-26: LGTM: URL change supports WebSocket architecture transition.

The change from MOTHERSHIP_GRAPHQL_LINK to MOTHERSHIP_BASE_URL with the new localhost URL correctly supports the shift to WebSocket-based communication.

api/dev/configs/connect.json (1)

11-18: LGTM: Configuration additions support enhanced connect functionality.

The new fields (version, extraOrigins, sandbox, and token fields) provide necessary configuration placeholders for the updated connect architecture. The JSON structure remains valid and maintains backward compatibility.

packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts (2)

4-5: LGTM: Proper imports for directory handling.

The addition of mkdir and dirname imports supports the enhanced directory creation functionality.


63-66: LGTM: Enhanced robustness with directory creation.

The addition of directory creation before writing the status file improves robustness by ensuring the target directory exists.

packages/unraid-api-plugin-connect/codegen.ts (1)

29-31: LGTM: GraphQL codegen removal aligns with WebSocket architecture.

Removing the mothership GraphQL type generation is the correct approach since the new UnraidServerClientService handles GraphQL operations dynamically over WebSocket connections rather than using pre-generated static types.

packages/unraid-api-plugin-connect/src/mothership-proxy/mothership.controller.ts (2)

5-19: LGTM! Clean transition to WebSocket-based architecture.

The migration from MothershipGraphqlClientService to UnraidServerClientService is well-implemented, with appropriate updates to comments and dependency injection.


51-57: Good defensive programming with proper validation.

The addition of the apiKey check alongside isLoaded ensures the mothership connection is only established when proper credentials are available.

packages/unraid-api-plugin-connect/src/mothership-proxy/local-graphql-executor.service.ts (1)

24-24: LocalGraphQLExecutor correctly injects InternalClientService

  • The constructor’s dependency on InternalClientService is intentional and matches its use elsewhere (e.g., in MothershipSubscriptionHandler). No change to use UnraidServerClientService is needed.
  • Remaining suggestions for this new executor:
    • Implement stopSubscription (around line 154) or document its intended behavior.
    • Externalize the hard-coded field-name mappings (currently in lines 68–109 and 125–150) into a configuration or constants file for easier maintenance.
      [f​ix_required]
packages/unraid-api-plugin-connect/src/mothership-proxy/unraid-server-client.service.ts (2)

402-418: LGTM! Proper cleanup in disconnect method.

The disconnect method correctly:

  • Disables reconnection
  • Clears ping interval
  • Clears reconnection timeout
  • Closes the WebSocket connection

This prevents memory leaks and ensures clean shutdown.


210-248: Excellent reconnection implementation with exponential backoff.

The reconnection logic is well-implemented with:

  • Exponential backoff starting at 1 second
  • Maximum delay cap of 30 minutes
  • Clear logging of reconnection attempts
  • Proper cleanup of existing timeouts
api/generated-schema.graphql (2)

1-3: Auto-generated file - changes look appropriate.

This schema file is automatically generated and reflects the architectural changes in the PR. The removal of remote access/cloud types and addition of comprehensive UPS management features align with the WebSocket-based mothership communication refactor.


1398-1526: Well-documented UPS types with clear field descriptions.

The new UPS-related types are thoroughly documented with units, examples, and clear explanations. This will greatly help API consumers understand the data structure.

Comment on lines +17 to 19
// Use environment variable if set, otherwise default to /var/local/emhttp/connectStatus.json
return this.configService.get('PATHS_CONNECT_STATUS') || '/var/local/emhttp/connectStatus.json';
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix critical path handling issue causing pipeline failures.

The pipeline failures indicate that configService.get('PATHS_CONNECT_STATUS') is returning an object instead of a string, causing a TypeError when passed to dirname().

The issue is likely that the configuration value is not being properly typed or retrieved. Apply this fix:

get statusFilePath() {
-    // Use environment variable if set, otherwise default to /var/local/emhttp/connectStatus.json
-    return this.configService.get('PATHS_CONNECT_STATUS') || '/var/local/emhttp/connectStatus.json';
+    // Use environment variable if set, otherwise default to /var/local/emhttp/connectStatus.json
+    return this.configService.get<string>('PATHS_CONNECT_STATUS') || '/var/local/emhttp/connectStatus.json';
}

Run this script to verify the configuration service implementation:


🏁 Script executed:

#!/bin/bash
# Description: Check how PATHS_CONNECT_STATUS is configured in the environment and config service

# Find configuration files that might define PATHS_CONNECT_STATUS
rg -n "PATHS_CONNECT_STATUS" --type json --type ts

# Look for ConfigService usage patterns
ast-grep --pattern 'configService.get($$$)'

Length of output: 428


Enforce string type and validate PATHS_CONNECT_STATUS to prevent TypeError

Ensure the config value is both statically typed and runtime-validated in connect-status-writer.service.ts so that a non-string never reaches dirname().

• File: packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts
Method: statusFilePath

get statusFilePath(): string {
-    // Use environment variable if set, otherwise default to /var/local/emhttp/connectStatus.json
-    return this.configService.get('PATHS_CONNECT_STATUS') || '/var/local/emhttp/connectStatus.json';
+    // Use environment variable if set, otherwise default to /var/local/emhttp/connectStatus.json
+    const rawPath = this.configService.get<string>('PATHS_CONNECT_STATUS');
+    if (typeof rawPath !== 'string') {
+        throw new TypeError(
+            `Invalid config "PATHS_CONNECT_STATUS": expected string, received ${typeof rawPath}`
+        );
+    }
+    return rawPath || '/var/local/emhttp/connectStatus.json';
}

Also verify in your configuration files (JSON, .env or TS) that PATHS_CONNECT_STATUS is defined as a string.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
packages/unraid-api-plugin-connect/src/connection-status/connect-status-writer.service.ts
around lines 17 to 19, the config value for PATHS_CONNECT_STATUS should be
explicitly checked to ensure it is a string before returning it. Update the
statusFilePath method to validate the type of the config value at runtime,
returning the default path only if the config value is either undefined or not a
string. Also, verify that PATHS_CONNECT_STATUS is defined as a string in all
configuration files such as JSON, .env, or TypeScript files.

Comment on lines +32 to +37
const client = this.mothershipClient.getClient();
if (client && subscription.operationId) {
// Note: We can't directly call stopSubscription on the client since it's private
// This would need to be exposed or handled differently in a real implementation
this.logger.debug(`Should stop subscription with operationId: ${subscription.operationId}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Address the architectural limitation with stopSubscription.

The comment indicates that stopSubscription cannot be called on the client because it's private. This is an architectural issue that should be resolved by exposing the necessary methods on the UnraidServerClient.

Consider exposing a public method on UnraidServerClient to stop subscriptions:

// In UnraidServerClient class
public async stopSubscription(operationId: string): Promise<void> {
    if (this.executor.stopSubscription) {
        await this.executor.stopSubscription(operationId);
    }
    // Send subscription_stop message to mothership
    this.sendResponse({
        operationId,
        event: 'query_response',
        type: 'complete',
        payload: { data: null },
    });
}
🤖 Prompt for AI Agents
In
packages/unraid-api-plugin-connect/src/mothership-proxy/mothership-subscription.handler.ts
around lines 32 to 37, the code cannot call stopSubscription on the client
because it is private. To fix this, add a public async method
stopSubscription(operationId: string) to the UnraidServerClient class that
internally calls the private executor.stopSubscription if available and sends
the appropriate subscription_stop message to mothership. Then update this
handler to call the new public stopSubscription method on the client instead of
accessing the private method directly.

Comment on lines +104 to 119
stopMothershipSubscription() {
this.logger.verbose('Stopping mothership subscription (not implemented yet)');
}

async handleRemoteGraphQLEvent(event: RemoteGraphQlEventFragmentFragment) {
const { body, type, sha256 } = event.remoteGraphQLEventData;
switch (type) {
case RemoteGraphQlEventType.REMOTE_QUERY_EVENT:
return this.safeExecuteQuery(sha256, body);
case RemoteGraphQlEventType.REMOTE_SUBSCRIPTION_EVENT:
return this.addSubscription(event.remoteGraphQLEventData);
case RemoteGraphQlEventType.REMOTE_SUBSCRIPTION_EVENT_PING:
return this.pingSubscription(sha256);
default:
return;
async subscribeToMothershipEvents() {
this.logger.log('Subscribing to mothership events via UnraidServerClient');

// For now, just log that we're connected
// The UnraidServerClient handles the WebSocket connection automatically
const client = this.mothershipClient.getClient();
if (client) {
this.logger.log('UnraidServerClient is connected and handling mothership communication');
} else {
this.logger.warn('UnraidServerClient is not available');
}
}
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

Implement proper mothership event subscription.

The current implementation only logs connection status. This should set up actual event subscriptions through the WebSocket client.

The method needs to:

  1. Subscribe to mothership events via the WebSocket client
  2. Handle incoming events and route them appropriately
  3. Manage subscription lifecycle
🤖 Prompt for AI Agents
In
packages/unraid-api-plugin-connect/src/mothership-proxy/mothership-subscription.handler.ts
around lines 104 to 119, the subscribeToMothershipEvents method currently only
logs connection status without setting up real event subscriptions. Update this
method to use the WebSocket client from mothershipClient to subscribe to actual
mothership events, add event listeners to handle incoming events and route them
properly, and implement logic to manage the subscription lifecycle including
cleanup or reconnection as needed.

Comment on lines +121 to 144
async executeQuery(sha256: string, body: string) {
this.logger.debug(`Request to execute query ${sha256}: ${body} (simplified implementation)`);

try {
// For now, just return a success response
// TODO: Implement actual query execution via the UnraidServerClient
return {
data: {
message: 'Query executed successfully (simplified)',
sha256,
}
},
});
};
} catch (error: any) {
this.logger.error(`Error executing query ${sha256}:`, error);
return {
errors: [
{
message: `Query execution failed: ${error?.message || 'Unknown error'}`,
extensions: { code: 'EXECUTION_ERROR' },
},
],
};
}
}
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

Complete the executeQuery implementation.

The method currently returns mock data with a TODO comment. This needs to be implemented to execute actual queries via the UnraidServerClient.

This method should execute queries through the WebSocket client. Do you want me to help implement the proper query execution logic?

🤖 Prompt for AI Agents
In
packages/unraid-api-plugin-connect/src/mothership-proxy/mothership-subscription.handler.ts
between lines 121 and 144, the executeQuery method currently returns mock data
and has a TODO to implement actual query execution. Replace the mock response by
invoking the appropriate method on the UnraidServerClient WebSocket client to
execute the query using the provided sha256 and body parameters. Handle the
response and errors properly, returning the actual query result or formatted
error information as needed.

Comment on lines +25 to +33
interface GraphQLExecutor {
execute(params: {
query: string
variables?: Record<string, any>
operationName?: string
operationType?: 'query' | 'mutation' | 'subscription'
}): Promise<any>
stopSubscription?(operationId: string): Promise<void>
}
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

Remove duplicate GraphQLExecutor interface.

This interface is already defined in local-graphql-executor.service.ts. Import it instead of duplicating the definition.

+import { GraphQLExecutor } from './local-graphql-executor.service.js';
+
-interface GraphQLExecutor {
-  execute(params: {
-    query: string
-    variables?: Record<string, any>
-    operationName?: string
-    operationType?: 'query' | 'mutation' | 'subscription'
-  }): Promise<any>
-  stopSubscription?(operationId: string): Promise<void>
-}
🤖 Prompt for AI Agents
In
packages/unraid-api-plugin-connect/src/mothership-proxy/unraid-server-client.service.ts
around lines 25 to 33, the GraphQLExecutor interface is duplicated but already
exists in local-graphql-executor.service.ts. Remove this local interface
definition and import the GraphQLExecutor interface from
local-graphql-executor.service.ts to avoid duplication and maintain consistency.

- Introduced new types and enums for managing remote access configurations, including AccessUrl, RemoteAccess, and Connect settings.
- Added mutations for updating API settings and managing remote access.
- Updated the API configuration to include the new connect plugin.
- Enhanced the pnpm lock file with the addition of the pify package.
- Implemented logic to skip file modifications in development mode.
Copy link

claude bot commented Jul 31, 2025

Claude finished @elibosley's task —— View job


PR Review: Mothership v2

I've completed a comprehensive review of this PR. Here's my detailed feedback:

Review Tasks

  • Analyze project structure and dependencies
  • Review new services (local-graphql-executor, unraid-server-client)
  • Examine refactored mothership components
  • Check configuration and environment changes
  • Assess security and performance implications
  • Evaluate test coverage
  • Provide comprehensive feedback

🔍 Code Quality & Architecture

Strengths:

  • Clean separation of concerns with LocalGraphQLExecutor and UnraidServerClient
  • Good use of NestJS patterns with proper dependency injection
  • Comprehensive WebSocket message handling with multiple format support
  • Proper cleanup in OnModuleDestroy lifecycle hooks

Areas for Improvement:

1. Error Handling & Logging 🚨

  • UnraidServerClient (unraid-server-client.service.ts:64, 76, 86): Uses console.log/error instead of proper logger
    // Instead of console.log
    console.log('Connected to mothership')
    // Use:
    this.logger.log('Connected to mothership')

2. Hardcoded Values & Magic Numbers

  • Ping interval: 30000 ms hardcoded (unraid-server-client.service.ts:200)
  • Max reconnect delay: 30 * 60 * 1000 hardcoded (unraid-server-client.service.ts:40)
  • Should be configurable via environment/config service

3. Code Duplication

  • Three similar handleRequestMessage, handleRouteMessage, and handleNewFormatGraphQLRequest methods with nearly identical logic (unraid-server-client.service.ts:314-400)
  • Consider extracting common GraphQL execution logic

🔒 Security Concerns

1. API Key Exposure ⚠️

  • API keys passed in WebSocket headers (unraid-server-client.service.ts:59) - consider if this is secure over the wire
  • Ensure mothership connection uses TLS in production

2. Input Validation

  • LocalGraphQLExecutor.transformRemoteQueryToLocal() (local-graphql-executor.service.ts:68) parses user-provided GraphQL without validation
  • Should validate/sanitize incoming queries before transformation

3. Message Size Limits

  • No limits on WebSocket message sizes could lead to DoS attacks
  • Consider implementing message size validation

Performance Considerations

1. Memory Management

  • WebSocket reconnection uses exponential backoff (good!)
  • But no cleanup of failed connection objects could lead to memory leaks
  • Consider implementing connection pool limits

2. GraphQL Query Transformation

  • transformRemoteQueryToLocal() parses and transforms every query (local-graphql-executor.service.ts:68-109)
  • Consider caching transformed queries for frequently used operations

3. Subscription Management

  • MothershipSubscriptionHandler has TODO comments for incomplete subscription cleanup (mothership-subscription.handler.ts:34, 51)
  • Incomplete subscription handling could lead to resource leaks

🧪 Test Coverage

Critical Gap:

  • No tests for the core new services:
    • LocalGraphQLExecutor - critical GraphQL transformation logic untested
    • UnraidServerClient - complex WebSocket handling untested
    • UnraidServerClientService - NestJS service wrapper untested

Existing Test:

  • Only mothership.events.test.ts exists, but it's for the event handler, not the core new functionality

Recommendations:

  • Add unit tests for GraphQL query transformation logic
  • Add integration tests for WebSocket connection/reconnection
  • Add tests for error handling scenarios
  • Mock the WebSocket dependency properly for testing

🐛 Potential Bugs

1. Race Conditions

  • shouldReconnect flag and WebSocket state management could have race conditions (unraid-server-client.service.ts:43, 52, 403)
  • Multiple rapid connect/disconnect calls might cause issues

2. Error Response Format

  • Inconsistent error response formats across different message handlers
  • Some return { errors: [...] }, others might return different formats

3. Incomplete Implementation

  • executeQuery() method in MothershipSubscriptionHandler returns hardcoded success response (mothership-subscription.handler.ts:127)
  • Should implement actual query execution

📝 Additional Recommendations

1. Configuration Management

  • Move hardcoded values to configuration:
    // Add to environment.ts
    export const WEBSOCKET_PING_INTERVAL = process.env.WS_PING_INTERVAL ? parseInt(process.env.WS_PING_INTERVAL) : 30000;
    export const MAX_RECONNECT_DELAY = process.env.MAX_RECONNECT_DELAY ? parseInt(process.env.MAX_RECONNECT_DELAY) : 30 * 60 * 1000;

2. Documentation

  • Add JSDoc comments to complex methods like transformRemoteQueryToLocal()
  • Document the WebSocket message protocol and expected formats

3. Monitoring & Observability

  • Add metrics for connection health, message counts, error rates
  • Add structured logging for better debugging

🎯 Priority Actions

  1. High Priority:

    • Add comprehensive tests for new services
    • Fix console.log usage (replace with proper logger)
    • Complete subscription cleanup implementation
  2. Medium Priority:

    • Refactor duplicate GraphQL execution logic
    • Add input validation for GraphQL queries
    • Make configuration values configurable
  3. Low Priority:

    • Add JSDoc documentation
    • Implement monitoring/metrics
    • Consider caching for query transformations

Overall Assessment

This is a solid architectural improvement moving to WebSocket-based communication. The code structure is clean and follows good patterns. However, test coverage is critically missing and there are several security and performance considerations that should be addressed before production deployment.

The refactoring successfully centralizes the mothership communication logic, which should improve maintainability and reliability compared to the previous implementation.


@coderabbitai coderabbitai bot requested review from mdatelle, pujitm and zackspear July 31, 2025 16:06
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