The application ingests meeting audio, runs it through AWS Transcribe, and produces action-oriented notes with OpenAI. It contains an Express API, a React dashboard, and AWS Lambda workers that glue the pipeline together.
- 1. Overview
- 2. High-Level Design
- 3. Features
- 4. Limits
- 5. Architecture
- 6. Project Structure
- 7. Tech Stack
- 8. Prerequisites
- 9. Environment Variables
- 10. Getting Started
- 11. Running the Workers
- 12. API Endpoints
- 13. Database Schema
- 14. Troubleshooting
- 15. Useful Commands
ff-clone is a meeting assistant that lets users upload recorded conversations, automatically produces transcripts, and summarizes them into structured insights and action items. Audio is stored privately, transcription is performed via AWS Transcribe, and notes are generated with OpenAI responses adhering to a strict JSON schema.
- Authenticate & initiate meeting – the React dashboard exchanges Google OAuth credentials for a JWT issued by the Express API, then calls
POST /api/v1/meetingsto create a meeting stub with statusNONE. - Upload audio – the dashboard requests a presigned POST payload from
POST /api/v1/meetings/:uuid/upload, uploads audio directly to the raw S3 bucket, and the API marks the meetingRECORDING. S3 emits anObjectCreatedevent. - Kick off transcription – the Raw Audio bucket event invokes the Transcriber Lambda, which updates the meeting to
RECORDED, persists S3 metadata, and starts an AWS Transcribe job targeting the upload. - Collect transcript artifacts – AWS Transcribe writes JSON into the transcript bucket. The Post-Transcribe Lambda validates completion details, notifies the API, and the meeting progresses to
TRANSCRIBED. - Generate summaries – after a short defer timer, the API calls the OpenAI summarizer with the transcript URL. Structured notes are persisted, the meeting becomes
SUMMARIZED, and cache entries are primed for quick reads. - Serve results to the UI – the dashboard polls
GET /api/v1/meetings/:uuid/noteStatusuntilSUMMARIZED, then retrieves transcript and summary payloads. Subsequent reads can hit the cache with PostgreSQL as fallback.
| Status | Meaning | Set by |
|---|---|---|
NONE |
Meeting created; no audio yet | API (meeting.service.create) |
RECORDING |
Presigned upload issued / upload in flight | API (meeting.service.generateUploadPresignParams) |
RECORDED |
Audio stored in raw bucket | workers/transcriber/transcriber.aws.lambda.mjs |
TRANSCRIBED |
AWS Transcribe completed | workers/post-transcribe/post.transcribe.worker.aws.lambda.mjs |
SUMMARIZED |
OpenAI-generated notes stored | api/src/services/summarizer.service.ts |
FAILED |
Summarization failed | Summarizer error path |
View the full interaction sequence diagram.
- Google OAuth sign-in with JWT-protected API routes.
- Meeting creation, audio uploads via presigned S3 POST forms, and status tracking.
- Automatic transcription through AWS Transcribe with speaker diarization.
- Structured summaries (overview, key insights, action items) produced with OpenAI.
- React dashboard for listing meetings, viewing transcripts, and polling note status.
- Recording page for simulating live meeting recording.
- Sample audio for local experimentation (
sample-audio/with generated transcript fixtures).
- File upload limit via presigned S3 POST is restricted to 60 MB.
- Maximum audio track length is 5 minutes.
- Default API rate limit is 60 rpm; override with
RATE_LIMIT_RPM. - Frontend is desktop-only at the moment (not mobile responsive).
- Express + TypeScript service (
api/src/app.ts) with helmet, CORS, and rate limiting. - Prisma/PostgreSQL persistence (
api/prisma/schema.prisma). - Google OAuth & JWT authentication via Passport (
api/src/utils/passport.util.ts). - Meeting workflow services (
meeting.service.ts,summarizer.service.ts,webhook.service.ts). - Pluggable cache abstraction (
api/src/services/cache.service.ts, NodeCache by default).
- Vite + React 19 SPA (
ui/src/main.tsx) using React Router. - Auth token management in
ui/src/components/AuthHandler. - Dashboard, Meeting Details (with status polling), and simulated recorder under
ui/src/pages.
workers/transcriber/handles raw audio S3 events; starts AWS Transcribe and marks meetingsRECORDED.workers/post-transcribe/handles transcript S3 events and marks meetingsTRANSCRIBED.- Root-level fixtures (sample audio + generated transcripts) for local testing.
- AWS S3 (raw + transcript buckets).
- AWS Transcribe.
- OpenAI Chat Completions API.
- Google OAuth 2.0.
/
├── api/ # Express backend
│ ├── config.ts # Centralised runtime config loader
│ ├── src/
│ │ ├── app.ts # Express bootstrap
│ │ ├── controllers/ # Route handlers (auth, meetings, webhook)
│ │ ├── middleware/ # Auth, validators, logger, error handler
│ │ │ └── validators/ # Zod schemas for request validation
│ │ ├── routes/ # Route registration modules
│ │ ├── services/ # Biz logic (meeting, summarizer, upload, DB, cache)
│ │ └── utils/ # AWS S3, JWT, OpenAI, strings, constants
│ ├── prisma/
│ │ ├── schema.prisma # Database schema and enums
│ │ └── migrations/ # Prisma migration files
│ ├── Dockerfile # Container build for API
│ ├── package.json / yarn.lock # Backend dependencies
│ └── dist/ # Transpiled JS output (generated)
├── ui/ # React dashboard
│ ├── src/
│ │ ├── assets/ # SVG/React icon components
│ │ ├── components/ # Shared UI (Navbar, StatusPill, AuthHandler, etc.)
│ │ ├── pages/ # Dashboard, MeetingDetails, RecordMeeting, Login
│ │ ├── services/ # Axios API client & auth helpers
│ │ ├── utils/ # Shared utilities and types
│ │ └── main.tsx # App entry point
│ ├── public/ # Static assets served by Vite
│ ├── vite.config.ts / tsconfig.* # Build & TypeScript configs
│ └── package.json / yarn.lock # Frontend dependencies
├── workers/ # AWS Lambda workers + fixtures
│ ├── transcriber/ # Raw-audio event handler package
│ │ ├── transcriber.aws.lambda.mjs # Lambda entrypoint
│ │ ├── package.json / yarn.lock # Worker dependencies
│ └── post-transcribe/ # Transcript event handler package
│ ├── post.transcribe.worker.aws.lambda.mjs
│ ├── package.json / yarn.lock
├── sample-audio/ # Sample audio files for quick testing
└── README.md # This documentation
- Backend: Node 20, Express 5, Prisma, PostgreSQL, Passport, AWS SDK v3, OpenAI SDK.
- Frontend: React 19, Vite 7, React Router 7, Axios, TypeScript 5.
- Workers: AWS Lambda (Node 20 runtime) with AWS Transcribe client and
fetch. - Infrastructure: AWS S3, AWS Transcribe, Redis-compatible cache, Google OAuth 2.0.
- Node.js 20+ and Yarn.
- PostgreSQL instance.
- AWS account with S3 buckets and Transcribe permissions.
- OpenAI API key (defaults to
gpt-4o-miniwhenOPENAI_MODELis unset). - Google OAuth web client credentials.
- Optional Redis deployment (backend falls back to NodeCache).
# APP
DATABASE_URL=postgresql://localuser:localpwd@localhost:5432/ffdb?schema=public
PORT=8081
RATE_LIMIT_RPM=3000
JWT_SECRET=jwt_secret
WEBHOOK_SECRET=webhook_secret
FE_DOMAIN_NAME=http://localhost:5173
# Google OAuth
GOOGLE_CLIENT_ID=xxxx
GOOGLE_CLIENT_SECRET=oauth_secret
# AWS
AWS_ACCESS_KEY_ID=aws_xxx_id
AWS_SECRET_ACCESS_KEY=aws_xxx_secret
AWS_REGION=ap-south-1
S3_BUCKET=example.bucket
S3_TRANSCRIPT_BUCKET=example.transcript.bucket
# OpenAI
OPENAI_API_KEY=sk-proj-xxxxxxxxxcd api && yarn install
cd ../ui && yarn installcd api
yarn prisma generate
yarn prisma migrate devcd api
yarn dev # http://localhost:8081cd ui
yarn dev # http://localhost:5173The frontend currently imports
BASE_URLfromui/src/services/api.service.ts; consider switching toVITE_API_URLfor multi-environment builds.
Visit /login on the frontend to initiate Google OAuth; the redirect stores a JWT in localStorage.
Use “Upload Meeting Audio” in the dashboard (≤ five-minute clips enforced). Try files in sample-audio/ for quick tests.
Open the meeting row to monitor status, transcript, and summary.
Without the AWS workers, meetings stay in
RECORDING. Simulate transitions locally byPOSTing to/api/v1/webhook/:meetingUuidwith the correct secret.
Each worker is packaged as its own mini-project under workers/.
Path: workers/transcriber/
Responsibilities: Handles s3:ObjectCreated:* events from the raw audio bucket, posts meeting metadata, starts AWS Transcribe, and marks status RECORDED.
Setup
cd workers/transcriber
yarn installRequired env
AWS_REGION=ap-south-1
TRANSCRIBE_OUTPUT_BUCKET=your-transcript-bucket
WEBHOOK_DOMAIN_NAME=https://your-api-domain
WEBHOOK_SECRET=super-secretDeployment tips
- Zip
transcriber.aws.lambda.mjstogether withnode_modules/. - Runtime: Node.js 20.x.
- Attach IAM policy allowing
s3:GetObject,s3:PutObject, andtranscribe:StartTranscriptionJob. - Configure the raw audio S3 bucket to trigger the Lambda on object creation.
Path: workers/post-transcribe/
Responsibilities: Handles s3:ObjectCreated:* events from the transcript bucket, signals the API that transcription finished, and marks status TRANSCRIBED.
Setup
cd workers/post-transcribe
yarn installRequired env
REGION=ap-south-1
WEBHOOK_DOMAIN_NAME=https://your-api-domain
WEBHOOK_SECRET=super-secretDeployment tips
- Zip
post.transcribe.worker.aws.lambda.mjstogether withnode_modules/. - Runtime: Node.js 20.x.
- IAM policy needs S3 read access for the transcript bucket (metadata only).
- Configure the transcript bucket to trigger this Lambda on new objects.
Base: http://localhost:8081/api
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /v1/auth/google | Begin Google OAuth flow | Public |
| GET | /v1/auth/google/redirect | OAuth callback handler | Public |
| POST | /v1/meetings | Create meeting | Bearer JWT |
| GET | /v1/meetings | List user meetings | Bearer JWT |
| GET | /v1/meetings/:uuid | Fetch meeting details | Bearer JWT |
| GET | /v1/meetings/:uuid/noteStatus | Fetch meeting note status | Bearer JWT |
| GET | /v1/meetings/:uuid/transcript | Fetch transcript JSON | Bearer JWT |
| POST | /v1/meetings/:uuid/upload/presign | Generate presigned S3 POST | Bearer JWT |
| POST | /v1/webhook/:meetingUuid | Worker webhook (status updates) | Webhook key |
- users: Google-authenticated users.
- meetings: Metadata, S3 keys, summary payloads, and processing status. See
api/prisma/schema.prismaand migrations for field-level details and indexes.
- 401/403 – token missing/expired or
JWT_SECRETmismatch. The UI clears tokens automatically on auth failures. - Stuck at
RECORDING– check the raw S3 bucket trigger for the Transcriber Lambda and confirmWEBHOOK_SECRET. - Transcript fetch errors – validate transcript bucket permissions and
S3_TRANSCRIPT_BUCKET. - No summaries – ensure
OPENAI_API_KEYis configured, the model is enabled, and inspect API logs for OpenAI errors. - CORS failures – align
FE_DOMAIN_NAMEwith the deployed frontend origin.
cd api
npx prisma generate
npx prisma migrate dev
yarn dev # start in dev mode
yarn startcd ui
yarn dev # start in dev mode
yarn buildcd workers/transcriber
yarn install
node transcriber.aws.lambda.mjs # run with a mocked event payload
cd ../post-transcribe
yarn install
node post.transcribe.worker.aws.lambda.mjs # run with a mocked event payload