Skip to content

Add restore API endpoint#158

Merged
David-Crty merged 3 commits intomainfrom
feature/restore-api-endpoint
Mar 12, 2026
Merged

Add restore API endpoint#158
David-Crty merged 3 commits intomainfrom
feature/restore-api-endpoint

Conversation

@David-Crty
Copy link
Owner

@David-Crty David-Crty commented Mar 12, 2026

Summary

  • Add POST /api/v1/database-servers/{id}/restore endpoint that accepts snapshot_id and schema_name, validates compatibility, and dispatches a restore job
  • Extract isAppDatabase() from RestoreModal and DatabaseServerController into DatabaseServer model to eliminate duplication
  • Replace RuntimeException with ValidationException in TriggerBackupAction so the framework handles the 422 response automatically, removing the manual try/catch in the backup endpoint

Test plan

  • 7 new tests in RestoreApiTest covering auth, authorization, happy path, validation, type mismatch, and invalid snapshot
  • Updated TriggerBackupActionTest to expect ValidationException
  • Full test suite passes (722 tests)
  • PHPStan clean, Pint clean

Summary by CodeRabbit

  • New Features
    • New API endpoint to start async restores from snapshots; returns 202 with structured restore, snapshot, server, and job details.
  • Validation / Bug Fixes
    • Enforces snapshot ↔ target database type compatibility, schema-name validation, and blocks restoring over the application database; clearer field-level validation errors.
  • Tests
    • Added comprehensive tests covering auth, authorization, validation, and response structure for backup and restore flows.

- Add POST /api/v1/database-servers/{id}/restore endpoint with validation
  for snapshot compatibility, schema name format, and app database protection
- Create RestoreRequest form request and RestoreResource API resource
- Extract isAppDatabase() to DatabaseServer model, removing duplication
  from RestoreModal and the controller
- Replace RuntimeException with ValidationException in TriggerBackupAction
  so the framework handles the 422 response automatically
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Adds a restore API endpoint and supporting pieces: a RestoreRequest, RestoreResource, controller restore method, BackupJobFactory restore validations, DatabaseServer::isAppDatabase, route, and tests; adjusts TriggerBackupAction to throw ValidationException for missing backup config.

Changes

Cohort / File(s) Summary
Controller & Route
app/Http/Controllers/Api/V1/DatabaseServerController.php, routes/api.php
New restore method on DatabaseServerController; authorizes, validates snapshot vs server type, prevents restoring over the application DB, creates restore via BackupJobFactory, dispatches ProcessRestoreJob, and returns 202 with RestoreResource. Added POST route database-servers.restore.
Request Validation
app/Http/Requests/Api/V1/RestoreRequest.php
New form request validating snapshot_id (required, exists) and schema_name with rules that vary by target server database type (SQLite vs others) and custom regex message.
API Resource
app/Http/Resources/RestoreResource.php
New RestoreResource shaping API response (id, schema_name, timestamps) and conditionally including related snapshot, target_server, and job when loaded.
Model
app/Models/DatabaseServer.php
Added isAppDatabase(string $schemaName) to detect app DB for MySQL/Postgres by comparing driver/type, host, port, and schema.
Backup Factory & Service
app/Services/Backup/BackupJobFactory.php, app/Services/Backup/TriggerBackupAction.php
createRestore now validates snapshot/server type match and prevents app-database restores, throwing ValidationException on violations. TriggerBackupAction::execute now throws ValidationException for missing backup config (imports/docblocks updated).
Frontend Livewire
app/Livewire/DatabaseServer/RestoreModal.php
Removed internal isAppDatabase() helper and its use; app-database check delegated to model/service logic.
Tests
tests/Feature/Api/RestoreApiTest.php, tests/Feature/Api/BackupApiTest.php, tests/Feature/Services/Backup/TriggerBackupActionTest.php
New restore API feature tests covering auth, authorization, validation, and response shape. Backup tests added/updated; TriggerBackupAction test expects ValidationException instead of RuntimeException.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant API as "DatabaseServerController"
    participant Auth as "Authorization"
    participant DB as "Models (Snapshot, DatabaseServer)"
    participant Factory as "BackupJobFactory"
    participant Queue as "Job Queue"

    Client->>API: POST /api/v1/database-servers/{id}/restore (snapshot_id, schema_name)
    API->>Auth: authorize(restore)
    Auth-->>API: authorized
    API->>DB: find Snapshot by ID
    DB-->>API: snapshot
    API->>API: validate snapshot.database_type == server.database_type
    API->>DB: DatabaseServer::isAppDatabase(schema_name)
    DB-->>API: isAppDatabase result
    API->>Factory: createRestore(snapshot, server, schema_name)
    Factory-->>API: Restore instance
    API->>Queue: dispatch ProcessRestoreJob
    Queue-->>API: job queued
    API-->>Client: 202 RestoreResource (restore + snapshot + target_server + job)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped in quick with a snapshot in paw,
Matched types and schemas and checked every law,
I left the app's own garden safe and sound,
Queued jobs to mend what backups found,
A tiny hop — restore complete, hooray! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add restore API endpoint' clearly and concisely describes the main change: a new POST endpoint for restoring database servers from snapshots.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% 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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/restore-api-endpoint

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

🧹 Nitpick comments (3)
app/Http/Controllers/Api/V1/DatabaseServerController.php (1)

116-116: Redundant relation loading.

The BackupJobFactory::createRestore() method already loads these relations (see app/Services/Backup/BackupJobFactory.php:137). This line can be removed to avoid unnecessary database queries.

♻️ Proposed fix
         ProcessRestoreJob::dispatch($restore->id);

-        $restore->load(['job', 'snapshot', 'targetServer']);
-
         return response()->json([
             'message' => 'Restore started successfully!',
             'restore' => new RestoreResource($restore),
         ], 202);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/Api/V1/DatabaseServerController.php` at line 116, The
line calling $restore->load(['job', 'snapshot', 'targetServer']) is redundant
because BackupJobFactory::createRestore() already eager-loads those relations;
remove that load call from DatabaseServerController::(the method containing
$restore) to avoid the extra DB query and rely on the relations already
populated by BackupJobFactory::createRestore().
app/Http/Requests/Api/V1/RestoreRequest.php (1)

44-49: Consider adding custom messages for other validation rules.

Currently only schema_name.regex has a custom message. For API consistency, you might want to add messages for snapshot_id.required, snapshot_id.exists, and schema_name.required to provide clearer error responses.

💡 Example messages
     public function messages(): array
     {
         return [
             'schema_name.regex' => 'Database name can only contain letters, numbers, and underscores.',
+            'snapshot_id.required' => 'A snapshot ID is required.',
+            'snapshot_id.exists' => 'The specified snapshot does not exist.',
+            'schema_name.required' => 'A schema/database name is required.',
         ];
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Requests/Api/V1/RestoreRequest.php` around lines 44 - 49, In
RestoreRequest::messages() add explicit custom error messages for the other
validation keys so API responses are consistent: include entries for
'snapshot_id.required', 'snapshot_id.exists', and 'schema_name.required'
alongside the existing 'schema_name.regex' message, make the messages clear and
user-facing (e.g., "Snapshot ID is required", "Snapshot not found", "Schema name
is required") and ensure the keys exactly match the validation rules defined in
the rules() method so Laravel maps them correctly.
app/Models/DatabaseServer.php (1)

304-306: Consider hostname normalization for edge cases.

The host comparison uses strict equality, which may not match when the app config uses 127.0.0.1 but the server uses localhost (or vice versa). If this is a concern in your deployment, you could normalize both values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Models/DatabaseServer.php` around lines 304 - 306, The host comparison in
DatabaseServer (the block comparing $this->host, $this->port, $schemaName to
$appDbHost, $appDbPort, $appDbDatabase) should normalize hostnames before strict
comparison to handle 'localhost' vs '127.0.0.1' and casing/whitespace
differences; update the check to normalize both $this->host and $appDbHost
(e.g., trim/lowercase and resolve to an IP via gethostbyname or a small
normalizeHost(string $h): string helper) and then compare the normalized hosts
along with the ports and schema name in the existing method to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Feature/Api/RestoreApiTest.php`:
- Around line 30-57: The test sets Queue::fake() but never asserts a job was
enqueued; update the test in RestoreApiTest (the 'authenticated users can
trigger a restore' test) to assert the restore job is dispatched by adding
Queue::assertPushed(ProcessRestoreJob::class) after the POST and response
assertions so the test verifies the ProcessRestoreJob is queued when calling the
/database-servers/{id}/restore endpoint with snapshot_id and schema_name.
- Around line 103-115: The test "restore rejects nonexistent snapshot" uses
'nonexistent-id' which may fail format validation; instead create a real
Snapshot record for $server (or capture an existing snapshot key), store its
id/key, delete the record so the ID is well-formed but missing, then call the
POST to /api/v1/database-servers/{$server->id}/restore with that deleted
snapshot id (snapshot_id) and assert Unprocessable with snapshot_id validation
error; update the test function (test('restore rejects nonexistent snapshot'))
to create-and-delete the snapshot so the request reaches the "snapshot does not
exist" path.

---

Nitpick comments:
In `@app/Http/Controllers/Api/V1/DatabaseServerController.php`:
- Line 116: The line calling $restore->load(['job', 'snapshot', 'targetServer'])
is redundant because BackupJobFactory::createRestore() already eager-loads those
relations; remove that load call from DatabaseServerController::(the method
containing $restore) to avoid the extra DB query and rely on the relations
already populated by BackupJobFactory::createRestore().

In `@app/Http/Requests/Api/V1/RestoreRequest.php`:
- Around line 44-49: In RestoreRequest::messages() add explicit custom error
messages for the other validation keys so API responses are consistent: include
entries for 'snapshot_id.required', 'snapshot_id.exists', and
'schema_name.required' alongside the existing 'schema_name.regex' message, make
the messages clear and user-facing (e.g., "Snapshot ID is required", "Snapshot
not found", "Schema name is required") and ensure the keys exactly match the
validation rules defined in the rules() method so Laravel maps them correctly.

In `@app/Models/DatabaseServer.php`:
- Around line 304-306: The host comparison in DatabaseServer (the block
comparing $this->host, $this->port, $schemaName to $appDbHost, $appDbPort,
$appDbDatabase) should normalize hostnames before strict comparison to handle
'localhost' vs '127.0.0.1' and casing/whitespace differences; update the check
to normalize both $this->host and $appDbHost (e.g., trim/lowercase and resolve
to an IP via gethostbyname or a small normalizeHost(string $h): string helper)
and then compare the normalized hosts along with the ports and schema name in
the existing method to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a00bae9-a35e-42eb-ae0a-7fa4a5749f40

📥 Commits

Reviewing files that changed from the base of the PR and between fd1079e and 9f6029c.

📒 Files selected for processing (9)
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • app/Http/Resources/RestoreResource.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Models/DatabaseServer.php
  • app/Services/Backup/TriggerBackupAction.php
  • routes/api.php
  • tests/Feature/Api/RestoreApiTest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-app-image
  • GitHub Check: ci
🧰 Additional context used
📓 Path-based instructions (13)
**/{app,tests}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/{app,tests}/**/*.php: Use Eloquent models and Model::query() before suggesting raw database queries. Avoid using DB::. Generate code that leverages Laravel's ORM capabilities.
Never append to static properties in services/classes, as they accumulate in memory across requests (Octane issue).

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • tests/Feature/Api/RestoreApiTest.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.php: Use constructor property promotion in __construct() with PHP 8+ syntax (e.g., public function __construct(public GitHub $github) { }). Do not allow empty constructors with zero parameters unless private.
Always use explicit return type declarations for methods and functions. Use appropriate PHP type hints for method parameters.
Use curly braces for control structures, even for single-line bodies.
Prefer PHPDoc blocks over inline comments. Never use comments within code itself unless logic is exceptionally complex.
Add useful array shape type definitions in PHPDoc blocks when appropriate.

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • tests/Feature/Api/RestoreApiTest.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
  • routes/api.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
**/{app,routes}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • routes/api.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
**/{app,resources/views}/**/*.{php,blade.php}

📄 CodeRabbit inference engine (CLAUDE.md)

When generating links to other pages, prefer named routes and the route() function.

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
**/{app,routes,tests}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Never use env() function directly outside of config files. Always use config('app.name') instead of env('APP_NAME').

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • tests/Feature/Api/RestoreApiTest.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
  • routes/api.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
{lang/**/*.json,**/{app,resources/views}/**/*.{php,blade.php}}

📄 CodeRabbit inference engine (CLAUDE.md)

Translations use Laravel's JSON translation files with the __('...') helper. Translations live in lang/{locale}.json. Use typographic apostrophes (' U+2019) instead of ASCII apostrophes in translation values to avoid HTML encoding artifacts.

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
**/*.{php,js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use descriptive names for variables and methods (e.g., isRegisteredForDiscounts, not discount()).

Files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • tests/Feature/Api/RestoreApiTest.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
  • routes/api.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
**/{app/Models,database/migrations}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Database models use ULIDs instead of auto-incrementing integers for primary keys.

Files:

  • app/Models/DatabaseServer.php
**/Models/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/Models/**/*.php: All database models must use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. Use eager loading to prevent N+1 query problems.
Use casts via a casts() method on models rather than the $casts property to follow existing conventions.

Files:

  • app/Models/DatabaseServer.php
**/{tests,database/factories}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use factories for creating models in tests. Check if the factory has custom states before manually setting up the model.

Files:

  • tests/Feature/Api/RestoreApiTest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
**/tests/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use faker methods such as $this->faker->word() or fake()->randomDigit() in tests. Follow existing conventions whether to use $this->faker or fake().

Files:

  • tests/Feature/Api/RestoreApiTest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
**/Livewire/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/Livewire/**/*.php: Use #[Validate] attributes or Form objects for validation in Livewire components. Form objects encapsulate validation logic.
Use class-based Livewire components for all interactive pages and modal interactions. Public properties are automatically bound to views. Use wire:model for two-way binding.

Files:

  • app/Livewire/DatabaseServer/RestoreModal.php
**/Requests/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Always create Form Request classes for validation rather than inline validation. Include both validation rules and custom error messages. Check sibling Form Requests to see if the application uses array or string-based validation rules.

Files:

  • app/Http/Requests/Api/V1/RestoreRequest.php
🧠 Learnings (11)
📚 Learning: 2026-02-25T10:48:17.811Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Console/Commands/RecoverAgentLeasesCommand.php:44-48
Timestamp: 2026-02-25T10:48:17.811Z
Learning: When reviewing PHP code, especially with foreign keys that use cascadeOnDelete and are non-nullable, assume child relations exist at runtime (the database will delete children when the parent is deleted). Do not rely on null-safe operators for these relations, as PHPStan already models them as non-null. This guideline applies broadly to PHP files that define models with foreign keys using cascade delete; verify there are no unnecessary null checks or optional chaining on such relations.

Applied to files:

  • app/Http/Resources/RestoreResource.php
  • app/Models/DatabaseServer.php
  • tests/Feature/Api/RestoreApiTest.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Http/Requests/Api/V1/RestoreRequest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
  • routes/api.php
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/TriggerBackupAction.php
📚 Learning: 2026-03-10T22:19:40.736Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T22:19:40.736Z
Learning: When creating tests, use `php artisan make:test` with `--pest` option for Pest tests or `--unit` for unit tests. Most tests should be feature tests.

Applied to files:

  • tests/Feature/Api/RestoreApiTest.php
📚 Learning: 2026-02-17T08:43:03.630Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 114
File: tests/Integration/SshTunnelTest.php:37-46
Timestamp: 2026-02-17T08:43:03.630Z
Learning: In the databasement project, integration tests use RefreshDatabase (configured in tests/Pest.php), which automatically rolls back all database changes after each test. Therefore, explicit model deletion calls like $server->delete(), $volume->delete(), or $sshConfig->delete() are unnecessary in integration tests.

Applied to files:

  • tests/Feature/Api/RestoreApiTest.php
📚 Learning: 2026-03-02T13:13:34.079Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: tests/Unit/Services/Backup/DTO/BackupConfigTest.php:34-45
Timestamp: 2026-03-02T13:13:34.079Z
Learning: In tests/Unit/Services/Backup/DTO/BackupConfigTest.php and similar DTO symmetry tests, prioritize asserting execution-critical fields (database connection, compression, paths) over display-only metadata (volume.name, volume.config, database.serverName) that don't affect backup execution.

Applied to files:

  • tests/Feature/Api/RestoreApiTest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
📚 Learning: 2026-02-13T11:05:37.072Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 105
File: tests/Feature/Services/Backup/Databases/SqliteDatabaseTest.php:8-11
Timestamp: 2026-02-13T11:05:37.072Z
Learning: Adopt a global afterEach hook in tests/Pest.php (or equivalent Pest bootstrap) to clean up temporary directories created during tests. Specifically handle temp dirs named with the prefixes sqlite-db-test-*, backup-task-test-*, restore-task-test-*, and volume-test-*, so individual test files don’t need their own cleanup logic. This applies to all PHP test files under the tests directory.

Applied to files:

  • tests/Feature/Api/RestoreApiTest.php
  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
📚 Learning: 2026-02-18T09:45:52.485Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 116
File: app/Livewire/DatabaseServer/ConnectionStatus.php:18-18
Timestamp: 2026-02-18T09:45:52.485Z
Learning: In Livewire components, Eloquent model properties (e.g., public DatabaseServer $server) are automatically locked by the framework to prevent client-side ID tampering. The #[Locked] attribute is only needed for scalar properties (int, string, bool, etc.) that require protection from client-side mutation. Apply this guidance to all Livewire PHP components; use #[Locked] only on primitive properties that you want to shield from client manipulation, and rely on automatic locking for Eloquent model properties.

Applied to files:

  • app/Livewire/DatabaseServer/RestoreModal.php
📚 Learning: 2026-03-10T22:19:40.736Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T22:19:40.736Z
Learning: Applies to **/Requests/**/*.php : Always create Form Request classes for validation rather than inline validation. Include both validation rules and custom error messages. Check sibling Form Requests to see if the application uses array or string-based validation rules.

Applied to files:

  • app/Http/Requests/Api/V1/RestoreRequest.php
📚 Learning: 2026-02-25T10:48:46.954Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Services/Agent/AgentJobPayloadBuilder.php:63-76
Timestamp: 2026-02-25T10:48:46.954Z
Learning: In app/Services/Agent/AgentJobPayloadBuilder.php and similar service classes, prefer fail-fast error handling over defensive null-safe operators when upstream callers (e.g., TriggerBackupAction, RunScheduledBackups) guarantee certain relationships exist. For example, in resolveBackupPath(), $server->backup is intentionally accessed without the null-safe operator because backup existence is enforced at call sites; using the null-safe operator would hide bugs that violate this invariant.

Applied to files:

  • tests/Feature/Services/Backup/TriggerBackupActionTest.php
  • app/Services/Backup/TriggerBackupAction.php
📚 Learning: 2026-03-10T22:19:40.736Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T22:19:40.736Z
Learning: Applies to **/routes/web.php : Use `Route::livewire()` for full-page Livewire components in route definitions (e.g., `Route::livewire('database-servers', \App\Livewire\DatabaseServer\Index::class)`).

Applied to files:

  • routes/api.php
📚 Learning: 2026-02-04T12:26:27.374Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 72
File: routes/web.php:66-81
Timestamp: 2026-02-04T12:26:27.374Z
Learning: In Laravel Livewire v4, prefer routing full-page Livewire components using Route::livewire('/path', ComponentClass::class) for all full-page components (whether class-based or view-based). While Route::get('/path', ComponentClass::class) remains technically functional for backward compatibility, it is not recommended in v4. Apply this guideline in all route files under routes (e.g., routes/web.php, routes/api.php) to ensure consistent, future-proof routing for Livewire components.

Applied to files:

  • routes/api.php
📚 Learning: 2026-02-25T10:46:42.914Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Http/Controllers/Api/V1/AgentController.php:175-206
Timestamp: 2026-02-25T10:46:42.914Z
Learning: In the agent job processing flow (app/Http/Controllers/Api/V1/AgentController.php), state updates in endpoints like ack() and fail() are not wrapped in DB::transaction(). The codebase relies on the scheduled agent:recover-leases command to handle recovery of incomplete or failed jobs, rather than using strict transactional consistency for each endpoint.

Applied to files:

  • app/Services/Backup/TriggerBackupAction.php
🪛 PHPMD (2.15.0)
app/Http/Resources/RestoreResource.php

[warning] 19-19: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (12)
routes/api.php (1)

15-16: LGTM!

The new restore route follows the established pattern of the existing backup route, using consistent naming and middleware.

app/Models/DatabaseServer.php (1)

274-307: LGTM!

The centralized isAppDatabase method cleanly consolidates the app-database detection logic. The driver-to-type mapping correctly handles MySQL, MariaDB, and PostgreSQL.

app/Livewire/DatabaseServer/RestoreModal.php (1)

172-176: LGTM!

Clean refactor delegating the app-database check to the centralized DatabaseServer::isAppDatabase() method.

app/Http/Resources/RestoreResource.php (1)

1-41: LGTM!

Well-structured API resource with proper use of whenLoaded() for conditional relation inclusion. The $request parameter flagged by PHPMD is required by the JsonResource::toArray() interface signature, so the static analysis warning is a false positive.

app/Http/Requests/Api/V1/RestoreRequest.php (1)

1-50: LGTM!

Clean form request with appropriate conditional validation based on database type. The validation rules correctly mirror the Livewire component's schema validation logic.

app/Http/Controllers/Api/V1/DatabaseServerController.php (2)

57-71: LGTM!

Clean simplification of the backup method. The ValidationException thrown by TriggerBackupAction will automatically propagate to Laravel's exception handler, which converts it to a 422 response.


80-122: Well-structured restore endpoint with proper validation and authorization.

The restore method correctly:

  • Authorizes the action
  • Validates snapshot compatibility with target server
  • Prevents restoring over the application database
  • Dispatches the restore job asynchronously
app/Services/Backup/TriggerBackupAction.php (1)

29-33: LGTM!

Good change to use ValidationException instead of RuntimeException. This allows Laravel's exception handler to automatically return a proper 422 response with structured error messages for API consumers, while existing Livewire callers that catch \Throwable continue to work correctly.

tests/Feature/Services/Backup/TriggerBackupActionTest.php (1)

15-24: LGTM!

Test correctly updated to expect ValidationException matching the implementation change.

tests/Feature/Api/RestoreApiTest.php (3)

8-14: Auth gating coverage looks good.

These two tests lock down both the 401 and 403 branches for the new endpoint.

Also applies to: 16-28


59-68: Required-field validation coverage is solid.

This keeps the endpoint's base 422 contract pinned down.


70-86: Nice coverage of the restore compatibility rules.

The type-mismatch and schema-format cases are both worth having here.

Also applies to: 88-101

Comment on lines +103 to +115
test('restore rejects nonexistent snapshot', function () {
$user = User::factory()->create();
$server = DatabaseServer::factory()->create();

$response = $this->actingAs($user, 'sanctum')
->postJson("/api/v1/database-servers/{$server->id}/restore", [
'snapshot_id' => 'nonexistent-id',
'schema_name' => 'testdb',
]);

$response->assertUnprocessable()
->assertJsonValidationErrors(['snapshot_id']);
});
Copy link

@coderabbitai coderabbitai bot Mar 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a valid-but-missing snapshot id in this test.

'nonexistent-id' can fail on format/type validation before it ever exercises the "snapshot does not exist" path. Capturing a real snapshot key and deleting the record first makes the test match its name.

Suggested tweak
 test('restore rejects nonexistent snapshot', function () {
     $user = User::factory()->create();
     $server = DatabaseServer::factory()->create();
+    $snapshot = Snapshot::factory()->forServer($server)->create();
+    $missingSnapshotId = $snapshot->getKey();
+    $snapshot->delete();

     $response = $this->actingAs($user, 'sanctum')
         ->postJson("/api/v1/database-servers/{$server->id}/restore", [
-            'snapshot_id' => 'nonexistent-id',
+            'snapshot_id' => $missingSnapshotId,
             'schema_name' => 'testdb',
         ]);

     $response->assertUnprocessable()
         ->assertJsonValidationErrors(['snapshot_id']);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Feature/Api/RestoreApiTest.php` around lines 103 - 115, The test
"restore rejects nonexistent snapshot" uses 'nonexistent-id' which may fail
format validation; instead create a real Snapshot record for $server (or capture
an existing snapshot key), store its id/key, delete the record so the ID is
well-formed but missing, then call the POST to
/api/v1/database-servers/{$server->id}/restore with that deleted snapshot id
(snapshot_id) and assert Unprocessable with snapshot_id validation error; update
the test function (test('restore rejects nonexistent snapshot')) to
create-and-delete the snapshot so the request reaches the "snapshot does not
exist" path.

Copy link
Owner Author

Choose a reason for hiding this comment

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

The validation rule is 'snapshot_id' => ['required', 'string', 'exists:snapshots,id']. The exists rule just checks if the value exists in the snapshots table, it doesn't care about format.
'nonexistent-id' is a string that won't exist, so the exists rule fails with a validation error. The test already passes and correctly reaches the "snapshot does not exist" path.

Copy link

Choose a reason for hiding this comment

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

@David-Crty, you're right — the exists:snapshots,id rule only performs a database lookup and doesn't impose any format constraints. Since 'nonexistent-id' satisfies both required and string, it correctly reaches and fails the exists check, which is exactly the path the test is meant to cover. My concern was unfounded — apologies for the noise!


🧠 Learnings used
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 114
File: tests/Integration/SshTunnelTest.php:37-46
Timestamp: 2026-02-17T08:43:03.630Z
Learning: In the databasement project, integration tests use RefreshDatabase (configured in tests/Pest.php), which automatically rolls back all database changes after each test. Therefore, explicit model deletion calls like $server->delete(), $volume->delete(), or $sshConfig->delete() are unnecessary in integration tests.

Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: tests/Unit/Services/Backup/DTO/BackupConfigTest.php:34-45
Timestamp: 2026-03-02T13:13:34.079Z
Learning: In tests/Unit/Services/Backup/DTO/BackupConfigTest.php and similar DTO symmetry tests, prioritize asserting execution-critical fields (database connection, compression, paths) over display-only metadata (volume.name, volume.config, database.serverName) that don't affect backup execution.

Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T22:19:40.736Z
Learning: When creating tests, use `php artisan make:test` with `--pest` option for Pest tests or `--unit` for unit tests. Most tests should be feature tests.

Learnt from: David-Crty
Repo: David-Crty/databasement PR: 105
File: tests/Feature/Services/Backup/Databases/SqliteDatabaseTest.php:8-11
Timestamp: 2026-02-13T11:05:37.072Z
Learning: Adopt a global afterEach hook in tests/Pest.php (or equivalent Pest bootstrap) to clean up temporary directories created during tests. Specifically handle temp dirs named with the prefixes sqlite-db-test-*, backup-task-test-*, restore-task-test-*, and volume-test-*, so individual test files don’t need their own cleanup logic. This applies to all PHP test files under the tests directory.

Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Console/Commands/RecoverAgentLeasesCommand.php:44-48
Timestamp: 2026-02-25T10:48:17.811Z
Learning: When reviewing PHP code, especially with foreign keys that use cascadeOnDelete and are non-nullable, assume child relations exist at runtime (the database will delete children when the parent is deleted). Do not rely on null-safe operators for these relations, as PHPStan already models them as non-null. This guideline applies broadly to PHP files that define models with foreign keys using cascade delete; verify there are no unnecessary null checks or optional chaining on such relations.

@codecov
Copy link

codecov bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 98.85057% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.84%. Comparing base (fd1079e) to head (c43ca10).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/Http/Requests/Api/V1/RestoreRequest.php 93.75% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #158      +/-   ##
============================================
+ Coverage     91.54%   91.84%   +0.29%     
- Complexity     1720     1725       +5     
============================================
  Files           158      160       +2     
  Lines          6364     6413      +49     
============================================
+ Hits           5826     5890      +64     
+ Misses          538      523      -15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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/Http/Controllers/Api/V1/DatabaseServerController.php (1)

102-104: Localize the new API success message.

This is a new user-facing string in app/**, so it should go through the JSON translation flow instead of being hardcoded in English.

Suggested change
         return response()->json([
-            'message' => 'Restore started successfully!',
+            'message' => __('Restore started successfully!'),
             'restore' => new RestoreResource($restore),
         ], 202);

As per coding guidelines, "translations use Laravel's JSON translation files with the __('...') helper."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/Api/V1/DatabaseServerController.php` around lines 102 -
104, Replace the hardcoded English success message in DatabaseServerController
where the response()->json([... 'message' => 'Restore started successfully!',
...]) is returned with a call to Laravel's JSON translation helper (use
__('Restore started successfully!')) so the message is localized; keep the rest
of the payload (the 'restore' => new RestoreResource($restore)) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Services/Backup/BackupJobFactory.php`:
- Around line 130-139: The ValidationException thrown by
BackupJobFactory::withMessages() produces field-level errors that the
RestoreModal currently hides by only using $e->getMessage(); update
app/Livewire/DatabaseServer/RestoreModal.php (the catch block around lines
handling exceptions) to detect if ($e instanceof
\Illuminate\Validation\ValidationException) and pull the field messages via
$e->errors() (or extract the first relevant field error like
errors()['snapshot_id'][0] / errors()['schema_name'][0]) and present that to the
user instead of $e->getMessage(); this keeps the factory HTTP-agnostic and
provides actionable field-level feedback in the modal.

---

Nitpick comments:
In `@app/Http/Controllers/Api/V1/DatabaseServerController.php`:
- Around line 102-104: Replace the hardcoded English success message in
DatabaseServerController where the response()->json([... 'message' => 'Restore
started successfully!', ...]) is returned with a call to Laravel's JSON
translation helper (use __('Restore started successfully!')) so the message is
localized; keep the rest of the payload (the 'restore' => new
RestoreResource($restore)) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: adb18dcc-2d03-4776-8c8f-9dd584706805

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6029c and deb85be.

📒 Files selected for processing (4)
  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Livewire/DatabaseServer/RestoreModal.php
  • app/Services/Backup/BackupJobFactory.php
  • tests/Feature/Api/RestoreApiTest.php
💤 Files with no reviewable changes (1)
  • app/Livewire/DatabaseServer/RestoreModal.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/Feature/Api/RestoreApiTest.php
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-app-image
  • GitHub Check: ci
🧰 Additional context used
📓 Path-based instructions (7)
**/{app,tests}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/{app,tests}/**/*.php: Use Eloquent models and Model::query() before suggesting raw database queries. Avoid using DB::. Generate code that leverages Laravel's ORM capabilities.
Never append to static properties in services/classes, as they accumulate in memory across requests (Octane issue).

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.php: Use constructor property promotion in __construct() with PHP 8+ syntax (e.g., public function __construct(public GitHub $github) { }). Do not allow empty constructors with zero parameters unless private.
Always use explicit return type declarations for methods and functions. Use appropriate PHP type hints for method parameters.
Use curly braces for control structures, even for single-line bodies.
Prefer PHPDoc blocks over inline comments. Never use comments within code itself unless logic is exceptionally complex.
Add useful array shape type definitions in PHPDoc blocks when appropriate.

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
**/{app,routes}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
**/{app,resources/views}/**/*.{php,blade.php}

📄 CodeRabbit inference engine (CLAUDE.md)

When generating links to other pages, prefer named routes and the route() function.

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
**/{app,routes,tests}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Never use env() function directly outside of config files. Always use config('app.name') instead of env('APP_NAME').

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
{lang/**/*.json,**/{app,resources/views}/**/*.{php,blade.php}}

📄 CodeRabbit inference engine (CLAUDE.md)

Translations use Laravel's JSON translation files with the __('...') helper. Translations live in lang/{locale}.json. Use typographic apostrophes (' U+2019) instead of ASCII apostrophes in translation values to avoid HTML encoding artifacts.

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
**/*.{php,js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use descriptive names for variables and methods (e.g., isRegisteredForDiscounts, not discount()).

Files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
🧠 Learnings (3)
📚 Learning: 2026-02-25T10:48:17.811Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Console/Commands/RecoverAgentLeasesCommand.php:44-48
Timestamp: 2026-02-25T10:48:17.811Z
Learning: When reviewing PHP code, especially with foreign keys that use cascadeOnDelete and are non-nullable, assume child relations exist at runtime (the database will delete children when the parent is deleted). Do not rely on null-safe operators for these relations, as PHPStan already models them as non-null. This guideline applies broadly to PHP files that define models with foreign keys using cascade delete; verify there are no unnecessary null checks or optional chaining on such relations.

Applied to files:

  • app/Http/Controllers/Api/V1/DatabaseServerController.php
  • app/Services/Backup/BackupJobFactory.php
📚 Learning: 2026-03-02T13:13:34.079Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: tests/Unit/Services/Backup/DTO/BackupConfigTest.php:34-45
Timestamp: 2026-03-02T13:13:34.079Z
Learning: In tests/Unit/Services/Backup/DTO/BackupConfigTest.php and similar DTO symmetry tests, prioritize asserting execution-critical fields (database connection, compression, paths) over display-only metadata (volume.name, volume.config, database.serverName) that don't affect backup execution.

Applied to files:

  • app/Services/Backup/BackupJobFactory.php
📚 Learning: 2026-02-25T10:48:46.954Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Services/Agent/AgentJobPayloadBuilder.php:63-76
Timestamp: 2026-02-25T10:48:46.954Z
Learning: In app/Services/Agent/AgentJobPayloadBuilder.php and similar service classes, prefer fail-fast error handling over defensive null-safe operators when upstream callers (e.g., TriggerBackupAction, RunScheduledBackups) guarantee certain relationships exist. For example, in resolveBackupPath(), $server->backup is intentionally accessed without the null-safe operator because backup existence is enforced at call sites; using the null-safe operator would hide bugs that violate this invariant.

Applied to files:

  • app/Services/Backup/BackupJobFactory.php

Comment on lines +130 to +139
if ($snapshot->database_type !== $targetServer->database_type) {
throw ValidationException::withMessages([
'snapshot_id' => 'Snapshot database type does not match the target server.',
]);
}

if ($targetServer->isAppDatabase($schemaName)) {
throw ValidationException::withMessages([
'schema_name' => 'Cannot restore over the application database.',
]);
Copy link

@coderabbitai coderabbitai bot Mar 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Laravel 12, what message does Illuminate\Validation\ValidationException::withMessages() expose through getMessage(), and how should callers retrieve the field-level validation errors?

💡 Result:

In Laravel 12, ValidationException::withMessages() builds an internal validator containing your messages, and the exception’s getMessage() is a summary string produced by ValidationException::summarize()—specifically the first validation message, plus (if there are more) "(and :count more error/errors)". If there are no messages, it falls back to "The given data was invalid.". [1]

Callers should not parse getMessage() to get per-field errors. Instead, retrieve the field-level errors via $e->errors(), which returns the array of messages keyed by field (the same shape Laravel returns under the JSON "errors" key). [1][2]


Update the Livewire modal exception handler or keep the factory HTTP-agnostic.

The new ValidationException::withMessages() throws at lines 130-139 will break the restore modal's error feedback. app/Livewire/DatabaseServer/RestoreModal.php:158-192 catches \Exception and displays $e->getMessage(), but ValidationException::getMessage() returns only a summary string (the first error plus "(and X more errors)" or the generic fallback "The given data was invalid."), not the field-level error text. Users will see a generic message instead of the actionable reason for the failure.

Either extract the field errors in the modal by checking instanceof ValidationException and calling $e->errors(), or map the factory errors to ValidationException at the HTTP controller boundary instead of inside the shared service.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/Backup/BackupJobFactory.php` around lines 130 - 139, The
ValidationException thrown by BackupJobFactory::withMessages() produces
field-level errors that the RestoreModal currently hides by only using
$e->getMessage(); update app/Livewire/DatabaseServer/RestoreModal.php (the catch
block around lines handling exceptions) to detect if ($e instanceof
\Illuminate\Validation\ValidationException) and pull the field messages via
$e->errors() (or extract the first relevant field error like
errors()['snapshot_id'][0] / errors()['schema_name'][0]) and present that to the
user instead of $e->getMessage(); this keeps the factory HTTP-agnostic and
provides actionable field-level feedback in the modal.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I tested, and it's ok, we render the error as expected:

Failed to queue restore: Cannot restore over the application database.

Copy link

Choose a reason for hiding this comment

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

@David-Crty, thanks for testing and confirming! That makes sense — since each ValidationException thrown here contains exactly one field message, getMessage() returns that message text directly (Laravel's summarize() just returns the first error string when there's only one). My concern would only apply if multiple messages were present, where the summary would add the "(and X more errors)" suffix. You're good to go! 🐇✨


🧠 Learnings used
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Console/Commands/RecoverAgentLeasesCommand.php:44-48
Timestamp: 2026-02-25T10:48:17.811Z
Learning: When reviewing PHP code, especially with foreign keys that use cascadeOnDelete and are non-nullable, assume child relations exist at runtime (the database will delete children when the parent is deleted). Do not rely on null-safe operators for these relations, as PHPStan already models them as non-null. This guideline applies broadly to PHP files that define models with foreign keys using cascade delete; verify there are no unnecessary null checks or optional chaining on such relations.

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.

🧹 Nitpick comments (2)
tests/Feature/Api/BackupApiTest.php (2)

30-33: Make the backup precondition explicit in this fixture.

The 202 path only works when the server already has a backup config, but this setup only declares database_names and database_selection_mode. Prefer a factory state or explicit related factory for backup here so the test does not silently depend on DatabaseServer::factory() defaults.

Based on learnings: "Use factories for creating models in tests. Check if the factory has custom states before manually setting up the model."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Feature/Api/BackupApiTest.php` around lines 30 - 33, The fixture must
explicitly provide a backup config instead of relying on
DatabaseServer::factory() defaults; update the test creation to include a
backup-related factory or factory state (e.g., use
DatabaseServer::factory()->has(Backup::factory(), 'backup') or a named state
like ->state('with_backup')) so the created server has the required backup
configuration for the 202 path; modify the test to reference the Backup
factory/state and remove implicit reliance on DatabaseServer::factory()
defaults.

8-14: Add explicit : void return types to the Pest closures.

These test closures are still functions, so they should declare : void to match the repo PHP guideline.

Proposed change
-test('unauthenticated users cannot trigger a backup', function () {
+test('unauthenticated users cannot trigger a backup', function (): void {
@@
-test('viewer users cannot trigger a backup', function () {
+test('viewer users cannot trigger a backup', function (): void {
@@
-test('authenticated users can trigger a backup', function () {
+test('authenticated users can trigger a backup', function (): void {

As per coding guidelines: "Always use explicit return type declarations for methods and functions."

Also applies to: 16-24, 26-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Feature/Api/BackupApiTest.php` around lines 8 - 14, The Pest test
closures in BackupApiTest.php (e.g., the closure passed to test('unauthenticated
users cannot trigger a backup', function () { ... })) are missing explicit
return types; update each anonymous function used as a test closure to declare a
void return type (change function () { ... } to function (): void { ... })
including the other closures noted (lines 16-24, 26-48) so all test closures
conform to the repository PHP guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/Feature/Api/BackupApiTest.php`:
- Around line 30-33: The fixture must explicitly provide a backup config instead
of relying on DatabaseServer::factory() defaults; update the test creation to
include a backup-related factory or factory state (e.g., use
DatabaseServer::factory()->has(Backup::factory(), 'backup') or a named state
like ->state('with_backup')) so the created server has the required backup
configuration for the 202 path; modify the test to reference the Backup
factory/state and remove implicit reliance on DatabaseServer::factory()
defaults.
- Around line 8-14: The Pest test closures in BackupApiTest.php (e.g., the
closure passed to test('unauthenticated users cannot trigger a backup', function
() { ... })) are missing explicit return types; update each anonymous function
used as a test closure to declare a void return type (change function () { ... }
to function (): void { ... }) including the other closures noted (lines 16-24,
26-48) so all test closures conform to the repository PHP guideline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd1e5072-6a1a-47f9-8d69-a9acc799bd24

📥 Commits

Reviewing files that changed from the base of the PR and between deb85be and c43ca10.

📒 Files selected for processing (1)
  • tests/Feature/Api/BackupApiTest.php
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-app-image
  • GitHub Check: ci
🧰 Additional context used
📓 Path-based instructions (6)
**/{app,tests}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/{app,tests}/**/*.php: Use Eloquent models and Model::query() before suggesting raw database queries. Avoid using DB::. Generate code that leverages Laravel's ORM capabilities.
Never append to static properties in services/classes, as they accumulate in memory across requests (Octane issue).

Files:

  • tests/Feature/Api/BackupApiTest.php
**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.php: Use constructor property promotion in __construct() with PHP 8+ syntax (e.g., public function __construct(public GitHub $github) { }). Do not allow empty constructors with zero parameters unless private.
Always use explicit return type declarations for methods and functions. Use appropriate PHP type hints for method parameters.
Use curly braces for control structures, even for single-line bodies.
Prefer PHPDoc blocks over inline comments. Never use comments within code itself unless logic is exceptionally complex.
Add useful array shape type definitions in PHPDoc blocks when appropriate.

Files:

  • tests/Feature/Api/BackupApiTest.php
**/{app,routes,tests}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Never use env() function directly outside of config files. Always use config('app.name') instead of env('APP_NAME').

Files:

  • tests/Feature/Api/BackupApiTest.php
**/{tests,database/factories}/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use factories for creating models in tests. Check if the factory has custom states before manually setting up the model.

Files:

  • tests/Feature/Api/BackupApiTest.php
**/tests/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use faker methods such as $this->faker->word() or fake()->randomDigit() in tests. Follow existing conventions whether to use $this->faker or fake().

Files:

  • tests/Feature/Api/BackupApiTest.php
**/*.{php,js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use descriptive names for variables and methods (e.g., isRegisteredForDiscounts, not discount()).

Files:

  • tests/Feature/Api/BackupApiTest.php
🧠 Learnings (5)
📚 Learning: 2026-03-02T13:13:34.079Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: tests/Unit/Services/Backup/DTO/BackupConfigTest.php:34-45
Timestamp: 2026-03-02T13:13:34.079Z
Learning: In tests/Unit/Services/Backup/DTO/BackupConfigTest.php and similar DTO symmetry tests, prioritize asserting execution-critical fields (database connection, compression, paths) over display-only metadata (volume.name, volume.config, database.serverName) that don't affect backup execution.

Applied to files:

  • tests/Feature/Api/BackupApiTest.php
📚 Learning: 2026-03-10T22:19:40.736Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-10T22:19:40.736Z
Learning: When creating tests, use `php artisan make:test` with `--pest` option for Pest tests or `--unit` for unit tests. Most tests should be feature tests.

Applied to files:

  • tests/Feature/Api/BackupApiTest.php
📚 Learning: 2026-02-25T10:48:46.954Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Services/Agent/AgentJobPayloadBuilder.php:63-76
Timestamp: 2026-02-25T10:48:46.954Z
Learning: In app/Services/Agent/AgentJobPayloadBuilder.php and similar service classes, prefer fail-fast error handling over defensive null-safe operators when upstream callers (e.g., TriggerBackupAction, RunScheduledBackups) guarantee certain relationships exist. For example, in resolveBackupPath(), $server->backup is intentionally accessed without the null-safe operator because backup existence is enforced at call sites; using the null-safe operator would hide bugs that violate this invariant.

Applied to files:

  • tests/Feature/Api/BackupApiTest.php
📚 Learning: 2026-02-13T11:05:37.072Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 105
File: tests/Feature/Services/Backup/Databases/SqliteDatabaseTest.php:8-11
Timestamp: 2026-02-13T11:05:37.072Z
Learning: Adopt a global afterEach hook in tests/Pest.php (or equivalent Pest bootstrap) to clean up temporary directories created during tests. Specifically handle temp dirs named with the prefixes sqlite-db-test-*, backup-task-test-*, restore-task-test-*, and volume-test-*, so individual test files don’t need their own cleanup logic. This applies to all PHP test files under the tests directory.

Applied to files:

  • tests/Feature/Api/BackupApiTest.php
📚 Learning: 2026-02-25T10:48:17.811Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 132
File: app/Console/Commands/RecoverAgentLeasesCommand.php:44-48
Timestamp: 2026-02-25T10:48:17.811Z
Learning: When reviewing PHP code, especially with foreign keys that use cascadeOnDelete and are non-nullable, assume child relations exist at runtime (the database will delete children when the parent is deleted). Do not rely on null-safe operators for these relations, as PHPStan already models them as non-null. This guideline applies broadly to PHP files that define models with foreign keys using cascade delete; verify there are no unnecessary null checks or optional chaining on such relations.

Applied to files:

  • tests/Feature/Api/BackupApiTest.php

@David-Crty David-Crty merged commit 2f2fd38 into main Mar 12, 2026
6 checks passed
@David-Crty David-Crty deleted the feature/restore-api-endpoint branch March 12, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant