# Dental Manager - Starter

A monorepo setup to manage both Backend and Frontend of the Dental Manager application.

## 🔒 Security Setup (First Thing on a New PC)

### Step 1 — Change the user password

```bash
passwd
```

It will ask for the current password, then the new password twice.

### Step 2 — Set the root password

```bash
sudo passwd root
```

Enter a strong password. This prevents anyone from getting root access with a blank password.

### Step 3 — Check that SSH is not running

```bash
systemctl status sshd
```

If it shows "inactive (dead)" or "not found", you're safe — no one can remotely access this PC via SSH.

If it shows "active (running)", disable it:

```bash
sudo systemctl stop sshd && sudo systemctl disable sshd
```

---

## 🖥️ 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 10–30 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

---

## Network Backup Setup (PC-to-PC Sync)

Two PCs running the app can be linked so the backup PC automatically pulls a fresh copy of the main PC's database every night. The config survives database restores because it is stored in local files, not in the database.

**Prerequisites:** Both PCs must be on the same local network (e.g. connected to the same router or switch). Set a static IP on the main PC so its address never changes after a reboot (set in the OS network settings, not in the router).

### On PC1 (main server)

1. Open the app → **Database Management** → **Network Backup**
2. Under **This Machine's Backup Key**, click the eye icon to reveal the key
3. Click the copy button to copy it

### On PC2 (backup PC)

1. Open the app → **Database Management** → **Network Backup**
2. Under **Sync from Another PC**:
   - Toggle **Enable daily sync** on
   - Select the hour you want the sync to run (e.g. `12:00 AM (midnight)`)
   - Enter PC1's URL in the **Source PC URL** field, e.g. `http://192.168.0.94:3000`
   - Paste PC1's key into the **Source PC API Key** field
3. Click **Save Settings**
4. Click **Sync Now** to test — PC2's database will be replaced with PC1's

After a successful test, the sync will run automatically at the scheduled hour every day.

**Notes:**
- The API key and sync config are stored in `apps/Backend/network-backup-key.json` and `apps/Backend/network-sync-config.json` — they survive database restores
- If you regenerate PC1's key, you must update it on PC2 as well
- The sync is one-way: PC2 always mirrors PC1; PC1 is never modified

---

## Compile & Deploy to New PC

### The Plan

On your dev PC, run `npm run build` — this compiles the Frontend (React → `dist/`) and Backend (TypeScript → JS). The result is placed in a new deploy folder that contains no source code, then copied via USB to the office PC.

```
/home/ff/Desktop/DentalManagement-Deploy/   ← copy this to the new PC (no source code inside)
```

### New Office PC — One-time Setup (manual)

| Step | How |
|---|---|
| Install Linux, Chrome, PostgreSQL, Python, Node | Manually |
| Install RustDesk | Manually |
| Paste the deploy folder from USB | You |
| Run `setup.sh` | You |
| Enter license key in Activation page | You |

### Updates (after bug fixes)

Build on dev PC → copy only the updated `dist/` folders via USB to the office PC (not the whole folder):

```
apps/Backend/dist/    →   /home/ff/Desktop/DentalManagement-Deploy/apps/Backend/dist/
apps/Frontend/dist/   →   /home/ff/Desktop/DentalManagement-Deploy/apps/Frontend/dist/
```

### License System

- All PCs (including your dev PC) need a license key every 3 months
- Keys have no machine ID — just an expiry date + your HMAC signature
- Generate a key: `node /home/ff/Desktop/LicenseGenerator/generate-license.js`
- Key format: `DENTAL-{24-char-signature}-YYYY-MM-DD`
- `secret.key` must be backed up — losing it invalidates all existing keys

### Free vs Premium

| Tier | Features |
|---|---|
| **Free** | MassHealth Eligibility, MassHealth Claim, Documents, Payments, Database Backups, Reports |
| **Premium (license required)** | CCA, DDMA, United, Tufts Eligibility & Claims, Pre-Auths, AI SMS |

---

## 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.
