A production-ready lead generation and intelligent distribution platform built with modern web technologies. Prowider uses a fair, efficient allocation algorithm to distribute customer leads to service providers while respecting monthly quotas.
View Live Demo β’ GitHub Issues β’ Report Bug
- π Real-time Dashboard β Live provider metrics with Server-Sent Events (SSE)
- βοΈ Fair Lead Allocation β Intelligent round-robin algorithm with quota management
- π Concurrency-Safe β Serializable database transactions prevent race conditions
- π Customer Forms β Easy lead submission with service selection
- π Webhook Support β Idempotent quota reset via webhooks
- π Provider Analytics β Track leads, quota usage, and performance
- π οΈ Admin Tools β Built-in testing suite and load generation
- π¨ Responsive UI β Mobile-friendly design with Tailwind CSS
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Frontend | Next.js (App Router) | 16.2 | UI, routing, SSE streaming |
| Backend | Node.js + Express | Built-in | API routes and business logic |
| Database | PostgreSQL | Latest | Persistent data storage |
| ORM | Prisma | 7.8 | Type-safe database access |
| Real-time | Server-Sent Events | Native | Live dashboard updates |
| Styling | Tailwind CSS | 4 | Utility-first CSS framework |
| Validation | Zod | 4.4 | Type-safe schema validation |
Service (service, id, name)
βββ Lead (customer inquiry with phone, name, city, description)
β βββ LeadAssignment (assigned to 3 providers per lead)
βββ Provider (8 providers, each with monthly quota of 10 leads)
βββ AllocationState (tracks round-robin pointer per service)
WebhookEvent (idempotency keys for quota resets)
- Node.js 18+ or 20+
- npm or yarn
- PostgreSQL 12+
- Git
# 1. Clone the repository
git clone https://github.com/Shree-svg/Prowider.git
cd Prowider
# 2. Install dependencies
npm install
# 3. Set up environment variables
cp .env.local.example .env.local
# Edit .env.local and add your PostgreSQL connection string:
# DATABASE_URL="postgresql://user:password@localhost:5432/prowider"# 1. Create database schema
npx prisma db push
# 2. Seed with demo data (3 services, 8 providers, allocation state)
npx prisma db seed
# 3. (Optional) Generate Prisma client after schema changes
npx prisma generate# Development mode (hot reload enabled)
npm run dev
# Production build
npm run build
# Start production server
npm start
# The application will be available at http://localhost:3000# Deploy pending migrations (production)
npm run db:migrate
# View Prisma Studio (interactive database explorer)
npx prisma studio| URL Path | Purpose | Access |
|---|---|---|
/ |
Home page with navigation | Public |
/request-service |
Customer lead submission form | Public |
/dashboard |
Real-time provider dashboard & analytics | Public* |
/test-tools |
Webhook testing & load generation | Development |
*Dashboard displays real-time lead allocations and provider quotas via SSE.
Prowider implements a sophisticated allocation algorithm that:
- Assigns exactly 3 providers per lead to maximize reach
- Guarantees mandatory assignments for critical providers
- Uses persistent round-robin to fairly distribute remaining slots
- Respects monthly quotas (default: 10 leads/provider)
- Survives server restarts via database-backed state
| Service | Mandatory Providers | Round-Robin Pool |
|---|---|---|
| Service 1 | Provider 1 | [2, 3, 4] |
| Service 2 | Provider 5 | [6, 7, 8] |
| Service 3 | Providers 1, 4 | [2, 3, 5, 6, 7, 8] |
- β Serializable Transactions β Highest isolation level prevents anomalies
- β
Row-Level Locking β
SELECT β¦ FOR UPDATEensures atomic state updates - β Distributed-Safe β Works correctly across multiple server instances
- β No In-Memory Locks β All synchronization at database level
- Database Constraint β
UNIQUE(phone, serviceId)onLeadtable - API Response β Returns 409 Conflict with clear error message
- Enforced at DB Level β Not just application logic
Quota resets via webhooks are idempotent:
POST /api/webhook
{
"idempotencyKey": "unique-key-12345",
"action": "reset_quota",
"providerId": 1
}- β
Same key: Returns 200 with
idempotent: true, no quota reset - β New key: Processes quota reset, records key in database
- β Race-condition safe: Key recording happens in same transaction
# Create a new lead
POST /api/leads
Content-Type: application/json
{
"name": "John Doe",
"phone": "+1234567890",
"city": "New York",
"description": "Need business consultation",
"serviceId": 1
}
# Response (Success): 201 Created
{
"success": true,
"leadId": 42,
"providers": [1, 3, 5]
}
# Response (Duplicate): 409 Conflict
{
"error": "Lead already exists for this phone and service"
}# Get all providers
GET /api/providers
# Response: 200 OK
{
"providers": [
{ "id": 1, "name": "Provider A", "monthlyQuota": 10, "leadsCount": 7 },
{ "id": 2, "name": "Provider B", "monthlyQuota": 10, "leadsCount": 5 },
...
]
}# Get all services
GET /api/services
# Response: 200 OK
{
"services": [
{ "id": 1, "name": "Business Consultation" },
{ "id": 2, "name": "Legal Services" },
{ "id": 3, "name": "Tax Planning" }
]
}# Server-Sent Events stream
GET /api/sse
# Pushes updates when leads are created:
data: {
"type": "lead_created",
"lead": { "id": 42, "name": "John Doe", "providers": [1, 3, 5] },
"timestamp": "2024-05-18T10:30:00Z"
}POST /api/webhook
Content-Type: application/json
{
"idempotencyKey": "reset-provider-1-2024-05",
"action": "reset_quota",
"providerId": 1
}
# Response: 200 OK
{
"success": true,
"idempotent": false,
"message": "Provider 1 quota reset to 0"
}Access the internal testing suite at /test-tools:
- Manual Lead Creation β Create leads with custom data
- Load Testing β Generate bulk leads to test system capacity
- Webhook Testing β Send test webhooks with custom payloads
- Real-time Monitoring β Watch allocations happen live
# Lint the codebase (ESLint + Next.js rules)
npm run lint
# Generate TypeScript types
npm run db:generate
# Seed database with fresh demo data
npm run db:seed
# View database in Prisma Studio
npx prisma studioCreate a .env.local file:
# Required: PostgreSQL connection string
DATABASE_URL="postgresql://user:password@localhost:5432/prowider"
# Optional: Enable debug logging (development only)
DEBUG=prowider:*
# Optional: Node environment
NODE_ENV=development # or production# 1. Push code to GitHub
git push origin main
# 2. Connect repository to Vercel
# Visit: https://vercel.com/new
# 3. Set environment variables in Vercel dashboard
# - DATABASE_URL: Your production PostgreSQL URL
# 4. Vercel auto-deploys on pushSimilar process β ensure DATABASE_URL is set and run migrations:
npm run db:migrate # Deploy schema changes
npm run build # Build Next.js app
npm start # Start serverProwider/
βββ app/ # Next.js app router
β βββ api/ # API routes
β β βββ leads/ # Lead creation
β β βββ providers/ # Provider data
β β βββ services/ # Service data
β β βββ sse/ # Server-Sent Events
β β βββ webhook/ # Webhook handler
β β βββ test/ # Testing endpoints
β βββ dashboard/ # Real-time dashboard UI
β βββ request-service/ # Customer form page
β βββ test-tools/ # Testing UI
β βββ layout.tsx # Root layout
βββ lib/ # Utility functions
βββ prisma/
β βββ schema.prisma # Database schema
β βββ seed.ts # Database seed script
βββ public/ # Static assets
βββ tailwind.config.mjs # Tailwind configuration
βββ tsconfig.json # TypeScript configuration
βββ next.config.ts # Next.js configuration
- TypeScript β Strict mode enabled
- ESLint β Next.js recommended rules
- Formatting β Prettier compatible
- Tailwind β Utility-first CSS
Run linter:
npm run lintProblem: Multiple servers allocating leads concurrently might skip providers.
Solution:
- Serializable isolation β Strongest database guarantee
SELECT β¦ FOR UPDATEβ LocksAllocationStaterow during transaction- Result β Concurrent requests queue naturally; no race conditions
Problem: Retried webhooks could double-reset quotas.
Solution:
- Check
idempotencyKeyinWebhookEventtable first - If exists: Return 200 immediately (idempotent)
- If new: Process reset + record key in same transaction
- Result β Transactionally safe; no race window
This project is licensed under the MIT License β see LICENSE file for details.
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write TypeScript (no
anytypes) - Follow ESLint rules
- Add comments for complex logic
- Test changes locally before pushing
Found a bug or have a suggestion?
- Report Bug: GitHub Issues
- Feature Request: GitHub Discussions
- Security: Please don't open public issues for security vulnerabilities
For questions or issues:
- Check the Troubleshooting section
- Review API Endpoints documentation
- Open an issue on GitHub
- Check existing GitHub issues for similar problems
Q: Database connection fails
A: Verify DATABASE_URL is correct and PostgreSQL server is running.
Q: npm run db:seed fails
A: Run npx prisma db push first to create schema.
Q: Real-time dashboard not updating
A: Ensure Server-Sent Events (SSE) is not blocked by proxies/firewalls.
Q: Build fails with TypeScript errors
A: Run npm run db:generate to regenerate Prisma types.
- Built with Next.js and TypeScript
- Database powered by PostgreSQL and Prisma
- Styled with Tailwind CSS
- Deployed on Vercel
- v0.1.0 (Current) β Initial release with core lead distribution, real-time dashboard, and webhook support
Made with β€οΈ by Shree-svg