Skip to content

Conversation

@TinyMemoria
Copy link
Contributor

@TinyMemoria TinyMemoria commented Jan 26, 2026

Fixes: #87

Fixed the Edit workflow so that editing a subdomain updates Cloudflare correctly and does not delete the record on failure (some improved error handling).

Previous:
When you edit a subdomain name, the plugin calls Cloudflare in a way that incorrectly treats the existing record as a conflict. Throws exception, and deletes record.

Now:
When you edit a subdomain name, the plugin queries Cloudflare records by name and type, then checks each record ID, if record has a different ID, an exception is thrown, otherwise update the record.

Additional error handling:
If New Subdomain is created for the first time and there is an error, delete the record (as previous).
If Subdomain is being edited and there is an error, keep the record, as to not remove a current working record.

Summary by CodeRabbit

  • Bug Fixes

    • Added preflight validation to detect duplicate or conflicting DNS records earlier.
    • Unified record name handling (including SRV records) to ensure consistent creation and updates.
    • Ensured payload is validated against server allocation IPs for non-SRV records.
  • Refactor

    • Made create/update flow explicit with safer cleanup and state refresh to avoid deleting existing records on errors.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a preflight DNS-record search with unified name construction and earlier payload assembly in the Subdomain model; updates Cloudflare PATCH to use dns_records/{id}. Service now distinguishes create vs update, refreshes the model, and only deletes newly created records on error.

Changes

Cohort / File(s) Summary
Subdomain Model Updates
subdomains/src/Models/Subdomain.php
Introduces computed searchName for SRV/non-SRV, builds payload before Cloudflare lookup, performs a preflight search by name/type and throws on conflicting records, uses dns_records/{cloudflare_id} for PATCH, retains updating cloudflare_id and delete-on-cloudflare logic.
Service Layer Enhancement
subdomains/src/Services/SubdomainService.php
Splits create/update into explicit branches with a $newSubdomain flag, refreshes the Subdomain after save, and only deletes the DB record on error when it was created by this call; preserves exception rethrow and return behavior.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Service
    participant Model
    participant CF
    participant DB

    rect rgba(100,200,150,0.5)
    Note over Client,Service: Create flow
    Client->>Service: createOrUpdate(NULL, data)
    Service->>DB: create Subdomain (mark new)
    Service->>Model: build payload (includes searchName)
    Model->>CF: GET search by name/type
    alt conflict found
        CF-->>Model: searchResponse (conflict)
        Model-->>Service: throw exception
        Service->>DB: delete Subdomain (created here)
    else no conflict
        Model->>CF: POST create DNS record
        CF-->>Model: return cloudflare_id
        Model->>DB: update cloudflare_id
        Service->>DB: refresh Subdomain
    end
    end

    rect rgba(100,150,200,0.5)
    Note over Client,Service: Update flow
    Client->>Service: createOrUpdate(existingModel, data)
    Service->>DB: update Subdomain (mark not new)
    Service->>Model: build payload (includes searchName)
    Model->>CF: GET search by name/type
    alt conflict found
        CF-->>Model: searchResponse (conflict)
        Model-->>Service: throw exception
        Service-->>DB: keep existing Subdomain
    else no conflict
        Model->>CF: PATCH dns_records/{cloudflare_id} with payload
        CF-->>Model: confirm update
        Service->>DB: refresh Subdomain
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through names and clouds today,
I search before I build the way,
I patch with care or tidy new,
If I made it, I don't un-do,
DNS dreams snug in gentle play.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: fixing subdomain edit functionality and improving error handling for both new and existing records.
Linked Issues check ✅ Passed The code changes address the core requirements from issue #87: enabling reliable subdomain edits, preserving records on edit failure, and fixing the error handling flow.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives of fixing subdomain edit and improving error handling; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 a584e79 and 10ac7c5.

📒 Files selected for processing (2)
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/SubdomainService.php

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

146-150: Silent failure when Cloudflare operation fails without error details.

Similar to the search response handling, if $response['success'] is false but $response['errors'] is empty or falsy, the method returns without throwing an exception. The caller assumes success, but the Cloudflare operation may have failed, leading to local/Cloudflare state mismatch.

🛠️ Suggested fix
         if (!$response['success']) {
             if ($response['errors'] && count($response['errors']) > 0) {
                 throw new Exception($response['errors'][0]['message']);
             }
+            throw new Exception('Cloudflare DNS operation failed');
         }
🤖 Fix all issues with AI agents
In `@subdomains/src/Models/Subdomain.php`:
- Around line 114-128: The search response handling in Subdomain (the
$searchResponse block) silently continues when $searchResponse['success'] is
false but $searchResponse['errors'] is empty; update the failure branch inside
the if (!$searchResponse['success']) block to throw a generic Exception (e.g.,
'Cloudflare search failed' or include available status info) when no specific
error message exists so the code does not proceed on an ambiguous failed search;
ensure you still throw the specific errors when $searchResponse['errors'] is
non-empty and reference the same $searchResponse variable and surrounding
conditional logic in Subdomain.php.
🧹 Nitpick comments (1)
subdomains/src/Services/SubdomainService.php (1)

17-17: Consider using standard PHP variable naming convention.

$NewSubdomain uses PascalCase. PHP convention is $camelCase for variables. Consider renaming to $newSubdomain or $isNewSubdomain for consistency.

♻️ Suggested change
-        $NewSubdomain = false;
+        $isNewSubdomain = false;

And update references on lines 21 and 31 accordingly.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6a2910 and d0b827e.

📒 Files selected for processing (2)
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/SubdomainService.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/Services/SubdomainService.php (1)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (24-160)
  • upsertOnCloudflare (59-151)
🔇 Additional comments (5)
subdomains/src/Services/SubdomainService.php (1)

19-34: LGTM! Good fix for the edit workflow error handling.

The logic correctly:

  1. Distinguishes between creating a new subdomain vs updating an existing one
  2. Calls refresh() to ensure the model is in sync before the Cloudflare operation
  3. Only deletes the subdomain on failure if it was newly created, preserving existing records as intended by the PR objectives

This addresses issue #87 where editing a subdomain would delete the existing DNS record on failure.

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

78-91: LGTM! Good extraction of $searchName for SRV records.

The $searchName variable cleanly captures the constructed name used for both the payload and the preflight search, improving code clarity.


96-106: LGTM! Consistent pattern for non-SRV records.

The parallel structure with SRV records improves readability and ensures both branches construct $searchName before the unified search logic.


121-127: Good preflight validation for duplicate detection.

The loop correctly checks if any existing record has a different ID than the current cloudflare_id, which properly handles both:

  • Create scenario: cloudflare_id is null, any existing record is a conflict
  • Update scenario: cloudflare_id is set, only records with different IDs are conflicts

This addresses the core issue where editing was incorrectly treating the existing record as a conflict.


130-132: LGTM! Correct PATCH URL construction.

The PATCH request now properly uses the full path dns_records/{$this->cloudflare_id} to update the existing record.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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)

78-105: Search must use fully-qualified DNS record names (FQDN) to avoid missing existing records and creating duplicates.

The Cloudflare API name filter expects a complete DNS record name including the zone name (e.g., www.example.com), not just the subdomain part. Currently, the code searches using relative names only ($this->name), which will not match existing records in Cloudflare. This allows duplicate records to be created instead of updating the existing ones.

For SRV records, append $this->domain->name to the search query:

-            $searchName = "{$srvServiceType->value}.{$this->name}";
+            $searchName = "{$srvServiceType->value}.{$this->name}.{$this->domain->name}";

For non-SRV records, do the same:

-            $searchName = $this->name;
+            $searchName = "{$this->name}.{$this->domain->name}";
🤖 Fix all issues with AI agents
In `@subdomains/src/Models/Subdomain.php`:
- Around line 108-118: The code assumes ->json() returns an array and directly
indexes ['success'], which can cause TypeError on null/invalid JSON; update the
Cloudflare API calls (e.g., where Http::cloudflare()->get(...) is used in
Subdomain:: (the $searchResponse block) and similar calls in this file and
CloudflareDomain.php) to first store the HTTP response object, validate the HTTP
status (e.g., successful()/status() check) and ensure the decoded JSON is an
array and contains the 'success' key before indexing it; if the status is not OK
or the JSON is missing/invalid, throw a clear Exception or handle the error
instead of accessing ['success'] directly.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0b827e and a584e79.

📒 Files selected for processing (2)
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/SubdomainService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Services/SubdomainService.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)

131-134: PATCH endpoint update looks correct.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@TinyMemoria
Copy link
Contributor Author

Not making changes to $searchName as this is already handled in $searchResponse.

Do you want me to make the json guard change proposed by coderabbit?
I think this change is unnecessary.
I will make the change if you think otherwise.

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.

Thank you for your contribution!

@Boy132 Boy132 merged commit 99a9b39 into pelican-dev:main Jan 26, 2026
6 of 7 checks passed
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.

[Subdomains] Edit of a subdomain does not work

2 participants