Skip to content

Conversation

@HarlequinSin
Copy link

@HarlequinSin HarlequinSin commented Dec 28, 2025

There are some pretty sweeping changes here, but they can be summarized in to the following:

  1. Notification changes to reflect if DNS records updated in cloudflare
  2. Extracting CloudFlare methods into seperate file/class
  3. SRV record type toggle

Let me know if there are any issues (honestly never made a PR before and this is my first time with PHP)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added SRV record support for popular games (Minecraft, Rust, Factorio, TeamSpeak, Mumble, SCPSL).
    • Record type now visible in subdomain management interface.
    • Domain name suffix displayed in subdomain forms for clarity.
  • Documentation

    • Enhanced README with supported games, SRV requirements, troubleshooting guide, and configuration examples.
  • Improvements

    • Streamlined notification handling for subdomain operations.
    • Expanded translation strings for SRV messaging and settings feedback.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

This pull request introduces SRV (Service Record) support for subdomains, moving srv_target storage from CloudflareDomains to Nodes. It adds a new CloudflareService for DNS operations, updates models with SRV logic, introduces a ServiceRecordType enum, and provides Filament UI components for managing SRV records with Cloudflare integration.

Changes

Cohort / File(s) Summary
Localization & Translation Keys
subdomains/lang/en/strings.php
Added 12+ new public translation keys including srv_record, srv_target, srv_target_help, srv_target_missing, subdomain_limit, subdomain_change_limit, and a large nested notifications group for SRV-related messaging, Cloudflare operations, and record lifecycle events.
Model Layer - Core Domain Logic
subdomains/src/Models/CloudflareDomain.php
Changed cloudflare_id from nullable to non-nullable string. Updated boot logic to fetch Cloudflare zone ID via new CloudflareService::getZoneId() with success/failure notifications.
Model Layer - Subdomain Entity
subdomains/src/Models/Subdomain.php
Added srv_record as a fillable, cast, and appended boolean attribute with custom getter/setter logic. Changed cloudflare_id to non-nullable. Replaced created/updated/deleted hooks with creating/updating/deleting events. Introduced upsertOnCloudflare() and deleteOnCloudflare() methods with SRV vs. A/AAAA routing, validation, Cloudflare API integration, and detailed notifications and logging.
Services
subdomains/src/Services/CloudflareService.php
New service class providing getZoneId(), getRecord(), upsertDnsRecord(), and deleteDnsRecord() methods for Cloudflare API interactions. Implements conditional SRV payload construction, record existence checks, response parsing, and error logging.
Enums
subdomains/src/Enums/ServiceRecordType.php
New string enum with cases: factorio, minecraft, mumble, rust, scpsl, teamspeak. Implements HasLabel, fromServer(), fromTags() for tag-based mapping, and service()/protocol() parsers.
Admin Filament Resources
subdomains/src/Filament/Admin/Resources/CloudflareDomains/Pages/ManageCloudflareDomains.php
Disabled success notification on CreateAction via successNotification(null).
Admin Filament Relations
subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
Moved from Users to Servers namespace. Replaced Hidden::make('record_type') with Toggle::make('srv_record') including label and helper text.
Server Filament Resources
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
Added record_type TextColumn with conditional icon/color/tooltip based on SRV status. Disabled success notifications on CreateAction, EditAction, DeleteAction. Updated form: name input now shows domain suffix, domain_id defaults to first domain, record_type Hidden replaced with srv_record Toggle with reactive helperText and disabled state.
Custom Filament Actions
subdomains/src/Filament/Components/Actions/SetSrvTargetAction.php
New action class for assigning Node srv_target via modal form, with localized title/body and confirmation required.
Plugin Provider
subdomains/src/Providers/SubdomainsPluginProvider.php
Updated SubdomainRelationManager reference from Users to Servers namespace. Registered SetSrvTargetAction as a custom header action on EditNode via HeaderActionPosition::Before.
Database Migration
subdomains/database/migrations/004_add_srv_target_to_nodes.php
Removed nulls from cloudflare_domains.cloudflare_id and subdomains.cloudflare_id, dropped srv_target from cloudflare_domains, and added nullable srv_target to nodes. Includes reversible down() logic.
Documentation
subdomains/README.md
Enhanced with Cloudflare A/AAAA/SRV clarification, supported games table with required Egg tags, SRV prerequisites (Node srv_target, allocation port, Egg tag), troubleshooting scenarios, and example workflows.

Sequence Diagram

sequenceDiagram
    actor User as User (Filament)
    participant Filament as Filament Form
    participant Model as Subdomain Model
    participant Validation as Validation Logic
    participant CloudflareAPI as CloudflareService
    participant DB as Database
    participant Notification as Notification System

    User->>Filament: Submit Subdomain (srv_record=true)
    Filament->>Model: Create/Update with srv_record
    Model->>Validation: Validate SRV prerequisites<br/>(srv_target, port, service type)
    alt Prerequisites Missing
        Validation-->>Notification: Emit danger notification
        Notification-->>User: Display error (missing port/target)
    else Prerequisites Valid
        Validation->>CloudflareAPI: Call upsertDnsRecord<br/>(zone, SRV payload)
        CloudflareAPI->>CloudflareAPI: Build SRV record payload<br/>(service, protocol, port, target)
        alt Cloudflare Success
            CloudflareAPI->>DB: Return DNS record ID
            DB->>Model: Update cloudflare_id
            Model->>Notification: Emit success notification
            Notification-->>User: Display "Record updated"
        else Cloudflare Failure
            CloudflareAPI-->>Notification: Log error + emit danger notification
            Notification-->>User: Display error details
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The changes introduce a new feature spanning multiple layers (models, services, UI components, database), with significant logic density in CloudflareService (validation, conditional SRV vs. A/AAAA routing, API integration) and Subdomain model (lifecycle hooks, notification dispatch). Heterogeneous file changes across different patterns (enums, actions, migrations, localization) require separate reasoning for each cohort. Database schema changes with data cleanup operations add careful review requirements.

Poem

🐰✨ SRV records bound for distant shores,
Our little hops now reach game servers' doors!
Cloudflare APIs dance at our behest,
With srv_target moved—a schema quest,
From Nodes to glory, the DNS flows bright! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.14% 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 'SRV records support with endpoint notifications' accurately reflects the main changes: adding SRV record type support and implementing notification feedback for DNS operations.
✨ Finishing touches
  • 📝 Generate docstrings

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

🧹 Nitpick comments (3)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

120-124: Consider using a relationship or callback optimization.

The CloudflareDomain::find($get('domain_id')) query runs on every form render/interaction. While functional, this could be optimized by leveraging the relationship already defined on the model, or by using ->live() on the domain_id field to control when the lookup occurs.

🔎 Suggested optimization
 Toggle::make('srv_record')
     ->label(trans('subdomains::strings.srv_record'))
     ->helperText(trans('subdomains::strings.srv_record_help'))
-    ->disabled(fn (callable $get) => !$get('domain_id') || empty(CloudflareDomain::find($get('domain_id'))->srv_target ?? null))
+    ->disabled(fn (callable $get, ?Subdomain $record) => 
+        !$get('domain_id') || 
+        empty($record?->domain?->srv_target ?? CloudflareDomain::find($get('domain_id'))?->srv_target)
+    )
     ->default(false),
subdomains/src/Services/CloudflareService.php (1)

69-82: Consider making the SRV service prefix configurable or explicitly document the Minecraft-specific scope.

The _minecraft._tcp prefix is hardcoded at line 70 with no mechanism to customize it. Given that the plugin documentation describes it as supporting "game servers" generically, this architectural decision limits the plugin's reusability for other services that use SRV records (e.g., SIP, XMPP, other games). Either explicitly scope the plugin to Minecraft-only in documentation and naming, or refactor to accept the service prefix as a parameter or configuration option.

subdomains/src/Models/Subdomain.php (1)

10-10: Remove unused import.

The Http facade is imported but never used in this file. The code now uses CloudflareService for all HTTP interactions.

🔎 Proposed fix
-use Illuminate\Support\Facades\Http;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3f26b9 and 89f45b8.

📒 Files selected for processing (9)
  • subdomains/database/migrations/004_add_srv_target_to_cloudflare_domains.php
  • subdomains/lang/en/strings.php
  • subdomains/src/Filament/Admin/Resources/CloudflareDomains/CloudflareDomainResource.php
  • subdomains/src/Filament/Admin/Resources/CloudflareDomains/Pages/ManageCloudflareDomains.php
  • subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (25-218)
  • domain (58-61)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (17-55)
subdomains/src/Models/CloudflareDomain.php (1)
subdomains/src/Services/CloudflareService.php (2)
  • CloudflareService (9-161)
  • getZoneId (11-39)
🪛 GitHub Actions: Pint
subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php

[error] 1-1: pint --test: no_unused_imports violation detected.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php

[error] 1-1: pint --test: no_unused_imports violation detected.

subdomains/src/Services/CloudflareService.php

[error] 1-1: pint --test: class_attributes_separation violation detected.

subdomains/src/Models/CloudflareDomain.php

[error] 1-1: pint --test: no_unused_imports violation detected.


[error] 1-1: pint --test: ordered_imports violation detected.

subdomains/src/Models/Subdomain.php

[error] 1-1: pint --test: class_attributes_separation violation detected.


[error] 1-1: pint --test: statement_separation violation detected.

🔇 Additional comments (12)
subdomains/src/Filament/Admin/Resources/CloudflareDomains/Pages/ManageCloudflareDomains.php (1)

16-18: LGTM — notification delegation to model layer.

Disabling successNotification(null) here avoids duplicate notifications since the CloudflareDomain model now emits its own success/failure notifications during the created event. This is a reasonable approach for consolidated feedback.

subdomains/src/Filament/Admin/Resources/CloudflareDomains/CloudflareDomainResource.php (1)

56-57: LGTM — SRV target field implementation.

The new srv_target field is well-integrated with appropriate labels, helper text, and a solid hostname validation regex that follows RFC conventions for domain names.

Also applies to: 82-86

subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php (1)

87-90: LGTM — SRV record toggle implementation.

The toggle replaces the previous hidden record_type field with a user-controllable option, which aligns with the PR's goal of enabling SRV record support.

subdomains/lang/en/strings.php (1)

17-37: LGTM — comprehensive language strings.

The new translation keys provide clear, consistent messaging for SRV record functionality and Cloudflare notifications.

subdomains/database/migrations/004_add_srv_target_to_cloudflare_domains.php (1)

7-21: LGTM — clean migration.

The migration correctly adds a nullable srv_target column and provides proper rollback support.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

83-86: LGTM — ToggleColumn with appropriate guards.

The toggle is properly disabled when the domain is missing or lacks an srv_target, with contextual tooltips to inform users why it's unavailable.

subdomains/src/Services/CloudflareService.php (2)

41-120: LGTM — well-structured upsert logic.

The method properly validates inputs, handles both SRV and A/AAAA record types, and provides comprehensive error handling with structured response arrays. The separation between create (POST) and update (PUT) based on recordId presence follows Cloudflare API conventions.


11-39: LGTM — solid service layer implementation.

The getZoneId and deleteDnsRecord methods have appropriate input validation, exception handling, and logging. The structured return format enables consistent error handling in calling code.

Also applies to: 139-160

subdomains/src/Models/Subdomain.php (4)

27-38: LGTM!

The srv_record field is properly added to fillable and cast as boolean.


44-51: Consider whether Cloudflare failures should halt the save.

The upsertOnCloudflare() method returns early on failure but the model save proceeds regardless. This could leave the database record and Cloudflare DNS out of sync. If this is intentional (e.g., to allow offline editing), consider documenting this behavior; otherwise, consider throwing an exception to abort the save on Cloudflare failures.


73-76: LGTM!

Clean accessor implementation for the virtual srv_record attribute.


78-89: LGTM!

Proper null-safe checking for server allocation and correct IP version detection logic.

- Fixed potential potential NPE
- Corrected some notifications
- Removed inbuilt notifications in favor of service notifications
- Fixed Style/unused class issues
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

♻️ Duplicate comments (3)
subdomains/src/Models/CloudflareDomain.php (1)

28-47: Use translation keys for notification messages.

Lines 34 and 39 contain hardcoded English strings. The past review comment indicated that translation keys zone_request_failed and zone_request_succeeded are already defined in strings.php. Please use these keys instead of hardcoded strings for consistency and i18n support.

subdomains/src/Services/CloudflareService.php (1)

136-138: Fix formatting to resolve pipeline failure.

The pipeline is failing due to braces_position and blank_line violations. Based on past review comments, there may be spacing issues between methods. Ensure consistent spacing (single blank line) between method declarations per the project's PHP-CS-Fixer rules.

subdomains/src/Models/Subdomain.php (1)

190-211: Critical: Fix delete notification logic.

Multiple issues with the delete notifications:

  1. Success notification uses wrong severity: Lines 198-202 send a danger() notification on success when it should be success().
  2. Typo: Line 200 has "successed" which should be "succeeded".
  3. Unconditional failure notification: Lines 205-209 always execute, sending a failure notification even after a successful delete. This results in users seeing both success and failure notifications.

Add an else branch or early return after the success case.

🔎 Proposed fix
         if (!empty($result['success'])) {
+            Log::info('Deleted Cloudflare record for Subdomain ID ' . $this->id, ['result' => $result]);
+
             Notification::make()
-                ->danger()
-                ->title('Cloudflare: Delete successed')
+                ->success()
+                ->title('Cloudflare: Delete succeeded')
                 ->body('Successfully deleted Cloudflare record for ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . '.')
                 ->send();
+        } else {
+            Log::warning('Failed to delete Cloudflare record for Subdomain ID ' . $this->id, ['result' => $result]);
+
+            Notification::make()
+                ->danger()
+                ->title('Cloudflare: Delete failed')
+                ->body('Failed to delete Cloudflare record for ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . '. See logs for details. Errors: ' . json_encode($result['errors'] ?? $result['body'] ?? []))
+                ->send();
         }
-
-        Notification::make()
-            ->danger()
-            ->title('Cloudflare: Delete failed')
-            ->body('Failed to delete Cloudflare record for ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . '. See logs for details. Errors: ' . json_encode($result['errors'] ?? $result['body'] ?? []))
-            ->send();
🧹 Nitpick comments (1)
subdomains/src/Services/CloudflareService.php (1)

69-82: Hardcoded Minecraft SRV naming limits flexibility.

Line 70 hardcodes the SRV record name as _minecraft._tcp.%s, which restricts this implementation to Minecraft servers only. If the plugin is intended solely for Minecraft, this should be documented. Otherwise, consider making the service and protocol configurable.

🔎 Potential refactor to support configurable service/protocol
     public function upsertDnsRecord(
         string $zoneId,
         string $name,
         string $recordType,
         string $target,
         ?string $recordId = null,
         ?int $port = null,
+        ?string $service = '_minecraft',
+        ?string $protocol = '_tcp',
     ): array
     {
         // ...
         if ($recordType === 'SRV') {
             // ...
             $payload = [
-                'name' => sprintf('_minecraft._tcp.%s', $name),
+                'name' => sprintf('%s.%s.%s', $service, $protocol, $name),
                 // ...
             ];
         }
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89f45b8 and b42370d.

📒 Files selected for processing (5)
  • subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Models/CloudflareDomain.php (1)
subdomains/src/Services/CloudflareService.php (2)
  • CloudflareService (9-160)
  • getZoneId (11-39)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-160)
  • upsertDnsRecord (41-120)
  • deleteDnsRecord (138-159)
subdomains/src/Models/CloudflareDomain.php (1)
  • boot (24-48)
🪛 GitHub Actions: Pint
subdomains/src/Services/CloudflareService.php

[error] 1-1: pint --test failed: braces_position, blank_line issues detected in CloudflareService.php. Command failed with exit code 1.

subdomains/src/Models/Subdomain.php

[error] 1-1: pint --test failed: no_unused_imports, ordered_imports issues detected in Subdomain.php. Command failed with exit code 1.

🔇 Additional comments (7)
subdomains/src/Services/CloudflareService.php (3)

11-39: LGTM!

The getZoneId method properly validates inputs, handles exceptions, and returns the first matching zone ID. Error logging is appropriate.


94-120: LGTM!

The upsert logic correctly differentiates between create (POST) and update (PUT) based on the presence of $recordId. Error handling and logging are appropriate.


138-159: LGTM!

The deleteDnsRecord method properly validates inputs, handles the DELETE request, and includes appropriate error handling and logging.

subdomains/src/Models/Subdomain.php (4)

44-58: LGTM!

The lifecycle hooks are well-structured. Using saving to derive record_type from srv_record before persistence, then saved to trigger the Cloudflare upsert, ensures the model state is consistent before making external API calls.


75-91: LGTM!

The accessor/mutator pattern for srv_record is clean. The setRecordType method properly determines the record type (SRV, A, or AAAA) based on the boolean flag and IP version, with appropriate null checks.


110-149: LGTM!

The SRV record upsert logic properly validates the port, calls the service with the domain's srv_target, and uses updateQuietly on line 130 to avoid triggering lifecycle hooks recursively. Notifications provide clear feedback on success and failure.


151-187: LGTM!

The A/AAAA record upsert logic includes proper null checks for $this->server and $this->server->allocation (addressing the past review comment), validates IP addresses, and provides appropriate notifications. The use of updateQuietly prevents recursive hook invocation.

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

Caution

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

⚠️ Outside diff range comments (1)
subdomains/src/Models/Subdomain.php (1)

1-211: Fix Pint violation: Remove whitespace from blank lines.

The pipeline failure indicates the no_whitespace_in_blank_line rule is triggered. Ensure all blank lines throughout the file contain no spaces or tabs.

Run Pint locally to identify and auto-fix the whitespace:

./vendor/bin/pint subdomains/src/Models/Subdomain.php
🧹 Nitpick comments (1)
subdomains/src/Models/Subdomain.php (1)

92-187: Optional: Consider extracting duplicate success-handling logic.

The cloudflare_id update (lines 128-130, 166-168) and success notification (lines 132-136, 170-174) are duplicated across the SRV and A/AAAA paths. Extracting a small helper method would reduce repetition.

🔎 Example refactor to reduce duplication
private function handleUpsertSuccess(string $recordId): void
{
    if ($this->cloudflare_id !== $recordId) {
        $this->updateQuietly(['cloudflare_id' => $recordId]);
    }
    
    Notification::make()
        ->success()
        ->title('Cloudflare: Record updated')
        ->body('Successfully updated ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . ' to ' . $this->record_type)
        ->send();
}

Then replace lines 128-136 and 166-174 with:

$this->handleUpsertSuccess($result['id']);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b42370d and ea4e602.

📒 Files selected for processing (2)
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Services/CloudflareService.php
🧰 Additional context used
🧬 Code graph analysis (1)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-160)
  • upsertDnsRecord (41-120)
  • deleteDnsRecord (138-159)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (23-36)
🪛 GitHub Actions: Pint
subdomains/src/Models/Subdomain.php

[error] 1-1: pint --test failed with exit code 1. Rule 'no_whitespace_in_blank_line' triggered.

🔇 Additional comments (4)
subdomains/src/Models/Subdomain.php (4)

6-7: LGTM: Imports support new CloudflareService integration.

The new imports (CloudflareService, Notification, Log) are properly used throughout the upsert and delete flows.

Also applies to: 12-12


26-37: LGTM: srv_record field properly configured.

The fillable array and boolean cast correctly support the new SRV record toggle feature.


39-57: LGTM: Lifecycle hooks properly refactored.

The consolidation from created/updated to saving/saved is cleaner. The srv_record virtual attribute is correctly processed and unset before persistence, and Cloudflare operations are properly triggered.


74-90: LGTM: srv_record accessor/mutator logic is sound.

The accessor provides a clean boolean interface, and the mutator correctly derives record_type with proper null-safe checks for the server allocation.

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

♻️ Duplicate comments (1)
subdomains/src/Services/CloudflareService.php (1)

122-136: Refine success detection to match Cloudflare API semantics.

Line 127's logic $body['success'] === true || count($body['result']) > 0 treats success and result as interchangeable indicators, which doesn't align with the Cloudflare API's response structure where success is an explicit boolean flag and result is the data payload.

Issues:

  1. The OR condition returns success=true if only result has data, even when $body['success'] is false
  2. count() on a non-array result will error
  3. Line 131 extracts id from result without verifying result exists or is an array

The stricter pattern at line 30 (!empty($body['result']) && count($body['result']) > 0) is more defensive.

🔎 Proposed fix
     protected function parseCloudflareHttpResponse(Response $response): array
     {
         $status = $response->status();
         $body = $response->json() ?? [];
 
-        $success = $response->successful() && ($body['success'] === true || count($body['result']) > 0);
+        $success = $response->successful() && $body['success'] === true;
 
         return [
             'success' => $success,
-            'id' => $body['result']['id'] ?? null,
+            'id' => is_array($body['result'] ?? null) ? ($body['result']['id'] ?? null) : null,
             'errors' => $body['errors'] ?? [],
             'status' => $status,
             'body' => $body,
         ];
     }
🧹 Nitpick comments (1)
subdomains/src/Services/CloudflareService.php (1)

69-82: Consider making the SRV naming convention configurable.

Line 70 hardcodes the Minecraft-specific SRV naming convention (_minecraft._tcp.%s). While this works for the current use case, it limits the service's reusability for other SRV record types (e.g., _http._tcp, _xmpp._tcp).

Consider adding optional parameters for the service and protocol, or documenting that this service is Minecraft-specific.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea4e602 and 9804937.

📒 Files selected for processing (6)
  • subdomains/lang/en/strings.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
  • subdomains/src/SubdomainsPlugin.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Models/CloudflareDomain.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (24-212)
  • domain (59-62)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (16-54)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-160)
  • upsertDnsRecord (41-120)
  • deleteDnsRecord (138-159)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (23-36)
🪛 GitHub Actions: Pint
subdomains/src/Services/CloudflareService.php

[warning] 1-1: Pint lint issue: braces_position, blank_line...

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php

[error] 89-89: Parse error: syntax error, unexpected token '->', expecting ']' on line 89.

subdomains/src/Models/Subdomain.php

[warning] 1-1: Pint lint issue: blank_line_before_statement, no_whitespace...

🔇 Additional comments (9)
subdomains/src/SubdomainsPlugin.php (1)

50-50: LGTM! Proper internationalization.

The notification title is now properly localized using the translation key from the strings file.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

82-85: LGTM! Well-implemented ToggleColumn with proper validation.

The SRV record toggle includes helpful tooltips and correctly disables when the domain is missing an SRV target, preventing invalid configurations.

subdomains/src/Services/CloudflareService.php (2)

11-39: LGTM! Robust zone ID fetching with proper error handling.

The method includes appropriate validation, exception handling, and logging for both request failures and API errors.


138-159: LGTM! Consistent error handling pattern.

The delete method follows the same robust error handling pattern as the other methods, with proper validation, logging, and exception handling.

subdomains/lang/en/strings.php (1)

17-65: LGTM! Comprehensive and clear translation keys.

The new translation keys provide clear, user-friendly messages for SRV record functionality and Cloudflare operations. The notification messages include helpful context with placeholder variables.

subdomains/src/Models/Subdomain.php (4)

39-57: LGTM! Cleaner lifecycle hook implementation.

The refactored hooks consolidate create/update logic into saving/saved events, which is more maintainable than separate created/updated handlers. The srv_record attribute conversion before persistence is well-placed.


74-90: LGTM! Well-implemented record type logic.

The accessor and setter properly handle the SRV/A/AAAA record type determination with appropriate null safety checks for the server relationship.


92-187: LGTM! Comprehensive upsert implementation with excellent error handling.

The method properly:

  • Validates zone ID availability
  • Handles SRV and A/AAAA record types with appropriate validation
  • Provides clear user notifications for both success and failure cases
  • Updates the cloudflare_id when it changes
  • Uses the translation keys for user-facing messages

The delegation of SRV target validation to CloudflareService is appropriate.


189-211: LGTM! Correct notification flow with early return.

The delete method properly handles success and failure cases with the return statement on line 202 ensuring only one notification is sent. The notification types and translation keys are appropriate for delete operations.

@HarlequinSin HarlequinSin marked this pull request as draft December 28, 2025 23:39
@HarlequinSin
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

♻️ Duplicate comments (2)
subdomains/src/Models/Subdomain.php (2)

118-130: Add null-safe operator for server access.

Line 123 accesses $this->server->allocation?->ip without a null-safe operator on $this->server. If the server relationship is not loaded or is null, this will cause a fatal error.

🔎 Proposed fix
-            $ip = $this->server->allocation?->ip;
+            $ip = $this->server?->allocation?->ip;

136-137: Add null check for domain relationship.

Lines 136-137 access $this->domain->cloudflare_id and $this->domain->name without first verifying that $this->domain is not null. If the domain relationship is not loaded, this will cause a fatal error.

🔎 Proposed fix
     protected function upsertOnCloudflare(): bool
     {
         $registrar = app(CloudflareService::class);
 
+        if (!$this->domain) {
+            Log::warning('Domain relationship not loaded for subdomain', ['subdomain_id' => $this->id]);
+            Notification::make()
+                ->danger()
+                ->title(trans('subdomains::strings.notifications.cloudflare_upsert_failed_title'))
+                ->body(trans('subdomains::strings.notifications.cloudflare_upsert_failed', ['subdomain' => $this->name ?? 'unknown', 'errors' => 'Domain relation is null']))
+                ->send();
+
+            return false;
+        }
+
         $zoneId = $this->domain->cloudflare_id;
         $domainName = $this->domain->name;
🧹 Nitpick comments (4)
subdomains/src/Models/Subdomain.php (4)

6-20: Consider keeping cloudflare_id nullable in docblock.

Line 20 declares cloudflare_id as a non-nullable string, but this field is actually nullable until the Cloudflare record is successfully created. The code handles this with empty() checks throughout (e.g., lines 271, 277). Keeping the docblock as ?string would better reflect the actual behavior and prevent static analysis false positives.

🔎 Proposed fix
- * @property string $cloudflare_id
+ * @property ?string $cloudflare_id

139-139: Use null-safe operator for consistency.

Line 139 checks empty($this->server) || empty($this->server->node). While technically safe due to short-circuit evaluation, using the null-safe operator (empty($this->server?->node)) would be more consistent with the coding style used elsewhere in this file and prevents potential static analysis warnings.

🔎 Proposed fix
-        if (empty($this->server) || empty($this->server->node)) {
+        if (empty($this->server) || empty($this->server?->node)) {

173-230: SRV record logic is well-structured.

The SRV path correctly:

  • Validates port availability (lines 174-184)
  • Determines service record type from server egg tags (lines 186-196)
  • Validates node srv_target (lines 198-207)
  • Calls CloudflareService with correct parameters (line 209)
  • Uses updateQuietly for existing models and direct assignment for new models (lines 212-218)

While the guards at lines 139 and 150 protect against null server/allocation, consider adding null-safe operators defensively on lines 174 and 198 for consistency:

$port = $this->server?->allocation?->port;
// and
if (empty($this->server?->node?->srv_target)) {

232-266: A/AAAA record logic is correct.

The A/AAAA path correctly:

  • Validates IP address and excludes invalid IPs (lines 233-243)
  • Calls CloudflareService with correct parameters, passing null for SRV-specific fields (line 245)
  • Handles cloudflare_id updates consistently with the SRV path (lines 248-254)

Consider adding a null-safe operator on line 233 for defensive programming:

$ip = $this->server?->allocation?->ip;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 660b0b6 and 87d4de8.

📒 Files selected for processing (2)
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/Subdomain.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Models/Subdomain.php (3)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (10-215)
  • upsertDnsRecord (80-166)
  • deleteDnsRecord (171-195)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (27-40)
subdomains/src/Enums/ServiceRecordType.php (2)
  • getLabel (13-16)
  • fromServer (30-35)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • server (103-106)
  • Subdomain (26-304)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (16-71)
🔇 Additional comments (7)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (4)

17-17: LGTM!

The Toggle import is correctly added to support the new srv_record form field.


86-90: LGTM!

The record_type column correctly uses null-safe operators to prevent NPEs when accessing the server/node relationship chain.


93-107: LGTM!

The actions correctly suppress default success notifications by chaining ->successNotification(null), allowing the model lifecycle hooks to handle notifications instead.


129-133: LGTM!

The srv_record toggle correctly uses null-safe operators and provides appropriate helper text and disabled state based on srv_target availability.

subdomains/src/Models/Subdomain.php (3)

28-43: LGTM!

The srv_record field is properly configured as a virtual attribute with fillable, cast, and append declarations to support the new SRV record functionality.


49-96: LGTM!

The lifecycle hooks are well-structured:

  • Using creating/updating/deleting (instead of past-tense events) allows aborting operations by returning false when Cloudflare operations fail.
  • Line 51 manually sets the server relation during creation, which is necessary since the relationship doesn't exist yet—this is a valid workaround.
  • User-facing notifications are appropriately placed in the lifecycle hooks.

288-303: Delete logic handles edge cases well.

The deletion flow correctly:

  • Treats 404 responses as successful (line 292), acknowledging that the record may already be deleted
  • Provides detailed error information in failure notifications (lines 296-300)
  • Returns boolean to allow lifecycle hooks to abort deletion if needed

@HarlequinSin
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

♻️ Duplicate comments (1)
subdomains/src/Models/Subdomain.php (1)

136-170: Add null check for $this->domain before accessing properties.

Lines 136-137 access $this->domain->cloudflare_id and $this->domain->name without first verifying the domain relationship is loaded. If the relationship is null or not loaded, this will throw a fatal error before the validation at line 161 can execute.

🔎 Proposed fix
     protected function upsertOnCloudflare(): bool
     {
         $registrar = app(CloudflareService::class);
 
+        if (!$this->domain) {
+            Log::warning('Domain relationship not loaded for subdomain', ['subdomain_id' => $this->id]);
+            Notification::make()
+                ->danger()
+                ->title(trans('subdomains::strings.notifications.cloudflare_upsert_failed_title'))
+                ->body(trans('subdomains::strings.notifications.cloudflare_upsert_failed', ['subdomain' => $this->name ?? 'unknown', 'errors' => 'Domain relationship not loaded']))
+                ->send();
+            
+            return false;
+        }
+
         $zoneId = $this->domain->cloudflare_id;
         $domainName = $this->domain->name;
🧹 Nitpick comments (2)
subdomains/src/Models/Subdomain.php (1)

232-266: LGTM with minor suggestion: Add defensive null-safe operator.

The A/AAAA record path correctly validates IP, calls the Cloudflare service, and updates the cloudflare_id. While line 139 ensures $this->server exists before reaching this code, adding a null-safe operator on line 233 ($this->server?->allocation?->ip) would provide additional defensive protection against future refactoring that might change the validation flow.

🔎 Optional defensive improvement
         // A/AAAA
-        $ip = $this->server->allocation?->ip; // @phpstan-ignore nullsafe.neverNull
+        $ip = $this->server?->allocation?->ip; // @phpstan-ignore nullsafe.neverNull
         if (empty($ip) || $ip === '0.0.0.0' || $ip === '::') {
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

112-135: LGTM with minor suggestion: Add defensive null-safe operator.

The form correctly displays subdomain name with domain suffix, domain selection, and SRV toggle with appropriate helper text and disabled state based on srv_target availability. All null-safe operators are properly used to handle missing relationships.

While Filament::getTenant() always returns a Server in this resource context, adding a null-safe operator (getTenant()?->node?->srv_target) on lines 131 and 133 would provide additional defensive protection against future refactoring or edge cases.

🔎 Optional defensive improvement
                 Toggle::make('srv_record')
                     ->label(trans('subdomains::strings.srv_record'))
-                    ->helperText(fn () => Filament::getTenant()->node?->srv_target ? trans('subdomains::strings.srv_record_help') : trans('subdomains::strings.srv_target_missing')) // @phpstan-ignore property.notFound
+                    ->helperText(fn () => Filament::getTenant()?->node?->srv_target ? trans('subdomains::strings.srv_record_help') : trans('subdomains::strings.srv_target_missing')) // @phpstan-ignore property.notFound
                     ->reactive()
-                    ->disabled(fn () => !Filament::getTenant()->node?->srv_target), // @phpstan-ignore property.notFound
+                    ->disabled(fn () => !Filament::getTenant()?->node?->srv_target), // @phpstan-ignore property.notFound
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87d4de8 and a288d46.

📒 Files selected for processing (2)
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/Subdomain.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • server (103-106)
  • Subdomain (26-304)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (16-71)
subdomains/src/Models/Subdomain.php (4)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (10-215)
  • upsertDnsRecord (80-166)
  • deleteDnsRecord (171-195)
subdomains/src/Models/CloudflareDomain.php (1)
  • boot (23-47)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (27-40)
subdomains/src/Enums/ServiceRecordType.php (2)
  • getLabel (13-16)
  • fromServer (30-35)
🔇 Additional comments (3)
subdomains/src/Models/Subdomain.php (1)

268-303: LGTM!

The delete logic correctly handles missing cloudflare_id with an early return, validates the domain relationship using null-safe operators, calls the Cloudflare service, and treats both successful deletes and 404s (already deleted) as success. The error handling and notifications are appropriate.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)

35-46: LGTM!

The canAccess() logic correctly validates that the server has a valid, non-wildcard IP and that at least one CloudflareDomain exists before allowing subdomain management. The null-safe operator properly handles missing allocation relationships.


79-109: LGTM!

The table correctly displays subdomain labels and record types, with appropriate warning indicators when SRV records lack a target. The null-safe operators prevent NPEs when server or node relationships are missing. Suppressing default success notifications (lines 94, 96, 107) is appropriate since the model's lifecycle hooks send custom notifications, avoiding duplicates.

@HarlequinSin
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a288d46 and 0c75e5a.

📒 Files selected for processing (1)
  • subdomains/src/Models/Subdomain.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T11:13:37.620Z
Learnt from: HarlequinSin
Repo: pelican-dev/plugins PR: 67
File: subdomains/src/Models/Subdomain.php:49-65
Timestamp: 2026-01-04T11:13:37.620Z
Learning: In Subdomain model creation (subdomains/src/Models/Subdomain.php), use setRelation('server', Filament::getTenant()) instead of load() for the unsaved model during creation. This is intentional to populate the relationship in Filament context when the model hasn't been saved yet.

Applied to files:

  • subdomains/src/Models/Subdomain.php
🔇 Additional comments (1)
subdomains/src/Models/Subdomain.php (1)

211-211: Method signature verification complete. The CloudflareService::upsertDnsRecord method signature accepts 8 parameters as defined, and both calls at lines 211 and 247 pass the correct number and types of arguments. No signature mismatch detected.

@HarlequinSin
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (2)
subdomains/src/Models/Subdomain.php (2)

48-52: Consider optimizing for Filament context per past decision.

The current code uses loadMissing universally, which works but queries the database even in Filament contexts where Filament::getTenant() already provides the Server instance. Based on learnings, you can optimize by using setRelation when in Filament context:

if (Filament::hasTenant()) {
    $model->setRelation('server', Filament::getTenant());
    if (!$model->relationLoaded('server.allocation')) {
        $model->server->load('allocation');
    }
} elseif ($model->server_id) {
    $model->loadMissing('server.allocation');
}

This avoids redundant queries in Filament while still supporting non-Filament contexts.

Based on learnings, using setRelation in Filament contexts is the intended pattern for efficiency.


109-112: Make getLabel() defensive for error-path usage.

While not a changed line, getLabel() is called throughout the new notification code (lines 62, 77, 92, 145, 156, 168, etc.). Line 111 accesses $this->domain->name without null-safe handling. If the domain relationship isn't loaded when an error occurs, the error notification itself will fail with an NPE.

🔎 Proposed fix
     public function getLabel(): string|Htmlable|null
     {
-        return $this->name . '.' . $this->domain->name;
+        return $this->name . '.' . ($this->domain?->name ?? 'unknown');
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c75e5a and a82334a.

📒 Files selected for processing (1)
  • subdomains/src/Models/Subdomain.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T11:13:37.620Z
Learnt from: HarlequinSin
Repo: pelican-dev/plugins PR: 67
File: subdomains/src/Models/Subdomain.php:49-65
Timestamp: 2026-01-04T11:13:37.620Z
Learning: In Subdomain model creation (subdomains/src/Models/Subdomain.php), use setRelation('server', Filament::getTenant()) instead of load() for the unsaved model during creation. This is intentional to populate the relationship in Filament context when the model hasn't been saved yet.

Applied to files:

  • subdomains/src/Models/Subdomain.php
🧬 Code graph analysis (1)
subdomains/src/Models/Subdomain.php (3)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (10-215)
  • upsertDnsRecord (80-166)
  • deleteDnsRecord (171-195)
subdomains/src/Models/CloudflareDomain.php (1)
  • boot (23-47)
subdomains/src/Enums/ServiceRecordType.php (2)
  • getLabel (13-16)
  • fromServer (30-35)

@HarlequinSin HarlequinSin marked this pull request as ready for review January 4, 2026 11:57
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

🧹 Nitpick comments (4)
subdomains/src/Models/Subdomain.php (4)

48-52: Consider loading additional relationships upfront.

The code loads server.allocation but upsertOnCloudflare() also accesses $this->domain (lines 137-138, 162, 167, 200, 204) and $this->server->node (lines 140, 175, 187, 199, 210). These will trigger lazy loading, causing additional queries.

🔎 Proposed optimization
         static::creating(function (self $model) {
             // Relation does not exist yet, so we need to set it manually.
             if ($model->server_id) {
-                $model->loadMissing('server.allocation');
+                $model->loadMissing(['server.allocation', 'server.node', 'domain']);
             }
 
             $registrarUpdated = $model->upsertOnCloudflare();

77-78: Use null-safe operator for consistency with the rest of the file.

Although the notification only executes after upsertOnCloudflare() returns true (which validates domain), accessing $model->domain->name without null-safe is inconsistent with the defensive patterns used in lines 137-138. If domain is null, the expression throws before ?? evaluates.

🔎 Proposed fix
-                ->body(trans('subdomains::strings.notifications.cloudflare_record_updated', ['subdomain' => $model->name . '.' . ($model->domain->name ?? 'unknown'), 'record_type' => $model->record_type]))
+                ->body(trans('subdomains::strings.notifications.cloudflare_record_updated', ['subdomain' => $model->name . '.' . ($model->domain?->name ?? 'unknown'), 'record_type' => $model->record_type]))

92-93: Same null-safe operator issue in deleting hook.

🔎 Proposed fix
-                ->body(trans('subdomains::strings.notifications.cloudflare_record_deleted', ['subdomain' => $model->name . '.' . ($model->domain->name ?? 'unknown')]))
+                ->body(trans('subdomains::strings.notifications.cloudflare_record_deleted', ['subdomain' => $model->name . '.' . ($model->domain?->name ?? 'unknown')]))

109-112: Add null-safe operator to getLabel() for defensive coding.

This public method (implementing HasLabel) can be called in various Filament contexts. If the domain relationship isn't loaded, this will throw an NPE.

🔎 Proposed fix
     public function getLabel(): string|Htmlable|null
     {
-        return $this->name . '.' . $this->domain->name;
+        return $this->name . '.' . ($this->domain?->name ?? 'unknown');
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a82334a and 0ba3dc6.

📒 Files selected for processing (1)
  • subdomains/src/Models/Subdomain.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T11:13:37.620Z
Learnt from: HarlequinSin
Repo: pelican-dev/plugins PR: 67
File: subdomains/src/Models/Subdomain.php:49-65
Timestamp: 2026-01-04T11:13:37.620Z
Learning: In Subdomain model creation (subdomains/src/Models/Subdomain.php), use setRelation('server', Filament::getTenant()) instead of load() for the unsaved model during creation. This is intentional to populate the relationship in Filament context when the model hasn't been saved yet.

Applied to files:

  • subdomains/src/Models/Subdomain.php
🧬 Code graph analysis (1)
subdomains/src/Models/Subdomain.php (3)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (10-215)
  • upsertDnsRecord (80-166)
  • deleteDnsRecord (171-195)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (27-40)
subdomains/src/Enums/ServiceRecordType.php (2)
  • getLabel (13-16)
  • fromServer (30-35)
🔇 Additional comments (6)
subdomains/src/Models/Subdomain.php (6)

33-42: LGTM on the virtual attribute setup.

The srv_record property is correctly added to fillable (triggering the mutator on mass assignment), cast as boolean, and appended for serialization. This cleanly abstracts the underlying record_type field.


114-131: LGTM on accessor/mutator implementation.

The getSrvRecordAttribute correctly derives the boolean from record_type, and setSrvRecordAttribute properly handles the reverse mapping with correct IPv6 detection using filter_var() with appropriate flags.


133-171: Well-structured validation sequence.

The method properly validates all prerequisites (server, node, allocation, domain zone) before proceeding, with appropriate logging and user notifications for each failure case. The use of null-safe operators at lines 137-138 is correct.


173-231: SRV record path is comprehensive.

The SRV flow correctly validates port, service record type (derived from server egg tags), and node srv_target before making the API call. The cloudflare_id persistence logic handles both new and existing models appropriately.


233-267: A/AAAA record path is consistent with SRV path.

Good handling of edge cases (0.0.0.0, ::) for invalid IPs, and cloudflare_id update logic mirrors the SRV path as previously recommended.


269-304: Clean delete implementation with good edge case handling.

The method correctly:

  • Returns success when there's nothing to delete (no cloudflare_id)
  • Validates domain zone exists before attempting deletion
  • Treats 404 responses as success (record already gone)
  • Only sends failure notification on actual failures

@HarlequinSin
Copy link
Author

  • SRV target can now be set per-node
  • Service/protocol for SRV records can now be added as new cases to subdomains\src\Enums\ServiceRecordType.php. This will check against the server's egg tags

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.

Thanks for the changes!
There seems to be a bunch of null and nullable related mishaps.

@HarlequinSin HarlequinSin marked this pull request as draft January 4, 2026 20:00
@HarlequinSin HarlequinSin marked this pull request as ready for review January 6, 2026 08:55
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

🧹 Nitpick comments (2)
subdomains/README.md (1)

34-36: Optional: Vary sentence structure to improve readability.

The troubleshooting list has three consecutive items starting with "Missing". Consider rephrasing for variety, though this is a minor stylistic point.

Suggested alternative phrasing
 - **Missing IP for A/AAAA:** ensure the server has an allocation with a valid IP address.
 - **Missing SRV port:** ensure the server allocation includes a port.
-- **Missing SRV target:** configure the node's SRV target.
+- **SRV target not configured:** set the node's SRV target.
 - **Unsupported SRV service:** add the appropriate tag to the Egg (e.g., `minecraft`).

Based on static analysis hints.

subdomains/src/Models/Subdomain.php (1)

125-125: Add null-safe operator for defensive coding.

While the allocation should be loaded in normal flows, setSrvRecordAttribute() is a public mutator that could be called directly. Using the null-safe operator on $this->server provides extra safety.

🔎 Suggested fix
-            $ip = $this->server->allocation->ip;
+            $ip = $this->server?->allocation?->ip;
             if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
                 $this->attributes['record_type'] = 'AAAA';
             } else {
                 $this->attributes['record_type'] = 'A';
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba3dc6 and 543393f.

📒 Files selected for processing (5)
  • subdomains/README.md
  • subdomains/database/migrations/004_add_srv_target_to_nodes.php
  • subdomains/src/Enums/ServiceRecordType.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/Subdomain.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T11:13:37.620Z
Learnt from: HarlequinSin
Repo: pelican-dev/plugins PR: 67
File: subdomains/src/Models/Subdomain.php:49-65
Timestamp: 2026-01-04T11:13:37.620Z
Learning: In Subdomain model creation (subdomains/src/Models/Subdomain.php), use setRelation('server', Filament::getTenant()) instead of load() for the unsaved model during creation. This is intentional to populate the relationship in Filament context when the model hasn't been saved yet.

Applied to files:

  • subdomains/src/Models/Subdomain.php
🧬 Code graph analysis (2)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (10-215)
  • upsertDnsRecord (80-166)
  • deleteDnsRecord (171-195)
subdomains/src/Enums/ServiceRecordType.php (2)
  • getLabel (18-21)
  • fromServer (23-28)
subdomains/src/Enums/ServiceRecordType.php (2)
subdomains/src/Models/Subdomain.php (2)
  • getLabel (110-113)
  • server (105-108)
billing/src/Models/Product.php (1)
  • egg (89-92)
🪛 LanguageTool
subdomains/README.md

[style] ~36-~36: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... server allocation includes a port. - Missing SRV target: configure the node's SRV ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🔇 Additional comments (6)
subdomains/README.md (1)

8-51: Well-structured documentation!

The additions clearly explain SRV requirements, supported games, troubleshooting steps, and provide practical examples. This will help users configure the plugin correctly.

subdomains/src/Enums/ServiceRecordType.php (1)

1-55: Clean enum implementation!

The ServiceRecordType enum is well-structured with clear case definitions and helpful utility methods. The tag-based mapping from server to service type is elegant and extensible.

subdomains/database/migrations/004_add_srv_target_to_nodes.php (1)

10-44: Migration logic is sound.

The migration correctly:

  • Cleans up orphaned records before enforcing non-null constraints
  • Moves srv_target from cloudflare_domains to nodes
  • Provides a reversible down() method for schema changes

Note that data deleted in up() cannot be restored in down(), but this is expected and documented in the code comments.

subdomains/src/Models/Subdomain.php (3)

49-67: Good relationship preloading in creating hook.

The creating hook properly loads all necessary relationships (server.allocation, server.node, domain) before calling upsertOnCloudflare(), which prevents null-reference errors during the Cloudflare API operations.


134-256: Well-structured upsertOnCloudflare implementation.

The method properly handles both SRV and A/AAAA record types with comprehensive validation:

  • Validates all required relationships and data
  • Provides clear user notifications for each failure scenario
  • Correctly updates cloudflare_id using updateQuietly for existing models
  • Logs warnings for troubleshooting

The separation between SRV and IP-based paths is clean and maintainable.


258-293: Solid deleteOnCloudflare implementation.

The method handles edge cases well:

  • Returns true when no cloudflare_id exists (nothing to delete)
  • Treats 404 responses as success (already deleted)
  • Provides clear error notifications with context

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