Skip to content

feat: add Adminer database browser integration#199

Open
David-Crty wants to merge 10 commits intomainfrom
feat/adminer-integration
Open

feat: add Adminer database browser integration#199
David-Crty wants to merge 10 commits intomainfrom
feat/adminer-integration

Conversation

@David-Crty
Copy link
Copy Markdown
Owner

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

Summary

  • Adds a Browse button to the database server list that opens Adminer in a fullscreen modal, auto-connected to the selected server
  • Installs dg/adminer-custom via Composer and serves it through a Laravel controller with session-based credential passing
  • Adminer CSS is built via Vite (vendor base + custom overrides) for cache-busting and clean bundling
  • Hides Adminer branding (logo, logout, language selector) and the server breadcrumb link since navigation is handled by the app
  • Supports MySQL, PostgreSQL, SQLite, and MongoDB (Browse button hidden for Redis)

Architecture

  • AdminerController — handles auto-login by setting $_POST['auth'] on initial load, delegates to service
  • AdminerService — resolves Vite CSS path, serves static assets, configures output buffering, includes adminer.php
  • adminer_object.php — global-namespace function required by Adminer for auto-login, iframe headers (X-Frame-Options: SAMEORIGIN), and custom CSS
  • AdminerModal — Livewire component with fullscreen overlay + iframe
  • resources/css/adminer.css — Vite entry that imports vendor CSS and adds overrides (modern font, rounded inputs, uppercase table headers, hidden branding)
  • CSRF exemption for /adminer route (Adminer has its own form handling)

Test plan

  • All 822 existing tests pass
  • Click Browse on MySQL server → modal opens, Adminer auto-connects, shows tables
  • Click Browse on PostgreSQL server → same behavior
  • Browse button hidden for Redis servers
  • Close modal and re-open on different server
  • Adminer sidebar navigation works within the iframe
  • CSS overrides applied: no logo, no logout, rounded inputs, full-width breadcrumb

Summary by CodeRabbit

  • New Features

    • Integrated Adminer database browser with a "Browse Database" action, modal embed, and automatic credential injection; action appears only for supported DB types and when enabled/authorized.
    • New application settings to enable Adminer and require a minimum role.
  • Documentation

    • Removed Tailwind references from project docs/metadata.
  • Chores

    • Added Adminer package and wired its stylesheet into the build; adjusted CI/install step ordering.
  • Tests

    • Added feature and Livewire tests covering Adminer access and UI behavior.

David Courtey added 2 commits March 31, 2026 10:19
…-login

- Add Adminer vendor integration (`dg/adminer-custom`) with a Laravel route for secure access.
- Implement auto-login by passing credentials via session and AdminerController.
- Create a custom Adminer theme with Databasement-specific styles.
- Add "Browse Database" button to Database Server actions (excluding Redis).
- Display Adminer in an iframe modal for seamless browsing.
- Move Adminer logic from resources/adminer/index.php into
  AdminerService + adminer_object.php (global namespace required)
- Fix adminer_object() being scoped to App\Services namespace
  instead of global, which broke auto-login in iframe
- Vite-built CSS with vendor base + overrides (adminer.css)
- Full-width breadcrumb, hide logo/logout/lang/server link
- Modernized inputs, buttons, table headers
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

Introduces Adminer integration: composer dependency, rendering service, invokable controller and route, Livewire UI + modal to open Adminer, Adminer-specific CSS and Vite input, app config/authorization changes, tests, and small CI/tooling updates.

Changes

Cohort / File(s) Summary
Adminer service & integration
app/Services/AdminerService.php, app/Services/adminer_object.php
New service renders vendor Adminer, injects auto-login credentials and CSS path, serves whitelisted static assets with caching, and provides adminer_object() anonymous-class for Adminer overrides.
Controller & route
app/Http/Controllers/Web/AdminerController.php, routes/web.php, bootstrap/app.php
New invokable AdminerController reads session credentials, may populate $_POST['auth'], releases session lock, and delegates to service. Adds /adminer route and exempts it from CSRF in global middleware config.
Livewire changes
app/Livewire/DatabaseServer/Index.php, app/Livewire/DatabaseServer/AdminerModal.php
Index adds openAdminer() (authorize, session store, dispatch modal event, expose canAdminer). New AdminerModal component manages modal state and event handlers.
Views & assets
resources/views/livewire/database-server/adminer-modal.blade.php, resources/views/livewire/database-server/index.blade.php, resources/css/adminer.css, vite.config.js
Adds modal Blade view with iframe, updates index view to show conditional "Browse" action and include modal, introduces Adminer CSS overrides, and registers the CSS in Vite inputs.
Models, policies & config
app/Models/DatabaseServer.php, app/Models/User.php, app/Policies/DatabaseServerPolicy.php, app/Services/AppConfigService.php
Adds supportsAdminer() to DatabaseServer, meetsMinimumRole() to User, adminer policy method, and two AppConfigService keys (app.adminer_enabled, app.adminer_role).
Forms, Livewire config UI & tests
app/Livewire/Forms/ConfigurationForm.php, app/Livewire/Configuration/Index.php, resources/views/livewire/configuration/index.blade.php, tests/.../ConfigurationTest.php, tests/.../AdminerControllerTest.php, tests/.../Livewire/DatabaseServer/IndexTest.php
Adds Adminer form fields, load/validate/save logic and admin-only actions; expands configuration UI; and adds tests covering config and Adminer access/rendering flows.
Deps, tooling & CI
composer.json, phpstan.neon, CLAUDE.md, boost.json, .github/workflows/tests.yml
Adds dg/adminer-custom dependency, excludes adminer_object.php from PHPStan, removes Tailwind mentions in docs/boost, and moves composer install earlier in CI.
Other
resources/css/adminer.css (asset content)
New CSS file importing vendor Adminer CSS and applying UI overrides for integration styling.

Sequence Diagram

sequenceDiagram
    participant User
    participant Livewire as DatabaseServer Index
    participant Session
    participant Controller as AdminerController
    participant Service as AdminerService
    participant Adminer

    User->>Livewire: Click "Browse"
    Livewire->>Livewire: openAdminer(serverId) — authorize, build creds
    Livewire->>Session: Store adminer_server_id
    Livewire->>Livewire: Dispatch "open-adminer-modal" (adminerUrl)
    User->>Controller: GET /adminer (iframe request)
    Controller->>Session: Pull adminer_server_id / credentials
    Controller->>Controller: Inject $_POST['auth'] on initial load (if present)
    Controller->>Service: render(credentials)
    Service->>Service: Serve static assets or set globals, set CSS path
    Service->>Adminer: include adminer.php (adminer_object used)
    Adminer-->>User: Render Adminer UI in iframe
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through code with nimble feet,

Pushed creds to session for a login neat.
A modal pops open, iframe so bright,
Adminer served snug — CSS tucked tight.
Carrots and keys — browse safe tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding Adminer database browser integration to the project.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/adminer-integration

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown

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

🧹 Nitpick comments (4)
resources/views/livewire/database-server/adminer-modal.blade.php (1)

1-24: Consider accessibility improvements for the modal.

The modal implementation works well, but consider these accessibility enhancements:

  1. Add title attribute to the iframe for screen readers
  2. Add aria-label to the close button
  3. Consider adding keyboard escape handler to close the modal
♿ Proposed accessibility improvements
-                    <button class="btn btn-sm btn-ghost btn-circle" `@click`="$wire.closeModal()">
+                    <button class="btn btn-sm btn-ghost btn-circle" `@click`="$wire.closeModal()" aria-label="{{ __('Close') }}">
                         <x-icon name="o-x-mark" class="w-5 h-5" />
                     </button>
                     <iframe
                         src="{{ $adminerUrl }}"
                         class="w-full h-full border-0"
                         allow="fullscreen"
+                        title="{{ __('Database browser') }}"
                     ></iframe>

For keyboard escape handling, add to the outer div:

-        <div class="fixed inset-0 z-50 flex items-start justify-center bg-black/60 backdrop-blur-sm" `@click.self`="$wire.closeModal()">
+        <div class="fixed inset-0 z-50 flex items-start justify-center bg-black/60 backdrop-blur-sm" `@click.self`="$wire.closeModal()" `@keydown.escape.window`="$wire.closeModal()">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/views/livewire/database-server/adminer-modal.blade.php` around
lines 1 - 24, The modal lacks basic accessibility attributes and keyboard
support: add a meaningful title attribute to the iframe that uses the existing
$adminerUrl/$serverName context, add an aria-label (or aria-labelledby) to the
close button that calls $wire.closeModal(), and attach a keyboard Escape handler
on the modal container (e.g., a keydown/keydown.window escape listener that
invokes closeModal) so pressing Esc closes the modal when $showModal is true;
update the elements referencing $showModal, the iframe, and the close button
(the button that calls $wire.closeModal()) accordingly.
routes/web.php (1)

73-77: Consider adding rate limiting to mitigate abuse.

Since this route bypasses CSRF protection and provides database browser access, consider adding rate limiting middleware to prevent potential abuse (e.g., brute-force attempts through Adminer's login if session credentials are missing/expired).

🛡️ Proposed fix to add rate limiting
 // Adminer database browser (excluded from CSRF in bootstrap/app.php)
 Route::any('/adminer', \App\Http\Controllers\Web\AdminerController::class)
-    ->middleware('auth')
+    ->middleware(['auth', 'throttle:60,1'])
     ->name('adminer');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@routes/web.php` around lines 73 - 77, The Adminer route
(Route::any('/adminer', \App\Http\Controllers\Web\AdminerController::class) with
->middleware('auth') and name 'adminer') lacks rate limiting; add a throttle
middleware to that route (or a named limiter like 'adminer') and register the
limiter via RateLimiter::for('adminer', ...) in your bootstrapping (or define a
rule in App\Http\Kernel) so the route uses
->middleware(['auth','throttle:adminer']) or an equivalent throttle
configuration to limit requests and prevent abuse.
resources/css/adminer.css (1)

56-60: Replace the “I don't know why” note with the actual rendering constraint.

If this rule is required, document the specific browser/Adminer bug it fixes or link the upstream issue; otherwise it will be impossible to tell later whether Lines 57-60 are still safe to remove.

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

In `@resources/css/adminer.css` around lines 56 - 60, Replace the vague comment
above the thead CSS rule with a precise note describing the rendering constraint
this rule addresses (e.g., specific browser/version or Adminer issue and a link
or ticket ID), and if possible scope the workaround to that condition (for
example mention the user-agent or a feature-detection note such as "@supports"
or "only needed for Safari 14 on macOS" and the Adminer issue URL); update the
comment to state whether the rule is a workaround for a browser bug, Adminer
layout quirk, or a specific upstream issue and include the reference so future
maintainers can verify or remove the rule, ensuring the selector in thead { top:
0px; position: static; } remains documented.
app/Livewire/DatabaseServer/AdminerModal.php (1)

15-22: Add #[Locked] to shield the Adminer URL from client-side tampering.

$adminerUrl is a public scalar property assigned from a server-trusted source (route('adminer') in Lines 167-170 of Index.php) but remains client-writable. Without #[Locked], a client could modify it in DevTools to point to an unauthorized Adminer instance. Locking the property ensures the iframe target cannot be swapped after the server authorizes the endpoint.

🔒 Suggested change
 use Illuminate\View\View;
+use Livewire\Attributes\Locked;
 use Livewire\Attributes\On;
 use Livewire\Component;
@@
-    public string $adminerUrl = '';
+    #[Locked]
+    public string $adminerUrl = '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Livewire/DatabaseServer/AdminerModal.php` around lines 15 - 22, The
public scalar property $adminerUrl in AdminerModal remains client-writable, so
add the Livewire #[Locked] attribute to the $adminerUrl declaration to prevent
client-side tampering; update the AdminerModal class by annotating the
$adminerUrl property with #[Locked] (keep the existing openModal(string
$serverName, string $adminerUrl): void method and assignments intact) so the
server-provided adminer URL cannot be modified from the client.
🤖 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/Http/Controllers/Web/AdminerController.php`:
- Around line 13-30: Session-stored raw credentials are used to render Adminer
without re-checking authorization; instead, store only the DatabaseServer id in
session, then in AdminerController load the DatabaseServer model by that id
(e.g., $server = DatabaseServer::find($id)), call $this->authorize('view',
$server) (or authorize('view', $server)) to re-run the policy, rebuild the
$credentials array from the fresh $server record, and only then call
$adminer->render($credentials); also retain the existing initial-load logic that
populates $_POST['auth'] but use the freshly-authorized credentials rather than
the session blob.

In `@app/Livewire/DatabaseServer/Index.php`:
- Around line 159-165: Wrap the call to DatabaseServer::getDecryptedPassword()
in a try/catch inside the Livewire Index component before calling session()->put
so a decryption exception doesn't bubble and break the Livewire request; on
exception set the 'password' entry to the model's recovery message (e.g.
$server->getRecoveryMessage()) or another recovery accessor exposed by the
DatabaseServer model, then continue to session()->put(...) as before.
- Around line 159-165: Current code in Livewire/DatabaseServer/Index.php stores
decrypted DB passwords into session via session()->put including
getDecryptedPassword(), which persists raw secrets; change it to store only the
server identifier/context (e.g., server id, driver, host, db name) in session
and remove the decrypted password storage. Update AdminerController.php to
perform just-in-time decryption using Server::getDecryptedPassword() (or
equivalent) when handling the Adminer request and build credentials there
per-request, ensuring session contains no plaintext secrets and only a reference
to the target server/context.

In `@app/Services/AdminerService.php`:
- Around line 23-27: The AdminerService currently mutates process-global state
by calling chdir($vendorAdminer) before include and by defining ASSETS_VERSION
at runtime; update the code in the AdminerService (around the
startOutputBuffering(), defineAdminerObject(), chdir($vendorAdminer) and include
$vendorAdminer.'/adminer.php' sequence) to save the original working directory
with getcwd(), perform chdir() only for the include, and always restore the
original cwd in a finally block so the process-wide working directory is not
leaked; also remove the runtime define('ASSETS_VERSION', ...) and replace it
with a class constant (e.g., AdminerService::ASSETS_VERSION) referenced where
needed to avoid redefining a global constant across requests.

In `@bootstrap/app.php`:
- Around line 52-54: The /adminer route only has auth middleware and is exempted
from CSRF via validateCsrfTokens(except: ['adminer']), so add rate limiting to
prevent abuse: update the route registration for Adminer (the
Route::any('/adminer', AdminerController::class) entry) to include a throttle
middleware (for example 'throttle:60,1') alongside 'auth'; ensure openAdminer
(Livewire DatabaseServer Index) authorization remains intact and do not remove
the CSRF exemption, just attach the throttle middleware to the AdminerController
route.

In `@resources/css/adminer.css`:
- Line 2: The CSS `@import` referencing
'../../vendor/dg/adminer-custom/adminer.css' cannot be resolved by Vite (CI
error "Can't resolve ...") because it points outside the bundle root; fix by
either removing that import so the vendor stylesheet is served directly by
Adminer and this bundle contains only overrides, or copy the vendor file into
the build-managed path (e.g., place adminer.css into the same resources/css/
directory) and update the `@import` to the new local filename (e.g., change the
`@import` line in resources/css/adminer.css to import 'adminer.css' or
'adminer-vendor.css') so Vite can resolve it.

In `@resources/views/livewire/database-server/index.blade.php`:
- Around line 187-195: The Browse button rendering lacks an authorization check;
wrap the button block in an `@can`('browse', $server) directive (matching how
other actions use `@can`) and ensure the DatabaseServerPolicy implements a public
function browse(User $user, DatabaseServer $server) that denies Redis servers
(check $server->database_type !== \App\Enums\DatabaseType::REDIS) and verifies
the user's permission to access that server; keep the existing
wire:click="openAdminer('{{ $server->id }}')" and button attributes intact so
only authorized users see and can invoke openAdminer.

---

Nitpick comments:
In `@app/Livewire/DatabaseServer/AdminerModal.php`:
- Around line 15-22: The public scalar property $adminerUrl in AdminerModal
remains client-writable, so add the Livewire #[Locked] attribute to the
$adminerUrl declaration to prevent client-side tampering; update the
AdminerModal class by annotating the $adminerUrl property with #[Locked] (keep
the existing openModal(string $serverName, string $adminerUrl): void method and
assignments intact) so the server-provided adminer URL cannot be modified from
the client.

In `@resources/css/adminer.css`:
- Around line 56-60: Replace the vague comment above the thead CSS rule with a
precise note describing the rendering constraint this rule addresses (e.g.,
specific browser/version or Adminer issue and a link or ticket ID), and if
possible scope the workaround to that condition (for example mention the
user-agent or a feature-detection note such as "@supports" or "only needed for
Safari 14 on macOS" and the Adminer issue URL); update the comment to state
whether the rule is a workaround for a browser bug, Adminer layout quirk, or a
specific upstream issue and include the reference so future maintainers can
verify or remove the rule, ensuring the selector in thead { top: 0px; position:
static; } remains documented.

In `@resources/views/livewire/database-server/adminer-modal.blade.php`:
- Around line 1-24: The modal lacks basic accessibility attributes and keyboard
support: add a meaningful title attribute to the iframe that uses the existing
$adminerUrl/$serverName context, add an aria-label (or aria-labelledby) to the
close button that calls $wire.closeModal(), and attach a keyboard Escape handler
on the modal container (e.g., a keydown/keydown.window escape listener that
invokes closeModal) so pressing Esc closes the modal when $showModal is true;
update the elements referencing $showModal, the iframe, and the close button
(the button that calls $wire.closeModal()) accordingly.

In `@routes/web.php`:
- Around line 73-77: The Adminer route (Route::any('/adminer',
\App\Http\Controllers\Web\AdminerController::class) with ->middleware('auth')
and name 'adminer') lacks rate limiting; add a throttle middleware to that route
(or a named limiter like 'adminer') and register the limiter via
RateLimiter::for('adminer', ...) in your bootstrapping (or define a rule in
App\Http\Kernel) so the route uses ->middleware(['auth','throttle:adminer']) or
an equivalent throttle configuration to limit requests and prevent abuse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51d1b366-bee9-44cd-afc1-77cb1c1d21d5

📥 Commits

Reviewing files that changed from the base of the PR and between 08b7f26 and 353b1f1.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • CLAUDE.md
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/AdminerModal.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Services/AdminerService.php
  • app/Services/adminer_object.php
  • bootstrap/app.php
  • composer.json
  • phpstan.neon
  • resources/css/adminer.css
  • resources/views/livewire/database-server/adminer-modal.blade.php
  • resources/views/livewire/database-server/index.blade.php
  • routes/web.php
  • vite.config.js
📜 Review details
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2026-03-28T22:33:16.333Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 195
File: app/Notifications/CLAUDE.md:7-12
Timestamp: 2026-03-28T22:33:16.333Z
Learning: In this repository (David-Crty/databasement), do not flag fenced Markdown code blocks that omit a language identifier (i.e., MD040: missing language after ```). The project treats unlabeled fenced code blocks as plain text by default, so missing language tags are acceptable for Markdown files.

Applied to files:

  • CLAUDE.md
📚 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:

  • bootstrap/app.php
  • resources/views/livewire/database-server/index.blade.php
  • resources/views/livewire/database-server/adminer-modal.blade.php
  • routes/web.php
  • app/Services/AdminerService.php
  • app/Services/adminer_object.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/AdminerModal.php
📚 Learning: 2026-02-24T10:43:22.287Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 136
File: app/Models/DatabaseServer.php:250-258
Timestamp: 2026-02-24T10:43:22.287Z
Learning: Pattern mode (database_selection_mode = 'pattern') is not available for SQLite or Redis database servers. The UI conditionally hides the segmented control for these types in resources/views/livewire/database-server/_form.blade.php (line 242: !$form->isSqlite() && !$form->isRedis()).

Applied to files:

  • resources/views/livewire/database-server/index.blade.php
📚 Learning: 2026-01-30T22:27:46.107Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 61
File: resources/views/livewire/volume/connectors/s3-config.blade.php:1-13
Timestamp: 2026-01-30T22:27:46.107Z
Learning: In Blade template files (any .blade.php) within the databasement project, allow using alert-info for informational content inside <x-alert> components. The guideline that permits alert-success and alert-error does not exclude using alert-info for informational purposes. Apply this consistently to all Blade components that render alerts; ensure semantic usage and accessibility.

Applied to files:

  • resources/views/livewire/database-server/index.blade.php
  • resources/views/livewire/database-server/adminer-modal.blade.php
📚 Learning: 2026-02-06T10:34:43.585Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 75
File: resources/views/livewire/backup-job/_filters.blade.php:36-40
Timestamp: 2026-02-06T10:34:43.585Z
Learning: In Blade template files, when creating compact inline filter controls, prefer using native <input type="checkbox"> elements with daisyUI classes (e.g., checkbox checkbox-warning checkbox-xs) over the Mary UI <x-checkbox> component. The <x-checkbox> component adds wrapper markup (e.g., <div><fieldset><label> with gap-3) that can break tight inline flex layouts. Use the native input approach for compact inline controls, but reserve <x-checkbox> for form fields that require labels, hints, and errors.

Applied to files:

  • resources/views/livewire/database-server/index.blade.php
  • resources/views/livewire/database-server/adminer-modal.blade.php
📚 Learning: 2026-02-03T09:28:33.376Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 69
File: resources/views/livewire/configuration/index.blade.php:62-75
Timestamp: 2026-02-03T09:28:33.376Z
Learning: In **/*.blade.php files for the databasement project: The `<x-popover>` component (likely MaryUI/DaisyUI) properly displays popovers even when the trigger is a disabled `<x-button>`. Unlike standard HTML, the framework handles pointer events for disabled button triggers correctly.

Applied to files:

  • resources/views/livewire/database-server/adminer-modal.blade.php
📚 Learning: 2026-03-14T17:22:01.553Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 159
File: routes/ai.php:1-9
Timestamp: 2026-03-14T17:22:01.553Z
Learning: In the `laravel/mcp` package (first-party Laravel package, `laravel/mcp: "^0.6.2"`), the `McpServiceProvider` automatically loads `routes/ai.php`. Do NOT flag the absence of `routes/ai.php` registration in `bootstrap/app.php` as an issue — it is registered automatically by the package's service provider. MCP web and local server definitions (e.g., `Mcp::web(...)`, `Mcp::local(...)`) should be placed in `routes/ai.php` without any additional manual registration.

Applied to files:

  • routes/web.php
📚 Learning: 2026-02-04T12:26:48.720Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-04T12:26:48.720Z
Learning: Applies to routes/web.php : Use `Volt::route()` helper for single-file authentication components in routing. Use Livewire component classes directly for other routes (e.g., `Route::get('resource', ComponentClass::class)`).

Applied to files:

  • routes/web.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/web.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/Index.php
  • app/Livewire/DatabaseServer/AdminerModal.php
🪛 GitHub Actions: tests
resources/css/adminer.css

[error] 1-1: Build failed during Vite/Tailwind processing: [@tailwindcss/vite:generate:build] Can't resolve '../../vendor/dg/adminer-custom/adminer.css' referenced from '/home/runner/work/databasement/databasement/resources/css'.

🪛 PHPMD (2.15.0)
app/Services/adminer_object.php

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

(UnusedFormalParameter)


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

(UnusedFormalParameter)

app/Http/Controllers/Web/AdminerController.php

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

(UnusedFormalParameter)

🔇 Additional comments (6)
CLAUDE.md (1)

418-418: LGTM!

Documentation updated to reflect the current PHP runtime version.

phpstan.neon (1)

16-18: Acceptable exclusion for Adminer glue code.

The adminer_object.php file operates in the global namespace as required by Adminer's plugin architecture, which doesn't play well with PHPStan's analysis. This exclusion is a reasonable tradeoff for vendor integration code.

vite.config.js (1)

10-10: LGTM!

Adding the Adminer stylesheet to Vite's input array enables proper cache-busting via the manifest, which AdminerService uses to resolve the CSS path.

resources/views/livewire/database-server/index.blade.php (1)

250-252: LGTM!

The Adminer modal component is correctly placed alongside the other modals in the view.

composer.json (1)

19-19: Version constraint is appropriate.

The dg/adminer-custom package constraint ^3.3 correctly targets the latest version (v3.3.1). No changes needed.

app/Services/adminer_object.php (1)

9-54: Global hook placement looks right.

Keeping adminer_object() in the root namespace is the correct integration point for Adminer and fixes the namespace discovery problem cleanly.

Comment thread app/Http/Controllers/Web/AdminerController.php Outdated
Comment thread app/Livewire/DatabaseServer/Index.php Outdated
Comment on lines +159 to +165
session()->put('adminer_credentials', [
'driver' => $driver,
'server' => $serverAddress,
'username' => $server->username ?? '',
'password' => $server->getDecryptedPassword(),
'db' => $db,
]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle password decryption failures without blowing up the Livewire request.

DatabaseServer::getDecryptedPassword() can throw when the stored value is no longer decryptable, so clicking Browse currently turns into a failed Livewire request instead of the recovery message exposed by the model.

💡 Suggested guard
+        try {
+            $password = $server->getDecryptedPassword();
+        } catch (\Throwable $e) {
+            $this->error($e->getMessage(), position: 'toast-bottom');
+            return;
+        }
+
         session()->put('adminer_credentials', [
             'driver' => $driver,
             'server' => $serverAddress,
             'username' => $server->username ?? '',
-            'password' => $server->getDecryptedPassword(),
+            'password' => $password,
             'db' => $db,
         ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
session()->put('adminer_credentials', [
'driver' => $driver,
'server' => $serverAddress,
'username' => $server->username ?? '',
'password' => $server->getDecryptedPassword(),
'db' => $db,
]);
try {
$password = $server->getDecryptedPassword();
} catch (\Throwable $e) {
$this->error($e->getMessage(), position: 'toast-bottom');
return;
}
session()->put('adminer_credentials', [
'driver' => $driver,
'server' => $serverAddress,
'username' => $server->username ?? '',
'password' => $password,
'db' => $db,
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Livewire/DatabaseServer/Index.php` around lines 159 - 165, Wrap the call
to DatabaseServer::getDecryptedPassword() in a try/catch inside the Livewire
Index component before calling session()->put so a decryption exception doesn't
bubble and break the Livewire request; on exception set the 'password' entry to
the model's recovery message (e.g. $server->getRecoveryMessage()) or another
recovery accessor exposed by the DatabaseServer model, then continue to
session()->put(...) as before.

⚠️ Potential issue | 🟠 Major

Don't persist decrypted database passwords in session.

This materializes the raw secret and keeps it in the session store for the life of the web session. That widens the blast radius of a session compromise and makes it harder for app/Http/Controllers/Web/AdminerController.php to re-authorize access per request. Store only the target server/context in session, then decrypt just-in-time when serving Adminer.

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

In `@app/Livewire/DatabaseServer/Index.php` around lines 159 - 165, Current code
in Livewire/DatabaseServer/Index.php stores decrypted DB passwords into session
via session()->put including getDecryptedPassword(), which persists raw secrets;
change it to store only the server identifier/context (e.g., server id, driver,
host, db name) in session and remove the decrypted password storage. Update
AdminerController.php to perform just-in-time decryption using
Server::getDecryptedPassword() (or equivalent) when handling the Adminer request
and build credentials there per-request, ensuring session contains no plaintext
secrets and only a reference to the target server/context.

Comment thread app/Services/AdminerService.php
Comment thread bootstrap/app.php
Comment thread resources/css/adminer.css
Comment thread resources/views/livewire/database-server/index.blade.php Outdated
- Wrap getDecryptedPassword() in try/catch to show toast on failure
  instead of crashing the Livewire request
- Add @can('view', $server) authorization check on Browse button
  for consistency with other action buttons
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
app/Livewire/DatabaseServer/Index.php (1)

167-173: ⚠️ Potential issue | 🟠 Major

Do not store decrypted DB passwords in session state.

Line 171 persists plaintext credentials in session, and app/Http/Controllers/Web/AdminerController.php later reads and forwards them directly. This still expands secret exposure scope (session compromise, longer lifetime than needed). Keep only server/context in session and decrypt just-in-time in the controller per request.

[suggested direction]

🔐 Minimal direction for this component
-        session()->put('adminer_credentials', [
-            'driver' => $driver,
-            'server' => $serverAddress,
-            'username' => $server->username ?? '',
-            'password' => $password,
-            'db' => $db,
-        ]);
+        session()->put('adminer_context', [
+            'server_id' => $server->getKey(),
+            'db' => $db,
+        ]);

(Then resolve/decrypt in AdminerController right before building $_POST['auth'].)

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

In `@app/Livewire/DatabaseServer/Index.php` around lines 167 - 173, Remove storing
the plaintext DB password from the Livewire component: stop putting 'password'
into session in the Index class (the session()->put call that stores
'adminer_credentials'); instead persist only non-secret context (driver, server
id/address, username or server id if needed, db). Update AdminerController
(app/Http/Controllers/Web/AdminerController.php) to retrieve the server/context
from session and perform decryption of the secret just-in-time inside the
controller before constructing $_POST['auth'] (use the existing decryption
helper/logic used elsewhere), then forward the transient decrypted password and
immediately avoid re-persisting it to session. Ensure no plaintext password is
written to session anywhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/Livewire/DatabaseServer/Index.php`:
- Around line 167-173: Remove storing the plaintext DB password from the
Livewire component: stop putting 'password' into session in the Index class (the
session()->put call that stores 'adminer_credentials'); instead persist only
non-secret context (driver, server id/address, username or server id if needed,
db). Update AdminerController (app/Http/Controllers/Web/AdminerController.php)
to retrieve the server/context from session and perform decryption of the secret
just-in-time inside the controller before constructing $_POST['auth'] (use the
existing decryption helper/logic used elsewhere), then forward the transient
decrypted password and immediately avoid re-persisting it to session. Ensure no
plaintext password is written to session anywhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21cb2e80-189d-4320-a025-94670cc6c384

📥 Commits

Reviewing files that changed from the base of the PR and between 353b1f1 and e650aa3.

📒 Files selected for processing (2)
  • app/Livewire/DatabaseServer/Index.php
  • resources/views/livewire/database-server/index.blade.php
✅ Files skipped from review due to trivial changes (1)
  • resources/views/livewire/database-server/index.blade.php
📜 Review details
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 199
File: app/Http/Controllers/Web/AdminerController.php:13-30
Timestamp: 2026-03-31T09:47:40.852Z
Learning: In David-Crty/databasement, `DatabaseServerPolicy::view()` returns `true` for all authenticated users unconditionally. Therefore, skipping a per-request `authorize('view', $server)` call in `AdminerController` (which is already protected by `auth` middleware) is intentional and acceptable. Do not flag the absence of a re-authorization check in `AdminerController` as a security issue unless the policy becomes more restrictive.
📚 Learning: 2026-03-31T09:47:40.852Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 199
File: app/Http/Controllers/Web/AdminerController.php:13-30
Timestamp: 2026-03-31T09:47:40.852Z
Learning: In David-Crty/databasement, `DatabaseServerPolicy::view()` returns `true` for all authenticated users unconditionally. Therefore, skipping a per-request `authorize('view', $server)` call in `AdminerController` (which is already protected by `auth` middleware) is intentional and acceptable. Do not flag the absence of a re-authorization check in `AdminerController` as a security issue unless the policy becomes more restrictive.

Applied to files:

  • app/Livewire/DatabaseServer/Index.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/Index.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:

  • app/Livewire/DatabaseServer/Index.php
🔇 Additional comments (1)
app/Livewire/DatabaseServer/Index.php (1)

137-141: Good guardrails in the Adminer entry flow.

Redis is blocked early with a clear toast, and decryption failures are now handled without breaking the Livewire request. Nice improvement.

Also applies to: 159-165

@wblondel
Copy link
Copy Markdown

wblondel commented Apr 5, 2026

IMO, this feature is potentially unwanted and should be opt-in only.

@David-Crty
Copy link
Copy Markdown
Owner Author

IMO, this feature is potentially unwanted and should be opt-in only.

I'm planning to make it an opt-in feature, though it will likely be enabled by default.

To understand better why this feature might be unwanted @wblondel ? I have my own idea, but considering other viewpoints is always valuable.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

❌ Patch coverage is 49.13295% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.39%. Comparing base (71ab0d6) to head (c428bc0).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/Services/AdminerService.php 0.00% 66 Missing ⚠️
app/Services/adminer_object.php 0.00% 11 Missing ⚠️
app/Livewire/DatabaseServer/AdminerModal.php 18.18% 9 Missing ⚠️
app/Http/Controllers/Web/AdminerController.php 94.59% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #199      +/-   ##
============================================
- Coverage     91.31%   90.39%   -0.92%     
- Complexity     2176     2219      +43     
============================================
  Files           189      193       +4     
  Lines          7806     7979     +173     
============================================
+ Hits           7128     7213      +85     
- Misses          678      766      +88     

☔ 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.

David Courtey added 3 commits April 30, 2026 17:55
…nd role configuration

Move credential building from Livewire component to AdminerController,
pass only server ID via session. Add configurable minimum role for
Adminer access (adminer policy + User::meetsMinimumRole). Fix Adminer
plugin to delegate to parent credentials/login on subsequent requests,
preventing PostgreSQL socket fallback errors.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/Services/adminer_object.php (2)

9-44: ⚡ Quick win

Prefer native type declarations for function/method signatures (verify Adminer parent compatibility first).

At Line 9 / Line 32 / Line 37 / Line 43, signatures still depend on PHPDoc or are untyped. Add native parameter/return types to match project standards, but first confirm compatibility with \Adminer\Adminer overrides.

Proposed change (if vendor signatures allow it)
-function adminer_object()
+function adminer_object(): \Adminer\Adminer
 {
@@
-        public function login($login, $password)
+        public function login(mixed $login, mixed $password): bool
         {
             return true;
         }

-        public function headers()
+        public function headers(): void
         {
             header('X-Frame-Options: SAMEORIGIN');
         }
@@
-        public function css()
+        public function css(): array
         {
             return [$this->cssPath];
         }

As per coding guidelines: "Use explicit return type declarations and type hints for all method parameters: function isAccessible(User $user, ?string $path = null): bool".

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

In `@app/Services/adminer_object.php` around lines 9 - 44, The methods in the
anonymous class returned by adminer_object lack native type hints; update
signatures to use native types while verifying compatibility with
\Adminer\Adminer: add ?array $creds and string $cssPath types are already
present on __construct (ensure constructor visibility matches parent if needed),
change public function credentials(): array to explicitly return array, change
public function login($login, $password): bool to declare parameter and return
types (e.g. string $login, string $password): bool or match parent signature,
change public function headers(): void to return nothing, and change public
function css(): array to return array<string> (or array) — adjust nullable/union
types to exactly match the parent method types in \Adminer\Adminer before
committing.

16-27: ⚡ Quick win

Use an array-shape PHPDoc for $creds instead of a generic string map.

credentials() relies on fixed keys at Line 26, so array<string, string>|null is too loose and weakens static checks.

Proposed change
-        /** `@param` array<string, string>|null $creds */
+        /** `@param` array{server:string,username:string,password:string}|null $creds */
         public function __construct(
             private ?array $creds,
             private string $cssPath,
         ) {}

As per coding guidelines: "Use array shape type definitions in PHPDoc blocks".

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

In `@app/Services/adminer_object.php` around lines 16 - 27, Update the PHPDoc for
the constructor parameter $creds to an array-shape specifying the exact keys
used (e.g. array{server:string,username:string,password:string}|null) so static
analyzers know the expected keys, and ensure any property or class-level
docblocks referencing $creds reflect the same shape; the change should be made
near the __construct declaration and will align with the existing credentials():
array{string,string,string} method which accesses $this->creds['server'],
['username'], and ['password'].
🤖 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/Http/Controllers/Web/AdminerController.php`:
- Around line 60-63: Replace the current single-backup lookup that uses
$server->backups->first()?->database_names with a resolved, de-duplicated list
of database names collected from all backups (e.g.
collect($server->backups->pluck('database_names'))->flatten()->unique()->values()->all())
and then only auto-select into $db when that resulting $databaseNames array has
exactly one entry; update the code around the $databaseNames, $server->backups
and $db variables to use the flattened/unique list instead of the first backup's
database_names.

In `@tests/Feature/Livewire/DatabaseServer/IndexTest.php`:
- Around line 54-62: The test relies on the default app.adminer_enabled value;
make the precondition explicit by setting the config key to false at the start
of the test so it remains deterministic—add a configuration set for
"app.adminer_enabled" => false before creating the User/DatabaseServer or
invoking Livewire::actingAs in the "openAdminer is forbidden when adminer is
disabled" test (refer to the test function name and the Livewire::actingAs
->test(Index::class) ->call('openAdminer', ...) flow to locate where to insert
it).

---

Nitpick comments:
In `@app/Services/adminer_object.php`:
- Around line 9-44: The methods in the anonymous class returned by
adminer_object lack native type hints; update signatures to use native types
while verifying compatibility with \Adminer\Adminer: add ?array $creds and
string $cssPath types are already present on __construct (ensure constructor
visibility matches parent if needed), change public function credentials():
array to explicitly return array, change public function login($login,
$password): bool to declare parameter and return types (e.g. string $login,
string $password): bool or match parent signature, change public function
headers(): void to return nothing, and change public function css(): array to
return array<string> (or array) — adjust nullable/union types to exactly match
the parent method types in \Adminer\Adminer before committing.
- Around line 16-27: Update the PHPDoc for the constructor parameter $creds to
an array-shape specifying the exact keys used (e.g.
array{server:string,username:string,password:string}|null) so static analyzers
know the expected keys, and ensure any property or class-level docblocks
referencing $creds reflect the same shape; the change should be made near the
__construct declaration and will align with the existing credentials():
array{string,string,string} method which accesses $this->creds['server'],
['username'], and ['password'].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 73b0432d-473a-41b3-bf35-e1ee957cedcf

📥 Commits

Reviewing files that changed from the base of the PR and between 4cac657 and c428bc0.

📒 Files selected for processing (15)
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/Configuration/Index.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
  • app/Models/DatabaseServer.php
  • app/Models/User.php
  • app/Policies/DatabaseServerPolicy.php
  • app/Services/AppConfigService.php
  • app/Services/adminer_object.php
  • resources/views/livewire/configuration/index.blade.php
  • resources/views/livewire/database-server/adminer-modal.blade.php
  • resources/views/livewire/database-server/index.blade.php
  • tests/Feature/ConfigurationTest.php
  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
✅ Files skipped from review due to trivial changes (2)
  • resources/views/livewire/database-server/adminer-modal.blade.php
  • app/Services/AppConfigService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/views/livewire/database-server/index.blade.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). (1)
  • GitHub Check: ci
🧰 Additional context used
📓 Path-based instructions (8)
**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use curly braces for all control structures in PHP, even for single-line bodies

Use PHP 8 constructor property promotion: public function __construct(public GitHub $github) { }. Do not leave empty zero-parameter __construct() methods unless the constructor is private.

Use explicit return type declarations and type hints for all method parameters: function isAccessible(User $user, ?string $path = null): bool

Prefer PHPDoc blocks over inline comments. Only add inline comments for exceptionally complex logic.

Use array shape type definitions in PHPDoc blocks

All changes to PHP files must be formatted with Laravel Pint. Run vendor/bin/pint --dirty --format agent before finalizing changes.

Use descriptive names for variables and methods. For example, isRegisteredForDiscounts, not discount()

Files:

  • app/Policies/DatabaseServerPolicy.php
  • app/Models/DatabaseServer.php
  • app/Models/User.php
  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
  • app/Livewire/Configuration/Index.php
  • resources/views/livewire/configuration/index.blade.php
  • app/Services/adminer_object.php
**/*.{php,blade.php}

📄 CodeRabbit inference engine (CLAUDE.md)

Use translations with __('...') helper function. Keep technical terms like Backup, Restore, Snapshot(s) in English across all locales.

Files:

  • app/Policies/DatabaseServerPolicy.php
  • app/Models/DatabaseServer.php
  • app/Models/User.php
  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
  • app/Livewire/Configuration/Index.php
  • resources/views/livewire/configuration/index.blade.php
  • app/Services/adminer_object.php
app/Models/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

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

When creating models, create useful factories and seeders for them as well

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

When creating tests, use factories for creating models instead of manually setting up model properties

Use $this->faker->word() or fake()->randomDigit() for generating test data. Follow existing conventions in the codebase for whether to use $this->faker or fake()

Create feature tests by default using php artisan make:test [options] {name}. Pass --unit only for unit tests.

Use php artisan make:test --pest {name} to create tests with Pest. The {name} argument should not include the test suite directory.

Files:

  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
app/Livewire/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use #[Validate] attributes or Form objects for validation in Livewire components. Call $this->validate() before processing data.

Use Session::flash() for one-time messages in Livewire components and access them in Blade files with @if (session('success'))

Return $this->redirect() with navigate: true in Livewire components for SPA-like navigation between pages

Public properties in Livewire components are automatically bound to views. Use them for component state and form data.

Modal pattern in Mary UI: Add boolean property to Livewire component class, use wire:model in Blade file to bind modal visibility

Livewire components should use class-based components with reactive state. Use Alpine.js for client-side interactions instead of JavaScript frameworks.

Keep state server-side in Livewire components so the UI reflects it. Validate and authorize in actions as you would in HTTP requests.

Files:

  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
  • app/Livewire/Configuration/Index.php
resources/views/livewire/**/*.blade.php

📄 CodeRabbit inference engine (CLAUDE.md)

In Livewire Blade files, contain only view markup. Keep all PHP logic in component classes.

Files:

  • resources/views/livewire/configuration/index.blade.php
resources/views/**/*.blade.php

📄 CodeRabbit inference engine (CLAUDE.md)

All UI components use Mary UI (built on daisyUI). Use component prefixes (e.g., <x-button>, <x-input>, <x-card>) without custom prefixes.

Use Heroicons for icons in Mary UI components with format icon="o-user" for outline icons and icon="s-user" for solid icons

Select component pattern in Mary UI: Use :options prop with array format [['id' => 'value', 'name' => 'Label']]

Alert pattern in Mary UI: Use class="alert-success", class="alert-error", etc. instead of variant prop

Dark mode in Mary UI follows system preference using prefers-color-scheme

In Blade component attributes with translated strings, use :attr binding (dynamic syntax) like <x-header :title="__('Text')" /> instead of {{ }} interpolation to avoid double-encoding special characters

Files:

  • resources/views/livewire/configuration/index.blade.php
**/*.blade.php

📄 CodeRabbit inference engine (CLAUDE.md)

Use named routes with the route() function when generating links to other pages instead of hardcoding URLs

Files:

  • resources/views/livewire/configuration/index.blade.php
🧠 Learnings (41)
📓 Common learnings
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 199
File: app/Services/AdminerService.php:24-28
Timestamp: 2026-04-30T15:26:00.524Z
Learning: In David-Crty/databasement, the Adminer integration (app/Services/AdminerService.php) uses `chdir()` and runtime `define()` before `include adminer.php`. These are intentionally not guarded with save/restore or class constants because adminer.php calls `exit()` internally, so the PHP worker resets after every Adminer request anyway. Laravel Octane has been removed from the project entirely (as of PR `#199` follow-up). Do not flag these as process-global state leaks.
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 199
File: app/Http/Controllers/Web/AdminerController.php:13-30
Timestamp: 2026-03-31T09:47:44.827Z
Learning: In David-Crty/databasement, `DatabaseServerPolicy::view()` returns `true` for all authenticated users unconditionally. Therefore, skipping a per-request `authorize('view', $server)` call in `AdminerController` (which is already protected by `auth` middleware) is intentional and acceptable. Do not flag the absence of a re-authorization check in `AdminerController` as a security issue unless the policy becomes more restrictive.
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: All PHP commands MUST be run through Docker using Makefile targets or `docker compose exec --user application -T app <command>`. Never run `php`, `composer`, or `vendor/bin/*` commands directly on the host.
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Always use `make test` command for running tests. NEVER use `docker compose exec ... php artisan test` directly - it runs tests sequentially and is much slower. Tests run in parallel by default using Pest's parallel testing feature.
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Use `make test-filter FILTER=<TestClass>` to run specific tests by class or method name
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Run tests with `php artisan test --compact` or filter with `php artisan test --compact --filter=testName`
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Use `php artisan make:` commands to create new files (migrations, controllers, models, etc.) rather than manually creating them
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: When creating a generic PHP class, use `php artisan make:class` command
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Pass `--no-interaction` to all Artisan commands to ensure they work without user input and include correct `--options` for desired behavior
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Check for existing components to reuse before writing a new one
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming.
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important.
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Stick to existing directory structure; do not create new base folders without approval
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Do not change the application's dependencies without approval
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: If users don't see frontend changes reflected in the UI, they may need to run `npm run build`, `npm run dev`, or `composer run dev`
Learnt from: CR
Repo: David-Crty/databasement

Timestamp: 2026-04-30T17:45:09.999Z
Learning: Only create documentation files if explicitly requested by the user
📚 Learning: 2026-03-31T09:47:44.827Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 199
File: app/Http/Controllers/Web/AdminerController.php:13-30
Timestamp: 2026-03-31T09:47:44.827Z
Learning: In David-Crty/databasement, `DatabaseServerPolicy::view()` returns `true` for all authenticated users unconditionally. Therefore, skipping a per-request `authorize('view', $server)` call in `AdminerController` (which is already protected by `auth` middleware) is intentional and acceptable. Do not flag the absence of a re-authorization check in `AdminerController` as a security issue unless the policy becomes more restrictive.

Applied to files:

  • app/Policies/DatabaseServerPolicy.php
  • app/Models/DatabaseServer.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-14T09:35:21.994Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 212
File: resources/views/livewire/database-server/index.blade.php:185-193
Timestamp: 2026-04-14T09:35:21.994Z
Learning: In `app/Policies/DatabaseServerPolicy.php` (David-Crty/databasement), the `backup()` policy method guards on both `$databaseServer->backups_enabled` and `$databaseServer->backups->isNotEmpty()` before checking user role (`isDemo()` / `canPerformActions()`). The "Backup now" button in the index view relies entirely on this policy check (`can('backup', $server)`) — do not add redundant view-level guards for this condition.

Applied to files:

  • app/Policies/DatabaseServerPolicy.php
  • tests/Feature/Http/AdminerControllerTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-30T15:26:00.524Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 199
File: app/Services/AdminerService.php:24-28
Timestamp: 2026-04-30T15:26:00.524Z
Learning: In David-Crty/databasement, the Adminer integration (app/Services/AdminerService.php) uses `chdir()` and runtime `define()` before `include adminer.php`. These are intentionally not guarded with save/restore or class constants because adminer.php calls `exit()` internally, so the PHP worker resets after every Adminer request anyway. Laravel Octane has been removed from the project entirely (as of PR `#199` follow-up). Do not flag these as process-global state leaks.

Applied to files:

  • app/Policies/DatabaseServerPolicy.php
  • tests/Feature/Http/AdminerControllerTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Services/adminer_object.php
📚 Learning: 2026-03-23T11:45:17.545Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 178
File: app/Http/Controllers/Api/V1/VolumeController.php:116-120
Timestamp: 2026-03-23T11:45:17.545Z
Learning: In David-Crty/databasement, `testConnection` API endpoints (e.g., `VolumeController::testConnection`, `DatabaseServerController::testConnection`) intentionally use `$this->authorize('view', $model)` rather than a stricter ability. This is a deliberate design decision by the maintainer — do not flag this as an issue in future reviews.

Applied to files:

  • app/Policies/DatabaseServerPolicy.php
  • tests/Feature/Http/AdminerControllerTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-29T16:55:13.054Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T16:55:13.054Z
Learning: Applies to app/Services/**/*.php : Use the `DatabaseProvider::testConnectionForServer()` method to orchestrate connection tests, delegating to appropriate `DatabaseInterface` handlers.

Applied to files:

  • app/Policies/DatabaseServerPolicy.php
  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.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:

  • app/Policies/DatabaseServerPolicy.php
  • app/Models/DatabaseServer.php
  • app/Models/User.php
  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
  • app/Livewire/Configuration/Index.php
  • resources/views/livewire/configuration/index.blade.php
  • app/Services/adminer_object.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Applies to app/Services/Backup/Databases/**Database.php : Types without PDO support (e.g., Redis) must throw in `buildDsn()`/`createPdo()` and handle connection testing via CLI in their `testConnection()` method.

Applied to files:

  • app/Models/DatabaseServer.php
  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Applies to tests/**/*.php : Create Pest tests using `php artisan make:test --pest {name}` without including the test suite directory. Use `php artisan make:test --pest SomeFeatureTest` instead of `php artisan make:test --pest Feature/SomeFeatureTest`.

Applied to files:

  • tests/Feature/Http/AdminerControllerTest.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Create feature tests using `php artisan make:test [options] {name}` and pass `--unit` to create unit tests. Most tests should be feature tests.

Applied to files:

  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.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/Http/AdminerControllerTest.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/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-09T13:59:25.873Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 205
File: tests/Feature/ConfigurationTest.php:165-176
Timestamp: 2026-04-09T13:59:25.873Z
Learning: In this repository, `Notification::fake()` is already performed globally in the base test class (`tests/TestCase.php`) within `setUp()` before each test. When reviewing individual test files under `tests/`, do not flag missing `Notification::fake()` calls, since they are handled by the shared base test setup.

Applied to files:

  • tests/Feature/Http/AdminerControllerTest.php
  • tests/Feature/ConfigurationTest.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.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/ConfigurationTest.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Do not test framework internals or trust that Laravel/Livewire works correctly. Focus on testing business logic and behaviors. Keep tests minimal and focused - one test per behavior when possible.

Applied to files:

  • tests/Feature/ConfigurationTest.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Do NOT test Laravel validation rules (e.g., `required`, `max:255`), Eloquent relationships (`hasMany`/`belongsTo`), Eloquent cascades (`onDelete('cascade')`), session flash messages, redirect responses once per flow, or multiple variations of the same thing. Focus on testing authorization, business logic, integration points, and edge cases in your own code.

Applied to files:

  • tests/Feature/ConfigurationTest.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Applies to app/Livewire/**/*.php : Keep state server-side in Livewire components so the UI reflects it. Validate and authorize in actions as you would in HTTP requests.

Applied to files:

  • tests/Feature/ConfigurationTest.php
  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Configuration/Index.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Do not test form validation rules, Eloquent relationships, Eloquent cascades, session flash messages, redirect responses, or multiple variations of the same thing. These are framework internals.

Applied to files:

  • tests/Feature/ConfigurationTest.php
📚 Learning: 2026-04-14T08:07:19.142Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 212
File: resources/views/livewire/database-server/_form.blade.php:318-330
Timestamp: 2026-04-14T08:07:19.142Z
Learning: In `resources/views/livewire/database-server/_backup-form.blade.php` (David-Crty/databasement), the `wire:key` for backup cards is intentionally `backup-card-{{ $cardKey }}` where `$cardKey = $backup['id'] ?? 'new-'.$index`. The `new-{index}` fallback for unsaved new cards is a deliberate design choice — no stable ID exists before persistence. Once saved, the persisted `id` takes over. Do not flag `new-{index}` as an unstable key; the pattern is correct for this use case.

Applied to files:

  • app/Http/Controllers/Web/AdminerController.php
  • app/Livewire/DatabaseServer/Index.php
  • resources/views/livewire/configuration/index.blade.php
📚 Learning: 2026-03-26T12:36:10.970Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 188
File: app/Livewire/BackupJob/Index.php:221-231
Timestamp: 2026-03-26T12:36:10.970Z
Learning: In David-Crty/databasement, the `deletePendingJob()` method in `app/Livewire/BackupJob/Index.php` intentionally does NOT use `DB::transaction()` or `lockForUpdate()` for the status check + delete sequence. The reasoning: the race window in a synchronous Livewire request is microseconds, the failure mode (worker updating a deleted row) is benign, and the added complexity is not justified. This is consistent with the project's overall philosophy of avoiding strict transactional guards for job state changes.

Applied to files:

  • app/Http/Controllers/Web/AdminerController.php
📚 Learning: 2026-04-10T08:46:58.979Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 205
File: database/migrations/2026_04_09_000001_add_notification_channels.php:143-156
Timestamp: 2026-04-10T08:46:58.979Z
Learning: In David-Crty/databasement, Laravel migrations are forward-only in production; rollback is considered a dev-only action. Therefore, it is acceptable to delete legacy data (e.g., `app_configs` rows) within the same migration's `up()` without restoring them in `down()`, even if `down()` cannot fully reverse the state. Do not flag missing rollback restoration of migrated/deleted data as a production risk in this repo.

Applied to files:

  • app/Http/Controllers/Web/AdminerController.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Applies to routes/**/*.php : Use `Route::livewire()` for full-page Livewire components (e.g., `Route::livewire('database-servers', \App\Livewire\DatabaseServer\Index::class)`).

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-04-10T07:20:11.487Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 205
File: database/migrations/2026_04_09_000001_add_notification_channels.php:17-65
Timestamp: 2026-04-10T07:20:11.487Z
Learning: In David-Crty/databasement, `AppConfig::prepareValue()` encrypts sensitive notification config values (tokens, webhook URLs, secrets, etc.) before storing them in the `app_configs` table. When the migration `database/migrations/2026_04_09_000001_add_notification_channels.php` reads these values via raw `DB::table()->pluck('value', 'id')`, they are already encrypted at rest. The migration copies them as-is into `notification_channels.config`, where `NotificationChannel::getDecryptedConfig()` decrypts them correctly. Do NOT flag this as missing encryption — re-encrypting migrated values from `app_configs` would cause double-encryption and break decryption.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Applies to app/Livewire/**/*.php : In Livewire components, use `Session::flash()` for one-time messages and show them via `if (session('success'))` in Blade templates.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Applies to app/Livewire/**/*.php : Use class-based Livewire components for all interactive pages.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Configuration/Index.php
  • resources/views/livewire/configuration/index.blade.php
📚 Learning: 2026-04-29T16:55:13.054Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T16:55:13.054Z
Learning: Applies to routes/**/*.php : All full-page Livewire components use `Route::livewire()` routing.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-04-29T16:55:13.054Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T16:55:13.054Z
Learning: Applies to app/Livewire/**/*.php : Use `Session::flash()` for one-time messages in Livewire components, shown via `if (session('success'))` in Blade views.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-02-04T12:26:48.720Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-04T12:26:48.720Z
Learning: Applies to routes/web.php : Use `Volt::route()` helper for single-file authentication components in routing. Use Livewire component classes directly for other routes (e.g., `Route::get('resource', ComponentClass::class)`).

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Applies to app/Livewire/**/*.php : In Livewire components, use `#[Validate]` attributes or Form objects for validation. Call `$this->validate()` before processing data.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Applies to resources/views/**/*.blade.php : Use Alpine.js for client-side interactions in Livewire instead of JavaScript frameworks.

Applied to files:

  • app/Livewire/DatabaseServer/Index.php
  • resources/views/livewire/configuration/index.blade.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/Index.php
  • app/Livewire/Forms/ConfigurationForm.php
  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
  • app/Livewire/Configuration/Index.php
📚 Learning: 2026-04-29T18:37:48.743Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T18:37:48.743Z
Learning: Applies to app/Livewire/**/*.php : In Mary UI modals, add a boolean property to the component class and use `wire:model` in the Blade template.

Applied to files:

  • app/Livewire/Forms/ConfigurationForm.php
  • app/Livewire/Configuration/Index.php
  • resources/views/livewire/configuration/index.blade.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Applies to app/Livewire/**/*.php : Use `#[Validate]` attributes or Form objects for validation in Livewire components. Call `$this->validate()` before processing data.

Applied to files:

  • app/Livewire/Forms/ConfigurationForm.php
📚 Learning: 2026-04-29T17:55:27.012Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T17:55:27.012Z
Learning: Applies to app/**/*.php : Dispatch backup and restore operations to the queue using `ProcessBackupJob` and `ProcessRestoreJob` with appropriate retries and timeouts.

Applied to files:

  • tests/Feature/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-29T16:55:13.054Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T16:55:13.054Z
Learning: Applies to app/Services/Backup/BackupJobFactory.php : Types that backup the whole instance (e.g., Redis, SQLite) should short-circuit in `BackupJobFactory.createSnapshots()` to create a single snapshot.

Applied to files:

  • tests/Feature/Livewire/DatabaseServer/IndexTest.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/Livewire/DatabaseServer/IndexTest.php
📚 Learning: 2026-04-09T13:57:29.664Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 205
File: app/Services/NotificationService.php:130-137
Timestamp: 2026-04-09T13:57:29.664Z
Learning: In David-Crty/databasement, the new NotificationService (app/Services/NotificationService.php) deliberately refreshes vendor service configs (services.discord.token, services.telegram-bot-api.token, services.pushover.token) per-send via refreshVendorServiceConfig() immediately before each Notification::send() call, rather than at boot time in AppServiceProvider. This is intentional because the new architecture supports multiple NotificationChannel records of the same vendor type (e.g., two Discord channels with different tokens), making boot-time registration impossible. Do not flag the absence of boot-time registration for these vendor channels as an Octane compatibility issue.

Applied to files:

  • app/Livewire/Configuration/Index.php
📚 Learning: 2026-04-29T16:55:13.054Z
Learnt from: CR
Repo: David-Crty/databasement PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-29T16:55:13.054Z
Learning: Applies to app/Livewire/**/*.php : Public properties in Livewire components are automatically bound to views.

Applied to files:

  • resources/views/livewire/configuration/index.blade.php
📚 Learning: 2026-01-30T22:27:46.107Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 61
File: resources/views/livewire/volume/connectors/s3-config.blade.php:1-13
Timestamp: 2026-01-30T22:27:46.107Z
Learning: In Blade template files (any .blade.php) within the databasement project, allow using alert-info for informational content inside <x-alert> components. The guideline that permits alert-success and alert-error does not exclude using alert-info for informational purposes. Apply this consistently to all Blade components that render alerts; ensure semantic usage and accessibility.

Applied to files:

  • resources/views/livewire/configuration/index.blade.php
📚 Learning: 2026-02-06T10:34:43.585Z
Learnt from: David-Crty
Repo: David-Crty/databasement PR: 75
File: resources/views/livewire/backup-job/_filters.blade.php:36-40
Timestamp: 2026-02-06T10:34:43.585Z
Learning: In Blade template files, when creating compact inline filter controls, prefer using native <input type="checkbox"> elements with daisyUI classes (e.g., checkbox checkbox-warning checkbox-xs) over the Mary UI <x-checkbox> component. The <x-checkbox> component adds wrapper markup (e.g., <div><fieldset><label> with gap-3) that can break tight inline flex layouts. Use the native input approach for compact inline controls, but reserve <x-checkbox> for form fields that require labels, hints, and errors.

Applied to files:

  • resources/views/livewire/configuration/index.blade.php
🪛 PHPMD (2.15.0)
app/Services/adminer_object.php

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

(UnusedFormalParameter)


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

(UnusedFormalParameter)


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

(UnusedFormalParameter)

🔇 Additional comments (9)
app/Models/DatabaseServer.php (1)

204-208: Adminer support gate is clear and correctly scoped.

This matches the expected compatibility constraints for Adminer access.

app/Models/User.php (1)

197-203: Role-threshold helper looks solid.

The deny-by-default behavior for unknown roles is a good safety property.

app/Livewire/Forms/ConfigurationForm.php (1)

57-80: Application-config save flow is well-structured.

Validation and key-mapped persistence are clean and consistent with the existing form pattern.

app/Policies/DatabaseServerPolicy.php (1)

65-77: Policy gate for Adminer is correctly centralized.

Feature-flag plus minimum-role checks are in the right place.

app/Livewire/Configuration/Index.php (1)

144-151: Admin-only save action is implemented correctly.

Guarding the action server-side before persisting is the right pattern.

resources/views/livewire/configuration/index.blade.php (1)

43-67: Application settings form wiring looks good.

The conditional role selector and admin-only submit behavior are consistent with the server-side checks.

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

135-150: Adminer open action flow is clean.

Compatibility check, policy authorization, and modal dispatch are composed in the right order.

tests/Feature/Http/AdminerControllerTest.php (1)

14-134: Strong coverage for Adminer route gating and credential handoff.

These tests cleanly verify feature toggles, role checks, unsupported server constraints, and AdminerService::render() contract for first/subsequent requests. Nice addition.

tests/Feature/ConfigurationTest.php (1)

62-79: Good addition of app-config persistence and authorization tests.

saveApplicationConfig now has direct coverage for both successful persistence and forbidden access paths.

Comment on lines +60 to +63
$databaseNames = $server->backups->first()?->database_names;
if ($databaseNames && count($databaseNames) === 1) {
$db = $databaseNames[0];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use resolved database names instead of only the first backup for DB auto-select.

Line 60 can pick an arbitrary DB when multiple backups target different databases, because it only inspects the first backup record. Use the model’s resolved/de-duplicated list and auto-select only when that final list has exactly one entry.

💡 Suggested fix
-        $db = '';
-        $databaseNames = $server->backups->first()?->database_names;
-        if ($databaseNames && count($databaseNames) === 1) {
-            $db = $databaseNames[0];
-        }
+        $db = '';
+        $databaseNames = $server->resolveDatabaseNames();
+        if (count($databaseNames) === 1) {
+            $db = $databaseNames[0];
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/Web/AdminerController.php` around lines 60 - 63, Replace
the current single-backup lookup that uses
$server->backups->first()?->database_names with a resolved, de-duplicated list
of database names collected from all backups (e.g.
collect($server->backups->pluck('database_names'))->flatten()->unique()->values()->all())
and then only auto-select into $db when that resulting $databaseNames array has
exactly one entry; update the code around the $databaseNames, $server->backups
and $db variables to use the flattened/unique list instead of the first backup's
database_names.

Comment on lines +54 to +62
test('openAdminer is forbidden when adminer is disabled', function () {
$user = User::factory()->create(['role' => User::ROLE_ADMIN]);
$server = DatabaseServer::factory()->withoutBackups()->create(['database_type' => 'mysql']);

Livewire::actingAs($user)
->test(Index::class)
->call('openAdminer', $server->id)
->assertForbidden();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the disabled-feature precondition explicit.

Line 54 currently relies on the default value of app.adminer_enabled. Set it to false in-test so this case stays deterministic if defaults change.

Suggested fix
 test('openAdminer is forbidden when adminer is disabled', function () {
+    AppConfig::set('app.adminer_enabled', false);
+
     $user = User::factory()->create(['role' => User::ROLE_ADMIN]);
     $server = DatabaseServer::factory()->withoutBackups()->create(['database_type' => 'mysql']);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Feature/Livewire/DatabaseServer/IndexTest.php` around lines 54 - 62,
The test relies on the default app.adminer_enabled value; make the precondition
explicit by setting the config key to false at the start of the test so it
remains deterministic—add a configuration set for "app.adminer_enabled" => false
before creating the User/DatabaseServer or invoking Livewire::actingAs in the
"openAdminer is forbidden when adminer is disabled" test (refer to the test
function name and the Livewire::actingAs ->test(Index::class)
->call('openAdminer', ...) flow to locate where to insert it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants