{"id":47968140,"url":"https://github.com/5queezer/nexus-crm","last_synced_at":"2026-04-04T10:40:00.372Z","repository":{"id":343633291,"uuid":"1174477218","full_name":"5queezer/nexus-crm","owner":"5queezer","description":"Lead \u0026 opportunity management suite — Next.js, TanStack, Prisma, Google OAuth","archived":false,"fork":false,"pushed_at":"2026-04-01T11:17:18.000Z","size":1831,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T10:39:58.671Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/5queezer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY_AUDIT.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-06T13:42:30.000Z","updated_at":"2026-04-01T11:17:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/5queezer/nexus-crm","commit_stats":null,"previous_names":["5queezer/job-tracker","5queezer/nexus-crm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/5queezer/nexus-crm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fnexus-crm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fnexus-crm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fnexus-crm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fnexus-crm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/5queezer","download_url":"https://codeload.github.com/5queezer/nexus-crm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5queezer%2Fnexus-crm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31397055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T10:40:00.308Z","updated_at":"2026-04-04T10:40:00.364Z","avatar_url":"https://github.com/5queezer.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nexus CRM\n\nA 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.\n\n**Live:** [nexus.vasudev.xyz](https://nexus.vasudev.xyz)\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Tech Stack](#tech-stack)\n- [Prerequisites](#prerequisites)\n- [Getting Started](#getting-started)\n- [Architecture](#architecture)\n- [Environment Variables](#environment-variables)\n- [Available Scripts](#available-scripts)\n- [Testing](#testing)\n- [API](#api)\n- [MCP Server](#mcp-server)\n- [Deployment](#deployment)\n- [Pipeline Stages](#pipeline-stages)\n- [Troubleshooting](#troubleshooting)\n- [License](#license)\n\n---\n\n## Features\n\n- **Table \u0026 Kanban Views** — sortable, filterable table powered by TanStack Table, or drag-and-drop Kanban board with optimistic updates via @dnd-kit\n- **Follow-up Reminders** — set per-opportunity follow-up dates with overdue alerts\n- **Contact Management** — track contacts per opportunity (name, role, email, phone, LinkedIn)\n- **Document Storage** — upload PDFs and images, link to opportunities, shareable download links\n- **Client Portal** — read-only public share page with short-code URLs\n- **Analytics Dashboard** — interactive pipeline statistics and insights\n- **Resume Review** — AI-powered resume analysis and job matching\n- **Resume Tailoring** — duplicate and tailor resumes per application via Reactive Resume integration\n- **Dark / Light / System Theme** — three-way toggle, persisted in localStorage, flash-free\n- **DE / EN Language Switcher** — full i18n via next-intl (default: German)\n- **CSV Export** — one-click export, Excel-compatible\n- **Bulk Archive** — archive old or low-rated applications by age or star rating\n- **Google OAuth** — secure login, multi-user with per-user admin roles\n- **Email Intelligence** — Gmail integration to auto-detect and import client communications\n- **MCP Server** — Model Context Protocol endpoint for AI agent integration (OAuth 2.1 + PKCE)\n- **API Docs** — OpenAPI 3.1 spec with Swagger UI at `/api-docs`, LLM-friendly guide at `/llm.txt`\n- **Rate Limiting** — per-IP rate limits on all API routes with standard headers\n- **Security Hardened** — CSP, HSTS, X-Frame-Options, gitleaks pre-commit hooks, encrypted email tokens\n\n---\n\n## Tech Stack\n\n| Layer | Technology |\n| ----- | ---------- |\n| **Framework** | [Next.js 16](https://nextjs.org) — App Router, React 19, standalone output |\n| **Database** | [Prisma 6](https://prisma.io) + PostgreSQL (swappable adapter for Firestore) |\n| **Auth** | [better-auth](https://better-auth.com) — Google OAuth, session cookies |\n| **Data Fetching** | [TanStack Query v5](https://tanstack.com/query) — caching, optimistic UI |\n| **Table** | [TanStack Table v8](https://tanstack.com/table) — headless sort \u0026 filter |\n| **Drag \u0026 Drop** | [@dnd-kit](https://dndkit.com) — Kanban board |\n| **i18n** | [next-intl](https://next-intl-docs.vercel.app) — DE / EN |\n| **Styling** | [Tailwind CSS v3](https://tailwindcss.com) — class-based dark mode |\n| **Validation** | [Zod 4](https://zod.dev) — runtime schema validation |\n| **AI Integration** | [@modelcontextprotocol/sdk](https://modelcontextprotocol.io) — MCP server |\n| **File Storage** | Google Cloud Storage or local filesystem |\n| **Hosting** | [Google Cloud Run](https://cloud.google.com/run) — container hosting |\n| **Database Hosting** | [Neon](https://neon.tech) — serverless PostgreSQL |\n| **Testing** | [Vitest 4](https://vitest.dev) |\n| **CI/CD** | GitHub Actions — lint, build, test, deploy to Cloud Run |\n\n---\n\n## Prerequisites\n\n- **Node.js 22+** (matches the Docker image; 20+ works locally)\n- **PostgreSQL 15+** — or a hosted provider like [Neon](https://neon.tech) (free tier available)\n- **Google OAuth Client ID** — [create one here](https://console.cloud.google.com/apis/credentials)\n- **npm** — used as the package manager (see `.npmrc`)\n\nOptional:\n\n- **Docker** — for containerized deployment\n- **pre-commit** — for git hooks (`pip install pre-commit \u0026\u0026 pre-commit install`)\n- **gitleaks** — for secrets scanning (used by pre-commit hook)\n\n---\n\n## Getting Started\n\n### 1. Clone the Repository\n\n```bash\ngit clone https://github.com/5queezer/job-tracker.git\ncd job-tracker\n```\n\n### 2. Install Dependencies\n\n```bash\nnpm install\n```\n\n### 3. Environment Setup\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env` with your values. See [Environment Variables](#environment-variables) for the full reference.\n\nAt minimum, you need:\n\n- `DATABASE_URL` — a PostgreSQL connection string\n- `BETTER_AUTH_SECRET` — generate with `openssl rand -base64 32`\n- `BETTER_AUTH_URL` — your app's public URL (use `http://localhost:3001` for local dev)\n- `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` — from Google Cloud Console\n- `ALLOWED_EMAIL` — comma-separated list of emails permitted to log in\n\n### 4. Database Setup\n\nPush the Prisma schema to your database:\n\n```bash\nnpx prisma db push\n```\n\nThis creates all tables. For an existing database with prior migrations:\n\n```bash\nnpx prisma migrate deploy\n```\n\nGenerate the Prisma client (usually automatic after `npm install`):\n\n```bash\nnpx prisma generate\n```\n\n### 5. Start Development Server\n\n```bash\nnpm run dev\n```\n\nOpens at [http://localhost:3001](http://localhost:3001).\n\nIn development mode, if no Google OAuth session exists, a fake admin user (`dev@localhost`) is automatically used — no OAuth setup required to start coding.\n\n---\n\n## Architecture\n\n### Directory Structure\n\n```text\n├── app/                          # Next.js App Router\n│   ├── page.tsx                  # Main dashboard (server component)\n│   ├── layout.tsx                # Root layout with i18n + theme\n│   ├── providers.tsx             # TanStack Query provider\n│   ├── analytics/                # Analytics dashboard page\n│   ├── api/                      # API routes\n│   │   ├── applications/         # CRUD + contacts + documents + tailor\n│   │   ├── auth/[...all]/        # better-auth catch-all handler\n│   │   ├── documents/            # Document upload, download, metadata\n│   │   ├── email/                # Gmail OAuth + scanning\n│   │   ├── mcp/                  # MCP server + OAuth 2.1 endpoints\n│   │   ├── admin/                # User management + audit logs\n│   │   ├── share-links/          # Short-code share link management\n│   │   └── token/                # API token generation\n│   ├── api-docs/                 # Swagger UI page\n│   ├── documents/                # Document management page\n│   ├── resume-review/            # AI resume review page\n│   ├── s/[code]/                 # Short-code share link resolver\n│   ├── settings/                 # User settings page\n│   └── share/                    # Public read-only portal\n├── components/                   # React client components\n│   ├── dashboard.tsx             # Main dashboard with table/kanban toggle\n│   ├── application-table.tsx     # TanStack Table view\n│   ├── kanban-view.tsx           # Drag-and-drop Kanban board\n│   ├── application-modal.tsx     # Create/edit application modal\n│   ├── analytics-dashboard.tsx   # Charts and pipeline stats\n│   ├── documents-client.tsx      # Document manager\n│   ├── email-integration.tsx     # Gmail connection UI\n│   ├── scanned-emails.tsx        # Email scan results\n│   ├── resume-analyzer.tsx       # Resume review component\n│   ├── app-header.tsx            # Shared navigation header\n│   ├── settings-client.tsx       # Settings page (admin, tokens, email)\n│   └── ...                       # Theme, language, audit, API token components\n├── lib/                          # Server-side utilities\n│   ├── auth.ts                   # better-auth configuration\n│   ├── session.ts                # Auth middleware (session + Bearer + dev bypass)\n│   ├── db/                       # Database adapter layer\n│   │   ├── adapter.ts            # DatabaseAdapter interface\n│   │   ├── index.ts              # Factory: getDb() → Prisma or Firestore\n│   │   ├── prisma-adapter.ts     # PostgreSQL implementation\n│   │   ├── firestore-adapter.ts  # Firestore implementation\n│   │   └── types.ts              # Shared record types\n│   ├── storage.ts                # File storage (GCS or local filesystem)\n│   ├── mcp-oauth.ts              # MCP OAuth 2.1 server implementation\n│   ├── email/                    # Email scanning pipeline\n│   │   ├── gmail.ts              # Gmail API client\n│   │   ├── scanner.ts            # Email scanning orchestrator\n│   │   ├── classifier.ts         # Email classification logic\n│   │   └── encryption.ts         # AES-256-GCM token encryption\n│   ├── rate-limit.ts             # In-memory rate limiter (LRU)\n│   ├── token.ts                  # API token hashing utilities\n│   ├── reactive-resume.ts        # Reactive Resume API client\n│   ├── resume-analysis.ts        # AI resume analysis\n│   └── logger.ts                 # Logging utility\n├── prisma/\n│   ├── schema.prisma             # Database schema\n│   ├── seed.ts                   # Database seeder\n│   └── migrations/               # SQL migration history\n├── messages/\n│   ├── de.json                   # German translations\n│   └── en.json                   # English translations\n├── i18n/\n│   └── request.ts                # Locale detection (cookie-based, default: de)\n├── types/\n│   └── index.ts                  # Shared TypeScript types + status validation\n├── public/\n│   ├── openapi.json              # OpenAPI 3.1 spec\n│   └── llm.txt                   # LLM-friendly API guide\n├── middleware.ts                  # Rate limiting middleware\n├── Dockerfile                    # Multi-stage production build\n├── docker-compose.yml            # Self-hosted deployment\n└── .github/workflows/\n    ├── ci.yml                    # Lint + build + test\n    └── deploy-gcp.yml            # Cloud Run CI/CD\n```\n\n### Database Adapter Layer\n\nThe data layer uses a factory pattern (`lib/db/index.ts`). The `DB_PROVIDER` env var selects the backend at runtime:\n\n- **`prisma`** (default) — Prisma ORM with PostgreSQL\n- **`firestore`** — Firebase Admin SDK with Firestore\n\nAll API routes call `getDb()` which returns a `DatabaseAdapter` interface. Swapping backends requires zero code changes.\n\n### Storage Abstraction\n\nFile storage (`lib/storage.ts`) switches between:\n\n- **Google Cloud Storage** — when `GCS_BUCKET` is set\n- **Local filesystem** — default, stores in `uploads/`\n\n### Authentication Flow\n\n1. **Google OAuth** via better-auth → session cookie\n2. **API tokens** — per-user Bearer tokens (`jt_` prefix), generated in the dashboard\n3. **MCP OAuth 2.1** — full authorization code flow with PKCE for AI agent integration (`mcp_at_` prefix)\n4. **Dev bypass** — in development, a fake admin user is used when no session exists\n\nAdmins bypass per-user data scoping. The first user matching `ALLOWED_EMAIL` is auto-promoted to admin.\n\n### Request Lifecycle\n\n```text\nRequest → middleware.ts (rate limiting) → API route handler\n  → requireAuth() (session/Bearer/dev)\n  → getDb() (database adapter)\n  → Response with rate-limit headers\n```\n\n### Database Schema\n\n```text\nUser ─────────────┬── Application ──┬── Contact\n  │                │                 └── Document (M:N)\n  ├── Session      ├── Document\n  ├── Account      └── (via userId)\n  ├── UserApiToken\n  ├── ShareLink\n  ├── EmailIntegration\n  ├── ScannedEmail\n  ├── McpAccessToken\n  └── AdminAuditLog (actor + target)\n\nMcpOAuthClient\nMcpAuthCode\nMcpRefreshToken\nVerification\n```\n\nKey relationships:\n\n- Applications belong to a User and have many Contacts\n- Documents belong to a User and link to many Applications (M:N)\n- Each User has at most one EmailIntegration (encrypted Gmail refresh token)\n- MCP OAuth tables handle the full authorization code + refresh token flow\n\n---\n\n## Environment Variables\n\n### Required\n\n| Variable | Description | Example |\n| -------- | ----------- | ------- |\n| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/nexus` |\n| `BETTER_AUTH_SECRET` | Session encryption secret | `openssl rand -base64 32` |\n| `BETTER_AUTH_URL` | Public URL of the app | `https://nexus.vasudev.xyz` |\n| `GOOGLE_CLIENT_ID` | Google OAuth client ID | `xxx.apps.googleusercontent.com` |\n| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | `GOCSPX-xxx` |\n| `ALLOWED_EMAIL` | Comma-separated allowed emails | `user@example.com,other@example.com` |\n\n### Optional\n\n| Variable | Description | Default |\n| -------- | ----------- | ------- |\n| `DB_PROVIDER` | Database backend (`prisma` or `firestore`) | `prisma` |\n| `GCS_BUCKET` | Google Cloud Storage bucket name (omit for local filesystem) | — |\n| `UPLOAD_DIR` | Local upload directory path | `./uploads` |\n| `PUBLIC_READ_TOKEN` | Token for the read-only client portal | — |\n| `RR_API_URL` | Reactive Resume API URL | — |\n| `RR_API_KEY` | Reactive Resume API key | — |\n| `RR_BASE_RESUME_ID` | Base resume to duplicate for tailoring | — |\n\n### Development\n\n```env\nDB_PROVIDER=\"prisma\"\nDATABASE_URL=\"postgresql://localhost:5432/nexus_dev\"\nBETTER_AUTH_SECRET=\"dev-secret-change-me\"\nBETTER_AUTH_URL=\"http://localhost:3001\"\nGOOGLE_CLIENT_ID=\"your-client-id\"\nGOOGLE_CLIENT_SECRET=\"your-client-secret\"\nALLOWED_EMAIL=\"you@example.com\"\n```\n\nIn development mode, OAuth is bypassed — you can use the app without configuring Google credentials.\n\n---\n\n## Available Scripts\n\n| Command | Description |\n| ------- | ----------- |\n| `npm run dev` | Start development server on port 3001 (with Turbopack) |\n| `npm run build` | Production build (standalone output) |\n| `npm start` | Start production server on port 3001 |\n| `npm run lint` | Run ESLint |\n| `npm test` | Run Vitest test suite |\n| `npm run seed` | Seed the database (`tsx prisma/seed.ts`) |\n| `npx prisma studio` | Open Prisma Studio (database GUI) |\n| `npx prisma db push` | Push schema changes to the database |\n| `npx prisma migrate dev` | Create and apply a migration |\n| `npx prisma generate` | Regenerate Prisma client |\n\n---\n\n## Testing\n\n### Running Tests\n\n```bash\n# Run all tests\nnpm test\n\n# Run in watch mode\nnpx vitest\n\n# Run a specific test file\nnpx vitest lib/__tests__/rate-limit.test.ts\n```\n\n### Test Structure\n\n```text\nlib/__tests__/\n├── rate-limit.test.ts         # Rate limiter unit tests\n└── token.test.ts              # Token hashing tests\n\nlib/db/__tests__/\n└── firestore-adapter.test.ts  # Firestore adapter tests\n\nlib/email/__tests__/\n├── classifier.test.ts         # Email classification tests\n└── encryption.test.ts         # AES-256-GCM encryption tests\n\ntypes/__tests__/\n└── index.test.ts              # Type validation tests\n```\n\nTests use Vitest with the `@` path alias configured to the project root.\n\n---\n\n## API\n\n### OpenAPI Documentation\n\n- **Swagger UI**: [/api-docs](https://nexus.vasudev.xyz/api-docs) — interactive API explorer\n- **OpenAPI spec**: [/openapi.json](https://nexus.vasudev.xyz/openapi.json) — machine-readable spec\n- **LLM guide**: [/llm.txt](https://nexus.vasudev.xyz/llm.txt) — plain-text API reference for AI agents\n\n### Authentication\n\n| Method | How | Scope |\n| ------ | --- | ----- |\n| Session cookie | Google OAuth login at `/login` | Full access |\n| Bearer token | `Authorization: Bearer jt_\u003ctoken\u003e` | Owner's data (admin: all data) |\n| MCP OAuth | `Authorization: Bearer mcp_at_\u003ctoken\u003e` | Owner's data via MCP |\n| Share token | Query param on `/share` | Read-only portal |\n\nGenerate API tokens in the dashboard under Settings. Tokens are shown once and stored as SHA-256 hashes.\n\n### Endpoints\n\n#### Applications\n| Method | Path | Description |\n| ------ | ---- | ----------- |\n| `GET` | `/api/applications` | List all (paginated, filtered by user) |\n| `POST` | `/api/applications` | Create application |\n| `GET` | `/api/applications/:id` | Get single application |\n| `PATCH` | `/api/applications/:id` | Update application |\n| `DELETE` | `/api/applications/:id` | Delete application |\n\n#### Contacts (nested under application)\n| Method | Path | Description |\n| ------ | ---- | ----------- |\n| `GET` | `/api/applications/:id/contacts` | List contacts |\n| `POST` | `/api/applications/:id/contacts` | Add contact |\n| `PATCH` | `/api/applications/:id/contacts/:cid` | Update contact |\n| `DELETE` | `/api/applications/:id/contacts/:cid` | Delete contact |\n\n#### Documents\n| Method | Path | Description |\n| ------ | ---- | ----------- |\n| `GET` | `/api/documents` | List documents |\n| `POST` | `/api/documents` | Upload (multipart/form-data) |\n| `PATCH` | `/api/documents/:id` | Rename or update application links |\n| `DELETE` | `/api/documents/:id` | Delete document |\n| `GET` | `/api/documents/:id/file` | Download file |\n\n#### Admin (requires `isAdmin`)\n| Method | Path | Description |\n| ------ | ---- | ----------- |\n| `GET` | `/api/admin/users` | List all users |\n| `PATCH` | `/api/admin/users/:id` | Update admin status |\n| `GET` | `/api/admin/audit-logs` | View audit log |\n\n#### Token Management\n| Method | Path | Description |\n| ------ | ---- | ----------- |\n| `GET` | `/api/token` | Get current token metadata |\n| `POST` | `/api/token` | Generate new token |\n| `DELETE` | `/api/token` | Revoke token |\n\n### Rate Limits\n\nRate limits are enforced per IP via middleware:\n\n| Route Group | Limit (req/min) |\n| ----------- | --------------- |\n| `/api/auth` | 10 |\n| `/api/admin` | 20 |\n| `/api/email` | 20 |\n| `/api/applications` | 60 |\n| All other `/api/*` | 30 |\n\nStandard `X-RateLimit-*` and `Retry-After` headers are included on every response.\n\n---\n\n## MCP Server\n\nNexus CRM exposes a [Model Context Protocol](https://modelcontextprotocol.io) server at `/api/mcp` for AI agent integration.\n\n### Available Tools\n\n| Tool | Description |\n| ---- | ----------- |\n| `list_applications` | List all applications |\n| `get_application` | Get single application by ID |\n| `create_application` | Create a new application |\n| `update_application` | Update an existing application |\n| `delete_application` | Delete an application |\n| `batch_upsert_applications` | Create/update up to 50 applications |\n| `batch_delete_applications` | Delete up to 50 applications |\n| `list_applications_filtered` | List with filters, sorting, field selection |\n| `create_contact` | Add contact to application |\n| `update_contact` | Update a contact |\n| `delete_contact` | Delete a contact |\n| `list_documents` | List all documents |\n| `get_document` | Get document by ID |\n| `update_document_links` | Update document-application links |\n| `delete_document` | Delete a document |\n\n### Authentication\n\nThe MCP server supports two auth methods:\n\n1. **CRM API token** — use your `jt_` Bearer token from the dashboard\n2. **MCP OAuth 2.1** — full authorization code flow with PKCE:\n   - Discovery: `GET /.well-known/oauth-authorization-server`\n   - Registration: `POST /api/mcp/register`\n   - Authorization: `GET /api/mcp/authorize`\n   - Token exchange: `POST /api/mcp/token`\n\n### Claude Desktop Configuration\n\n```json\n{\n  \"mcpServers\": {\n    \"nexus-crm\": {\n      \"url\": \"https://nexus.vasudev.xyz/api/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer jt_\u003cyour-token\u003e\"\n      }\n    }\n  }\n}\n```\n\n---\n\n## Deployment\n\n### Docker (Self-Hosted)\n\nBuild and run:\n\n```bash\ndocker build -t nexus-crm .\ndocker run -p 3001:3001 --env-file .env -e PORT=3001 nexus-crm\n```\n\nOr use `docker-compose.yml` for a production setup:\n\n```bash\n# Edit .env.production with your values\ndocker compose up -d\n```\n\nThe compose file mounts `./data` for the Prisma directory and `./uploads` for file storage.\n\n### GCP Cloud Run (CI/CD)\n\nThe repo includes a GitHub Actions workflow (`.github/workflows/deploy-gcp.yml`) that on push to `main`:\n\n1. Pushes schema updates (`prisma db push`)\n2. Builds a Docker image\n3. Pushes to Artifact Registry (`europe-west1`)\n4. Deploys to Cloud Run with secrets from Secret Manager\n\nUses Workload Identity Federation — no service account keys. Requires these GitHub secrets:\n\n- `GCP_WORKLOAD_IDENTITY_PROVIDER`\n- `GCP_SERVICE_ACCOUNT`\n\nCloud Run configuration: 512Mi memory, 1 CPU, 0–2 instances, port 8080.\n\n### Manual / VPS\n\n```bash\nnpm run build\n./deploy.sh   # builds, copies standalone output, restarts systemd service\n```\n\nThe standalone output (via `next.config.ts` `output: \"standalone\"`) produces a self-contained `server.js` in `.next/standalone/`.\n\n---\n\n## Pipeline Stages\n\n| Stage | Meaning |\n| ----- | ------- |\n| `inbound` | New lead |\n| `applied` | Contacted |\n| `interview` | Negotiation |\n| `offer` | Closing |\n| `rejected` | Lost |\n\n---\n\n## Troubleshooting\n\n### Database Connection\n\n**Error:** `Can't reach database server`\n\n1. Verify PostgreSQL is running: `pg_isready -h localhost`\n2. Check `DATABASE_URL` format: `postgresql://USER:PASSWORD@HOST:PORT/DATABASE`\n3. For Neon: ensure the connection string includes `?sslmode=require`\n\n### Prisma Issues\n\n**Error:** `Prisma Client not generated`\n\n```bash\nnpx prisma generate\n```\n\n**Error:** `The database schema is not in sync`\n\n```bash\nnpx prisma db push\n```\n\n### Auth Not Working\n\n- Verify `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` are set\n- Ensure `BETTER_AUTH_URL` matches the URL you're accessing (including protocol)\n- Check that your email is in `ALLOWED_EMAIL`\n- In development, OAuth is bypassed — if you see `Dev User`, auth is intentionally skipped\n\n### Port Already in Use\n\nThe app runs on port 3001 by default. If it's taken:\n\n```bash\nnpm run dev -- -p 3002\n```\n\n### Pre-commit Hooks\n\nInstall hooks:\n\n```bash\npip install pre-commit\npre-commit install\n```\n\nThe hooks run gitleaks (secrets scan), ESLint, and general file hygiene checks.\n\n---\n\n## License\n\nPrivate project. All rights reserved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5queezer%2Fnexus-crm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F5queezer%2Fnexus-crm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5queezer%2Fnexus-crm/lists"}