From 4acc4fb84c810da6103cbf8ea0afcfdde29e7ab1 Mon Sep 17 00:00:00 2001 From: Ravi Gulhane <> Date: Thu, 29 Oct 2026 09:31:10 +0530 Subject: [PATCH] Changes --- README.md | 25 +++- backend/ecosystem.config.cjs | 19 +++ deploy.sh | 141 +++++++++++++++++++++ deploy/nginx-tgics.conf | 36 ++++++ frontend/src/components/DoctorsSection.jsx | 66 ++++++++-- frontend/src/pages/Dashboard.css | 1 + 6 files changed, 274 insertions(+), 14 deletions(-) create mode 100644 backend/ecosystem.config.cjs create mode 100755 deploy.sh create mode 100644 deploy/nginx-tgics.conf diff --git a/README.md b/README.md index 63f35dc..6f792fe 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,30 @@ npm run dev Set `VITE_API_BASE=http://localhost:3050`. -## Nginx (production sketch) +## Deploy (EC2) -Serve the React build on `tgics.cttr.in` and proxy `/api` (and `/health`) to the Hapi process, **or** set `VITE_API_BASE` to a separate API host. +```bash +cd /Applications/MAMP/htdocs/shabby/tgics +./deploy.sh +``` + +Defaults: + +| Setting | Value | +|---------|--------| +| SSH | `ssh -i ~/Documents/Keys/VirtualEngage.pem ubuntu@15.207.15.52` | +| Path | `/var/www/tgics` | +| Site | `https://tgics.cttr.in` | +| PM2 | `tgics-api` on `127.0.0.1:3050` | + +The script builds the frontend locally (same-origin `/api`), rsyncs to the server, installs nginx (`deploy/nginx-tgics.conf`), and reloads PM2. Remote `backend/.env` is never overwritten. + +First-time after deploy: + +1. DNS: `tgics.cttr.in` → `15.207.15.52` +2. Edit `/var/www/tgics/backend/.env` (`DB_*`, `RAPPORT_ACCESS_TOKEN`, `JWT_SECRET`) +3. `pm2 restart tgics-api --update-env` +4. `sudo certbot --nginx -d tgics.cttr.in` ## Rapport Laravel diff --git a/backend/ecosystem.config.cjs b/backend/ecosystem.config.cjs new file mode 100644 index 0000000..dd8986a --- /dev/null +++ b/backend/ecosystem.config.cjs @@ -0,0 +1,19 @@ +const path = require('node:path'); + +module.exports = { + apps: [ + { + name: 'tgics-api', + cwd: __dirname, + script: path.join(__dirname, 'src/server.js'), + interpreter: 'node', + instances: 1, + exec_mode: 'fork', + autorestart: true, + max_memory_restart: '512M', + env: { + NODE_ENV: 'production', + }, + }, + ], +}; diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..0ef2d36 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash +# Deploy TGICS (frontend + API) to the VirtualEngage EC2 host. +# +# Usage: +# ./deploy.sh +# +# Env overrides: +# DEPLOY_KEY default ~/Documents/Keys/VirtualEngage.pem +# DEPLOY_HOST default ubuntu@15.207.15.52 +# DEPLOY_PATH default /var/www/tgics +# DEPLOY_SITE default https://tgics.cttr.in +# API_PORT default 3050 +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && pwd)" +SSH_KEY="${DEPLOY_KEY:-$HOME/Documents/Keys/VirtualEngage.pem}" +SSH_HOST="${DEPLOY_HOST:-ubuntu@15.207.15.52}" +REMOTE_DIR="${DEPLOY_PATH:-/var/www/tgics}" +SITE="${DEPLOY_SITE:-https://tgics.cttr.in}" +API_PORT="${API_PORT:-3050}" +PM2_NAME="tgics-api" + +SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new) + +ssh_cmd() { + ssh "${SSH_OPTS[@]}" "$SSH_HOST" "$@" +} + +rsync_cmd() { + rsync -az --delete --progress \ + -e "ssh ${SSH_OPTS[*]}" \ + "$@" +} + +if [[ ! -f "$SSH_KEY" ]]; then + echo "SSH key not found: $SSH_KEY" >&2 + exit 1 +fi +chmod 400 "$SSH_KEY" 2>/dev/null || true + +echo "==> Building frontend (same-origin API via nginx)" +( + cd "$ROOT/frontend" + npm ci --cache "$ROOT/.npm-cache" + # Empty base → browser calls /api on tgics.cttr.in (proxied to Hapi) + VITE_API_BASE= npx vite build +) + +echo "==> Ensuring remote directories" +ssh_cmd "sudo mkdir -p '$REMOTE_DIR/frontend/dist' '$REMOTE_DIR/backend' '$REMOTE_DIR/deploy' && sudo chown -R ubuntu:ubuntu '$REMOTE_DIR'" + +echo "==> Syncing frontend build" +rsync_cmd \ + "$ROOT/frontend/dist/" \ + "$SSH_HOST:$REMOTE_DIR/frontend/dist/" + +echo "==> Syncing backend (keeps remote .env)" +rsync_cmd \ + --exclude node_modules \ + --exclude .env \ + "$ROOT/backend/" \ + "$SSH_HOST:$REMOTE_DIR/backend/" + +echo "==> Syncing deploy assets" +rsync_cmd \ + "$ROOT/deploy/" \ + "$SSH_HOST:$REMOTE_DIR/deploy/" + +echo "==> Installing deps, configuring nginx, restarting PM2" +ssh_cmd \ + "REMOTE_DIR=$(printf '%q' "$REMOTE_DIR") SITE=$(printf '%q' "$SITE") API_PORT=$(printf '%q' "$API_PORT") PM2_NAME=$(printf '%q' "$PM2_NAME") bash -s" <<'REMOTE' +set -euo pipefail +export PATH="/usr/bin:/usr/local/bin:$PATH" + +cd "$REMOTE_DIR/backend" + +if [[ ! -f .env ]]; then + if [[ ! -f .env.example ]]; then + echo "Missing $REMOTE_DIR/backend/.env on the server." >&2 + exit 1 + fi + cp .env.example .env + python3 - </dev/null 2>&1; then + sudo npm install -g pm2 +fi + +npm ci --omit=dev + +pm2 startOrReload ecosystem.config.cjs --update-env +pm2 save +pm2 status "$PM2_NAME" + +NGINX_AVAIL="/etc/nginx/sites-available/tgics" +NGINX_ENABLED="/etc/nginx/sites-enabled/tgics" +if [[ -f "$REMOTE_DIR/deploy/nginx-tgics.conf" ]]; then + sudo cp "$REMOTE_DIR/deploy/nginx-tgics.conf" "$NGINX_AVAIL" + sudo ln -sfn "$NGINX_AVAIL" "$NGINX_ENABLED" + sudo nginx -t + sudo systemctl reload nginx + echo "Nginx site installed for tgics.cttr.in" +fi + +sleep 1 +curl -fsS "http://127.0.0.1:${API_PORT}/health" +echo +REMOTE + +echo "==> Deployed to $SSH_HOST:$REMOTE_DIR" +echo " Site: $SITE" +echo " API: pm2 $PM2_NAME → 127.0.0.1:$API_PORT" +echo " First time:" +echo " 1) Point DNS tgics.cttr.in → 15.207.15.52" +echo " 2) Edit $REMOTE_DIR/backend/.env (DB_*, RAPPORT_ACCESS_TOKEN, JWT_SECRET)" +echo " 3) ssh … 'cd $REMOTE_DIR/backend && pm2 restart $PM2_NAME --update-env'" +echo " 4) sudo certbot --nginx -d tgics.cttr.in" diff --git a/deploy/nginx-tgics.conf b/deploy/nginx-tgics.conf new file mode 100644 index 0000000..b638602 --- /dev/null +++ b/deploy/nginx-tgics.conf @@ -0,0 +1,36 @@ +# TGICS — https://tgics.cttr.in +# After DNS points to this host, run: +# sudo certbot --nginx -d tgics.cttr.in + +server { + listen 80; + server_name tgics.cttr.in; + + root /var/www/tgics/frontend/dist; + index index.html; + + gzip on; + gzip_types text/plain text/css application/javascript application/json image/svg+xml; + gzip_min_length 256; + + client_max_body_size 6m; + + location /api/ { + proxy_pass http://127.0.0.1:3050; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + proxy_pass http://127.0.0.1:3050/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/src/components/DoctorsSection.jsx b/frontend/src/components/DoctorsSection.jsx index a44e34a..7e9507a 100644 --- a/frontend/src/components/DoctorsSection.jsx +++ b/frontend/src/components/DoctorsSection.jsx @@ -5,6 +5,30 @@ import DashboardLoading from './DashboardLoading'; import exportCsv from './exportCsv'; import useDashboardData, { matchesQuery } from './useDashboardData'; +const SURVEY_ORIGIN = 'https://tgics.cttr.in'; + +function doctorWhatsappLink(row) { + const userId = String(row.user_id || '').trim(); + const contactId = String(row.contact_id || '').trim(); + if (!userId || !contactId) { + return { href: '', label: 'Link unavailable' }; + } + let digits = String(row.phone || '').replace(/\D+/g, ''); + if (digits.length === 10) { + digits = `91${digits}`; + } + if (!digits) { + return { href: '', label: 'No WhatsApp number' }; + } + const name = String(row.name || '').trim(); + const link = `${SURVEY_ORIGIN}/${userId}/${contactId}`; + const text = `Dear Doctor${name ? ` ${name}` : ''},\n\nYou have been approved for TGICS (The Great Indian Cough Study).\nPlease open the link to add your surveys:\n${link}`; + return { + href: `https://api.whatsapp.com/send?phone=${digits}&text=${encodeURIComponent(text)}`, + label: '', + }; +} + export default function DoctorsSection({ onUnauthorized }) { const { data, loading, error, load } = useDashboardData( fetchDashboardDoctors, @@ -136,21 +160,39 @@ export default function DoctorsSection({ onUnauthorized }) { Employee code Position name Total surveys + WhatsApp - {filtered.map((row, index) => ( - - {index + 1} - {row.name || '-'} - {row.msl_code || '-'} - {row.phone || '-'} - {row.mr_name || '-'} - {row.employee_code || '-'} - {row.position_name || '-'} - {row.total_surveys} - - ))} + {filtered.map((row, index) => { + const whatsapp = doctorWhatsappLink(row); + return ( + + {index + 1} + {row.name || '-'} + {row.msl_code || '-'} + {row.phone || '-'} + {row.mr_name || '-'} + {row.employee_code || '-'} + {row.position_name || '-'} + {row.total_surveys} + + {whatsapp.href ? ( + + Send WhatsApp + + ) : ( + {whatsapp.label} + )} + + + ); + })} diff --git a/frontend/src/pages/Dashboard.css b/frontend/src/pages/Dashboard.css index 29f3e81..ed6a055 100644 --- a/frontend/src/pages/Dashboard.css +++ b/frontend/src/pages/Dashboard.css @@ -256,6 +256,7 @@ .dash-btn.info { background: var(--dash-info); } .dash-btn.dark { background: var(--dash-dark); } .dash-btn.success { background: var(--dash-success); } +.dash-btn.whatsapp { background: #25d366; } .dash-btn.warning { background: var(--dash-warning); color: var(--dash-heading); } .dash-table {