Skip to content

Conversation

@szpolny
Copy link

@szpolny szpolny commented Dec 20, 2025

Hi, i created plugin for managing mods for Arma Reforger. The plugin is mainly vibe-coded (because of my poor php knowledge), so free to suggest any improvements.

Summary by CodeRabbit

  • New Features

    • Full Arma Reforger Workshop integration: view/manage installed mods, add/remove, one‑click install, installed state indicator, open workshop in browser, and edit/open server config from the admin UI.
    • Browse & search Workshop with pagination, thumbnails, mod details (version, author, subscribers, rating), and install flows with user notifications.
    • Client-side caching for mod details and search results.
  • Documentation

    • Added README covering features, requirements, config override, and caching.
  • Localization

    • Added English UI translations.
  • Plugin

    • Added plugin manifest and admin panels.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

📝 Walkthrough

Walkthrough

Adds a new Arma Reforger Workshop Filament plugin: documentation and manifest, English translations, a Facade, a service to interact with Bohemia Workshop and edit server config.json, and two Filament server pages for browsing and managing workshop mods.

Changes

Cohort / File(s) Summary
Docs & Manifest
arma-reforger-workshop/README.md, arma-reforger-workshop/plugin.json
New README describing features, requirements, configuration (default CONFIG_FILE and override), caching policies; new plugin manifest with metadata and panel info.
Localization
arma-reforger-workshop/lang/en/arma-reforger-workshop.php
New English translation array covering navigation, labels, actions, forms, modals, sections, and notifications.
Plugin bootstrap
arma-reforger-workshop/src/ArmaReforgerWorkshopPlugin.php
New Filament plugin class implementing Plugin; registers/discovers pages for a panel; exposes getId(), register(), boot().
Facade
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php
New Laravel Facade exposing ArmaReforgerWorkshopService via docblock proxy annotations and returning the service class as the facade accessor.
Service layer
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php
New service for workshop interactions and server config management: server detection, getConfigPath, get/add/remove installed mods (idempotent), workshop URL, fetch/parse Next.js-embedded data, browse/search with caching (details ≈6h, search ≈15m), thumbnail extraction, and isModInstalled.
Filament pages
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php, arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php
Two new server-aware Filament pages: installed-mods manager (list, enrich with workshop details, add/remove, open config) and workshop browser (search, paginate, install). Both include access checks, UI actions, notifications, caching, and exception handling; use DaemonFileRepository and the service.

Sequence Diagrams

sequenceDiagram
    participant Admin as Admin User
    participant Page as ArmaReforgerWorkshopPage
    participant Service as ArmaReforgerWorkshopService
    participant FileRepo as Daemon File Repository
    participant Workshop as Bohemia Workshop

    rect rgb(240,248,255)
    Note over Admin,Workshop: Add Mod Flow
    Admin->>Page: Submit Add Mod (modId, name, version)
    Page->>Service: addMod(server, FileRepo, modId, name, version)
    Service->>FileRepo: read config.json
    FileRepo-->>Service: return config
    Service->>Service: merge/add mod entry (idempotent)
    Service->>FileRepo: write updated config.json
    FileRepo-->>Service: write result
    Service-->>Page: success / failure
    Page->>Admin: show notification
    end
Loading
sequenceDiagram
    participant Admin as Admin User
    participant Browse as BrowseWorkshopPage
    participant Service as ArmaReforgerWorkshopService
    participant Workshop as Bohemia Workshop
    participant FileRepo as Daemon File Repository

    rect rgb(250,240,255)
    Note over Admin,Workshop: Browse & Install Flow
    Admin->>Browse: Search or paginate
    Browse->>Service: browseWorkshop(search, page)
    Service->>Workshop: fetch search page (cached ~15m)
    Workshop-->>Service: HTML with embedded JSON
    Service->>Service: parse & build paginator
    Service-->>Browse: paginator (mods)
    Browse->>Admin: display mods
    Admin->>Browse: Confirm Install
    Browse->>Service: addMod(server, FileRepo, modId, name, version)
    Service->>FileRepo: read/update/write config.json
    FileRepo-->>Service: result
    Service-->>Browse: success / failure
    Browse->>Admin: show notification / update row
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through pages and parsed workshop light,
I cached tiny details to make listings bright.
I added mods gently, removed with care,
Tucked config in order — a tidy server lair.
Hooray for plugins, with carrots to share! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.39% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: introducing a new plugin for managing Arma Reforger mods. It is concise, specific, and directly reflects the changeset's primary purpose.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c24e825 and cd16777.

📒 Files selected for processing (3)
  • arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php
  • arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php
  • arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php

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

❤️ Share

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

Copy link
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: 2

🧹 Nitpick comments (6)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (2)

169-177: Consider using a generic error message instead of exposing exception details.

Displaying $exception->getMessage() directly to users could expose internal implementation details or sensitive information. Consider using a generic error message and logging the full exception for debugging.

🔎 Proposed fix
                     } catch (Exception $exception) {
                             report($exception);
 
                             Notification::make()
                                 ->title('Failed to add mod')
-                                ->body($exception->getMessage())
+                                ->body('Could not update the server configuration. Please try again.')
                                 ->danger()
                                 ->send();
                         }

156-168: Use localization strings instead of hardcoded text.

The localization file defines notification strings (notifications.mod_added, notifications.mod_added_body, etc.) that are not being used here. This duplicates text and makes translations harder to maintain.

🔎 Proposed fix
                         if ($success) {
                             Notification::make()
-                                ->title('Mod added')
-                                ->body("'{$record['name']}' has been added to your server configuration.")
+                                ->title(__('arma-reforger-workshop::arma-reforger-workshop.notifications.mod_added'))
+                                ->body(__('arma-reforger-workshop::arma-reforger-workshop.notifications.mod_added_body', ['name' => $record['name']]))
                                 ->success()
                                 ->send();
                         } else {
                             Notification::make()
-                                ->title('Failed to add mod')
-                                ->body('Could not update the server configuration.')
+                                ->title(__('arma-reforger-workshop::arma-reforger-workshop.notifications.failed_to_add'))
+                                ->body(__('arma-reforger-workshop::arma-reforger-workshop.notifications.config_update_failed'))
                                 ->danger()
                                 ->send();
                         }
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)

76-90: Unused $search parameter.

The $search parameter is declared but never used. Consider either implementing client-side filtering on the installed mods or removing the parameter if Filament allows.

Additionally, getModDetails() is called for each installed mod. While results are cached for 6 hours, the initial load with many mods could be slow due to sequential HTTP requests.

🔎 Proposed fix to use the search parameter
 $mods = ArmaReforgerWorkshop::getInstalledMods($server, $fileRepository);

+// Filter by search if provided
+if (!empty($search)) {
+    $mods = collect($mods)->filter(function ($mod) use ($search) {
+        return str_contains(strtolower($mod['name']), strtolower($search))
+            || str_contains(strtolower($mod['modId']), strtolower($search));
+    })->values()->toArray();
+}
+
 // Enrich with workshop details
 $enrichedMods = collect($mods)->map(function ($mod) {
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (3)

92-97: Case-sensitive comparison inconsistency.

The duplicate check here uses case-sensitive comparison (=== $modId), but isModInstalled() (line 318) uses case-insensitive comparison via strtoupper(). While the UI normalizes to uppercase before calling addMod, this inconsistency could cause issues if the service is called directly with mixed-case IDs.

Consider normalizing the modId to uppercase within this method for consistency:

🔎 Proposed fix
+        $modId = strtoupper($modId);
+
         // Check if mod already exists
         foreach ($config['game']['mods'] as $existingMod) {
-            if (($existingMod['modId'] ?? '') === $modId) {
+            if (strtoupper($existingMod['modId'] ?? '') === $modId) {
                 return true; // Already installed
             }
         }

138-141: Same case-sensitivity inconsistency in removeMod.

The filter uses case-sensitive comparison. For consistency with isModInstalled(), consider using case-insensitive comparison:

🔎 Proposed fix
+        $modId = strtoupper($modId);
+
         $config['game']['mods'] = collect($config['game']['mods'])
-            ->filter(fn ($mod) => ($mod['modId'] ?? '') !== $modId)
+            ->filter(fn ($mod) => strtoupper($mod['modId'] ?? '') !== $modId)
             ->values()
             ->toArray();

170-214: Error results may be cached for 6 hours.

If an exception occurs or the HTTP request fails inside the cache()->remember() callback, the minimal fallback result ['modId' => $modId] will be cached for 6 hours, preventing retry until the cache expires.

Consider caching only successful results:

🔎 Proposed fix using flexible() or manual caching
 public function getModDetails(string $modId): array
 {
-    return cache()->remember("arma_reforger_mod:$modId", now()->addHours(6), function () use ($modId) {
+    $cacheKey = "arma_reforger_mod:$modId";
+    $cached = cache()->get($cacheKey);
+    if ($cached !== null) {
+        return $cached;
+    }
+
+    $result = (function () use ($modId) {
         try {
             $response = Http::timeout(10)
                 ->connectTimeout(5)
                 ->get(self::WORKSHOP_URL . '/' . $modId);

             if (!$response->successful()) {
                 return ['modId' => $modId];
             }
             // ... rest of parsing logic ...

             return array_filter($details, fn ($v) => $v !== null);
         } catch (Exception $exception) {
             report($exception);
-            return ['modId' => $modId];
+            return null; // Don't cache errors
         }
-    });
+    })();
+
+    if ($result !== null && count($result) > 1) {
+        cache()->put($cacheKey, $result, now()->addHours(6));
+        return $result;
+    }
+
+    return ['modId' => $modId];
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 771c62c and 9914044.

📒 Files selected for processing (8)
  • arma-reforger-workshop/README.md (1 hunks)
  • arma-reforger-workshop/lang/en/arma-reforger-workshop.php (1 hunks)
  • arma-reforger-workshop/plugin.json (1 hunks)
  • arma-reforger-workshop/src/ArmaReforgerWorkshopPlugin.php (1 hunks)
  • arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1 hunks)
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1 hunks)
  • arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (1 hunks)
  • arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (3)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (9)
  • canAccess (38-48)
  • getNavigationLabel (50-53)
  • getModelLabel (55-58)
  • getPluralModelLabel (60-63)
  • getTitle (65-68)
  • table (73-173)
  • getHeaderActions (175-253)
  • ArmaReforgerWorkshopPage (27-285)
  • content (255-284)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (5)
  • isArmaReforgerServer (14-22)
  • browseWorkshop (222-285)
  • getModWorkshopUrl (158-161)
  • isModInstalled (313-324)
  • addMod (73-122)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (8)
  • ArmaReforgerWorkshopService (10-325)
  • isArmaReforgerServer (14-22)
  • getInstalledMods (29-55)
  • getModDetails (168-215)
  • getModWorkshopUrl (158-161)
  • removeMod (127-153)
  • addMod (73-122)
  • getConfigPath (60-68)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (1)
  • ArmaReforgerWorkshopService (10-325)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (3)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)
  • content (255-284)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (1)
  • content (209-219)
🪛 markdownlint-cli2 (0.18.1)
arma-reforger-workshop/README.md

61-61: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🪛 PHPMD (2.15.0)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php

76-76: Avoid unused parameters such as '$search'. (undefined)

(UnusedFormalParameter)

arma-reforger-workshop/src/ArmaReforgerWorkshopPlugin.php

22-22: Avoid unused parameters such as '$panel'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (19)
arma-reforger-workshop/plugin.json (1)

1-17: LGTM!

The plugin manifest is well-structured with all required metadata fields properly defined.

arma-reforger-workshop/src/ArmaReforgerWorkshopPlugin.php (1)

15-22: LGTM!

The plugin registration correctly uses Filament's page discovery mechanism. The empty boot() method with unused $panel parameter is required by the Plugin interface contract, so the static analysis warning is a false positive.

arma-reforger-workshop/README.md (1)

1-61: LGTM!

The README provides clear and comprehensive documentation covering features, requirements, configuration, and caching behavior. The bold styling for the author section is a minor style choice flagged by markdownlint, but it's acceptable for this context.

arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (3)

35-45: LGTM!

The access control correctly validates both tenant existence and server type before granting access.


70-83: LGTM!

The table configuration correctly integrates with the workshop service, using deferred loading and matching pagination to the service's per-page count.


195-219: LGTM!

Header actions and content structure are properly implemented with clear navigation between pages and an accessible link to the external workshop.

arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)

23-29: Facade implementation is correct with comprehensive PHPDoc annotations.

The @method annotations (lines 11-19) accurately document all service methods with proper type hints and return types. The getFacadeAccessor returns the service class name, which Laravel will auto-resolve from the container.

Verify that the ArmaReforgerWorkshopService binding is registered in the application's service provider (typically in a service provider's boot method) to ensure proper container resolution at runtime.

arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (5)

38-48: LGTM!

The access control logic properly validates the tenant exists before checking if it's an Arma Reforger server, and combines with parent access checks.


103-128: LGTM!

Good column configuration with appropriate placeholders, icons, and toggleable visibility.


130-172: LGTM!

The remove action has proper confirmation, error handling, and user feedback via notifications.


175-253: LGTM!

Good validation for mod ID (16-character hex), proper error handling, and consistent uppercase normalization before storing.


255-284: LGTM!

Good defensive error handling in the content display, gracefully falling back to 'Unknown' on failure.

arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (7)

14-22: LGTM!

Clean implementation with proper null coalescing for missing features/tags.


29-55: LGTM!

Robust implementation with proper error handling and filtering of invalid entries.


60-68: LGTM!

Clean implementation with sensible default fallback.


158-161: LGTM!

Simple and correct URL construction.


222-285: LGTM!

Good implementation with appropriate timeouts and 15-minute cache TTL. The same caching-of-failures concern applies here but is less impactful due to the shorter cache duration.


290-308: LGTM!

Good defensive implementation with proper fallbacks for missing thumbnail data.


313-324: LGTM!

Correct case-insensitive comparison for mod ID matching.

Copy link
Member

@Boy132 Boy132 left a comment

Choose a reason for hiding this comment

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

Please fix pint and what rabbit mentioned.

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

🧹 Nitpick comments (2)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (2)

76-76: Remove unused parameter.

The $search parameter is declared but never used in the records closure. Consider removing it or implementing search functionality.


271-276: Duplicate call to getInstalledMods().

The getInstalledMods() method is called both in the table() method (line 83) and here in content(). On a single page load, this reads and parses the same config file twice.

Consider caching the result in a property or using Livewire computed properties to avoid redundant file operations.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9914044 and 8befb7b.

📒 Files selected for processing (5)
  • arma-reforger-workshop/lang/en/arma-reforger-workshop.php
  • arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php
  • arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php
  • arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • arma-reforger-workshop/lang/en/arma-reforger-workshop.php
  • arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php
  • arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php
🧰 Additional context used
🧬 Code graph analysis (2)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (3)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (8)
  • ArmaReforgerWorkshopService (10-330)
  • isArmaReforgerServer (14-22)
  • getInstalledMods (29-56)
  • getModDetails (171-219)
  • getModWorkshopUrl (161-164)
  • removeMod (129-156)
  • addMod (74-124)
  • getConfigPath (61-69)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (5)
  • canAccess (37-47)
  • table (90-199)
  • getHeaderActions (201-213)
  • BrowseWorkshopPage (24-226)
  • content (215-225)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (3)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)
  • content (256-287)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (1)
  • content (215-225)
🪛 PHPMD (2.15.0)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php

76-76: Avoid unused parameters such as '$search'. (undefined)

(UnusedFormalParameter)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)

362-362: Dirname edge case still present.

As noted in previous reviews, for a config path like config.json at the root, dirname() returns '.' (current directory), not an empty string. The ?: '/' fallback won't trigger as expected.

🔎 Suggested fix
-->url(fn () => ListFiles::getUrl(['path' => dirname(ArmaReforgerWorkshop::getConfigPath($server)) ?: '/']), true),
+->url(fn () => ListFiles::getUrl(['path' => ($dir = dirname(ArmaReforgerWorkshop::getConfigPath($server))) === '.' ? '/' : $dir]), true),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8befb7b and 44722db.

📒 Files selected for processing (3)
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php
  • arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php
  • arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php
🧰 Additional context used
🧬 Code graph analysis (1)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (2)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)
  • content (366-397)

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

♻️ Duplicate comments (1)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)

224-231: Reconsider caching strategy for failed responses.

Failed responses (which return only ['modId' => $modId]) are still cached with the same 6-hour TTL as successful responses. Transient failures like temporary workshop downtime will remain cached, preventing retries and leaving users with incomplete mod information.

Consider implementing one of these approaches:

  • Skip caching entirely for minimal/failed responses
  • Use a significantly shorter TTL (e.g., 2-5 minutes) for failed responses
  • Add a failure marker to distinguish cached failures from cached successes
🔎 Example approach: differentiate TTL based on response richness
+            // Determine TTL based on whether we got rich details or just modId
+            $isMinimalResponse = count($details) === 1 && isset($details['modId']);
+            $ttl = $isMinimalResponse ? now()->addMinutes(5) : $cacheTtl;
+
             foreach ($uncachedMods as $index => $mod) {
                 $response = $responses[(string) $index] ?? null;
                 $details = $this->parseModDetailsFromResponse($response, $mod['modId']);
 
                 // Cache the result
-                cache()->put($cachePrefix . $mod['modId'], $details, $cacheTtl);
+                $responseTtl = (count($details) === 1 && isset($details['modId'])) 
+                    ? now()->addMinutes(5) 
+                    : $cacheTtl;
+                cache()->put($cachePrefix . $mod['modId'], $details, $responseTtl);
                 $cachedDetails[$index] = $details;
             }
🧹 Nitpick comments (1)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)

190-191: Consider extracting configuration values to class constants.

The cache TTL (6 hours), HTTP timeouts (10s/5s), and cache prefix could be extracted to class constants for easier configuration and maintenance. This is a minor improvement that would enhance code clarity.

🔎 Example refactoring
+    protected const CACHE_PREFIX = 'arma_reforger_mod:';
+    protected const CACHE_TTL_HOURS = 6;
+    protected const HTTP_TIMEOUT_SECONDS = 10;
+    protected const HTTP_CONNECT_TIMEOUT_SECONDS = 5;
+
     protected function enrichModsWithDetails(array $mods): array
     {
-        $cachePrefix = 'arma_reforger_mod:';
-        $cacheTtl = now()->addHours(6);
+        $cachePrefix = self::CACHE_PREFIX;
+        $cacheTtl = now()->addHours(self::CACHE_TTL_HOURS);
         
         // ... 
         
             $responses = Http::pool(function ($pool) use ($uncachedMods, $workshopUrl) {
                 $requests = [];
                 foreach ($uncachedMods as $index => $mod) {
                     $requests[$index] = $pool->as((string) $index)
-                        ->timeout(10)
-                        ->connectTimeout(5)
+                        ->timeout(self::HTTP_TIMEOUT_SECONDS)
+                        ->connectTimeout(self::HTTP_CONNECT_TIMEOUT_SECONDS)
                         ->get($workshopUrl . '/' . $mod['modId']);
                 }

Also applies to: 216-217

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44722db and 303de44.

📒 Files selected for processing (1)
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php
🧰 Additional context used
🧬 Code graph analysis (1)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (2)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (7)
  • ArmaReforgerWorkshopService (10-336)
  • isArmaReforgerServer (14-22)
  • getInstalledMods (29-56)
  • getModWorkshopUrl (167-170)
  • removeMod (132-162)
  • addMod (74-127)
  • getConfigPath (61-69)
🔇 Additional comments (3)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (3)

74-180: LGTM!

The table configuration is well-structured with proper pagination, search filtering, and enriched mod details. The concurrent fetching pattern in enrichModsWithDetails() effectively addresses the N+1 HTTP call concern from previous reviews. The remove action includes appropriate confirmation and error handling.


362-362: LGTM - dirname edge case properly handled.

The use of rtrim(dirname(...), '.') ?: '/' correctly addresses the edge case where dirname() returns '.' for root-level config files. This fix ensures the path resolves to '/' as expected.


292-350: Well-implemented add mod action.

The form validation is comprehensive with appropriate constraints for the mod ID (16-character hex validation), and error handling provides clear user feedback. The modId normalization to uppercase is consistent with the service layer expectations.

@szpolny szpolny requested a review from Boy132 December 27, 2025 01:08

if ($success) {
Notification::make()
->title(__('arma-reforger-workshop::arma-reforger-workshop.notifications.mod_added'))
Copy link
Member

Choose a reason for hiding this comment

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

Please use trans() instead of __()

/**
* Add a mod to the server's config.json
*/
public function addMod(Server $server, DaemonFileRepository $fileRepository, string $modId, string $name, string $version = ''): bool
Copy link
Member

Choose a reason for hiding this comment

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

If $version is an optional param you can make it nullable.

Copy link
Member

Choose a reason for hiding this comment

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

Add translations.

Copy link
Member

Choose a reason for hiding this comment

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

Add translations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (2)

322-333: Consider passing null for version instead of empty string.

Line 332 passes an empty string '' for the version parameter when not provided by the user. Since the addMod() method signature accepts ?string $version = null (nullable), passing null would be more semantically correct and consistent with the method's nullable parameter design.

🔎 Proposed fix
                        $success = ArmaReforgerWorkshop::addMod(
                            $server,
                            $fileRepository,
                            strtoupper($data['modId']),
                            $data['name'],
-                            $data['version'] ?? ''
+                            $data['version'] ?? null
                        );

386-400: Consider localizing the 'Unknown' fallback string.

Line 397 returns the hardcoded string 'Unknown' when an exception occurs. For consistency with the rest of the localized UI, this should use a translation key.

🔎 Proposed fix
                                } catch (Exception $exception) {
                                    report($exception);

-                                    return 'Unknown';
+                                    return trans('arma-reforger-workshop::arma-reforger-workshop.labels.unknown');
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (1)

301-319: Consider more explicit thumbnail size selection.

Line 314 uses end() to get the last thumbnail, assuming it's the smallest based on convention. The comment "usually last one" indicates uncertainty. If thumbnail size metadata is available in the response, consider selecting explicitly by size rather than position.

Suggested approach

If size information is available, select the smallest explicitly:

// Example if width/height are available
$smallest = collect($thumbnails)->sortBy('width')->first();

Otherwise, the current implementation is acceptable as a heuristic.

arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (1)

156-167: Consider passing null for version instead of empty string.

Similar to ArmaReforgerWorkshopPage, line 166 passes an empty string for version when requesting the latest version. Since addMod() accepts a nullable parameter, passing null would be more semantically correct.

🔎 Proposed fix
                            $success = ArmaReforgerWorkshop::addMod(
                                $server,
                                $fileRepository,
                                strtoupper($record['modId']),
                                $record['name'],
-                                '' // Don't specify version to get latest
+                                null // Don't specify version to get latest
                            );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 303de44 and c24e825.

📒 Files selected for processing (4)
  • arma-reforger-workshop/lang/en/arma-reforger-workshop.php
  • arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php
  • arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php
  • arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • arma-reforger-workshop/lang/en/arma-reforger-workshop.php
🧰 Additional context used
🧬 Code graph analysis (3)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (2)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (7)
  • ArmaReforgerWorkshopService (10-336)
  • isArmaReforgerServer (14-22)
  • getInstalledMods (29-56)
  • getModWorkshopUrl (167-170)
  • removeMod (132-162)
  • addMod (74-127)
  • getConfigPath (61-69)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (7)
  • canAccess (37-47)
  • getNavigationLabel (49-52)
  • getModelLabel (54-57)
  • getPluralModelLabel (59-62)
  • getTitle (64-67)
  • table (90-199)
  • BrowseWorkshopPage (24-226)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (3)
arma-reforger-workshop/src/Facades/ArmaReforgerWorkshop.php (1)
  • ArmaReforgerWorkshop (23-29)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (1)
  • content (373-404)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (1)
  • content (215-225)
arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (2)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (8)
  • canAccess (39-49)
  • getNavigationLabel (51-54)
  • getModelLabel (56-59)
  • getPluralModelLabel (61-64)
  • getTitle (66-69)
  • table (74-180)
  • getHeaderActions (293-371)
  • ArmaReforgerWorkshopPage (28-405)
arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (4)
  • isArmaReforgerServer (14-22)
  • getInstalledMods (29-56)
  • browseWorkshop (232-296)
  • getModWorkshopUrl (167-170)
🔇 Additional comments (16)
arma-reforger-workshop/src/Filament/Server/Pages/ArmaReforgerWorkshopPage.php (5)

1-49: LGTM - Clean class structure and access control.

The imports, class declaration, and access control logic are well-structured. The canAccess() method properly validates the tenant server and delegates to the service for server-type checking.


188-247: Well-implemented concurrent enrichment with differential caching.

The refactoring to use Http::pool() for concurrent requests effectively addresses the previous N+1 concern. The differential TTL strategy (6 hours for success, 2 minutes for failures) is excellent and allows quick retries on transient errors while reducing load on the workshop.


254-291: LGTM - Robust response parsing with graceful degradation.

The parsing logic properly extracts mod details from the embedded __NEXT_DATA__ JSON, handles missing data gracefully, and provides safe fallbacks. The exception handling with report() ensures errors are logged for debugging.


293-321: LGTM - Well-structured form validation.

The add_mod action includes proper validation for the mod ID (16-character hex with regex), required name field, and optional version. The validation messages are appropriately localized.


358-370: LGTM - Edge case handling for dirname.

Line 369 correctly uses rtrim() to handle the edge case where dirname() returns '.' for root-level config paths, addressing the previous review concern.

arma-reforger-workshop/src/Services/ArmaReforgerWorkshopService.php (8)

14-22: LGTM - Flexible server detection.

The method checks both features and tags, and handles both underscore and hyphen variants of the tag name, which demonstrates good defensive programming.


29-56: LGTM - Robust config parsing with safe fallbacks.

The method properly handles missing or malformed config files, filters out invalid mod entries, and returns an empty array on errors. The normalization of mod data structure is well-implemented.


61-69: LGTM - Clean config path resolution.

The method properly checks for a custom config path via the CONFIG_FILE environment variable and falls back to a sensible default. The nullable chaining syntax is used correctly.


74-127: LGTM - Well-implemented idempotent mod addition.

The nullable $version parameter (addressing the previous review comment) is properly handled, and the method correctly omits the version field when not provided. The case-insensitive duplicate check and idempotent behavior are well-designed, ensuring the method is safe to call multiple times.


132-162: LGTM - Clean mod removal with proper reindexing.

The method properly filters out the target mod with case-insensitive matching and uses values() to reindex the array, ensuring clean JSON output without gaps in array indices.


167-170: LGTM - Simple URL construction.

The URL construction is straightforward. Since mod IDs are validated as 16-character hex strings elsewhere, no additional encoding is needed.


232-296: LGTM - Well-structured workshop browsing with appropriate cache TTL.

The 15-minute cache TTL for browse results is appropriately shorter than the 6-hour TTL for individual mod details, reflecting the more dynamic nature of search/browse data. The parsing logic handles nested JSON structure well and provides safe fallbacks.


324-336: LGTM - Simple and correct mod installation check.

The case-insensitive comparison is consistent with the mod management methods. For typical mod list sizes, the linear search performance is acceptable.

arma-reforger-workshop/src/Filament/Server/Pages/BrowseWorkshopPage.php (3)

29-85: LGTM - Efficient caching eliminates N+1 queries.

The installedModIds property and getInstalledModIds() method effectively address the previous N+1 concern by caching installed mod IDs once per page instance. The lazy-loading pattern and uppercase normalization are well-implemented.


148-191: LGTM - N+1 optimization correctly implemented.

Lines 152 and 197 correctly use getInstalledModIds() with in-memory checks, eliminating the N+1 remote calls that were flagged in previous reviews. The conditional action visibility based on installation status is well-designed.


201-225: LGTM - Clean header actions and content layout.

The header actions provide intuitive navigation between installed mods and the workshop, and the content schema is properly structured with a descriptive section.

…th parseNextDataFromHtml for improved data extraction
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.

2 participants