Files
DentalManagementMH06/README.md
ff ddcc49b72c feat: add in-browser dial pad with Twilio Voice SDK
- New DialPad component on Patient Connection page: clickable keypad,
  call/hangup/mute buttons, duration timer, keyboard input support
- Backend: POST /api/twilio/voice-token issues Access Token for browser
  Device; POST /api/twilio/webhook/voice-browser is the TwiML webhook
  Twilio calls to bridge the browser to the patient's phone
- TwiML App SID field added to Twilio Settings (stored in templates JSON)
- README: one-time Twilio Console setup instructions for the dial pad

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 22:24:53 -04:00

476 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Dental Manager - Starter
A monorepo setup to manage both Backend and Frontend of the Dental Manager application.
## 🖥️ Setup Guide (Fresh Machine)
Follow these steps in order after cloning the repository.
### Step 1 — Clone the repository
```sh
git clone <your-repo-url>
cd DentalManagementMHAprilgg
```
### Step 2 — Install Node.js
Required to run the Backend and Frontend.
```sh
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify
node -v # should print v20.x.x
npm -v
```
### Step 3 — Install Python
Required to run the Selenium and OCR services.
```sh
sudo apt-get install -y python3 python3-pip python3-venv
# Verify
python3 --version # should print 3.10 or higher
```
### Step 4 — Install Chrome
Required for the Selenium service to control a browser.
```sh
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt-get update
sudo apt-get install -y google-chrome-stable
# Verify
google-chrome --version
```
> The `webdriver-manager` package (included in `requirements.txt`) automatically downloads the matching ChromeDriver — no manual driver setup needed.
### Step 5 — Install PostgreSQL
Primary database for the application.
```sh
sudo apt-get install -y postgresql postgresql-contrib
sudo systemctl enable postgresql
sudo systemctl start postgresql
# Create the database the app uses
sudo -u postgres psql -c "CREATE DATABASE dentalapp OWNER postgres;"
# Set the postgres user password to match packages/db/.env
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'mypassword';"
```
#### Enable password authentication over TCP
By default Debian uses `scram-sha-256` or `peer` auth for local connections, which blocks password login. Switch to `md5`:
```sh
sudo sed -i 's/scram-sha-256/md5/g' /etc/postgresql/*/main/pg_hba.conf
sudo systemctl restart postgresql
```
#### Verify
```sh
psql -U postgres -d dentalapp -h 127.0.0.1 -W
# Enter: mypassword
# You should see: dentalapp=#
```
> The `DATABASE_URL` in `packages/db/.env` is already set to:
> ```
> DATABASE_URL="postgresql://postgres:mypassword@localhost:5432/dentalapp"
> ```
### Step 6 — Install Redis
Used as the job queue for Selenium and OCR background tasks.
```sh
sudo apt-get install -y redis-server
sudo systemctl enable redis-server
sudo systemctl start redis-server
# Verify
redis-cli ping # should print: PONG
```
### Step 7 — Install Node.js dependencies
```sh
npm install
```
### Step 8 — Install Python dependencies
Python dependencies are installed automatically by `npm install` (Step 7) via each service's `postinstall` script. Each service creates its own `.venv` virtual environment — no manual pip commands needed.
> This approach is required on Debian 13+ where system-wide pip installs are blocked (PEP 668).
### Step 9 — Set up environment variables
Copy the `.env.example` files and fill in the required values.
```sh
npm run setup:env
```
### Step 9a — Set the Cloudflare subdomain for this office
After running `npm run setup:env`, open the two `.env` files and fill in this office's Cloudflare subdomain.
**`apps/Frontend/.env`**
```env
VITE_CLOUDFLARE_HOST=yoursubdomain.mydentalofficemanagement.com
```
**`apps/Backend/.env`**
```env
CLOUDFLARE_HOST=yoursubdomain.mydentalofficemanagement.com
FRONTEND_URLS=http://localhost:3000,http://yoursubdomain.mydentalofficemanagement.com,https://yoursubdomain.mydentalofficemanagement.com
```
Replace `yoursubdomain` with this office's actual subdomain (e.g. `summitdentalcare`).
> If you skip this step, Cloudflare tunnel access will not work. LAN access still works without it.
### Step 10 — Set up the database
```sh
# Run migrations
npm run db:migrate
# Generate Prisma types
npm run db:generate
# Insert seed data
npm run db:seed
```
### Step 11 — Configure nginx
The repo includes `nginx.conf` in the project root. Install it as the active site config:
```sh
sudo cp nginx.conf /etc/nginx/sites-available/dental-app
sudo ln -sf /etc/nginx/sites-available/dental-app /etc/nginx/sites-enabled/dental-app
sudo nginx -t && sudo systemctl reload nginx
```
> **Important:** The `/api/` location block must include `proxy_set_header Authorization $http_authorization;`
> Without it, nginx strips the Authorization header and the backend returns "Access denied. No token provided."
### Step 12 — Run the app
Open two terminals:
**Terminal 1** — Backend + Frontend:
```sh
npm run dev
```
> On first boot the server automatically seeds all AI chat templates, SMS templates, and greeting messages for every user — no manual configuration needed.
**Terminal 2** — Selenium service:
```sh
cd apps/SeleniumService
.venv/bin/python3 agent.py
```
### Step 13 — Create desktop shortcuts (optional)
Instead of opening two terminals manually every time, you can create desktop shortcuts that start and stop all services with a single double-click.
Run this once after cloning and installing:
```sh
bash ~/Desktop/DentalManagementMH06/setup-desktop-shortcut.sh
```
Two shortcuts will appear on your desktop:
- **Dental App** — starts the app. Double-clicking it opens:
- A terminal running `npm run dev` (Backend + Frontend + Python services)
- A terminal running the Selenium service
- **Stop Dental App** — stops all services and frees all ports (5000, 5001, 5002, 5003, 3000/3001)
> No username or path editing needed — the script automatically detects the current user's home folder.
---
## 📖 Developer Documentation
- [Setting up server environment](docs/server-setup.md) — the first step, to run this app in environment.
- [Development Hosts & Ports](docs/ports.md) — which app runs on which host/port, and how to configure `.env` for LAN or single-device access
---
## This is a Turborepo. What's inside?
### Apps and Packages
- `apps/Backend` — Express.js API server
- `apps/Frontend` — React + Vite frontend
- `apps/SeleniumService` — Python FastAPI service for browser automation (insurance eligibility, claims)
- `apps/PaymentOCRService` — Python service for payment OCR extraction
- `@repo/eslint-config` — shared ESLint configuration
- `@repo/typescript-config` — shared `tsconfig.json`s
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/) (except the Python services).
### Utilities
- [Tailwind CSS](https://tailwindcss.com/) for styles
- [TypeScript](https://www.typescriptlang.org/) for static type checking
- [ESLint](https://eslint.org/) for code linting
- [Prettier](https://prettier.io) for code formatting
---
## Cloudflare Tunnel Setup (Remote Access per Office)
This connects each office's local app to a public subdomain via a Cloudflare Tunnel — no port forwarding needed, local network access is unchanged.
**How it works:**
- Local network: other office PCs reach the app directly at `http://<machine-ip>:3000`
- Internet: anyone reaches the app via `https://<subdomain>.mydentalofficemanagement.com`
- Both paths hit the same app simultaneously with no conflict
---
### Step 1 — Add domain to Cloudflare (done once for all offices)
> Skip this step if the domain is already on Cloudflare.
1. Go to `dash.cloudflare.com`**Add a site** → enter `mydentalofficemanagement.com` (free plan)
2. Cloudflare scans existing DNS records — review and keep them
3. Cloudflare gives you 2 nameservers (e.g. `holly.ns.cloudflare.com`, `amir.ns.cloudflare.com`)
4. Log into **Ionos** → replace the domain's nameservers with Cloudflare's two
5. Wait 1030 min → Cloudflare emails you when active
6. DNSSEC: if not purchased on Ionos, it was never enabled — nothing to turn off
### Step 2 — Install `cloudflared` on the office PC
Run this in a terminal on the office machine:
```bash
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb
cloudflared --version
```
### Step 3 — Authenticate with Cloudflare
```bash
cloudflared tunnel login
```
A browser window opens → click `mydentalofficemanagement.com` → terminal shows success and saves a certificate to `~/.cloudflared/cert.pem`.
### Step 4 — Create a tunnel for this office
Use a unique tunnel name per office:
```bash
cloudflared tunnel create <office-tunnel-name>
# Example: cloudflared tunnel create summit-dental-app
```
Note the **tunnel ID** (UUID) printed — you need it in Step 5.
### Step 5 — Create the config file
```bash
sudo mkdir -p /etc/cloudflared
sudo nano /etc/cloudflared/config.yml
```
Paste (replace tunnel ID, credentials path, and subdomain for this office):
```yaml
tunnel: <tunnel-ID>
credentials-file: /home/ee/.cloudflared/<tunnel-ID>.json
ingress:
- hostname: <subdomain>.mydentalofficemanagement.com
service: http://localhost:3000
- service: http_status:404
```
Save: `Ctrl+O` → Enter → `Ctrl+X`.
### Step 6 — Route DNS for this office's subdomain
```bash
cloudflared tunnel route dns <office-tunnel-name> <subdomain>.mydentalofficemanagement.com
```
### Step 7 — Install as a system service (auto-starts on boot)
```bash
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared
```
### Step 8 — Allow the subdomain in Vite
In `apps/Frontend/vite.config.js`, add the subdomain to `server.allowedHosts` so Vite does not block external requests.
### Step 9 — Allow the subdomain in backend CORS
In `apps/Backend`, add the subdomain URL to the allowed CORS origins so login and API calls work from the public URL.
---
### Example — Adding Summit Dental Care
**Office subdomain:** `summitdentalcare.mydentalofficemanagement.com`
**Step 1:** Already done (domain is on Cloudflare).
**Step 2:** Install `cloudflared` on Summit Dental's PC.
**Step 3:** Run `cloudflared tunnel login` on Summit Dental's PC.
**Step 4:**
```bash
cloudflared tunnel create summit-dental-app
# Example output: Created tunnel summit-dental-app with id a1b2c3d4-...
```
**Step 5 — `/etc/cloudflared/config.yml`:**
```yaml
tunnel: a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /home/ee/.cloudflared/a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
ingress:
- hostname: summitdentalcare.mydentalofficemanagement.com
service: http://localhost:3000
- service: http_status:404
```
**Step 6:**
```bash
cloudflared tunnel route dns summit-dental-app summitdentalcare.mydentalofficemanagement.com
```
**Step 7:**
```bash
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
```
**Step 8:** Add `summitdentalcare.mydentalofficemanagement.com` to `allowedHosts` in `vite.config.js`.
**Step 9:** Add `https://summitdentalcare.mydentalofficemanagement.com` to backend CORS allowed origins.
---
### Multi-office overview
Each office runs its own `cloudflared` tunnel on its own PC. Ports never conflict because each PC is a separate machine.
| Office | Local access | Public access | Tunnel name |
|---|---|---|---|
| Community Dentists of Lowell | `http://192.168.1.236:3000` | `https://communitydentistsoflowell.mydentalofficemanagement.com` | `dental-app` |
| Summit Dental Care | `http://<its-ip>:3000` | `https://summitdentalcare.mydentalofficemanagement.com` | `summit-dental-app` |
| Next office | `http://<its-ip>:3000` | `https://<subdomain>.mydentalofficemanagement.com` | `<office>-app` |
---
## Twilio In-Browser Calling Setup (Dial Pad)
The dial pad on the Patient Connection page lets staff make real phone calls directly through the browser (mic + speaker) using Twilio Voice SDK. One-time setup is required in the Twilio Console.
### One-time Twilio Console setup (required before first call)
1. Go to **Twilio Console → Explore Products → Voice → TwiML Apps**
2. Click **Create new TwiML App**
3. Set the **Voice Request URL** to:
```
https://communitydentistsoflowell.mydentalofficemanagement.com/api/twilio/webhook/voice-browser
```
4. Save — copy the **TwiML App SID** (starts with `AP`)
5. In the dental app, go to **Settings → Twilio Settings → TwiML App SID** → paste the SID and save
Once saved, the dial pad on the Patient Connection page is fully functional. The staff member's browser mic/speaker is used for the call; the patient receives a normal phone call from the office Twilio number.
---
## License Key Generator
The license key generator is a private tool that lives only on your dev PC. Use it to generate a new license key for any office every 3 months.
**Location:** `/home/ff/Desktop/LicenseGenerator/`
**Generate a 3-month key (default):**
```bash
node /home/ff/Desktop/LicenseGenerator/generate-license.js
```
**Generate a key with a custom duration:**
```bash
node /home/ff/Desktop/LicenseGenerator/generate-license.js --months=6
```
**Example output:**
```
=== Dental App License Key ===
License Key: DENTAL-8ED7AAEF3E0CA008D98CC1E0-2026-08-26
Expires: 2026-08-26
Duration: 3 month(s)
Paste this key into the Activation page in the app.
```
**Workflow:**
1. Office pays renewal fee
2. Run the generator script above
3. Copy the License Key
4. Paste it into the **Activation** page in the app (via RustDesk or in person)
5. Record the key, office name, expiry, and payment in your records
**Important — `secret.key`:**
- `/home/ff/Desktop/LicenseGenerator/secret.key` is the private secret used to sign all keys
- Back it up on a USB drive or password manager
- If lost, all existing keys become invalid and new keys must be issued to all offices
---
## Claude Code Memory
Claude Code (the AI assistant used to build this project) stores its memory locally on the PC. This memory contains project context, architecture decisions, feature history, and working preferences — allowing Claude to pick up where it left off in new sessions.
**Memory location:**
```
/home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
```
**To copy to a new PC:**
1. On the old PC, copy the memory folder:
```bash
cp -r /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/ /media/usb/claude-memory-backup/
```
2. On the new PC, recreate the directory and paste:
```bash
mkdir -p /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
cp -r /media/usb/claude-memory-backup/* /home/ff/.claude/projects/-home-ff-Desktop-DentalManagementMH06/memory/
```
The memory is plain markdown files and can also be copied manually via a USB drive or file manager. Enable "show hidden files" (Ctrl+H) in the file manager to see the `.claude` folder.