547 lines
25 KiB
Markdown
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 |
|