Skip to content

5queezer/nexus-crm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

126 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nexus CRM

A lead and opportunity management suite for tracking your sales pipeline. Manage applications across a Kanban board or sortable table, store documents, track contacts, scan emails for leads, and integrate with AI agents via a built-in MCP server.

Live: nexus.vasudev.xyz


Table of Contents


Features

  • Table & Kanban Views — sortable, filterable table powered by TanStack Table, or drag-and-drop Kanban board with optimistic updates via @dnd-kit
  • Follow-up Reminders — set per-opportunity follow-up dates with overdue alerts
  • Contact Management — track contacts per opportunity (name, role, email, phone, LinkedIn)
  • Document Storage — upload PDFs and images, link to opportunities, shareable download links
  • Client Portal — read-only public share page with short-code URLs
  • Analytics Dashboard — interactive pipeline statistics and insights
  • Resume Review — AI-powered resume analysis and job matching
  • Resume Tailoring — duplicate and tailor resumes per application via Reactive Resume integration
  • Dark / Light / System Theme — three-way toggle, persisted in localStorage, flash-free
  • DE / EN Language Switcher — full i18n via next-intl (default: German)
  • CSV Export — one-click export, Excel-compatible
  • Bulk Archive — archive old or low-rated applications by age or star rating
  • Google OAuth — secure login, multi-user with per-user admin roles
  • Email Intelligence — Gmail integration to auto-detect and import client communications
  • MCP Server — Model Context Protocol endpoint for AI agent integration (OAuth 2.1 + PKCE)
  • API Docs — OpenAPI 3.1 spec with Swagger UI at /api-docs, LLM-friendly guide at /llm.txt
  • Rate Limiting — per-IP rate limits on all API routes with standard headers
  • Security Hardened — CSP, HSTS, X-Frame-Options, gitleaks pre-commit hooks, encrypted email tokens

Tech Stack

Layer Technology
Framework Next.js 16 — App Router, React 19, standalone output
Database Prisma 6 + PostgreSQL (swappable adapter for Firestore)
Auth better-auth — Google OAuth, session cookies
Data Fetching TanStack Query v5 — caching, optimistic UI
Table TanStack Table v8 — headless sort & filter
Drag & Drop @dnd-kit — Kanban board
i18n next-intl — DE / EN
Styling Tailwind CSS v3 — class-based dark mode
Validation Zod 4 — runtime schema validation
AI Integration @modelcontextprotocol/sdk — MCP server
File Storage Google Cloud Storage or local filesystem
Hosting Google Cloud Run — container hosting
Database Hosting Neon — serverless PostgreSQL
Testing Vitest 4
CI/CD GitHub Actions — lint, build, test, deploy to Cloud Run

Prerequisites

  • Node.js 22+ (matches the Docker image; 20+ works locally)
  • PostgreSQL 15+ — or a hosted provider like Neon (free tier available)
  • Google OAuth Client IDcreate one here
  • npm — used as the package manager (see .npmrc)

Optional:

  • Docker — for containerized deployment
  • pre-commit — for git hooks (pip install pre-commit && pre-commit install)
  • gitleaks — for secrets scanning (used by pre-commit hook)

Getting Started

1. Clone the Repository

git clone https://github.com/5queezer/job-tracker.git
cd job-tracker

2. Install Dependencies

npm install

3. Environment Setup

cp .env.example .env

Edit .env with your values. See Environment Variables for the full reference.

At minimum, you need:

  • DATABASE_URL — a PostgreSQL connection string
  • BETTER_AUTH_SECRET — generate with openssl rand -base64 32
  • BETTER_AUTH_URL — your app's public URL (use http://localhost:3001 for local dev)
  • GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET — from Google Cloud Console
  • ALLOWED_EMAIL — comma-separated list of emails permitted to log in

4. Database Setup

Push the Prisma schema to your database:

npx prisma db push

This creates all tables. For an existing database with prior migrations:

npx prisma migrate deploy

Generate the Prisma client (usually automatic after npm install):

npx prisma generate

5. Start Development Server

npm run dev

Opens at http://localhost:3001.

In development mode, if no Google OAuth session exists, a fake admin user (dev@localhost) is automatically used — no OAuth setup required to start coding.


Architecture

Directory Structure

├── app/                          # Next.js App Router
│   ├── page.tsx                  # Main dashboard (server component)
│   ├── layout.tsx                # Root layout with i18n + theme
│   ├── providers.tsx             # TanStack Query provider
│   ├── analytics/                # Analytics dashboard page
│   ├── api/                      # API routes
│   │   ├── applications/         # CRUD + contacts + documents + tailor
│   │   ├── auth/[...all]/        # better-auth catch-all handler
│   │   ├── documents/            # Document upload, download, metadata
│   │   ├── email/                # Gmail OAuth + scanning
│   │   ├── mcp/                  # MCP server + OAuth 2.1 endpoints
│   │   ├── admin/                # User management + audit logs
│   │   ├── share-links/          # Short-code share link management
│   │   └── token/                # API token generation
│   ├── api-docs/                 # Swagger UI page
│   ├── documents/                # Document management page
│   ├── resume-review/            # AI resume review page
│   ├── s/[code]/                 # Short-code share link resolver
│   ├── settings/                 # User settings page
│   └── share/                    # Public read-only portal
├── components/                   # React client components
│   ├── dashboard.tsx             # Main dashboard with table/kanban toggle
│   ├── application-table.tsx     # TanStack Table view
│   ├── kanban-view.tsx           # Drag-and-drop Kanban board
│   ├── application-modal.tsx     # Create/edit application modal
│   ├── analytics-dashboard.tsx   # Charts and pipeline stats
│   ├── documents-client.tsx      # Document manager
│   ├── email-integration.tsx     # Gmail connection UI
│   ├── scanned-emails.tsx        # Email scan results
│   ├── resume-analyzer.tsx       # Resume review component
│   ├── app-header.tsx            # Shared navigation header
│   ├── settings-client.tsx       # Settings page (admin, tokens, email)
│   └── ...                       # Theme, language, audit, API token components
├── lib/                          # Server-side utilities
│   ├── auth.ts                   # better-auth configuration
│   ├── session.ts                # Auth middleware (session + Bearer + dev bypass)
│   ├── db/                       # Database adapter layer
│   │   ├── adapter.ts            # DatabaseAdapter interface
│   │   ├── index.ts              # Factory: getDb() → Prisma or Firestore
│   │   ├── prisma-adapter.ts     # PostgreSQL implementation
│   │   ├── firestore-adapter.ts  # Firestore implementation
│   │   └── types.ts              # Shared record types
│   ├── storage.ts                # File storage (GCS or local filesystem)
│   ├── mcp-oauth.ts              # MCP OAuth 2.1 server implementation
│   ├── email/                    # Email scanning pipeline
│   │   ├── gmail.ts              # Gmail API client
│   │   ├── scanner.ts            # Email scanning orchestrator
│   │   ├── classifier.ts         # Email classification logic
│   │   └── encryption.ts         # AES-256-GCM token encryption
│   ├── rate-limit.ts             # In-memory rate limiter (LRU)
│   ├── token.ts                  # API token hashing utilities
│   ├── reactive-resume.ts        # Reactive Resume API client
│   ├── resume-analysis.ts        # AI resume analysis
│   └── logger.ts                 # Logging utility
├── prisma/
│   ├── schema.prisma             # Database schema
│   ├── seed.ts                   # Database seeder
│   └── migrations/               # SQL migration history
├── messages/
│   ├── de.json                   # German translations
│   └── en.json                   # English translations
├── i18n/
│   └── request.ts                # Locale detection (cookie-based, default: de)
├── types/
│   └── index.ts                  # Shared TypeScript types + status validation
├── public/
│   ├── openapi.json              # OpenAPI 3.1 spec
│   └── llm.txt                   # LLM-friendly API guide
├── middleware.ts                  # Rate limiting middleware
├── Dockerfile                    # Multi-stage production build
├── docker-compose.yml            # Self-hosted deployment
└── .github/workflows/
    ├── ci.yml                    # Lint + build + test
    └── deploy-gcp.yml            # Cloud Run CI/CD

Database Adapter Layer

The data layer uses a factory pattern (lib/db/index.ts). The DB_PROVIDER env var selects the backend at runtime:

  • prisma (default) — Prisma ORM with PostgreSQL
  • firestore — Firebase Admin SDK with Firestore

All API routes call getDb() which returns a DatabaseAdapter interface. Swapping backends requires zero code changes.

Storage Abstraction

File storage (lib/storage.ts) switches between:

  • Google Cloud Storage — when GCS_BUCKET is set
  • Local filesystem — default, stores in uploads/

Authentication Flow

  1. Google OAuth via better-auth → session cookie
  2. API tokens — per-user Bearer tokens (jt_ prefix), generated in the dashboard
  3. MCP OAuth 2.1 — full authorization code flow with PKCE for AI agent integration (mcp_at_ prefix)
  4. Dev bypass — in development, a fake admin user is used when no session exists

Admins bypass per-user data scoping. The first user matching ALLOWED_EMAIL is auto-promoted to admin.

Request Lifecycle

Request → middleware.ts (rate limiting) → API route handler
  → requireAuth() (session/Bearer/dev)
  → getDb() (database adapter)
  → Response with rate-limit headers

Database Schema

User ─────────────┬── Application ──┬── Contact
  │                │                 └── Document (M:N)
  ├── Session      ├── Document
  ├── Account      └── (via userId)
  ├── UserApiToken
  ├── ShareLink
  ├── EmailIntegration
  ├── ScannedEmail
  ├── McpAccessToken
  └── AdminAuditLog (actor + target)

McpOAuthClient
McpAuthCode
McpRefreshToken
Verification

Key relationships:

  • Applications belong to a User and have many Contacts
  • Documents belong to a User and link to many Applications (M:N)
  • Each User has at most one EmailIntegration (encrypted Gmail refresh token)
  • MCP OAuth tables handle the full authorization code + refresh token flow

Environment Variables

Required

Variable Description Example
DATABASE_URL PostgreSQL connection string postgresql://user:pass@host:5432/nexus
BETTER_AUTH_SECRET Session encryption secret openssl rand -base64 32
BETTER_AUTH_URL Public URL of the app https://nexus.vasudev.xyz
GOOGLE_CLIENT_ID Google OAuth client ID xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET Google OAuth client secret GOCSPX-xxx
ALLOWED_EMAIL Comma-separated allowed emails user@example.com,other@example.com

Optional

Variable Description Default
DB_PROVIDER Database backend (prisma or firestore) prisma
GCS_BUCKET Google Cloud Storage bucket name (omit for local filesystem)
UPLOAD_DIR Local upload directory path ./uploads
PUBLIC_READ_TOKEN Token for the read-only client portal
RR_API_URL Reactive Resume API URL
RR_API_KEY Reactive Resume API key
RR_BASE_RESUME_ID Base resume to duplicate for tailoring

Development

DB_PROVIDER="prisma"
DATABASE_URL="postgresql://localhost:5432/nexus_dev"
BETTER_AUTH_SECRET="dev-secret-change-me"
BETTER_AUTH_URL="http://localhost:3001"
GOOGLE_CLIENT_ID="your-client-id"
GOOGLE_CLIENT_SECRET="your-client-secret"
ALLOWED_EMAIL="you@example.com"

In development mode, OAuth is bypassed — you can use the app without configuring Google credentials.


Available Scripts

Command Description
npm run dev Start development server on port 3001 (with Turbopack)
npm run build Production build (standalone output)
npm start Start production server on port 3001
npm run lint Run ESLint
npm test Run Vitest test suite
npm run seed Seed the database (tsx prisma/seed.ts)
npx prisma studio Open Prisma Studio (database GUI)
npx prisma db push Push schema changes to the database
npx prisma migrate dev Create and apply a migration
npx prisma generate Regenerate Prisma client

Testing

Running Tests

# Run all tests
npm test

# Run in watch mode
npx vitest

# Run a specific test file
npx vitest lib/__tests__/rate-limit.test.ts

Test Structure

lib/__tests__/
├── rate-limit.test.ts         # Rate limiter unit tests
└── token.test.ts              # Token hashing tests

lib/db/__tests__/
└── firestore-adapter.test.ts  # Firestore adapter tests

lib/email/__tests__/
├── classifier.test.ts         # Email classification tests
└── encryption.test.ts         # AES-256-GCM encryption tests

types/__tests__/
└── index.test.ts              # Type validation tests

Tests use Vitest with the @ path alias configured to the project root.


API

OpenAPI Documentation

  • Swagger UI: /api-docs — interactive API explorer
  • OpenAPI spec: /openapi.json — machine-readable spec
  • LLM guide: /llm.txt — plain-text API reference for AI agents

Authentication

Method How Scope
Session cookie Google OAuth login at /login Full access
Bearer token Authorization: Bearer jt_<token> Owner's data (admin: all data)
MCP OAuth Authorization: Bearer mcp_at_<token> Owner's data via MCP
Share token Query param on /share Read-only portal

Generate API tokens in the dashboard under Settings. Tokens are shown once and stored as SHA-256 hashes.

Endpoints

Applications

Method Path Description
GET /api/applications List all (paginated, filtered by user)
POST /api/applications Create application
GET /api/applications/:id Get single application
PATCH /api/applications/:id Update application
DELETE /api/applications/:id Delete application

Contacts (nested under application)

Method Path Description
GET /api/applications/:id/contacts List contacts
POST /api/applications/:id/contacts Add contact
PATCH /api/applications/:id/contacts/:cid Update contact
DELETE /api/applications/:id/contacts/:cid Delete contact

Documents

Method Path Description
GET /api/documents List documents
POST /api/documents Upload (multipart/form-data)
PATCH /api/documents/:id Rename or update application links
DELETE /api/documents/:id Delete document
GET /api/documents/:id/file Download file

Admin (requires isAdmin)

Method Path Description
GET /api/admin/users List all users
PATCH /api/admin/users/:id Update admin status
GET /api/admin/audit-logs View audit log

Token Management

Method Path Description
GET /api/token Get current token metadata
POST /api/token Generate new token
DELETE /api/token Revoke token

Rate Limits

Rate limits are enforced per IP via middleware:

Route Group Limit (req/min)
/api/auth 10
/api/admin 20
/api/email 20
/api/applications 60
All other /api/* 30

Standard X-RateLimit-* and Retry-After headers are included on every response.


MCP Server

Nexus CRM exposes a Model Context Protocol server at /api/mcp for AI agent integration.

Available Tools

Tool Description
list_applications List all applications
get_application Get single application by ID
create_application Create a new application
update_application Update an existing application
delete_application Delete an application
batch_upsert_applications Create/update up to 50 applications
batch_delete_applications Delete up to 50 applications
list_applications_filtered List with filters, sorting, field selection
create_contact Add contact to application
update_contact Update a contact
delete_contact Delete a contact
list_documents List all documents
get_document Get document by ID
update_document_links Update document-application links
delete_document Delete a document

Authentication

The MCP server supports two auth methods:

  1. CRM API token — use your jt_ Bearer token from the dashboard
  2. MCP OAuth 2.1 — full authorization code flow with PKCE:
    • Discovery: GET /.well-known/oauth-authorization-server
    • Registration: POST /api/mcp/register
    • Authorization: GET /api/mcp/authorize
    • Token exchange: POST /api/mcp/token

Claude Desktop Configuration

{
  "mcpServers": {
    "nexus-crm": {
      "url": "https://nexus.vasudev.xyz/api/mcp",
      "headers": {
        "Authorization": "Bearer jt_<your-token>"
      }
    }
  }
}

Deployment

Docker (Self-Hosted)

Build and run:

docker build -t nexus-crm .
docker run -p 3001:3001 --env-file .env -e PORT=3001 nexus-crm

Or use docker-compose.yml for a production setup:

# Edit .env.production with your values
docker compose up -d

The compose file mounts ./data for the Prisma directory and ./uploads for file storage.

GCP Cloud Run (CI/CD)

The repo includes a GitHub Actions workflow (.github/workflows/deploy-gcp.yml) that on push to main:

  1. Pushes schema updates (prisma db push)
  2. Builds a Docker image
  3. Pushes to Artifact Registry (europe-west1)
  4. Deploys to Cloud Run with secrets from Secret Manager

Uses Workload Identity Federation — no service account keys. Requires these GitHub secrets:

  • GCP_WORKLOAD_IDENTITY_PROVIDER
  • GCP_SERVICE_ACCOUNT

Cloud Run configuration: 512Mi memory, 1 CPU, 0–2 instances, port 8080.

Manual / VPS

npm run build
./deploy.sh   # builds, copies standalone output, restarts systemd service

The standalone output (via next.config.ts output: "standalone") produces a self-contained server.js in .next/standalone/.


Pipeline Stages

Stage Meaning
inbound New lead
applied Contacted
interview Negotiation
offer Closing
rejected Lost

Troubleshooting

Database Connection

Error: Can't reach database server

  1. Verify PostgreSQL is running: pg_isready -h localhost
  2. Check DATABASE_URL format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE
  3. For Neon: ensure the connection string includes ?sslmode=require

Prisma Issues

Error: Prisma Client not generated

npx prisma generate

Error: The database schema is not in sync

npx prisma db push

Auth Not Working

  • Verify GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set
  • Ensure BETTER_AUTH_URL matches the URL you're accessing (including protocol)
  • Check that your email is in ALLOWED_EMAIL
  • In development, OAuth is bypassed — if you see Dev User, auth is intentionally skipped

Port Already in Use

The app runs on port 3001 by default. If it's taken:

npm run dev -- -p 3002

Pre-commit Hooks

Install hooks:

pip install pre-commit
pre-commit install

The hooks run gitleaks (secrets scan), ESLint, and general file hygiene checks.


License

Private project. All rights reserved.

About

Lead & opportunity management suite — Next.js, TanStack, Prisma, Google OAuth

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages