# M-TEER Learning Platform

A content-driven interactive learning platform for M-TEER education.

The governing principle: **the admin panel owns the content, the mobile app
owns the experience.** New sections, modules, activities, quizzes, decision
trees and drawing templates are created by the medical/content team and appear
in the Flutter app through the API — with no source change and no app release.

```
React Admin Panel ──┐
                    ├──► NestJS API ──► MySQL + object storage
Flutter Mobile App ─┘
```

## Repository layout

| Path | What it is |
| --- | --- |
| `backend-api/` | NestJS + TypeORM API, SQL migrations, seed script |
| `adminpanel/` | React + Vite + Ant Design content management system |
| `mobile/` | Flutter app: dynamic content engine, drawing/quiz/decision-tree engines, offline cache |
| `docs/` | ERD and API reference |
| `migrations/` | (reserved) |

## Getting started

### 1. Backend

```bash
cd backend-api
npm install
cp ../.env.example ../.env      # then fill in the database credentials
npm run migrate                 # applies every SQL file, tracked in schema_migrations
npm run seed                    # roles, permissions, default settings, first admin
npm run seed:content            # the M-TEER Workbook content, loaded as DRAFT
npm run start:dev
```

`npm run seed:content` transcribes `inputdata.docx` (the M-TEER Workbook) into
the content tables: 8 sections, 24 modules, 60 activities, 167 content blocks
and the 8 Pocket Reference Card entries. It is idempotent — sections and modules
match on slug, activities on a `seedKey` in their config — so re-running updates
in place instead of duplicating.

Content loads as `DRAFT` so it goes through review before learners see it. Add
`-- --publish` to publish it immediately, or `-- --dry-run` to preview the counts
without writing.

`npm run seed` prints a generated password for `admin@mteer.local` unless you
set `SEED_ADMIN_EMAIL` / `SEED_ADMIN_PASSWORD`. Change it after first login.

The API listens on `http://localhost:3000`:

- `/api/v1/public` — content for the mobile app (no auth)
- `/api/v1/admin` — content management (JWT + permissions)

### 2. Admin panel

```bash
cd adminpanel
npm install
cp .env.example .env            # VITE_API_URL=http://localhost:3000
npm run dev                     # http://localhost:5173
```

### 3. Mobile app

Requires the [Flutter SDK](https://docs.flutter.dev/get-started/install). Three
platform files are machine-specific and gitignored (`android/local.properties`,
`android/gradlew`, the Gradle wrapper jar), so generate them once:

```powershell
cd mobile
powershell -ExecutionPolicy Bypass -File tool/setup.ps1
```

Then run it — the API must already be running:

```bash
# Android emulator (10.0.2.2 is the host machine as seen from the emulator)
flutter run --dart-define=API_BASE_URL=http://10.0.2.2:3000

# Chrome — needs no Android SDK
flutter run -d chrome --dart-define=API_BASE_URL=http://localhost:3000
```

See [mobile/README.md](mobile/README.md) for physical devices and troubleshooting.

## Verifying the whole loop

`backend-api/scripts/e2e-smoke.js` walks the acceptance path from the
requirements: admin login → build a section/module/lesson/activity → add content
blocks → publish → fetch from the mobile API → save a drawing → sync progress →
submit a quiz → check the audit trail.

```bash
cd backend-api
npm run start:dev                                  # in one terminal
ADMIN_PW='<seeded password>' node scripts/e2e-smoke.js
```

It asserts the security boundaries too: anonymous requests are rejected, a
rotated refresh token cannot be replayed, `status` cannot be forced through an
update, unpublished content is invisible to the mobile API, quiz answer keys are
stripped, and one learner session cannot touch another's work.

## Architecture notes

**Content model.** `Section → Module → Lesson → Activity → Content Block`. A
content block is `{ type, payload }`; the payload shape is defined per type in
`adminpanel/src/components/blockSchemas.ts` and consumed by the matching Flutter
widget. Adding a block type means adding a schema entry and a widget — existing
content is unaffected, and an app build that does not know a type renders a
readable placeholder rather than crashing.

**Publishing and versioning.** Content moves `DRAFT → IN_REVIEW → PUBLISHED →
ARCHIVED`. Publishing a module snapshots its entire tree into `module_versions`
with a version number and change reason, so a published version stays readable
after the working copy is edited. Only `PUBLISHED` content reaches learners.

**Authorisation.** Roles and permissions are data, not code. The backend
resolves the caller's permissions from the database on every request; the admin
panel's `Can` component only hides UI. `status` is deliberately absent from the
update DTOs so `*.update` cannot be used to publish.

**No mobile login.** Learners are identified by an anonymous device session
(section 24 of the requirements). Progress, drawings, annotations, notes,
favourites and case logs are keyed by that id, stored on the device first, and
synchronised opportunistically.

**Offline.** The app caches the catalogue and each module tree on disk. A
manifest endpoint returns a content fingerprint plus per-module timestamps, so a
sync downloads only what changed.

**Medical safety.** No educational or clinical content is compiled into any of
the three applications. The disclaimer text is served from settings so the
medical team controls its wording. Cases use patient codes, never names.

## Testing

```bash
cd backend-api && npm run typecheck
cd adminpanel  && npm run typecheck && npm run build
cd mobile      && flutter analyze && flutter test
```
