Skip to content

feat: add org-scoped saved vault searches with alert subscriptions (#…#910

Open
OpadijoIdris wants to merge 1 commit into
Disciplr-Org:mainfrom
OpadijoIdris:feature/org-saved-searches
Open

feat: add org-scoped saved vault searches with alert subscriptions (#…#910
OpadijoIdris wants to merge 1 commit into
Disciplr-Org:mainfrom
OpadijoIdris:feature/org-saved-searches

Conversation

@OpadijoIdris

Copy link
Copy Markdown
Contributor

Closes #848

What

Adds org-scoped saved vault searches with optional change-alert subscriptions to src/routes/orgVaults.ts.

Why

Operators need a way to persist named queries over their org's vault set and be notified when the result set changes — without re-running the same search manually.

Changes

  • src/routes/orgVaults.tsPOST/GET/DELETE /api/orgs/:orgId/vault-searches CRUD endpoints; validateAndSanitizeQueryDefinition helper (injection-safe field whitelist); runSavedSearch + hashResultSet evaluation helpers
  • src/jobs/types.tssaved-search.evaluate job type and SavedSearchEvaluateJobPayload interface
  • src/jobs/handlers.ts — evaluation handler: queries due alert searches, diffs SHA-256 result hash, dispatches notification only on change
  • src/jobs/system.ts — registers handler and schedules recurring evaluation (default 15 min via SAVED_SEARCH_EVAL_INTERVAL_MS)
  • db/migrations/20260628100000_create_org_vault_searches.cjsorg_vault_searches table with org-scoped and alert-evaluation indexes
  • src/tests/orgVaults.savedSearches.test.ts — 30+ tests covering CRUD, validation, cross-org isolation, per-org cap, injection prevention
  • docs/vaults-api.md — endpoint reference, query_definition field table, alert subscription behaviour, env vars

Behaviour

  • Per-org cap: max 20 saved searches per org; overflow → HTTP 422
  • Alert frequency floor: alert_frequency_ms ≥ 3 600 000 ms (1 hour); alert_recipient required when alerts_enabled: true
  • Change detection: job hashes result IDs with SHA-256; notification fires only when hash differs from last_result_hash
  • Query safety: sort_by validated against a whitelist; q has injection chars stripped before storage and re-use

Test plan

  • Valid + invalid query_definition (status, amounts, dates, sort fields, q injection, multi-error)
  • hashResultSet — deterministic, order-sensitive, 64-char hex
  • POST 201 happy path; POST 201 with alert subscription
  • POST 400 — missing name, bad status, no recipient, frequency below floor
  • POST 403 for member role; 401 for unauthenticated
  • GET list — all org searches returned; empty array when none exist
  • GET single — 200 with body; 404 for unknown id
  • DELETE 204 on success; 404 unknown; 403 for member role
  • Cross-org isolation — org B cannot read, create, or delete org A searches
  • Per-org cap — 21st search → 422; cap is per-org so other orgs are unaffected
  • Injection prevention — SQL chars stripped from q; non-whitelisted sort_by → 400

@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@OpadijoIdris Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…isciplr-Org#848)

- POST/GET/DELETE /api/orgs/:orgId/vault-searches CRUD endpoints
- Query definition validated & sanitized (injection-safe whitelist)
- Per-org cap of 20 searches enforced (HTTP 422 on overflow)
- Alert subscriptions: SHA-256 hash comparison detects result-set changes
- Alert frequency floor of 1 hour; alert_recipient required when enabled
- saved-search.evaluate job type with periodic scheduler (default 15 min)
- Knex migration: org_vault_searches table with GIN-friendly indexes
- Comprehensive test suite: CRUD, cross-org isolation, cap, injection prevention
- Docs appended to docs/vaults-api.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@OpadijoIdris OpadijoIdris force-pushed the feature/org-saved-searches branch from 32c62ee to 9a09b4c Compare June 28, 2026 03:58
Comment thread src/routes/orgVaults.ts

orgVaultsRouter.post(
'/:orgId/vault-searches',
authenticate,
Comment thread src/routes/orgVaults.ts

orgVaultsRouter.get(
'/:orgId/vault-searches',
authenticate,
Comment thread src/routes/orgVaults.ts

orgVaultsRouter.get(
'/:orgId/vault-searches/:searchId',
authenticate,
Comment thread src/routes/orgVaults.ts

orgVaultsRouter.delete(
'/:orgId/vault-searches/:searchId',
authenticate,
// POST /:orgId/vault-searches
app.post(
'/api/orgs/:orgId/vault-searches',
mockAuthenticate,
// GET /:orgId/vault-searches
app.get(
'/api/orgs/:orgId/vault-searches',
mockAuthenticate,
// GET /:orgId/vault-searches/:searchId
app.get(
'/api/orgs/:orgId/vault-searches/:searchId',
mockAuthenticate,
// DELETE /:orgId/vault-searches/:searchId
app.delete(
'/api/orgs/:orgId/vault-searches/:searchId',
mockAuthenticate,
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.

Add an org-scoped saved-search and alert-subscription endpoint over vaults in src/routes/orgVaults.ts

2 participants