Skip to content

feat: add drizzle studio to admin area#62

Merged
toddeTV merged 12 commits into
mainfrom
feat/admin-drizzle-studio
May 3, 2026
Merged

feat: add drizzle studio to admin area#62
toddeTV merged 12 commits into
mainfrom
feat/admin-drizzle-studio

Conversation

@toddeTV
Copy link
Copy Markdown
Owner

@toddeTV toddeTV commented May 3, 2026

Summary by CodeRabbit

  • New Features

    • Added a Database Admin page at /admin/database with embedded DB viewer and “open in new tab” link; menu entry added.
  • Documentation

    • Added Database Admin docs and updated deployment, quick-start, setup, storage, and API docs to cover access, behavior, and configuration.
  • Chores

    • Server now applies pending DB migrations on startup; updated example environment file with populated defaults (including internal studio port).

@toddeTV toddeTV self-assigned this May 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: aaf5473a-33b3-4724-8498-06ddb62e7821

📥 Commits

Reviewing files that changed from the base of the PR and between 442afed and e1dcfb0.

📒 Files selected for processing (1)
  • server/api/admin/drizzle-studio/app/[...asset].get.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/api/admin/drizzle-studio/app/[...asset].get.ts

📝 Walkthrough

Walkthrough

Adds an embedded Drizzle Studio database admin UI at /admin/database, protected server-side proxy and RPC endpoints, a Nitro startup plugin that applies local SQLite migrations, utilities to start/poll an embedded Studio process, a protected frontend page with i18n, a runtime config option for the Studio port, updates to storage init behavior, and corresponding docs and .env example changes.

Changes

Drizzle Studio integration

Layer / File(s) Summary
Runtime config
nuxt.config.ts
Adds runtimeConfig.private.drizzleStudioInternalPort.
Startup migrations plugin
server/plugins/migrations.ts
New Nitro plugin applies local SQLite migrations on server startup; logs and rethrows on error.
Studio server utilities
server/utils/drizzle-studio.ts
New module exports ensureDrizzleStudioServer and getDrizzleStudioInternalRpcUrl; validates internal port, builds internal URL, starts embedded Studio once per process, and polls readiness with timeout and error reporting.
Storage init change
server/utils/storage.ts
initStorage no longer applies migrations; it only initializes the shared SQLite client and delegates migrations to the startup plugin.
Admin API: shell & assets
server/api/admin/drizzle-studio/app/index.get.ts, server/api/admin/drizzle-studio/app/[...asset].get.ts
Admin-protected endpoints that fetch/sanitize Drizzle Studio HTML shell and proxy static assets; implement timeouts, error mapping (504/502), path validation, and forward a header allowlist.
RPC proxy & init route
server/routes/index.post.ts, server/routes/init.ts
POST route proxies embedded Studio RPC requests to internal RPC URL with timeouts and header forwarding; /init route verifies admin and returns 404.
Frontend admin UI
app/pages/admin/database.vue, app/pages/admin/index.vue
New protected /admin/database page embedding the Studio in an iframe and adding localized entry in admin index (EN/DE/JA).
Docs & README
README.md, docs/api.md, docs/quick-start.md, docs/deployment-docker.md, docs/setup-production.md, docs/storage.md
Documentation updated to document /admin/database, new API endpoints, optional NUXT_DRIZZLE_STUDIO_INTERNAL_PORT, lazy worker startup, and revised storage/migration behavior.
Env examples
.env.example
Expanded and populated example server/public env vars including NUXT_DRIZZLE_STUDIO_INTERNAL_PORT, admin credentials, and JWT secret.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Server as Nuxt/Nitro Server
    participant Studio as Embedded Drizzle Studio
    participant DB as SQLite Database

    Note over Server: Server startup
    Server->>Server: server/plugins/migrations.ts\napplyLocalMigrations()
    Server->>DB: Run migrations

    Browser->>Server: GET /admin/database -> requests /api/admin/drizzle-studio/app
    Server->>Server: verifyAdmin()
    Server->>Server: ensureDrizzleStudioServer()
    activate Server
    Server->>Studio: Poll POST / with {type: 'init'}
    Studio->>DB: Open DB, report dialect
    Studio-->>Server: { dialect: 'sqlite' }
    deactivate Server

    Server->>Studio: Fetch https://local.drizzle.studio/
    Studio-->>Server: App HTML
    Server->>Server: sanitizeStudioHtml()
    Server-->>Browser: Sanitized HTML (served)

    Browser->>Server: GET /api/admin/drizzle-studio/app/<asset>
    Server->>Server: verifyAdmin()
    Server->>Studio: Fetch asset
    Studio-->>Server: Asset bytes
    Server-->>Browser: Asset

    Browser->>Server: POST / (RPC)
    Server->>Server: verifyAdmin()
    Server->>Server: ensureDrizzleStudioServer()
    Server->>Studio: Forward RPC POST to internal RPC URL
    Studio->>DB: Query
    DB-->>Studio: Result
    Studio-->>Server: RPC response
    Server-->>Browser: RPC response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Drizzle Studio integration to the admin section, which is reflected across all modified files including new admin pages, routes, utilities, and documentation.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
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.


Review rate limit: 4/5 reviews remaining, refill in 12 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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/api/admin/drizzle-studio/app/`[...asset].get.ts:
- Around line 25-32: The upstream fetch for upstreamUrl is not protected by a
timeout and network-level failures (e.g., DNS, connection reset, or AbortError)
bypass the existing response.ok mapping; update the fetch call to use an
AbortController with a short timeout (setTimeout -> controller.abort) and pass
controller.signal to fetch, clear the timeout on success/response, and catch
errors from fetch: if error.name === 'AbortError' or timeout triggered throw
createError with statusCode 504 and a message including assetPath, otherwise
throw createError with statusCode 502 for other network errors including the
original error message; keep the existing response.ok branch intact to handle
non-2xx upstream responses.

In `@server/api/admin/drizzle-studio/app/index.get.ts`:
- Around line 7-13: The current analyticsScript RegExp in index.get.ts is too
strict and only matches one exact attribute order/format so small upstream
changes bypass it; update the removal to robustly target the third-party script
by either parsing the HTML and removing any <script> elements whose src contains
"assets.onedollarstats.com/stonks.js" and/or have data-site-id
"local.drizzle.studio", or replace analyticsScript with a more permissive,
case-insensitive/global RegExp that matches a <script> tag with those attributes
in any order (allowing optional whitespace, single/double quotes, and optional
defer) and use html.replace(...) to remove all matches.
- Around line 18-25: Wrap the unprotected fetch call that assigns response in a
try-catch and handle network errors/timeouts by throwing the existing
createError with statusCode 502 and a descriptive statusMessage including the
caught error message; specifically, in the code that calls
fetch(`${DRIZZLE_STUDIO_APP_ORIGIN}/`) (and likewise the fetch in
app/[...asset].get.ts), use an AbortController to enforce a timeout, await fetch
inside try, and in catch detect AbortError/timeouts and other errors then call
throw createError({ statusCode: 502, statusMessage: `Failed to load Drizzle
Studio: ${err.message}` }) so failures produce the intended proxy 502 instead of
an unhandled 500.

In `@server/routes/index.post.ts`:
- Around line 26-33: The fetch to the internal RPC URL (inside the call to
fetch(getDrizzleStudioInternalRpcUrl(event), {...}) that sends requestBody)
needs an AbortSignal timeout so the endpoint cannot hang indefinitely; update
the fetch options to include a signal:
AbortSignal.timeout(<reasonable-ms-or-configured-constant>) and ensure you clear
or handle the abort appropriately (e.g., catch the AbortError) so the call using
getDrizzleStudioInternalRpcUrl, requestBody and the surrounding logic in
index.post.ts fails fast on timeout; make the timeout value configurable (env or
constant) rather than hard-coding.

In `@server/utils/drizzle-studio.ts`:
- Around line 43-50: The fetch in waitForDrizzleStudio lacks a timeout and can
hang; wrap the POST fetch to url with an AbortController, pass controller.signal
into fetch, and start a setTimeout that calls controller.abort() after a short
timeout (e.g., a few seconds or the remaining time before the 15s deadline),
then clear that timeout after fetch completes; ensure you catch AbortError
specifically and treat it like a failed attempt so the loop continues and
resources are cleaned up (clearTimeout and not leaking controllers).
🪄 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 0c13b5ad-8092-484d-8bcf-2cea27a82508

📥 Commits

Reviewing files that changed from the base of the PR and between b925b44 and bf89023.

📒 Files selected for processing (16)
  • README.md
  • app/pages/admin/database.vue
  • app/pages/admin/index.vue
  • docs/api.md
  • docs/deployment-docker.md
  • docs/quick-start.md
  • docs/setup-production.md
  • docs/storage.md
  • nuxt.config.ts
  • server/api/admin/drizzle-studio/app/[...asset].get.ts
  • server/api/admin/drizzle-studio/app/index.get.ts
  • server/plugins/migrations.ts
  • server/routes/index.post.ts
  • server/routes/init.ts
  • server/utils/drizzle-studio.ts
  • server/utils/storage.ts

Comment thread server/api/admin/drizzle-studio/app/[...asset].get.ts Outdated
Comment thread server/api/admin/drizzle-studio/app/index.get.ts Outdated
Comment thread server/api/admin/drizzle-studio/app/index.get.ts Outdated
Comment thread server/routes/index.post.ts
Comment thread server/utils/drizzle-studio.ts
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Around line 15-17: Remove the hard-coded NUXT_JWT_SECRET value in .env.example
and replace it with a placeholder and a generation comment: delete the concrete
secret line containing NUXT_JWT_SECRET=tryUJ0z... and instead provide a
blank/placeholder like NUXT_JWT_SECRET=your_jwt_secret_here plus a one-line
instruction to generate a strong secret (for example using openssl rand -base64
48) so examples never ship reusable secrets.

In `@server/api/admin/drizzle-studio/app/`[...asset].get.ts:
- Around line 15-25: Validate the catch-all assetPath before constructing
upstreamUrl: split assetPath by '/' (or path.sep), reject any segment equal to
"." or ".." (throw createError with 400/Bad Request or 404/Drizzle Studio asset
not found to match existing handling), and only proceed to call
getRequestURL(event) and build upstreamUrl when all segments are safe; update
the code around assetPath, getRequestURL, and upstreamUrl to perform this check
(use the same createError helper and verifyAdmin flow already present).
🪄 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: dee083e2-f8e3-45fa-b135-00893209db6f

📥 Commits

Reviewing files that changed from the base of the PR and between bf89023 and 442afed.

📒 Files selected for processing (5)
  • .env.example
  • server/api/admin/drizzle-studio/app/[...asset].get.ts
  • server/api/admin/drizzle-studio/app/index.get.ts
  • server/routes/index.post.ts
  • server/utils/drizzle-studio.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/api/admin/drizzle-studio/app/index.get.ts
  • server/utils/drizzle-studio.ts

Comment thread .env.example
Comment thread server/api/admin/drizzle-studio/app/[...asset].get.ts
@toddeTV toddeTV merged commit 5fb2fd1 into main May 3, 2026
5 checks passed
@toddeTV toddeTV deleted the feat/admin-drizzle-studio branch May 3, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant