Skip to content

feat(db): Support custom PostgreSQL schema via DATABASE_URL (Vibe Kanban)#3

Open
jtomaszewski wants to merge 1 commit intodevelopfrom
vk/f68f-support-custom-s
Open

feat(db): Support custom PostgreSQL schema via DATABASE_URL (Vibe Kanban)#3
jtomaszewski wants to merge 1 commit intodevelopfrom
vk/f68f-support-custom-s

Conversation

@jtomaszewski
Copy link
Copy Markdown

@jtomaszewski jtomaszewski commented Feb 25, 2026

Summary

This PR adds support for custom PostgreSQL schemas specified via the DATABASE_URL query parameter. Users can now configure a custom schema using the standard PostgreSQL format:

postgresql://user:password@localhost:5432/dbname?options=-c%20search_path=custom_schema

Changes Made

Problem

The codebase had 8 hardcoded references to the 'public' schema across 5 files, which prevented users from deploying to databases with custom schemas.

Solution

Use PostgreSQL's built-in current_schema() function everywhere. The database connection's search_path (set via DATABASE_URL) becomes the single source of truth.

Files Modified

File Change
packages/cli/src/lib/db/commands.ts Use current_schema() in raw SQL query
packages/cli/src/mercato.ts Use current_schema() for table listing and to_regclass()
packages/core/src/modules/catalog/migrations/Migration20251116191744.ts Replace 4x table_schema = 'public' with current_schema()
packages/core/src/modules/customers/cli.ts Use .whereRaw('table_schema = current_schema()')
packages/core/src/modules/query_index/lib/coverage.ts Use .whereRaw('table_schema = current_schema()')

Why This Approach

  1. Single source of truth: The database connection's search_path determines the schema
  2. No new dependencies: Uses PostgreSQL's built-in function (available since v7.3)
  3. No extra env vars: Leverages existing DATABASE_URL format
  4. Cleaner code: Eliminated the need for a custom getDbSchema() utility

Related Discussion

See PR #685 discussion for context on why this change was needed.


This PR was written using Vibe Kanban

@jtomaszewski jtomaszewski changed the title Support Custom Schema via DATABASE_URL (vibe-kanban) feat(db): Support custom PostgreSQL schema via DATABASE_URL (Vibe Kanban) Feb 25, 2026
Use PostgreSQL's built-in current_schema() function everywhere instead
of a custom getDbSchema() utility. The database connection's search_path
(set via DATABASE_URL?options=-c%20search_path=custom_schema) becomes
the single source of truth.

Changes:
- Replace hardcoded 'public' schema with current_schema() in all SQL
- Use whereRaw('table_schema = current_schema()') for Knex queries
- Migration already uses current_schema() for automatic resolution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jtomaszewski jtomaszewski force-pushed the vk/f68f-support-custom-s branch from 22fd8e1 to dee2817 Compare February 26, 2026 15:55
@jtomaszewski jtomaszewski changed the base branch from develop to main February 26, 2026 15:59
@jtomaszewski jtomaszewski changed the base branch from main to develop February 26, 2026 15:59
jtomaszewski pushed a commit that referenced this pull request Apr 11, 2026
…cato#988) (open-mercato#1051)

* fix(customer_accounts): address customer portal UX feedback (open-mercato#988)

- Account settings section now uses 2-column layout to save space
- Added portal access info banners with URL and demo credentials on list and detail pages
- CRM links (person/company) are now editable with async search and unlink buttons
- Added "Send Reset Link" button and API endpoint to generate shareable password reset links
- Improved role permissions UI with feature descriptions, card-based groups in 2-column grid
- Fixed bug where role permission changes were silently dropped (now saves via ACL endpoint)
- Portal title no longer falls back to raw org slug — always uses DB-resolved name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(customer_accounts): implement remaining portal UX changes (open-mercato#988)

- Enhanced account-status widget: "Invite to Portal" button on CRM person profile
  when no portal account exists, with inline form for email, name, and role selection
- Created portal settings page at /backend/customer_accounts/settings with portal
  URL info, demo credentials, org slug guidance, and quick links
- Fixed portal logo colors: removed dark:invert filter that distorted the gradient
- Converted role create page to CrudForm for UMES extensibility
- Converted role detail/edit page to CrudForm with custom permissions editor component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(customer_accounts): address security and code quality findings

Critical:
- C1: Remove raw auth tokens from event bus payloads — tokens must never
  travel through events where any subscriber can intercept them
- C2: Magic link request now emits `magic_link.requested` instead of
  incorrectly emitting `login.success` before authentication
- C3: Email verification nativeUpdate now scopes by tenantId as
  defense-in-depth; event payload includes tenantId

Medium:
- M1: Replace raw SQL (em.getConnection().execute) with ORM queries
  in autoLinkCrm and autoLinkCrmReverse subscribers
- M5: Deduplicate readCookieFromHeader — 3 local copies replaced with
  import from customerAuth.ts

Low:
- L3: Remove unused batchSize parameter from cleanup workers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(customer_accounts): remove static cross-module entity import (H1)

Replace static import of Organization entity from directory module with a
raw SQL existence check. This eliminates compile-time coupling between
customer_accounts and directory modules.

The CRM link subscribers (autoLinkCrm, autoLinkCrmReverse) already use
dynamic imports (await import(...)) which is the accepted pattern for
cross-module access in event subscribers — no compile-time coupling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve ISSUE-001, ISSUE-002, ISSUE-004, ISSUE-005 from ISSUE_LOG

- ISSUE-001: Auto-resolve orgSlug in portal injected menu item hrefs
  so widgets can use simple `/portal/...` paths without manual slug resolution
- ISSUE-002: Clear MikroORM MetadataStorage between module iterations in
  dbGenerate to prevent polluted migrations for new modules
- ISSUE-004: Add secondary table name lookup in resolveEntityTableName
  (scan ORM metadata by candidate table names) + warning log on fallback
- ISSUE-005: Only reject system role name changes when the value actually
  differs from the current name, not when it's merely present in the payload
- Update standalone app template to use `yarn mercato db generate/migrate`

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(create-app): improve standalone AI guides for icons, pagination, and context loading

- Add CRITICAL rule #5: mandatory guide loading before writing code
- Strengthen Task → Context Map intro with STOP-and-Read gate
- Add sidebar icon guidance (lucide-react, not React.createElement) to
  module-scaffold skill and troubleshooter
- Add DataTable pagination props guidance to backend-ui-design skill
- Add icon and pagination error patterns to troubleshooter diagnostics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve framework issues #3, #4, #5, open-mercato#8 from hackathon report

- #5 (HIGH): Include QueryProvider in AppShell so backend pages can use
  React Query hooks without crashing
- #4 (HIGH): Improve command handler error message to list same-module
  registered commands and suggest side-effect import in index.ts
- #3 (HIGH): Fix old-style backend route path duplication — skip modId
  prefix when directory segments already start with it
- open-mercato#8 (MEDIUM): Add ID and href deduplication to mergeMenuItems to prevent
  duplicate portal sidebar entries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(enterprise): add missing @simplewebauthn/types dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ui): provide global fetch stub in AppShell test to prevent infinite recursion

jsdom lacks a native fetch API, so QueryProvider's ensureGlobalFetchInterception
saved undefined as __omOriginalFetch. When apiFetch fell back to globalThis.fetch
(now the patched wrapper), it recursed infinitely. Adding a fetch stub in beforeAll
gives the interception a real function to delegate to.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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