@ratim818/allyve-wellness-backend 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +49 -0
- package/.github/workflows/ci.yml +34 -0
- package/.github/workflows/publish.yml +81 -0
- package/README.md +140 -0
- package/docs/HIPAA_COMPLIANCE.md +141 -0
- package/docs/frontend-api-client.ts +259 -0
- package/package.json +60 -0
- package/src/config/database.ts +45 -0
- package/src/config/index.ts +52 -0
- package/src/middleware/auth.ts +167 -0
- package/src/middleware/security.ts +101 -0
- package/src/migrations/rollback.ts +17 -0
- package/src/migrations/run.ts +17 -0
- package/src/migrations/schema.ts +339 -0
- package/src/migrations/seed.ts +159 -0
- package/src/routes/appointments.ts +293 -0
- package/src/routes/audit.ts +29 -0
- package/src/routes/auth.ts +141 -0
- package/src/routes/health.ts +387 -0
- package/src/server.ts +124 -0
- package/src/services/audit.ts +117 -0
- package/src/services/auth.ts +293 -0
- package/src/services/encryption.ts +76 -0
- package/src/utils/logger.ts +57 -0
- package/tsconfig.json +24 -0
package/.env.example
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# ALLYVE WELLNESS AI — BACKEND CONFIGURATION
|
|
3
|
+
# HIPAA-Compliant Environment Variables
|
|
4
|
+
# ============================================
|
|
5
|
+
# IMPORTANT: Never commit this file to version control.
|
|
6
|
+
# Copy to .env and fill in your values.
|
|
7
|
+
|
|
8
|
+
# ── Server ────────────────────────────────────
|
|
9
|
+
NODE_ENV=development
|
|
10
|
+
PORT=3001
|
|
11
|
+
API_VERSION=v1
|
|
12
|
+
CORS_ORIGIN=http://localhost:5173
|
|
13
|
+
|
|
14
|
+
# ── Database (PostgreSQL with SSL) ────────────
|
|
15
|
+
# Use a HIPAA-eligible provider: AWS RDS, GCP Cloud SQL, Azure DB, or Supabase Pro
|
|
16
|
+
DATABASE_URL=postgresql://allyve_user:STRONG_PASSWORD@localhost:5432/allyve_wellness
|
|
17
|
+
DATABASE_SSL=false
|
|
18
|
+
DATABASE_POOL_MIN=2
|
|
19
|
+
DATABASE_POOL_MAX=10
|
|
20
|
+
|
|
21
|
+
# ── Encryption ────────────────────────────────
|
|
22
|
+
# AES-256 key for encrypting PHI at rest (generate with: openssl rand -hex 32)
|
|
23
|
+
ENCRYPTION_KEY=GENERATE_WITH_openssl_rand_hex_32
|
|
24
|
+
ENCRYPTION_IV_LENGTH=16
|
|
25
|
+
|
|
26
|
+
# ── Authentication ────────────────────────────
|
|
27
|
+
# JWT secret (generate with: openssl rand -base64 64)
|
|
28
|
+
JWT_SECRET=GENERATE_WITH_openssl_rand_base64_64
|
|
29
|
+
JWT_EXPIRES_IN=15m
|
|
30
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
31
|
+
|
|
32
|
+
# ── Session ───────────────────────────────────
|
|
33
|
+
SESSION_SECRET=GENERATE_WITH_openssl_rand_base64_32
|
|
34
|
+
SESSION_MAX_AGE=900000
|
|
35
|
+
|
|
36
|
+
# ── HIPAA Audit ───────────────────────────────
|
|
37
|
+
AUDIT_LOG_RETENTION_DAYS=2190
|
|
38
|
+
AUDIT_LOG_LEVEL=info
|
|
39
|
+
|
|
40
|
+
# ── Rate Limiting ─────────────────────────────
|
|
41
|
+
RATE_LIMIT_WINDOW_MS=900000
|
|
42
|
+
RATE_LIMIT_MAX_REQUESTS=100
|
|
43
|
+
|
|
44
|
+
# ── Wearable Integration (future) ────────────
|
|
45
|
+
# APPLE_HEALTHKIT_KEY=
|
|
46
|
+
# GOOGLE_FIT_CLIENT_ID=
|
|
47
|
+
# GOOGLE_FIT_CLIENT_SECRET=
|
|
48
|
+
# FITBIT_CLIENT_ID=
|
|
49
|
+
# FITBIT_CLIENT_SECRET=
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18, 20]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Setup Node.js ${{ matrix.node-version }}
|
|
22
|
+
uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: ${{ matrix.node-version }}
|
|
25
|
+
cache: 'npm'
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: npm ci
|
|
29
|
+
|
|
30
|
+
- name: Build
|
|
31
|
+
run: npm run build
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: npx vitest run --passWithNoTests
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
name: Publish Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout repository
|
|
12
|
+
uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Setup Node.js
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
cache: 'npm'
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: npm ci
|
|
22
|
+
|
|
23
|
+
- name: Build
|
|
24
|
+
run: npm run build
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: npx vitest run --passWithNoTests
|
|
28
|
+
|
|
29
|
+
publish-npm:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- name: Checkout repository
|
|
34
|
+
uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Setup Node.js for npm
|
|
37
|
+
uses: actions/setup-node@v4
|
|
38
|
+
with:
|
|
39
|
+
node-version: 20
|
|
40
|
+
registry-url: 'https://registry.npmjs.org'
|
|
41
|
+
cache: 'npm'
|
|
42
|
+
|
|
43
|
+
- name: Install dependencies
|
|
44
|
+
run: npm ci
|
|
45
|
+
|
|
46
|
+
- name: Build
|
|
47
|
+
run: npm run build
|
|
48
|
+
|
|
49
|
+
- name: Publish to npm
|
|
50
|
+
run: npm publish --access public
|
|
51
|
+
env:
|
|
52
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
53
|
+
|
|
54
|
+
publish-gpr:
|
|
55
|
+
needs: build
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
permissions:
|
|
58
|
+
contents: read
|
|
59
|
+
packages: write
|
|
60
|
+
steps:
|
|
61
|
+
- name: Checkout repository
|
|
62
|
+
uses: actions/checkout@v4
|
|
63
|
+
|
|
64
|
+
- name: Setup Node.js for GitHub Packages
|
|
65
|
+
uses: actions/setup-node@v4
|
|
66
|
+
with:
|
|
67
|
+
node-version: 20
|
|
68
|
+
registry-url: 'https://npm.pkg.github.com'
|
|
69
|
+
scope: '@ratim818'
|
|
70
|
+
cache: 'npm'
|
|
71
|
+
|
|
72
|
+
- name: Install dependencies
|
|
73
|
+
run: npm ci
|
|
74
|
+
|
|
75
|
+
- name: Build
|
|
76
|
+
run: npm run build
|
|
77
|
+
|
|
78
|
+
- name: Publish to GitHub Packages
|
|
79
|
+
run: npm publish
|
|
80
|
+
env:
|
|
81
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# 🩺 Allyve Wellness AI — HIPAA-Compliant Backend
|
|
2
|
+
|
|
3
|
+
Production-grade backend for the Allyve maternal health monitoring platform.
|
|
4
|
+
Built with Express, TypeScript, PostgreSQL, and full HIPAA technical safeguards.
|
|
5
|
+
|
|
6
|
+
## Quick Start
|
|
7
|
+
|
|
8
|
+
### Prerequisites
|
|
9
|
+
|
|
10
|
+
- **Node.js** ≥ 18
|
|
11
|
+
- **PostgreSQL** ≥ 15
|
|
12
|
+
- **OpenSSL** (for generating keys)
|
|
13
|
+
|
|
14
|
+
### 1. Install dependencies
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. Configure environment
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cp .env.example .env
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Generate your secrets:
|
|
27
|
+
```bash
|
|
28
|
+
# Encryption key (AES-256 — 32 bytes)
|
|
29
|
+
openssl rand -hex 32
|
|
30
|
+
|
|
31
|
+
# JWT secret
|
|
32
|
+
openssl rand -base64 64
|
|
33
|
+
|
|
34
|
+
# Session secret
|
|
35
|
+
openssl rand -base64 32
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Paste these into your `.env` file.
|
|
39
|
+
|
|
40
|
+
### 3. Create the database
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
createdb allyve_wellness
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 4. Run migrations
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run migrate
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 5. Seed demo data (optional)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm run seed
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Demo login: `sarah.johnson@example.com` / `SecureDemo123!`
|
|
59
|
+
|
|
60
|
+
### 6. Start the server
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run dev
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Server starts at `http://localhost:3001`. API docs at `http://localhost:3001/api/v1`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Architecture
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
src/
|
|
74
|
+
├── config/ # Environment config, database connection
|
|
75
|
+
├── middleware/ # Auth (JWT), security (Helmet, CORS, rate limiting)
|
|
76
|
+
├── migrations/ # Database schema, seed data
|
|
77
|
+
├── routes/ # API endpoints (auth, health, appointments, sharing, audit)
|
|
78
|
+
├── services/ # Core logic (auth, encryption, audit trail)
|
|
79
|
+
├── utils/ # Logger (HIPAA-compliant, no PHI in logs)
|
|
80
|
+
└── server.ts # Express app entry point
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## HIPAA Safeguards
|
|
84
|
+
|
|
85
|
+
| Safeguard | Implementation |
|
|
86
|
+
|---|---|
|
|
87
|
+
| **Encryption at rest** | AES-256-GCM on all PHI fields |
|
|
88
|
+
| **Encryption in transit** | HSTS, TLS required in production |
|
|
89
|
+
| **Authentication** | JWT + bcrypt (12 rounds) + account lockout |
|
|
90
|
+
| **Access control** | RBAC (patient/provider/admin) + ownership checks |
|
|
91
|
+
| **Audit trail** | Immutable PostgreSQL table with DB-level triggers |
|
|
92
|
+
| **Session management** | 15-min timeout, sliding window, revocation |
|
|
93
|
+
| **Input validation** | Zod schemas on every endpoint |
|
|
94
|
+
| **Rate limiting** | 100 req/15min general, 10/15min for auth |
|
|
95
|
+
|
|
96
|
+
See `docs/HIPAA_COMPLIANCE.md` for the full compliance reference.
|
|
97
|
+
|
|
98
|
+
## Connecting the Frontend
|
|
99
|
+
|
|
100
|
+
Copy `docs/frontend-api-client.ts` into your Lovable frontend at `src/services/api.ts`.
|
|
101
|
+
|
|
102
|
+
Add to your frontend `.env`:
|
|
103
|
+
```
|
|
104
|
+
VITE_API_URL=http://localhost:3001/api/v1
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Then update your components to import from `services/api` instead of `data/mockData`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## API Endpoints
|
|
112
|
+
|
|
113
|
+
| Method | Endpoint | Auth | Description |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| POST | /auth/register | ✗ | Create account |
|
|
116
|
+
| POST | /auth/login | ✗ | Login → JWT tokens |
|
|
117
|
+
| POST | /auth/refresh | ✗ | Refresh access token |
|
|
118
|
+
| POST | /auth/logout | ✓ | Logout + revoke session |
|
|
119
|
+
| GET | /auth/me | ✓ | Current user profile |
|
|
120
|
+
| GET | /health/metrics | ✓ | List health metrics |
|
|
121
|
+
| POST | /health/metrics | ✓ | Record health metric |
|
|
122
|
+
| GET | /health/symptoms | ✓ | List symptoms |
|
|
123
|
+
| POST | /health/symptoms | ✓ | Record symptom |
|
|
124
|
+
| GET | /health/mood | ✓ | List mood entries |
|
|
125
|
+
| POST | /health/mood | ✓ | Record mood |
|
|
126
|
+
| GET | /health/journal | ✓ | List journal entries |
|
|
127
|
+
| POST | /health/journal | ✓ | Record journal entry |
|
|
128
|
+
| GET | /health/cardiovascular-risk | ✓ | Latest CV risk |
|
|
129
|
+
| GET | /appointments | ✓ | List appointments |
|
|
130
|
+
| POST | /appointments | ✓ | Create appointment |
|
|
131
|
+
| PUT | /appointments/:id | ✓ | Update appointment |
|
|
132
|
+
| DELETE | /appointments/:id | ✓ | Soft-delete appointment |
|
|
133
|
+
| GET | /share | ✓ | List shared data |
|
|
134
|
+
| POST | /share | ✓ | Share data + consent |
|
|
135
|
+
| POST | /share/:id/revoke | ✓ | Revoke sharing |
|
|
136
|
+
| GET | /audit/logs | ✓ admin | Query audit trail |
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
Proprietary — Allyve Wellness AI
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Allyve Wellness AI — HIPAA Compliance Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This backend implements the **HIPAA Security Rule** technical safeguards
|
|
6
|
+
(45 CFR §164.312) for protecting electronic Protected Health Information (ePHI).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Technical Safeguards Implemented
|
|
11
|
+
|
|
12
|
+
### §164.312(a)(1) — Access Control
|
|
13
|
+
|
|
14
|
+
| Requirement | Implementation |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Unique user identification | UUID-based user IDs, unique email (HMAC-indexed) |
|
|
17
|
+
| Emergency access procedure | `EMERGENCY_ACCESS` audit action, admin override role |
|
|
18
|
+
| Automatic logoff | 15-min session timeout with sliding window |
|
|
19
|
+
| Encryption/decryption | AES-256-GCM for all PHI at rest |
|
|
20
|
+
|
|
21
|
+
**Files:** `middleware/auth.ts`, `services/encryption.ts`, `services/auth.ts`
|
|
22
|
+
|
|
23
|
+
### §164.312(b) — Audit Controls
|
|
24
|
+
|
|
25
|
+
| Requirement | Implementation |
|
|
26
|
+
|---|---|
|
|
27
|
+
| Hardware/software/procedure audit | Immutable `audit_logs` table with DB-level triggers |
|
|
28
|
+
| Tamper evidence | PostgreSQL triggers prevent UPDATE/DELETE on audit_logs |
|
|
29
|
+
| 6-year retention | Configurable retention, default 2190 days (6 years) |
|
|
30
|
+
| Audit trail contents | WHO, WHAT, WHEN, WHERE, OUTCOME for every action |
|
|
31
|
+
|
|
32
|
+
**Files:** `services/audit.ts`, `routes/audit.ts`, `migrations/schema.ts`
|
|
33
|
+
|
|
34
|
+
### §164.312(c)(1) — Integrity Controls
|
|
35
|
+
|
|
36
|
+
| Requirement | Implementation |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Data integrity | AES-256-GCM provides authenticated encryption (tamper detection) |
|
|
39
|
+
| Soft deletes | `is_deleted` flags — no hard deletes of PHI |
|
|
40
|
+
| Input validation | Zod schemas on all API inputs |
|
|
41
|
+
|
|
42
|
+
**Files:** `services/encryption.ts`, `routes/health.ts`
|
|
43
|
+
|
|
44
|
+
### §164.312(d) — Authentication
|
|
45
|
+
|
|
46
|
+
| Requirement | Implementation |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Person/entity authentication | JWT tokens (short-lived) + refresh tokens |
|
|
49
|
+
| Password requirements | 12+ chars, upper, lower, number, special character |
|
|
50
|
+
| Account lockout | 5 failed attempts → 30-minute lockout |
|
|
51
|
+
| Brute force protection | Rate limiting (10 auth requests / 15 min) |
|
|
52
|
+
|
|
53
|
+
**Files:** `services/auth.ts`, `middleware/security.ts`
|
|
54
|
+
|
|
55
|
+
### §164.312(e)(1) — Transmission Security
|
|
56
|
+
|
|
57
|
+
| Requirement | Implementation |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Encryption in transit | HSTS headers, TLS required in production |
|
|
60
|
+
| Integrity controls | Helmet security headers, CORS restrictions |
|
|
61
|
+
| Cookie security | httpOnly, secure, sameSite=strict |
|
|
62
|
+
|
|
63
|
+
**Files:** `middleware/security.ts`
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Data Encryption Map
|
|
68
|
+
|
|
69
|
+
| Data Field | Storage | Encryption |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Email | `email_encrypted` | AES-256-GCM |
|
|
72
|
+
| Email (lookup) | `email_hash` | HMAC-SHA256 |
|
|
73
|
+
| Password | `password_hash` | bcrypt (12 rounds) |
|
|
74
|
+
| Full name | `full_name_encrypted` | AES-256-GCM |
|
|
75
|
+
| Date of birth | `date_of_birth_encrypted` | AES-256-GCM |
|
|
76
|
+
| Phone number | `phone_encrypted` | AES-256-GCM |
|
|
77
|
+
| Health metric values | `value_encrypted` | AES-256-GCM |
|
|
78
|
+
| ECG waveform data | `waveform_encrypted` | AES-256-GCM |
|
|
79
|
+
| Symptom notes | `notes_encrypted` | AES-256-GCM |
|
|
80
|
+
| Mood notes | `notes_encrypted` | AES-256-GCM |
|
|
81
|
+
| Journal content | `content_encrypted` | AES-256-GCM |
|
|
82
|
+
| Appointment location | `location_encrypted` | AES-256-GCM |
|
|
83
|
+
| Provider contact info | `*_encrypted` | AES-256-GCM |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Remaining Steps for Full HIPAA Compliance
|
|
88
|
+
|
|
89
|
+
> ⚠️ **This codebase provides the technical safeguards. Full HIPAA compliance
|
|
90
|
+
> also requires administrative and physical safeguards.**
|
|
91
|
+
|
|
92
|
+
### Before Production
|
|
93
|
+
|
|
94
|
+
- [ ] **Business Associate Agreement (BAA)** — Sign with your cloud provider
|
|
95
|
+
(AWS, GCP, Azure, or Supabase Pro all offer HIPAA BAAs)
|
|
96
|
+
- [ ] **SSL/TLS Certificate** — Enforce HTTPS in production (nginx/Caddy/CloudFront)
|
|
97
|
+
- [ ] **Database encryption at rest** — Enable at the infrastructure level
|
|
98
|
+
(AWS RDS encryption, GCP Cloud SQL encryption)
|
|
99
|
+
- [ ] **Key management** — Move `ENCRYPTION_KEY` to a KMS (AWS KMS, GCP KMS, HashiCorp Vault)
|
|
100
|
+
- [ ] **Backup encryption** — All database backups must be encrypted
|
|
101
|
+
- [ ] **Penetration testing** — Annual security assessment
|
|
102
|
+
- [ ] **Risk assessment** — Document threats, vulnerabilities, and mitigations
|
|
103
|
+
- [ ] **Workforce training** — All team members handling PHI must be trained
|
|
104
|
+
- [ ] **Incident response plan** — Document breach notification procedures
|
|
105
|
+
- [ ] **Privacy officer** — Designate a HIPAA privacy officer
|
|
106
|
+
|
|
107
|
+
### Administrative Safeguards (Organizational)
|
|
108
|
+
|
|
109
|
+
- [ ] Written security policies and procedures
|
|
110
|
+
- [ ] Workforce access management procedures
|
|
111
|
+
- [ ] Security incident response procedures
|
|
112
|
+
- [ ] Contingency / disaster recovery plan
|
|
113
|
+
- [ ] Regular security evaluations
|
|
114
|
+
|
|
115
|
+
### Physical Safeguards
|
|
116
|
+
|
|
117
|
+
- [ ] Facility access controls (if self-hosting)
|
|
118
|
+
- [ ] Workstation security policies
|
|
119
|
+
- [ ] Device and media disposal procedures
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## API Security Headers
|
|
124
|
+
|
|
125
|
+
All responses include these security headers (via Helmet):
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
|
129
|
+
X-Content-Type-Options: nosniff
|
|
130
|
+
X-Frame-Options: DENY
|
|
131
|
+
X-XSS-Protection: 1; mode=block
|
|
132
|
+
Content-Security-Policy: default-src 'self'
|
|
133
|
+
Referrer-Policy: strict-origin-when-cross-origin
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Contact
|
|
139
|
+
|
|
140
|
+
For HIPAA compliance questions or to report a security concern,
|
|
141
|
+
contact your designated Security Officer.
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ═══════════════════════════════════════════════════════════════
|
|
3
|
+
* ALLYVE API CLIENT
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════
|
|
5
|
+
* Drop this file into your Lovable frontend at:
|
|
6
|
+
* src/services/api.ts
|
|
7
|
+
*
|
|
8
|
+
* Then update your hooks and components to use these functions
|
|
9
|
+
* instead of importing from data/mockData.ts.
|
|
10
|
+
* ═══════════════════════════════════════════════════════════════
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const API_BASE = import.meta.env.VITE_API_URL || "http://localhost:3001/api/v1";
|
|
14
|
+
|
|
15
|
+
// ── Token Management ────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
let accessToken: string | null = null;
|
|
18
|
+
|
|
19
|
+
export function setAccessToken(token: string | null) {
|
|
20
|
+
accessToken = token;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getAccessToken(): string | null {
|
|
24
|
+
return accessToken;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── HTTP Client with Auto-Refresh ───────────────────────────
|
|
28
|
+
|
|
29
|
+
async function apiRequest<T>(
|
|
30
|
+
endpoint: string,
|
|
31
|
+
options: RequestInit = {}
|
|
32
|
+
): Promise<T> {
|
|
33
|
+
const url = `${API_BASE}${endpoint}`;
|
|
34
|
+
|
|
35
|
+
const headers: Record<string, string> = {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...(options.headers as Record<string, string>),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (accessToken) {
|
|
41
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let response = await fetch(url, { ...options, headers, credentials: "include" });
|
|
45
|
+
|
|
46
|
+
// Auto-refresh on 401 with TOKEN_EXPIRED
|
|
47
|
+
if (response.status === 401) {
|
|
48
|
+
const body = await response.json();
|
|
49
|
+
if (body.code === "TOKEN_EXPIRED") {
|
|
50
|
+
const refreshed = await refreshToken();
|
|
51
|
+
if (refreshed) {
|
|
52
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
53
|
+
response = await fetch(url, { ...options, headers, credentials: "include" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
60
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Auth API ────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
export async function login(email: string, password: string) {
|
|
69
|
+
const data = await apiRequest<{ accessToken: string; expiresIn: string }>("/auth/login", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: JSON.stringify({ email, password }),
|
|
72
|
+
});
|
|
73
|
+
setAccessToken(data.accessToken);
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function register(data: {
|
|
78
|
+
email: string;
|
|
79
|
+
password: string;
|
|
80
|
+
fullName: string;
|
|
81
|
+
dateOfBirth: string;
|
|
82
|
+
phone?: string;
|
|
83
|
+
}) {
|
|
84
|
+
return apiRequest<{ userId: string }>("/auth/register", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
body: JSON.stringify(data),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function refreshToken(): Promise<boolean> {
|
|
91
|
+
try {
|
|
92
|
+
const data = await apiRequest<{ accessToken: string }>("/auth/refresh", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
});
|
|
95
|
+
setAccessToken(data.accessToken);
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
setAccessToken(null);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function logout() {
|
|
104
|
+
try {
|
|
105
|
+
await apiRequest("/auth/logout", { method: "POST" });
|
|
106
|
+
} finally {
|
|
107
|
+
setAccessToken(null);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function getProfile() {
|
|
112
|
+
return apiRequest<{
|
|
113
|
+
id: string;
|
|
114
|
+
email: string;
|
|
115
|
+
fullName: string;
|
|
116
|
+
dateOfBirth: string;
|
|
117
|
+
phone: string | null;
|
|
118
|
+
pregnancyWeek: number | null;
|
|
119
|
+
role: string;
|
|
120
|
+
}>("/auth/me");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Health Metrics API ──────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
export async function getHealthMetrics(params?: { limit?: number; metric?: string; since?: string }) {
|
|
126
|
+
const query = new URLSearchParams();
|
|
127
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
128
|
+
if (params?.metric) query.set("metric", params.metric);
|
|
129
|
+
if (params?.since) query.set("since", params.since);
|
|
130
|
+
|
|
131
|
+
return apiRequest<{ data: any[]; total: number }>(`/health/metrics?${query}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function recordHealthMetric(data: {
|
|
135
|
+
metricName: string;
|
|
136
|
+
value: number | string;
|
|
137
|
+
unit: string;
|
|
138
|
+
status: "normal" | "warning" | "critical" | "unknown";
|
|
139
|
+
trend?: string;
|
|
140
|
+
}) {
|
|
141
|
+
return apiRequest<{ id: string }>("/health/metrics", {
|
|
142
|
+
method: "POST",
|
|
143
|
+
body: JSON.stringify(data),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Symptoms API ────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
export async function getSymptoms(limit?: number) {
|
|
150
|
+
return apiRequest<{ data: any[] }>(`/health/symptoms?limit=${limit || 50}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function recordSymptom(data: {
|
|
154
|
+
name: string;
|
|
155
|
+
severity: number;
|
|
156
|
+
notes?: string;
|
|
157
|
+
isCardiovascularRelated: boolean;
|
|
158
|
+
}) {
|
|
159
|
+
return apiRequest<{ id: string }>("/health/symptoms", {
|
|
160
|
+
method: "POST",
|
|
161
|
+
body: JSON.stringify(data),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Mood API ────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
export async function getMoodEntries(limit?: number) {
|
|
168
|
+
return apiRequest<{ data: any[] }>(`/health/mood?limit=${limit || 50}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function recordMood(data: {
|
|
172
|
+
mood: string;
|
|
173
|
+
notes?: string;
|
|
174
|
+
anxietyLevel?: number;
|
|
175
|
+
}) {
|
|
176
|
+
return apiRequest<{ id: string }>("/health/mood", {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: JSON.stringify(data),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Journal API ─────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
export async function getJournalEntries(limit?: number) {
|
|
185
|
+
return apiRequest<{ data: any[] }>(`/health/journal?limit=${limit || 50}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function recordJournalEntry(data: {
|
|
189
|
+
content: string;
|
|
190
|
+
tags?: string[];
|
|
191
|
+
painScore?: number;
|
|
192
|
+
anxietyLevel?: number;
|
|
193
|
+
}) {
|
|
194
|
+
return apiRequest<{ id: string }>("/health/journal", {
|
|
195
|
+
method: "POST",
|
|
196
|
+
body: JSON.stringify(data),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Cardiovascular Risk API ─────────────────────────────────
|
|
201
|
+
|
|
202
|
+
export async function getCardiovascularRisk() {
|
|
203
|
+
return apiRequest<{ data: any }>("/health/cardiovascular-risk");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Appointments API ────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
export async function getAppointments() {
|
|
209
|
+
return apiRequest<{ data: any[] }>("/appointments");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function createAppointment(data: {
|
|
213
|
+
title: string;
|
|
214
|
+
providerName: string;
|
|
215
|
+
providerType: string;
|
|
216
|
+
date: string;
|
|
217
|
+
location?: string;
|
|
218
|
+
notes?: string;
|
|
219
|
+
isTelehealth?: boolean;
|
|
220
|
+
}) {
|
|
221
|
+
return apiRequest<{ id: string }>("/appointments", {
|
|
222
|
+
method: "POST",
|
|
223
|
+
body: JSON.stringify(data),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function updateAppointment(id: string, data: Record<string, unknown>) {
|
|
228
|
+
return apiRequest(`/appointments/${id}`, {
|
|
229
|
+
method: "PUT",
|
|
230
|
+
body: JSON.stringify(data),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function deleteAppointment(id: string) {
|
|
235
|
+
return apiRequest(`/appointments/${id}`, { method: "DELETE" });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Data Sharing API ────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
export async function getSharedData() {
|
|
241
|
+
return apiRequest<{ data: any[] }>("/share");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function shareData(data: {
|
|
245
|
+
recipientName: string;
|
|
246
|
+
recipientType: "provider" | "family" | "other";
|
|
247
|
+
recipientEmail?: string;
|
|
248
|
+
dataTypes: string[];
|
|
249
|
+
expiresInDays?: number;
|
|
250
|
+
}) {
|
|
251
|
+
return apiRequest<{ id: string }>("/share", {
|
|
252
|
+
method: "POST",
|
|
253
|
+
body: JSON.stringify(data),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function revokeShare(id: string) {
|
|
258
|
+
return apiRequest(`/share/${id}/revoke`, { method: "POST" });
|
|
259
|
+
}
|