Production-readiness + feature advancements (analytics, notifications, email, semantic search) - #1
Merged
Merged
Conversation
added 7 commits
June 22, 2026 21:18
Vitest+Supertest setup, GitHub Actions CI (lint/typecheck/test/build), multi-stage Dockerfile with Chromium for PDF export, helmet+rate-limit middleware, and a NODE_ENV-gated logger. Replaces the broken eslint-config-next config with a proper Vite flat config.
Extract deterministic risk/markdown logic into src/api/lib/compliance.ts (unit-tested). Replace as-any JWT casts, fix latent bugs (intCompanyId undefined, change-password req.user.id, apiOrigin typo), align frontend types. Remove dead authMiddleware.
New /analytics page (recharts) backed by GET /api/analytics/summary (auth + company-scoped) with tested pure aggregation. Dashboard now shows real period-over-period deltas instead of hardcoded values.
Notification bell with unread badge + feed sourced from AuditLog via shared getScopedAuditLogs. Secures GET /api/audit (previously unauthenticated + unscoped) and adds GET /api/notifications.
Pluggable email layer with tested pure templates. Sends only when EMAIL_ENABLED=true and a key is set; never under tests.
Embedding helper, pgvector SQL migration + match_proposals RPC, backfill script, and Proposals UI that ranks by similarity with substring fallback. Inactive until the migration is run; see docs/SEMANTIC_SEARCH.md.
Mount analytics/notifications routers + production SPA serving in server.ts; add email-on-status-change and embed-on-create + /search to proposals.ts; add analytics/notifications/search API clients; add deps (helmet, rate-limit, vitest, supertest, resend) and email/embedding env vars.
There was a problem hiding this comment.
Pull request overview
This PR combines a production-readiness sweep (security hardening, CI/Docker, lint/typecheck cleanup, and a new test suite) with feature additions: an analytics dashboard, in-app notifications/activity feed, gated email notifications, and a semantic-search scaffold (pgvector + embeddings with graceful fallback).
Changes:
- Added Vitest/Supertest test harness and new unit tests for compliance, analytics, auth/RBAC, notifications, and email template generation.
- Implemented analytics summary API + new
/analyticsUI, plus notifications API + notification bell UI (sourced fromAuditLog). - Added semantic search scaffolding (embeddings generator, pgvector SQL migration + backfill script, frontend debounced search) and introduced baseline security middleware (Helmet + rate limiting) and deployment artifacts (Docker/CI/docs).
Reviewed changes
Copilot reviewed 47 out of 48 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.ts | Adds standalone Vitest config (Node env, aliasing, coverage). |
| tests/setup.ts | Global env setup for deterministic tests (JWT/Azure/Supabase dummy config). |
| tests/notifications.test.ts | Adds notifications endpoint tests with Supabase + scoped-audit mock. |
| tests/auth.test.ts | Adds auth middleware, RBAC helper, and login endpoint tests. |
| src/pages/Proposals.tsx | Adds debounced semantic search + similarity ranking fallback behavior. |
| src/pages/Integrations.tsx | Fixes integration PATCH URL to use computed API base. |
| src/pages/Home.tsx | Minor typing tweak to animation variants (as const). |
| src/pages/Dashboard.tsx | Fetches real analytics deltas and renders trend badges conditionally. |
| src/pages/Compliance.tsx | Switches Rule typing source and adjusts rule type handling. |
| src/pages/Audit.tsx | Aligns AuditLog typing import with API client types. |
| src/pages/Analytics.tsx | Adds new analytics dashboard page with recharts visualizations. |
| src/lib/api-client.ts | Adds analytics/notifications clients and semantic search API call. |
| src/components/NotificationBell.tsx | Adds polling notification bell + activity popover UI. |
| src/components/layout/ClientLayout.tsx | Adds Analytics nav item and renders notification bell when logged in. |
| src/App.tsx | Registers /analytics route behind ProtectedRoute. |
| src/api/proposals.ts | Adds embeddings storage, semantic search endpoint, AI rate limiting, and status-change email hook. |
| src/api/oauth.ts | Removes as any JWT cast in query auth and swaps console logs for logger. |
| src/api/notifications.ts | Adds notifications API backed by scoped audit querying. |
| src/api/middleware/rateLimit.ts | Introduces auth + AI rate limiters with relaxed non-prod limits. |
| src/api/lib/logger.ts | Adds minimal leveled logger for API layer. |
| src/api/lib/embeddings.ts | Adds Azure OpenAI embedding generation helper (best-effort). |
| src/api/lib/emailTemplates.ts | Adds pure email template builder for status-change notifications. |
| src/api/lib/emailTemplates.test.ts | Adds unit tests for email template correctness + HTML escaping. |
| src/api/lib/email.ts | Adds gated Resend email sender (disabled in tests/CI). |
| src/api/lib/compliance.ts | Extracts shared compliance/risk helpers + markdown renderer. |
| src/api/lib/compliance.test.ts | Adds unit tests for compliance helpers and markdown conversion. |
| src/api/lib/auditQuery.ts | Adds shared scoped audit querying used by audit + notifications routes. |
| src/api/lib/analytics.ts | Adds pure analytics aggregation functions + delta calculations. |
| src/api/lib/analytics.test.ts | Adds unit tests for analytics aggregation and delta logic. |
| src/api/integrations.ts | Replaces console logs with logger and fixes integration sync scoping variable. |
| src/api/auth.ts | Tightens JWT typing, fixes change-password user id lookup, removes legacy auth middleware. |
| src/api/audit.ts | Secures audit endpoint with requireAuth and scoped querying. |
| src/api/analyze.ts | Normalizes AI analysis output and centralizes auto-review threshold logic. |
| src/api/analytics.ts | Adds analytics summary API endpoint with company scoping. |
| server.ts | Adds Helmet, rate limiting on auth/AI endpoints, new routes, and production SPA serving. |
| scripts/backfill-embeddings.ts | Adds embeddings backfill script for existing proposals. |
| prisma/schema.prisma | Documents pgvector embedding column on Proposal. |
| prisma/manual/semantic_search.sql | Adds pgvector extension, embedding column/index, and match_proposals RPC. |
| package.json | Adds test/typecheck scripts and new deps (vitest/supertest/helmet/rate-limit/resend). |
| package-lock.json | Locks dependency additions/updates for new tooling and features. |
| eslint.config.mjs | Replaces broken Next.js eslint config with working Vite/TS flat config. |
| docs/SEMANTIC_SEARCH.md | Documents semantic search setup and fallback behavior. |
| docs/DEPLOYMENT.md | Adds deployment guide for Node/Docker, env config, and CI notes. |
| Dockerfile | Adds multi-stage build and runtime Chromium install for PDF export. |
| docker-compose.yml | Adds compose file for running the combined service on port 3001. |
| .github/workflows/ci.yml | Adds CI workflow: install → prisma generate → lint → typecheck → test → build. |
| .env.example | Documents embedding + email env vars and defaults. |
| .dockerignore | Adds Docker context ignores, keeps .env.example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+73
to
+82
| /** | ||
| * Convert a subset of markdown to HTML for rendering. Note: this does NOT | ||
| * escape HTML — callers that embed untrusted content elsewhere must escape | ||
| * separately. Preserved verbatim from the proposal PDF renderer. | ||
| */ | ||
| export function markdownToHtml(text: string): string { | ||
| if (!text) return ''; | ||
|
|
||
| let html = text; | ||
|
|
Comment on lines
+138
to
+145
| // Company scoping: admins search everything, others their company only. | ||
| const filterCompany = isAdmin(req) ? null : req.user?.companyId ?? null; | ||
|
|
||
| const { data, error } = await supabase.rpc('match_proposals', { | ||
| query_embedding: queryEmbedding, | ||
| match_count: 20, | ||
| filter_company: filterCompany, | ||
| }); |
Comment on lines
+32
to
+34
| if (!isAdmin(req) && req.user?.companyId) { | ||
| query = query.eq('company_id', req.user.companyId); | ||
| } |
Comment on lines
+66
to
+71
| const { data: companyProposals } = await supabase | ||
| .from('Proposal') | ||
| .select('id') | ||
| .eq('company_id', companyId); | ||
| const ids = (companyProposals || []).map((p: { id: string }) => p.id); | ||
|
|
Comment on lines
118
to
+119
| "vite": "^5.4.19", | ||
| "vitest": "^4.1.9", |
Add render.yaml Blueprint (Docker web service, auto-generated NEXTAUTH_SECRET, secrets via dashboard). Server now binds Render's injected PORT; Docker healthcheck is port-aware. Document the Render deploy flow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two bodies of work, in 7 logical commits.
Production-readiness sprint
helmet+ rate limiting on auth/AI endpoints; securedGET /api/audit(was readable unauthenticated).src/api/lib/compliance.ts; removedas anyJWT casts; fixed 3 latent bugs (intCompanyIdundefined, change-passwordreq.user.id,apiOrigintypo); replaced the brokeneslint-config-nextconfig with a working Vite flat config (tsc --noEmitandeslintnow clean)..dockerignore, GitHub Actions CI, production SPA serving,docs/DEPLOYMENT.md.Feature advancements
/analytics(recharts) +GET /api/analytics/summary(company-scoped); Dashboard trends are now real deltas.GET /api/notificationssourced fromAuditLog.EMAIL_ENABLED+ key), never sends in tests.GET /api/proposals/search+ pgvector migration/backfill; falls back to substring search until activated.Verification
npm run lint✅ ·npm run typecheck✅ · 47 tests pass ✅ ·prisma validate✅ ·vite build✅Follow-ups needed (by design)
RESEND_API_KEY+EMAIL_ENABLED=truein.envto actually send.prisma/manual/semantic_search.sqlon Supabase, thennpm run backfill:embeddings(seedocs/SEMANTIC_SEARCH.md).NEXTAUTH_SECRETthat were surfaced in plaintext.