PPPAIMT-Crest/files/cursor_prompts.md

547 lines
25 KiB
Markdown

# Extended-Dose Pack Study: Cursor Prompt Pack
## How to use this file
1. Create the project folder and open it in Cursor.
2. Copy the `docs/` folder (`agreement.md`, `questionnaire.md`) into the project root.
3. Complete **Step 0** below (project rules file). Cursor reads it in every chat.
4. Run the phase prompts **in order**, one per new Cursor chat (Agent mode).
5. After each phase: run the app, test the "Done when" list, commit to git, then move on.
Tip: if Cursor drifts, reply "Re-read .cursor/rules/project.mdc and continue."
---
## Step 0: Project rules file (do once)
Create `.cursor/rules/project.mdc` with this exact content:
```
---
description: Extended-Dose Pack Study project context
alwaysApply: true
---
# Project
Web app for CREST Private Limited (Pune). Physicians sign a Consultation
Agreement (MOU), admins approve or reject it, approved physicians receive a
single-use link to an online survey. Source texts: docs/agreement.md and
docs/questionnaire.md.
# Stack
- Backend: Node.js 20, Express, Knex + mysql2 (MySQL 8), Zod validation, Pino logging
- Frontend: React + Vite + React Router, React Hook Form + Zod, TanStack Query
- PDF: HTML template rendered with Puppeteer
- Email: SendGrid (@sendgrid/mail)
- Storage: AWS S3 (private bucket, presigned URLs only)
- Hosting: AWS EC2 (Nginx + PM2), AWS RDS MySQL
- Language: JavaScript with JSDoc, or TypeScript if I say so. Default: JavaScript.
- Monorepo layout: /server, /client, /docs
# Roles and flow
- Physician: NO login. Opens a public link, fills the agreement form, gets an
emailed private "resume link" (random token, only its SHA-256 hash stored).
- Application status: DRAFT -> SUBMITTED -> APPROVED | REJECTED.
REJECTED goes back to DRAFT when the physician edits; the old signature is
voided and the physician must sign again.
- Admin: email + password login, ONE role, multiple users.
- Survey: single-use signed token, no expiry, save progress until final submit,
link dies on final submit.
- CREST signatory: Dr Sudeep Salvi. Name, designation, signature image are
editable in admin Settings, never hard-coded. Signature is auto-applied on
approval.
- Physician signature: drawn or typed on screen. Always record timestamp, IP,
user agent.
# Engineering rules
- Never store secrets in code. Use .env and provide .env.example.
- All input validated with Zod on the server, even if validated on the client.
- Parameterised queries only (Knex). No string-built SQL.
- Every state change writes a row to audit_log (actor, action, entity, ip, time).
- Errors: central Express error handler, consistent JSON {error:{code,message}}.
- Tokens: crypto.randomBytes(32), stored hashed, compared in constant time.
- Small, testable modules. Add or update tests for what you build.
- Do not add features I did not ask for. Ask before deviating from this file.
- After finishing a task, list files changed and how to run/test it.
```
---
## Phase 0: Foundations
```
Read .cursor/rules/project.mdc first.
Goal: scaffold the monorepo and get a "hello world" deployable.
Tasks:
1. Create /server (Express, ES modules) with: GET /api/health returning
{status:"ok", time}. Use helmet, cors (origin from env), pino-http,
express-rate-limit, dotenv. Structure: src/app.js, src/server.js,
src/config/env.js (validate env with Zod), src/routes, src/middleware,
src/services, src/db.
2. Create /client with Vite + React + React Router + TanStack Query.
Routes: "/" placeholder. Proxy /api to the server in dev.
3. Root package.json with npm workspaces and scripts: dev (runs both),
lint, test, build.
4. ESLint + Prettier config shared by both packages. Vitest for both.
5. Knex setup in /server (knexfile.js, migrations folder, seeds folder) reading
DB settings from env. Do not write tables yet.
6. .env.example for server and client, listing every variable used
(DB_*, S3_*, SENDGRID_API_KEY, MAIL_FROM, APP_BASE_URL, JWT_SECRET,
TOKEN_PEPPER, RECAPTCHA_*).
7. .gitignore, README with setup steps.
8. Deployment files: ecosystem.config.cjs (PM2), an example nginx.conf
(serves client build, proxies /api to port 4000, gzip, security headers),
and docs/DEPLOY.md with step-by-step commands for Ubuntu on EC2
(Node install, PM2, Nginx, certbot HTTPS), RDS connection notes and the
S3 bucket policy (private, encrypted).
Done when: `npm run dev` runs both apps, /api/health works, lint and test pass.
```
---
## Phase 1: Database and backend core
```
Read .cursor/rules/project.mdc first.
Goal: create the schema, migrations and core backend services.
Create Knex migrations for these tables (utf8mb4, timestamps, sensible
indexes and foreign keys):
- applications: id, public_id (uuid, unique), email (indexed), status
(DRAFT/SUBMITTED/APPROVED/REJECTED), agreement_date, full_name, age,
address, registration_no (unique), medical_council, pan, gstin (nullable),
bank_name, account_holder, account_number, ifsc, accepted (boolean),
rejection_reason (nullable), submitted_at, decided_at, decided_by
(fk admin_users), draft_pdf_key, signed_pdf_key, final_pdf_key,
signed_pdf_sha256, version (int, increments on each edit).
- resume_tokens: id, application_id, token_hash, created_at, revoked_at.
- signatures: id, application_id, version, type (DRAWN/TYPED), image_key or
typed_text, signed_at, ip, user_agent, voided_at (nullable).
- admin_users: id, name, email (unique), password_hash (argon2 or bcrypt),
is_active, last_login_at.
- settings: key (pk), value (json). Seed keys: signatory_name
("Dr Sudeep Salvi"), signatory_designation (empty), signatory_signature_key
(empty), mail_from_name.
- audit_log: id, actor_type (ADMIN/PHYSICIAN/SYSTEM), actor_id, action,
entity_type, entity_id, metadata (json), ip, created_at.
- email_log: id, application_id, template, to_email, sendgrid_message_id,
status, error, created_at.
- survey_definitions: id, version, title, definition (json), is_active.
- survey_tokens: id, application_id, token_hash, created_at, used_at,
revoked_at.
- survey_responses: id, application_id, survey_definition_id, answers (json),
status (IN_PROGRESS/COMPLETED), started_at, completed_at, ip, user_agent.
Then build:
1. src/services/tokens.js: generateToken(), hashToken() (SHA-256 with
TOKEN_PEPPER), verifyToken(). Unit tests.
2. src/services/audit.js and an Express middleware helper to log actions.
3. src/services/storage.js: S3 put/get, presigned URL (5 min), with a local
disk fallback when NODE_ENV=development. Server-side encryption on upload.
4. Admin auth: POST /api/admin/auth/login, /logout, GET /me. Use httpOnly,
secure, sameSite cookies with a JWT or session. Account lockout after
5 failed attempts for 15 minutes. requireAdmin middleware.
5. A seed script `npm run seed:admin` that creates the first admin from env
variables ADMIN_EMAIL and ADMIN_PASSWORD.
6. Zod schemas for the agreement fields in src/schemas/application.js:
PAN /^[A-Z]{5}[0-9]{4}[A-Z]$/, IFSC /^[A-Z]{4}0[A-Z0-9]{6}$/, GSTIN
15-char pattern (optional), account number 9-18 digits, age 21-100,
registration number required.
Write integration tests with a test database.
Done when: `npm run migrate` builds the schema, admin can log in, tests pass.
```
---
## Phase 2: Agreement form (physician side)
```
Read .cursor/rules/project.mdc first. Use docs/agreement.md for the blanks.
Goal: public agreement form with save-draft and resume.
Backend:
1. POST /api/applications: body {email}. Verify CAPTCHA (reCAPTCHA v3 or
Cloudflare Turnstile, keep behind an env flag so dev can skip). Create a
DRAFT application, create a resume token, email the physician the resume
link {APP_BASE_URL}/resume/{token} via a mail service stub (real SendGrid
comes in Phase 5; log the link in dev).
If the email already has a DRAFT, resend a new link instead of creating a
duplicate.
2. GET /api/applications/me (auth by resume token in Authorization header):
returns the application.
3. PUT /api/applications/me: saves fields (partial allowed while DRAFT;
full validation when `finalize=true`). Reject edits if status is
SUBMITTED or APPROVED. Reject registration_no duplicates with a clear
message. Increment version on each change after PDF/signature exists.
4. Rate limit these endpoints. Audit log every write.
Frontend (client/src/pages):
1. /start: email field, CAPTCHA, "We will email you a private link".
2. /resume/:token: stores the token in memory/sessionStorage, redirects to
/agreement.
3. /agreement: React Hook Form + Zod with sections:
Agreement date (default today, editable), Physician details (name, age,
address, registration number, Medical Council, PAN, GSTIN optional),
Bank details (bank name, account holder name, account number, IFSC), and
a required "I accept" checkbox with a short statement that the physician
has read and accepts the agreement terms. Show the agreement text in a
scrollable read-only panel (from docs/agreement.md) above the checkbox.
Autosave draft every 10 seconds and via a "Save draft" button.
"Continue to review" button runs full validation.
4. Uppercase PAN/IFSC/GSTIN automatically. Mobile responsive. Accessible labels.
5. Clear error states for expired/invalid resume links.
Done when: a doctor can fill part of the form, close the tab, open the emailed
link and continue; duplicate registration numbers are blocked.
```
---
## Phase 3: PDF, review and signing
```
Read .cursor/rules/project.mdc first. Use docs/agreement.md as the full legal
text; do not change any wording.
Goal: generate the filled agreement PDF, let the physician review, edit, sign
and submit.
Backend:
1. src/pdf/agreement.html.js: an HTML template function that renders the
complete agreement text with the physician's data merged in place of the
blanks (date, name, age, address, registration number, Medical Council,
PAN, GSTIN, bank details, the acceptance line). Clean typography, A4,
CREST header/footer, page numbers, clause numbering preserved.
Signature block: physician signature image or typed name; CREST block shows
name/designation/signature from settings ONLY when the application is
APPROVED (otherwise show "Pending approval").
2. src/pdf/render.js: Puppeteer with a single reused browser instance,
--no-sandbox flags suitable for EC2, timeout, and a concurrency limit.
3. POST /api/applications/me/preview: validates the application, renders a
DRAFT PDF (watermark "DRAFT - NOT SIGNED"), stores it in S3, returns a
presigned URL.
4. POST /api/applications/me/sign: body {type: DRAWN|TYPED, image (PNG
base64, max 200KB) or typedName, consent: true}. Only allowed when the
application is DRAFT, fully valid, and accepted=true. Steps: save the
signature with IP, user agent and timestamp; render the signed PDF (no
watermark); compute SHA-256; upload to S3; set status SUBMITTED, submitted_at,
signed_pdf_key, signed_pdf_sha256; write audit log. Make it atomic.
5. POST /api/applications/me/edit: allowed for DRAFT or REJECTED. If REJECTED,
set status back to DRAFT, void the current signature (voided_at), bump
version, keep rejection_reason visible for reference.
Frontend:
1. /review: embedded PDF preview (iframe or react-pdf), buttons "Edit details"
(goes back to /agreement) and "Proceed to sign".
2. /sign: signature pad (react-signature-canvas) with Clear, plus a "Type
instead" tab using a signature-style font. Consent checkbox: "I agree
that this is my electronic signature." Submit button with loading state.
3. /submitted: confirmation with status timeline (Submitted, Under review,
Decision emailed).
4. If the status is SUBMITTED or APPROVED the form is read-only and shows the
signed PDF download.
5. If REJECTED, show a banner with the reason and an "Edit and resubmit"
button.
Tests: HTML template merges every field; sign endpoint rejects incomplete
data; a rejected then edited application voids the old signature.
Done when: full flow works from draft to a signed PDF stored in S3 and status
SUBMITTED.
```
---
## Phase 4: Admin panel
```
Read .cursor/rules/project.mdc first.
Goal: admin UI and APIs to review and decide on applications.
Backend (all behind requireAdmin):
1. GET /api/admin/dashboard: counts by status, submitted today/this week,
surveys completed, pending surveys, average review time.
2. GET /api/admin/applications: pagination, search (name, email, registration
no, PAN), filter by status, date range, sort.
3. GET /api/admin/applications/:id: all fields, signature metadata (time, IP,
device), PDF presigned URL, email log, audit trail for the record.
4. POST /api/admin/applications/:id/approve: only from SUBMITTED. Loads the
signatory name/designation/signature from settings (fail with a clear
error if the signature image is missing). Re-renders the PDF with the CREST
signature and the approval date, computes a new SHA-256, stores it as
final_pdf_key, sets APPROVED, decided_at, decided_by. Then creates the
survey token and triggers the approval email (mail service from Phase 5;
leave a clearly marked hook for now). All in a transaction with audit log.
5. POST /api/admin/applications/:id/reject: body {reason} (required, min 10
chars). Only from SUBMITTED. Sets REJECTED, stores the reason, triggers the
rejection email hook, and revokes nothing else.
6. Admin users CRUD (list, create, deactivate, reset password). Prevent an
admin from deactivating themselves or the last active admin.
7. Settings endpoints: GET/PUT signatory name and designation,
POST signature image upload (PNG only, max 500KB, transparent background
recommended, validate magic bytes, store in S3).
8. GET /api/admin/audit-log with filters (entity, actor, action, date).
Frontend (client/src/admin, route prefix /admin):
1. Login page, protected routes, logout, session expiry handling.
2. Dashboard with stat cards and a status chart (use recharts).
3. Applications table with search/filter/pagination; status badges.
4. Application detail: left side data, right side PDF viewer. Approve button
(confirm dialog) and Reject button (modal requiring a reason). Show the
timeline of events and emails sent.
5. Settings page (signatory name, designation, signature image preview),
Admin users page, Audit log page.
6. Clean, simple UI (Tailwind or shadcn/ui).
Tests: cannot approve unless SUBMITTED; cannot approve without signatory
signature configured; reject requires a reason; every decision is audited.
Done when: an admin can log in, review a submitted agreement, approve or
reject it, and the record and PDFs update correctly.
```
---
## Phase 5: Emails and the survey link
```
Read .cursor/rules/project.mdc first.
Goal: real transactional emails through SendGrid and the single-use survey link.
Tasks:
1. src/services/mail.js using @sendgrid/mail. A sendTemplate(name, to, data)
function that renders HTML + plain text, logs to email_log (status,
message id, errors), retries transient failures, and never throws into the
request path (queue-lite: fire after commit, log failures).
2. Email templates (responsive HTML + text), CREST branded:
a. resume_link: "Continue your agreement" with the private link.
b. submitted_ack: confirmation to the physician that the signed agreement
was received (attach the signed PDF).
c. admin_new_submission: to a list in env ADMIN_NOTIFY_EMAILS with a
link to the admin detail page.
d. approved: says the agreement is approved, attaches the final PDF, and
includes the survey link {APP_BASE_URL}/survey/{token}. State that the
link works for one submission.
e. rejected: states the reason and includes the resume link so the
physician can edit and resubmit. Create a fresh resume token and revoke
the old ones.
3. Wire the hooks from Phases 2, 3 and 4 to the real mail service.
4. Survey tokens: generate on approval, stored hashed in survey_tokens. The raw
token appears only in the email. Provide POST
/api/admin/applications/:id/resend-survey-link, which revokes the old
token and issues a new one (audit logged).
5. SendGrid event webhook endpoint POST /api/webhooks/sendgrid (verify the
signature) updating email_log with delivered, bounced, dropped.
Show delivery status in the admin detail page and warn on bounces.
6. Add docs/EMAIL.md: SendGrid domain authentication (SPF/DKIM/DMARC) steps,
env vars, and how to test using SendGrid sandbox mode.
Tests: the approval email contains a valid link; the token verifies once;
revoked tokens are refused; email failures do not break approval.
Done when: submitting, approving and rejecting each send the right email, and
the survey link in the approval email validates.
```
---
## Phase 6: Survey web form
```
Read .cursor/rules/project.mdc first. Use docs/questionnaire.md as the source.
Goal: build the survey as a data-driven web form.
Part A: survey definition (data, not hard-coded UI)
Define a JSON schema for survey definitions supporting these question types:
short_text, long_text, single_choice (with optional "other" text),
multi_choice (with optional "other"), number (min/max, integer), percentage,
grid_percentage (rows x columns of percentages), grid_number (rows x columns,
e.g. "__ / 100"), likert_matrix (rows x 5-point scale), section headers, and
help notes. Add optional per-question rules: required, sum-to-100 for a group
of cells, min/max.
Write a seed that inserts survey_definitions version 1 (active) faithfully
transcribing ALL questions from docs/questionnaire.md, including:
- Basic demographics: name, qualification (MBBS, MD Chest, DM Pulmo, DNB,
D3CH, MD Med, DNB Med, Other specify), years of practice, practice setting,
specialization, city, state.
- Physician practice profile: patients per month; asthma new/follow-up;
COPD new/follow-up.
- Foracort prescribing pattern: % asthma and % COPD on Formoterol/Budesonide;
a pack-type grid (Regular pMDI, Regular Rotacap, Extended-Dose pMDI,
Extended-Dose Rotacap) x (Asthma, COPD) where each column must sum to 100%.
Show both pack footnotes.
- Adherence estimate grid out of 100 for the same 4 rows x 2 columns.
- Comparison of extended-dose vs regular adherence (Much better ... Much
worse, Unable to assess).
- Barriers to adherence (multi-choice + Other).
- Patient characteristics likely to benefit (multi-choice + Other).
- Advantages and disadvantages (two long-text boxes).
- Likert matrix: all 10 attribute rows with Strongly disagree to Strongly agree.
The last two rows ("Patients are reluctant to bear the higher initial
purchase amount" and "Higher chance of damage to Rotacaps due to humidity
in the extended-dose pack") use the same 5-point scale.
Keep the wording exactly as in the document. Note that the survey title is
"Physicians' Perspectives on Patient Adherence to Inhaled Maintenance
Therapy".
Part B: backend
1. GET /api/survey/:token: verifies the token (not used, not revoked),
returns the active definition plus any saved answers. Prefill name, city
etc. NOT required.
2. PUT /api/survey/:token/progress: saves partial answers (survey_responses
IN_PROGRESS). No validation of required fields yet.
3. POST /api/survey/:token/submit: full server-side validation against the
definition (required, ranges, sum-to-100). In one transaction: save
answers, mark COMPLETED, set survey_tokens.used_at, audit log. Any later
use of the token returns "already submitted".
4. Guard against double submission (unique constraint and transaction lock).
5. Pin each response to the definition version it was answered against.
Part C: frontend
1. /survey/:token renders any definition dynamically, section by section with
a progress bar, Back/Next, autosave every 10 seconds, and a review screen
before final submit.
2. Grid components with live totals ("Total: 80% - must equal 100%"),
keyboard accessible, mobile friendly (matrix becomes stacked cards on small
screens).
3. States: invalid link, already submitted, thank-you page after submit.
4. Confirm dialog on final submit: "You cannot change answers after this."
Tests: each question type validates; grids that don't sum to 100 are rejected;
the second submit with the same token fails; a new definition version does not
alter old responses.
Done when: a physician can complete the survey once, progress survives a
refresh, and the link is dead after submit.
```
---
## Phase 7: Reports and reminders
```
Read .cursor/rules/project.mdc first.
Goal: Excel export and scheduled reminders.
Excel export:
1. GET /api/admin/survey/export?status=COMPLETED&from=&to= (admin only,
audit logged). Use ExcelJS with streaming for large data.
2. Sheet "Responses": one row per completed survey. Columns: application id,
physician name, registration no, completion date, then one column per
answer field. Flatten grids into readable headers such as
"Prescribing % - Extended-Dose Rotacap - COPD". Multi-choice answers become
semicolon-separated text. Map codes to the exact label text.
3. Sheet "Questions": the survey definition (question id, text, type,
options) so analysts can decode columns.
4. Sheet "Summary": counts per single_choice and multi_choice option, and mean
for numeric/grid cells.
5. Admin UI: "Export to Excel" button with date filters on a Reports page.
Warn that the file contains personal data.
Reminders (node-cron, run in the PM2 app with a DB lock to avoid duplicates
if multiple instances run):
1. Daily at 10:00 IST: DRAFT applications untouched for 3 days get a reminder
with a fresh resume link (max 3 reminders, 3 days apart).
2. Applications APPROVED whose survey is not COMPLETED after 5 days get a
reminder with the same survey link (max 3, 5 days apart). Do not create a
new token; resend the existing one securely by issuing a replacement token
and revoking the old.
3. Record every reminder in email_log with template names and a
reminder_count so nothing is sent twice. Never remind after
completion or rejection.
4. Admin setting toggles: reminders on/off and interval days (stored in
settings).
5. Add unsubscribe-free transactional wording (these are operational emails).
Tests: reminder selection logic with fake dates, no duplicate sends, stops
after max count, export headers match definition.
Done when: the export opens cleanly in Excel and reminders send exactly on
schedule without repeats.
```
---
## Phase 8: Hardening, testing and go-live
```
Read .cursor/rules/project.mdc first.
Goal: make it production-ready.
Security:
1. Review every route for authorization. Add a test that every /api/admin/*
route returns 401 without a session.
2. Rate limits: strict on login, /api/applications, token endpoints and
survey submit. Add generic slow-down on public endpoints.
3. Set helmet CSP, HSTS, cookie flags, CORS allow-list. Sanitise all text
inputs (strip HTML). Cap request sizes. Validate uploaded PNG signatures
(magic bytes, dimensions).
4. Token safety: constant-time compares, no tokens in logs, referrer policy
"no-referrer" on token pages. Redact PAN, account number and tokens in
logs. Mask PAN and account number in admin lists (reveal only on detail).
5. Encrypt PAN and bank account number at rest at the application level
(AES-256-GCM, key from env/Secrets Manager) with helper functions and a
migration for existing rows.
6. Run `npm audit` and fix high issues. Add dependency check to CI.
Reliability:
1. Structured logging, request ids, health endpoint checking DB and S3.
2. Graceful shutdown; Puppeteer browser cleanup; PM2 restart policy.
3. Database backups: document RDS automated backups (7-35 days) and a
restore drill in docs/RUNBOOK.md. S3 versioning enabled.
4. Error alerting (Sentry or CloudWatch alarms) with docs.
Testing:
1. Playwright end-to-end test of the whole journey: start -> fill -> review ->
edit -> sign -> submit -> admin approves -> survey email link -> complete
survey -> second attempt refused. Plus the rejection path: reject ->
resume link -> edit -> re-sign -> approve.
2. Unit and API test coverage report; aim for the critical services above 80%.
3. Basic load test with k6 or autocannon on public endpoints.
Compliance and content:
1. Privacy notice page and a data-retention note (personal data used only for
the study, payments, compliance, audit per Section 8 of the agreement).
2. A simple footer with CREST details and a support email.
3. Accessibility pass (labels, focus states, contrast).
Delivery:
1. GitHub Actions: lint, test, build; manual deploy job or a deploy script
that SSHs to EC2 and reloads PM2.
2. Update docs/DEPLOY.md and docs/RUNBOOK.md (env vars, rotating secrets,
adding an admin, resending a survey link, restoring a backup).
3. A UAT checklist in docs/UAT.md that the CREST team can tick through.
Done when: the Playwright suite passes on staging, UAT is signed off, and the
production checklist in docs/RUNBOOK.md is complete.
```
---
## Quick reference: things you must supply
| Item | Needed by | Notes |
|---|---|---|
| Dr Sudeep Salvi's designation | Phase 4 | Enter in admin Settings |
| Signature image (PNG, transparent) | Phase 4 | Upload in admin Settings |
| SendGrid API key and verified sender domain | Phase 5 | Set up SPF/DKIM early |
| AWS: EC2, RDS MySQL, private S3 bucket | Phase 0 | Enable S3 encryption and versioning |
| CAPTCHA keys (reCAPTCHA or Turnstile) | Phase 2 | Can skip in dev |
| Admin notification emails | Phase 5 | `ADMIN_NOTIFY_EMAILS` |
| CREST logo (PNG/SVG) | Phase 3 | For the PDF and emails |
| Final questionnaire wording | Phase 6 | Draft 03 may change; edit the seed only |