https://github.com/aarnphm/scemas-platform
https://github.com/aarnphm/scemas-platform
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/aarnphm/scemas-platform
- Owner: aarnphm
- Created: 2026-03-19T17:49:32.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-07T17:15:13.000Z (2 months ago)
- Last Synced: 2026-04-07T18:18:26.365Z (2 months ago)
- Language: TypeScript
- Homepage: https://scemas-dashboard.aarnphm.workers.dev
- Size: 5 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: .github/README.md
Awesome Lists containing this project
README
# scemas-platform
smart city environmental monitoring and alert system. SE 3A04 course project demonstrating PAC (presentation-abstraction-control) architecture.
## architecture
three PAC agents (distinct dashboards) fed by four controllers:
```
┌─────────────────────┐
│ DataDistribution │
│ Manager (tRPC) │
└─────────┬───────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Operator │ │ Admin │ │ Public │
│ Agent │ │ Agent │ │ Agent │
│ (operator/)│ │ (admin/) │ │ (public/) │
└─────────────┘ └─────────────┘ └─────────────┘
Controllers:
┌──────────────────┐ ┌──────────────────┐
│ TelemetryManager │ │ AccessManager │
│ (pipe-and-filter)│ │ (repository) │
└──────────────────┘ └──────────────────┘
┌──────────────────┐
│ AlertingManager │
│ (blackboard) │
└──────────────────┘
```
**operator agent**: dashboard with map, metrics, alerts, personalized subscriptions. full data access.
**admin agent**: threshold rules CRUD, user management, platform health, audit logs.
**public agent**: aggregated AQI display for digital signage. abstracted (sensitive data stripped). shared view for public users and third-party developers.
We also ship a small CLI for better usability.
## directory map
```
scemas-platform/
├── crates/ rust workspace (internal processing engine)
│ ├── scemas-core/ shared entity types from UML class diagram
│ ├── scemas-telemetry/ pipe-and-filter validation pipeline
│ ├── scemas-alerting/ blackboard alert evaluation + lifecycle
│ ├── scemas-server/ axum internal API on :3001
│ └── scemas-desktop/ tauri host app and embedded-postgres runtime
│
├── packages/ bun workspace (typescript)
│ ├── api/ cloudflare worker + container wrapper for rust
│ ├── db/ drizzle schema (database source of truth)
│ ├── types/ zod schemas + shared types
│ ├── dashboard/ next.js 15 + tRPC (3 PAC agent dashboards)
│ └── desktop/ vite/react frontend for the tauri desktop app
│
├── data/ sample JSON sensor data (hamilton, ON)
├── scripts/ seed script for data ingestion
├── docs/diagrams/ UML source of truth (.puml files)
└── docker-compose.yml postgres
```
## getting started
Linux is recommended. For Windows recommend to run in WSL.
install rustup, bun (>= 1.2), and docker manually. the repo pins rust in `rust-toolchain.toml`, so `rustup` will pull the correct stable toolchain automatically.
```sh
source scripts/start-scemas.sh # shell helpers + first-time setup
scemas-dev # starts db (docker) + schema + accounts + engine + dashboard
```
first-time setup (`.env` copy, `bun install`) runs automatically on first source and is tracked via a `.derived` sentinel file. delete `.derived` to re-run it.
### default accounts
`bun db:push` runs `@scemas/db`'s `ensure-users` script. it creates one account per role, skips any that already exist, and uses `1234` for all three defaults.
| email | role | dashboard |
| ---------------------- | -------- | ----------------------------------------------------- |
| `admin@example.com` | admin | `/rules`, `/users`, `/health`, `/audit` |
| `operator@example.com` | operator | `/dashboard`, `/alerts`, `/subscriptions`, `/metrics` |
| `viewer@example.com` | viewer | `/display` (public AQI grid) |
| `public@example.com` | viewer | |
### seed sensor data
```sh
scemas dev seed # nix shell, or if you build the CLI
scemas-seed # non-nix
```
pass `--spike` to generate readings that trigger alerts.
pass `--rate 8` or `--rate=8` to increase the aggregate generation frequency across all sensors.
### shell helpers
both paths give you the same functions:
| function | description |
| --------------- | -------------------------------------------------------------- |
| `scemas-dev` | start everything (db + schema + accounts + engine + dashboard) |
| `scemas-engine` | rust engine on :3001 |
| `scemas-dash` | next.js dashboard on :3000 |
| `scemas-seed` | seed sample data (supports `--spike` and `--rate `) |
| `scemas-check` | run all lints (cargo fmt + clippy + tsc) |
| `scemas-nuke` | stop everything |
### environment variables
see `.env.example`. defaults work out of the box for the web stack. desktop-specific knobs:
| variable | default | meaning |
| ------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `DATABASE_URL` | `postgres://scemas:scemas@localhost:5432/scemas` | external postgres connection, used by the web stack and by desktop when embedded mode is disabled or when a local postgres is already reachable |
| `POSTGRES_BIN_DIR` | unset, auto-detected | desktop-only path to a postgres 16 `bin/` directory containing `pg_ctl`, `initdb`, `postgres`, `createdb`, and `psql` |
| `INTERNAL_RUST_URL` | `http://localhost:3001` | rust engine URL for remote auth fallback and sync. shared with dashboard. desktop also accepts legacy `SCEMAS_REMOTE_URL` |
## architecture tests
Run the following:
```bash
cargo test --all
```
### pipe-and-filter
_location_: `crates/scemas-telemetry/src/validate.rs`
chains `schema_validator`, `range_validator`, `timestamp_validator` via `and_then`. failure at any stage drops the reading. tests cover the happy path, each gate rejecting independently, and all four metric types.
### blackboard
_location_: `crates/scemas-alerting/src/blackboard.rs`
knowledge sources (`evaluator`, `lifecycle`, `dispatcher`) coordinate through shared mutable state on the `Blackboard`. tests import from both `evaluator` and `lifecycle` to exercise cross-module reads/writes, rule replacement, and the full evaluate-post-lifecycle transition.
### pac
_location_: `crates/scemas-core/src/models.rs`
the `Role` enum sits between the control layer (`AccessManager` / JWT) and the three presentation agents. route enforcement lives in `packages/dashboard/middleware.ts` and the `(operator)/`, `(admin)/`, `(public)/` route groups. tests verify role round-tripping through strings, rejection of unknown roles, and distinctness of all three agents.
## source of truth
all classes, attributes, methods, and relationships derive from the UML class diagram at [`docs/diagrams/class_diagram.puml`](docs/diagrams/class_diagram.puml). sequence diagrams and state charts in the same directory define interaction contracts.
## route map
### web routes
| route | audience | purpose |
| ------------------- | -------------------------------------- | --------------------------------------------- |
| `/` | everyone | root redirect to `/sign-in` |
| `/sign-in` | operator, admin, viewer | canonical login page |
| `/sign-up` | new users | canonical signup page |
| `/dashboard` | operator | main city operator dashboard |
| `/alerts` | operator | live alert queue |
| `/alerts/[alertId]` | operator | alert detail drill-down |
| `/subscriptions` | operator | personal alert subscription controls |
| `/metrics` | operator | sensor subagent overview |
| `/metrics/[zone]` | operator | zone-specific metric drill-down |
| `/rules` | admin | threshold rule CRUD |
| `/rules/[ruleId]` | admin | threshold rule detail page |
| `/users` | admin | user and role management |
| `/users/[userId]` | admin | account-specific audit trail |
| `/health` | admin | ingestion counters, failures, platform status |
| `/audit` | admin | audit log viewer |
| `/display` | public, viewer, third-party developers | public AQI display |
### internal rust routes
these are server-to-server routes used by the next/tRPC layer and seed scripts, not browser pages.
| route | method | purpose |
| -------------------------------------------------- | ------ | ---------------------------------- |
| `/internal/auth/signup` | `POST` | create account and issue session |
| `/internal/auth/login` | `POST` | authenticate and issue session |
| `/internal/auth/reset-password` | `POST` | admin password reset (argon2 hash) |
| `/internal/alerting/rules` | `POST` | create threshold rule |
| `/internal/alerting/rules/{rule_id}/status` | `POST` | activate or pause rule |
| `/internal/alerting/rules/{rule_id}/delete` | `POST` | delete rule |
| `/internal/alerting/alerts/{alert_id}/acknowledge` | `POST` | acknowledge alert |
| `/internal/alerting/alerts/{alert_id}/resolve` | `POST` | resolve alert |
| `/internal/telemetry/ingest` | `POST` | ingest seeded or device telemetry |
| `/internal/health` | `GET` | ingestion health counters |
### public API routes
| route | method | purpose |
| ------------------- | ------------- | ---------------------------------------------------- |
| `/api/v1/zones/aqi` | `GET` | public, versioned AQI feed |
| `/api/trpc/*` | `GET`, `POST` | dashboard tRPC transport for authenticated app views |
## document map
| path | purpose |
| ------------------------------------------------------------ | ------------------------------------------- |
| `docs/D1.pdf` | requirements and system framing |
| `docs/D2.pdf` | design/package deliverable |
| `docs/D3.pdf` | Detailed Design Document |
| `docs/diagrams/class_diagram.puml` | UML class source of truth |
| `docs/diagrams/signup_and_login.puml` | signup/login interaction flow |
| `docs/diagrams/define_alert_rule.puml` | admin rule creation sequence |
| `docs/diagrams/acknowledge_critical_env.puml` | operator alert acknowledgement sequence |
| `docs/diagrams/data_distribution_management_controller.puml` | distribution controller sequence/state flow |
| `docs/diagrams/encryption_manager.puml` | auth/encryption manager notes |
## building desktop releases
### local build
```sh
nix develop
cargo tauri build --manifest-path crates/scemas-desktop/Cargo.toml
```
output in `target/release/bundle/`:
- macOS: `macos/SCEMAS.app` + `dmg/SCEMAS_0.1.0_aarch64.dmg`
- linux: `appimage/` + `deb/`
- windows: `msi/` + `nsis/`
### via nix
```sh
nix build .#scemas-desktop # builds the desktop app
nix build .#scemas # builds the CLI only
```
### via CI (GitHub Actions)
push a version tag to trigger cross-platform builds:
```sh
git tag desktop-v0.1.0
git push origin desktop-v0.1.0
```
the `desktop.yml` workflow builds for macOS (arm64 + x86_64), linux (x86_64), and windows (x86_64). artifacts are uploaded as a draft GitHub release.
### macOS gatekeeper
the app is ad-hoc signed (no apple developer account). macOS will block it on first launch. users need to right-click the .app → Open, or:
```sh
xattr -cr /Applications/SCEMAS.app
```
### postgres bundling
`scripts/bundle-postgres.sh` stages postgres 16 binaries into `crates/scemas-desktop/resources/pg/`. CI runs this automatically. for local builds:
```sh
bash scripts/bundle-postgres.sh # macOS/linux
```
## tech stack
| layer | tech |
| ----------- | ------------------------------------------------------ |
| rust engine | axum, sqlx, tokio, argon2, jsonwebtoken |
| database | postgresql (drizzle migrations) |
| api surface | tRPC v11 (next.js server) |
| frontend | next.js 15, tailwind v4, shadcn/ui, recharts, maplibre |
| validation | zod (typescript), thiserror (rust) |
| deployment | cloudflare workers via opennext |
| runtime | bun (typescript), tokio (rust) |
## webhook dispatch
the alert subscription system supports outbound webhooks. when a matching alert fires, the rust engine POSTs a JSON payload to the configured URL.
### 1. start the echo server
```sh
bun run scripts/webhook-echo.ts
# webhook echo listening on http://localhost:9999
# paste http://localhost:9999/webhook into subscription settings
```
optional: `--port 8888` to use a different port.
### 2. configure a subscription
1. log in as operator (`operator@example.com` / `1234`)
2. open the alert subscription settings (drawer on the alerts page)
3. check the metric types and zones you want
4. paste `http://localhost:9999/webhook` in the webhook URL field
5. save
### 3. ensure a threshold rule exists
log in as admin (`admin@example.com` / `1234`), go to `/rules`, create a rule:
- metric: `temperature`, comparison: `gt`, threshold: `30`
- leave zone blank for global, or pick a specific zone
### 4. spike the seed
```sh
bun run scripts/seed.ts --spike
```
`--spike` forces extreme values that exceed thresholds. the echo server terminal should print:
```
2026-03-21T15:30:00.000Z POST /webhook
CRITICAL — temperature at 45.2 in downtown_core (sensor hamilton-aqhi-001)
alert id: a1b2c3d4-...
created: 2026-03-21T15:30:00.000Z
```
### webhook payload format
```json
{
"type": "alert.triggered",
"alert": {
"id": "uuid",
"zone": "downtown_core",
"metricType": "temperature",
"severity": 3,
"triggeredValue": 45.2,
"sensorId": "hamilton-aqhi-001",
"createdAt": "2026-03-21T15:30:00.000Z"
}
}
```
severity: 1 = low, 2 = warning, 3 = critical. the webhook fires best-effort (fire-and-forget via `tokio::spawn`). failures are logged server-side but don't block the alert pipeline.