Skip to content

Conversation

@DaNussi
Copy link
Contributor

@DaNussi DaNussi commented Nov 29, 2025

Added an endpoint for importing and deleting eggs via the api.


http://localhost/api/application/eggs/import?format=json

  • Create a new egg on the Panel. Returns the created/updated egg and an HTTP/201 status response on success
  • If no uuid is supplied a new one will be generated
  • If an uuid is supplied, and it already exists the old configuration get overwritten

http://localhost/api/application/eggs/{egg:id}

  • Delete eggs via id

http://localhost/api/application/eggs/{egg:uuid}/uuid

  • Delete eggs via uuid

This is the first time I'm working with PHP and any criticism is welcome. If anything needs to be changed let me know. 😄

@github-actions
Copy link
Contributor

github-actions bot commented Nov 29, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

📝 Walkthrough

Walkthrough

This PR adds egg import and delete functionality to the application's API. It introduces a new ImportEggRequest class, extends EggImporterService with multiple content ingestion methods (fromContent, fromUrl, fromFile), adds import and delete endpoint handlers to EggController, and registers three new routes for POST import and DELETE operations.

Changes

Cohort / File(s) Summary
Controller Updates
app/Http/Controllers/Api/Application/Eggs/EggController.php
Injected EggImporterService dependency; added delete() method to remove eggs with no-content response; added import() method to create eggs from request content, returning 201 with transformed egg data.
Request Validation
app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php
New request class for egg imports extending ApplicationApiRequest with write permission checks and resource binding to Egg::RESOURCE_NAME.
Import Service
app/Services/Eggs/Sharing/EggImporterService.php
Added three public entry points: fromContent() for string content with format selection, fromUrl() to fetch and import from URLs, and fromFile() with upload validation; refactored parsing to support YAML/JSON via format-aware parse() method; added fromParsed() for transactional egg and variable creation.
API Routes
routes/api-application.php
Added POST /import route to EggController::import and DELETE routes for /{egg:id} and /uuid/{egg:uuid} mapped to EggController::delete.

Possibly related PRs

  • pelican-dev/panel#1760: Modifies the same EggController with constructor injection and adds egg-related API endpoints.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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
Title check ✅ Passed The title accurately summarizes the main changes by highlighting the two primary features added: egg import and delete functionality via the API.
Description check ✅ Passed The description clearly relates to the changeset by detailing the new import and delete endpoints, their usage, expected behaviors, and HTTP status codes.

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

@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 (2)
app/Services/Eggs/Sharing/EggImporterService.php (2)

35-98: fromContent/fromFile factoring is good; tighten handling of $parsed['variables']

The new fromContent() + fromParsed() split nicely separates transport (string/file/url) from the core import logic, which makes the new API endpoint straightforward to use.

One edge case in fromParsed():

for ($i = 0; $i < count($parsed['variables']); $i++) {
    unset($parsed['variables'][$i]['field_type']);
}

If the parsed egg has no variables key or it’s not an array, count($parsed['variables']) will emit warnings. Since later code already guards with ?? [] in some places, you could make this loop more defensive:

-        for ($i = 0; $i < count($parsed['variables']); $i++) {
-            unset($parsed['variables'][$i]['field_type']);
-        }
+        $variables = $parsed['variables'] ?? [];
+
+        foreach ($variables as $i => $variable) {
+            unset($variables[$i]['field_type']);
+        }
+
+        $parsed['variables'] = $variables;

This keeps behavior the same for normal inputs while avoiding noisy warnings on minimal/edge-case egg definitions.


128-235: Use UploadedFile::get() instead of getContent() — this is a critical correctness issue

The current code calls $file->getContent() in the parseFile() method, but Illuminate\Http\UploadedFile does not expose a getContent() method. The correct public API method is get(), which returns the file contents as a string.

Using getContent() will fail at runtime. The Laravel UploadedFile implementation wraps Symfony's File class, which internally uses getContent(), but it is not part of Laravel's public API for UploadedFile.

-            $content = $file->getContent();
+            $content = $file->get();

Additionally, the current error handling wraps parse errors twice: parse() already catches errors and throws InvalidFileUploadException('File parse failed: ...'), and then parseFile() catches and re-wraps them. This can produce nested messages like "File parse failed: File parse failed: ...". To avoid this:

         } catch (Throwable $e) {
-            throw new InvalidFileUploadException('File parse failed: ' . $e->getMessage());
+            if ($e instanceof InvalidFileUploadException) {
+                throw $e;
+            }
+
+            throw new InvalidFileUploadException('File parse failed: ' . $e->getMessage());
         }
🧹 Nitpick comments (2)
routes/api-application.php (1)

101-108: Duplicate route name for two DELETE egg endpoints

Both DELETE /eggs/{egg:id} and DELETE /eggs/{egg:uuid}/uuid share the same route name (api.application.eggs.eggs.delete). Laravel will let both routes exist, but route('api.application.eggs.eggs.delete', ...) will only ever point to the last-defined one.

If you plan to generate URLs for both forms, consider giving them distinct names (e.g., .delete vs .delete_uuid). Also, you may want to drop the duplicated eggs segment in the name (api.application.eggs.delete) for consistency with other groups.

app/Http/Controllers/Api/Application/Eggs/EggController.php (1)

58-70: delete() implementation is fine; unused request is intentional

The delete endpoint cleanly delegates to $egg->delete() and returns a 204 via returnNoContent(). The $request parameter is unused in the method body, but it’s still useful to keep for validation/authorization tied to GetEggRequest. If PHPMD complains, I’d treat that as a false positive rather than removing the type-hinted request.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd6e3d and f42d39f.

📒 Files selected for processing (5)
  • app/Http/Controllers/Api/Application/Eggs/EggController.php (3 hunks)
  • app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1 hunks)
  • app/Http/Requests/Api/Application/Eggs/PostEggRequest.php (1 hunks)
  • app/Services/Eggs/Sharing/EggImporterService.php (5 hunks)
  • routes/api-application.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1)
app/Http/Requests/Api/Application/Eggs/PostEggRequest.php (1)
  • PostEggRequest (9-14)
routes/api-application.php (1)
app/Http/Controllers/Api/Application/Eggs/EggController.php (2)
  • delete (65-70)
  • EggController (21-120)
app/Http/Controllers/Api/Application/Eggs/EggController.php (3)
app/Exceptions/Service/InvalidFileUploadException.php (1)
  • InvalidFileUploadException (7-7)
app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1)
  • ImportEggRequest (5-13)
app/Services/Eggs/Sharing/EggImporterService.php (3)
  • EggImporterService (20-300)
  • __construct (33-33)
  • fromContent (40-45)
app/Services/Eggs/Sharing/EggImporterService.php (1)
app/Exceptions/Service/InvalidFileUploadException.php (1)
  • InvalidFileUploadException (7-7)
🪛 PHPMD (2.15.0)
app/Http/Controllers/Api/Application/Eggs/EggController.php

65-65: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (2)
app/Http/Requests/Api/Application/Eggs/PostEggRequest.php (1)

5-14: PostEggRequest wiring looks consistent

Binding the resource to Egg::RESOURCE_NAME and permission to AdminAcl::WRITE matches the intended write-only semantics for POST-style egg operations. No issues from a correctness perspective.

app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1)

5-12: Import format validation is solid

Restricting format to yaml|json and making it nullable aligns well with EggFormat::tryFrom(... ) ?? EggFormat::YAML in the controller. This gives a clear contract for callers and keeps the enum mapping straightforward.

@DaNussi
Copy link
Contributor Author

DaNussi commented Nov 29, 2025

I have read the CLA Document and I hereby sign the CLA

Copy link

@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: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f42d39f and da7ff9e.

📒 Files selected for processing (3)
  • app/Http/Controllers/Api/Application/Eggs/EggController.php (3 hunks)
  • app/Services/Eggs/Sharing/EggImporterService.php (5 hunks)
  • routes/api-application.php (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T19:40:12.104Z
Learnt from: rmartinoscar
Repo: pelican-dev/panel PR: 1671
File: app/Filament/Admin/Resources/ServerResource/Pages/EditServer.php:690-700
Timestamp: 2025-09-05T19:40:12.104Z
Learning: In admin panel contexts (app/Filament/Admin/Resources/), it's acceptable to show raw exception messages ($e->getMessage()) to admin users for debugging purposes, as they need detailed error information.

Applied to files:

  • app/Http/Controllers/Api/Application/Eggs/EggController.php
🧬 Code graph analysis (2)
routes/api-application.php (1)
app/Http/Controllers/Api/Application/Eggs/EggController.php (2)
  • delete (65-70)
  • EggController (21-119)
app/Services/Eggs/Sharing/EggImporterService.php (1)
app/Exceptions/Service/InvalidFileUploadException.php (1)
  • InvalidFileUploadException (7-7)
🪛 PHPMD (2.15.0)
app/Http/Controllers/Api/Application/Eggs/EggController.php

65-65: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (6)
routes/api-application.php (1)

104-107: LGTM!

The new egg routes are well-structured and consistent with existing patterns in the file. Providing both numeric ID and UUID-based delete endpoints offers good flexibility for API consumers.

app/Http/Controllers/Api/Application/Eggs/EggController.php (1)

58-70: LGTM!

The delete method is clean and follows Laravel conventions. The GetEggRequest parameter handles authorization even though it's not explicitly used in the method body—this is a standard Laravel pattern.

app/Services/Eggs/Sharing/EggImporterService.php (4)

35-45: LGTM!

The new fromContent method provides a clean entry point for content-based egg imports. The separation of parsing (parse) and processing (fromParsed) follows good design principles and enables reuse across different import sources.


52-100: LGTM!

The refactoring to extract fromParsed is excellent—it centralizes the transaction logic and makes it reusable across all import methods (fromContent, fromFile, fromUrl). This design follows the single responsibility principle and improves testability.


137-207: LGTM!

The refactored parse method effectively decouples parsing logic from file handling. The use of a match expression for format selection is clean and type-safe. Error wrapping with InvalidFileUploadException provides consistent exception handling for API responses.


209-236: LGTM!

The new parseFile method properly handles uploaded files with appropriate error checking. The format detection logic using both file extension and MIME type is robust, and delegating to the parse method maintains good separation of concerns.

Copy link

@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)
app/Http/Controllers/Api/Application/Eggs/EggController.php (1)

88-118: Don’t expose internal exception messages in the 500 response; log instead

The overall import flow (detect EggFormat, read raw body, delegate to EggImporterService::fromContent, and return a 201 with the transformed egg) looks solid, and mapping InvalidFileUploadException to a 422 is reasonable.

However, the generic catch (\Throwable $e) block still concatenates $e->getMessage() into the client-facing message field, which can leak internal details and was already flagged in a previous review. It also swallows the exception without logging/reporting it.

Consider either letting the exception bubble to Laravel’s handler, or updating the block along these lines:

-        } catch (\Throwable $e) {
-            return response()->json([
-                'error' => 'Unable to import egg',
-                'message' => 'An unexpected error occurred. ' . $e->getMessage(),
-            ], 500);
-        }
+        } catch (\Throwable $e) {
+            report($e);
+
+            return response()->json([
+                'error' => 'Unable to import egg',
+                'message' => 'An unexpected error occurred while importing the egg.',
+            ], 500);
+        }

This avoids leaking internal messages while still returning a stable 500 payload and ensures the exception is logged.

🧹 Nitpick comments (1)
app/Http/Controllers/Api/Application/Eggs/EggController.php (1)

58-70: Delete implementation is fine; keep the unused FormRequest parameter

The delete logic ($egg->delete() then returnNoContent()) is straightforward and appropriate.

PHPMD flags $request as unused, but in this pattern the GetEggRequest parameter is typically there to trigger its authorize()/rules() side effects (auth/validation) even if the variable is not referenced. I’d keep the parameter and, if the PHPMD warning is noisy, consider suppressing it at the rule or method level instead of removing the parameter.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da7ff9e and 0f46ee2.

📒 Files selected for processing (1)
  • app/Http/Controllers/Api/Application/Eggs/EggController.php (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/Http/Controllers/Api/Application/Eggs/EggController.php (3)
app/Exceptions/Service/InvalidFileUploadException.php (1)
  • InvalidFileUploadException (7-7)
app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1)
  • ImportEggRequest (5-13)
app/Services/Eggs/Sharing/EggImporterService.php (2)
  • EggImporterService (20-301)
  • fromContent (40-45)
🪛 PHPMD (2.15.0)
app/Http/Controllers/Api/Application/Eggs/EggController.php

65-65: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (1)
app/Http/Controllers/Api/Application/Eggs/EggController.php (1)

5-26: Constructor wiring and new imports look consistent

Injecting EggImporterService and adding the corresponding imports (ImportEggRequest, InvalidFileUploadException, JsonResponse, Response) aligns with the new import endpoint usage; types are consistent and there’s no obvious wiring issue.

DaNussi and others added 3 commits December 13, 2025 15:30
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
combined ImportEggRequest.php and PostEggRequest.php
@DaNussi DaNussi requested a review from Boy132 December 13, 2025 14:37
@Boy132 Boy132 self-assigned this Dec 15, 2025
Copy link

@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 (1)
app/Services/Eggs/Sharing/EggImporterService.php (1)

74-93: Consider wrapping HTTP exceptions for consistency.

The fromFile method wraps all exceptions in InvalidFileUploadException, but fromUrl allows HTTP exceptions (from ->throw() on line 90) to bubble up unwrapped. While not incorrect, this creates a minor inconsistency in the error handling approach.

Apply this diff to align error handling:

     public function fromUrl(string $url, ?Egg $egg = null): Egg
     {
         $info = pathinfo($url);
         $extension = strtolower($info['extension']);

         $format = match ($extension) {
             'yaml', 'yml' => EggFormat::YAML,
             'json' => EggFormat::JSON,
             default => throw new InvalidFileUploadException('Unsupported file format.'),
         };

-        $content = Http::timeout(5)->connectTimeout(1)->get($url)->throw()->body();
-
-        return $this->fromContent($content, $format, $egg);
+        try {
+            $content = Http::timeout(5)->connectTimeout(1)->get($url)->throw()->body();
+
+            return $this->fromContent($content, $format, $egg);
+        } catch (Throwable $e) {
+            throw new InvalidFileUploadException('Failed to fetch egg from URL: ' . $e->getMessage());
+        }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5024b96 and e4b3dd1.

📒 Files selected for processing (3)
  • app/Http/Controllers/Api/Application/Eggs/EggController.php (3 hunks)
  • app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1 hunks)
  • app/Services/Eggs/Sharing/EggImporterService.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php
🧰 Additional context used
🧬 Code graph analysis (1)
app/Http/Controllers/Api/Application/Eggs/EggController.php (2)
app/Http/Requests/Api/Application/Eggs/ImportEggRequest.php (1)
  • ImportEggRequest (9-14)
app/Services/Eggs/Sharing/EggImporterService.php (2)
  • EggImporterService (20-279)
  • fromContent (40-45)
🪛 PHPMD (2.15.0)
app/Http/Controllers/Api/Application/Eggs/EggController.php

65-65: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (7)
app/Http/Controllers/Api/Application/Eggs/EggController.php (3)

10-10: LGTM! Imports are appropriate.

The new imports for ImportEggRequest, EggImporterService, Exception, JsonResponse, Response, and Throwable are correctly added to support the import and delete functionality.

Also applies to: 13-13, 15-19


23-28: LGTM! Clean dependency injection.

The EggImporterService is properly injected via constructor, following Laravel's dependency injection patterns.


58-70: No action needed — egg deletion is safely protected by model validation.

The Egg model's deleting event handler (lines 162-166 in app/Models/Egg.php) prevents deletion if the egg has associated servers or child eggs, throwing HasActiveServersException before deletion occurs. Additionally, the foreign key constraint on servers.egg_id is configured without cascading deletes, providing database-level protection. The UI also disables the delete button for eggs in use (Filament UI, line 453).

The $request parameter is correctly used for authorization through route model binding.

Likely an incorrect or invalid review comment.

app/Services/Eggs/Sharing/EggImporterService.php (4)

35-45: LGTM! Clean entry point for content-based import.

The fromContent method provides a clear public API for importing eggs from string content with configurable format. The delegation to parse and fromParsed follows good separation of concerns.


52-72: LGTM! Robust file upload handling.

The method properly validates upload success, determines format from both extension and MIME type, and wraps parsing errors in InvalidFileUploadException with helpful context. The fallback to JSON when YAML is not detected is a reasonable default.


95-136: LGTM! Solid transactional import logic.

The fromParsed method correctly handles egg creation/updates within a database transaction, manages UUID generation and lookup, processes variables with flexible rule handling, and cleans up variables that are no longer in the import. The visibility change to protected is appropriate for the refactored architecture.


138-214: LGTM! Comprehensive parsing with excellent backward compatibility.

The parse method handles multiple egg format versions (PTDL_v1, PTDL_v2, PLCN_v1/v2, PLCN_v3), normalizes nested config and script structures to JSON, and intelligently renames reserved environment variables by prefixing them with SERVER_. The upgrade variable substitution (lines 173-178) ensures legacy configuration references are updated. Error handling provides helpful context for debugging.

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!

@notAreYouScared notAreYouScared merged commit 014e866 into pelican-dev:main Dec 16, 2025
25 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Dec 16, 2025
m41denx added a commit to m41denx/Pelican.ts that referenced this pull request Dec 19, 2025
m41denx added a commit to m41denx/Pelican.ts that referenced this pull request Dec 20, 2025
* nullish?

* Fix reading files contents

axios was deserializing json automatically before

* Fix serverfile "no root" error

root is unset when dir is set but empty

* autogenerated types

* nodes allocs support pagination

* Docs [1/X]

* fix states for server types

* Implement pelican-dev/panel#1947

* Closes pelican-dev/panel#1947

* DOCS done Im lazy to do more
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants