Digital reputation management SaaS for local businesses, focused on Google Reviews
- π Google Business Profile Integration - OAuth2 integration with automatic token refresh
- π¬ Reviews Inbox - Centralized review management with AI-powered reply suggestions
- π± Smart NFC/QR Tags - Intelligent redirect system for review collection
- π₯ Mini CRM - Customer contact management and segmentation
- π§ Email Campaigns - Targeted customer engagement campaigns
- π Analytics Dashboard - Comprehensive business insights
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS
- State Management: React Hooks + Server Components
- Framework: NestJS
- Language: TypeScript
- Authentication: Passport.js with Google OAuth2
- Session Management: Redis + express-session
- Database: PostgreSQL 16
- ORM: Prisma
- Cache: Redis 7
- Monorepo: pnpm workspaces
- Token Encryption: AES-256-GCM
- Session Storage: HttpOnly cookies with Redis backing
- OAuth: Google OAuth2 with automatic token refresh
- Tenant Isolation: Row-level security
- Node.js 20 LTS or higher
- pnpm 9+
- Docker & Docker Compose
git clone https://github.com/sassongal/revWave.git
cd revWave
pnpm install# Copy environment template
cp .env.example .env
# Edit .env with your configuration
# Required variables:
# - DATABASE_URL
# - GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET (for user auth)
# - GOOGLE_BUSINESS_CLIENT_ID & GOOGLE_BUSINESS_CLIENT_SECRET (for integrations)
# - SESSION_SECRET (generate with: openssl rand -base64 32)
# - ENCRYPTION_KEY (generate with: openssl rand -base64 32)# Start PostgreSQL, Redis, pgAdmin, Redis Commander
pnpm docker:up
# Verify services are running
docker ps# Generate Prisma client
pnpm db:generate
# Run migrations
pnpm db:migrate:dev
# (Optional) Seed development data
pnpm db:seed# Start both API and Web concurrently
pnpm dev
# Or start individually:
pnpm dev:api # http://localhost:3001
pnpm dev:web # http://localhost:3000Once everything is running, verify the services:
| Service | URL | Credentials |
|---|---|---|
| Web App | http://localhost:3000 | - |
| API Health | http://localhost:3001/health | - |
| pgAdmin | http://localhost:5050 | admin@revwave.local / admin123 |
| Redis Commander | http://localhost:8081 | - |
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Navigate to Credentials β Create Credentials β OAuth 2.0 Client ID
- Configure OAuth consent screen (add your email as test user)
- Create OAuth client:
- Application type: Web application
- Authorized redirect URIs:
http://localhost:3001/auth/google/callback
- Copy credentials to
.env:GOOGLE_CLIENT_ID="your-client-id" GOOGLE_CLIENT_SECRET="your-client-secret"
- In the same Google Cloud project
- Enable Google My Business API
- Create another OAuth 2.0 Client ID (or use the same)
- Add redirect URI:
http://localhost:3001/integrations/google/callback - Add to
.env:GOOGLE_BUSINESS_CLIENT_ID="your-business-client-id" GOOGLE_BUSINESS_CLIENT_SECRET="your-business-client-secret"
revWave/
βββ apps/
β βββ api/ # NestJS backend
β β βββ src/
β β β βββ auth/ # Authentication (Google OAuth, sessions)
β β β βββ integrations/ # Google Business Profile integration
β β β βββ common/ # Guards, decorators, crypto
β β β βββ database/ # Prisma service
β β β βββ health/ # Health check endpoint
β β βββ test/
β βββ web/ # Next.js frontend
β βββ src/
β β βββ app/ # App router pages
β β βββ components/ # React components
β β βββ lib/ # Utilities, API client
β βββ public/
βββ packages/
β βββ db/ # Shared Prisma schema
β βββ prisma/
β β βββ schema.prisma # Database schema
β β βββ migrations/ # Migration history
β β βββ seed.ts # Seed data
β βββ src/
βββ infra/ # Docker infrastructure
β βββ docker-compose.yml
βββ docs/ # Documentation
βββ package.json # Root package.json
pnpm dev # Start all apps in development mode
pnpm dev:api # Start API only
pnpm dev:web # Start web onlypnpm db:generate # Generate Prisma client
pnpm db:migrate # Run production migrations
pnpm db:migrate:dev # Run development migrations
pnpm db:push # Push schema to database (dev only)
pnpm db:studio # Open Prisma Studio
pnpm db:seed # Seed database
pnpm db:reset # Reset database (dev only)pnpm docker:up # Start infrastructure
pnpm docker:down # Stop infrastructure
pnpm docker:logs # View logspnpm lint # Lint all packages
pnpm lint:fix # Fix linting issues
pnpm format # Format code with Prettier
pnpm typecheck # Type check all packages
pnpm test # Run testspnpm build # Build all packages
pnpm build:api # Build API only
pnpm build:web # Build web only# Run all tests
pnpm test
# Run tests for specific module
pnpm test contacts.service.spec.ts
pnpm test campaigns.service.spec.ts
pnpm test unsubscribe.service.spec.ts
# Run tests with coverage
pnpm test:covpnpm docker:up
pnpm db:generate
pnpm db:migrate:dev
pnpm dev- Visit http://localhost:3000
- Click "Sign in with Google"
- Complete OAuth flow
- Should redirect to dashboard
- Verify session cookie is set (HttpOnly)
- Navigate to
/crm/contacts - Click "Add Contact"
- Fill form: email (required), name, phone, source
- Submit and verify contact appears in table
- Test "Revoke Consent" action
- Verify contact status changes to "Unsubscribed"
- Navigate to
/crm/campaigns - Click "New Campaign"
- Fill form:
- Name: "Test Campaign"
- Subject: "Test Email"
- Body (HTML):
<h1>Hello</h1><p>This is a test email.</p>
- Submit and verify redirect to campaign detail page
- On campaign detail page, click "Send Campaign"
- Confirm sending
- Verify:
- Campaign status changes to "scheduled" then "sent"
- Recipients are created (only for subscribed contacts)
- Delivery report shows stats (total, sent, failed, skipped)
- Find a campaign recipient with unsubscribe token
- Visit:
http://localhost:3000/unsubscribe/{token} - Verify:
- Page redirects to API endpoint
- API processes unsubscribe
- Redirects back with success message
- Contact consent status is revoked
- Recipient status is
skipped_unsubscribed(if pending)
# Check health
curl http://localhost:3001/health
# Get campaigns (requires auth cookie)
curl -b cookies.txt http://localhost:3001/campaigns
# Create campaign (requires auth cookie)
curl -X POST -b cookies.txt \
-H "Content-Type: application/json" \
-d '{"name":"Test","subject":"Test","bodyHtml":"<p>Test</p>"}' \
http://localhost:3001/campaigns
# Test unsubscribe (public, no auth)
curl http://localhost:3001/unsubscribe/{valid-token}- Open pgAdmin: http://localhost:5050
- Check tables:
contacts- verify tenant scopingcampaigns- verify tenant scopingcampaign_recipients- verify unsubscribe tokens- Verify all queries include
tenant_idfilter
The application uses a multi-tenant architecture with the following core models:
- User - Application users (Google OAuth)
- Tenant - Customer organizations (auto-created on first login)
- Membership - User-to-Tenant relationships with roles
- Session - Server-side session storage
- Integration - Google Business Profile connections (encrypted tokens)
- Location - Business locations from Google
- Review - Customer reviews
- Tag - NFC/QR tags for review collection
- Contact - CRM contacts
- Campaign - Email campaigns
- β HttpOnly Cookies - Session cookies not accessible via JavaScript
- β Token Encryption - OAuth tokens encrypted at rest with AES-256-GCM
- β Tenant Isolation - All queries scoped to tenant context
- β CSRF Protection - SameSite cookie attribute
- β Rate Limiting - Configurable rate limits on public endpoints
- β Environment Variables - Sensitive config never committed
We welcome contributions! Please see CONTRIBUTING.md for details.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Next.js
- Powered by NestJS
- Database by Prisma
- Deployed on Vercel (frontend) and Railway (backend)
- π§ Email: support@revwave.com
- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
Made with β€οΈ for local businesses