diff --git a/docs/learnings.md b/docs/learnings.md index 42b45473..5d2b9195 100644 --- a/docs/learnings.md +++ b/docs/learnings.md @@ -13,6 +13,13 @@ Durable rules worth keeping across sessions. - Idempotency hashing that runs in callable code must be async and Web Crypto-safe; a `require('node:crypto')` fallback can fail under ESM/browser bundling even when the code works in unit tests. - When a callable looks like an auth or App Check problem, verify the initialized functions region before chasing browser state; a region mismatch can produce misleading unauthenticated failures. - **Stale compiled functions binary is the first thing to check when `FirebaseError: internal` appears in E2E but unit tests pass.** The emulator runs `functions/lib/`, not `functions/src/`. If source was changed (e.g. `enforceAppCheck`) but `pnpm --filter @bantayog/functions build` was not re-run, the emulator silently enforces the old setting. Fix: rebuild before running `firebase emulators:exec`. +- `createTestEnv()` in this repo expects Firestore, Database, and Storage emulators. If you only boot Firestore, rules tests that initialize the shared harness fail before they reach assertions. +- When a payload schema is `strict()` and a field is truly transitional, strip that field before validation instead of widening the schema and accidentally allowing unrelated junk. +- If a transitional field is part of the contract, model it explicitly in the schema instead of stripping it in the trigger. Stripping is only the fallback when the field is truly out-of-band. +- Ops-facing document schemas should use ops-specific enums, not the broader public report enum, or the rules/tests will drift silently. +- Do not assume `tsc --outDir lib` will refresh every checked-in declaration the way you expect; verify the emitted `.d.ts` against source and patch the artifact if the generator still leaves stale enum ordering behind. +- `z.string().uuid()` trips `@typescript-eslint/no-deprecated` under the current lint config. Use `z.uuid()` in shared validators. +- Collection query tests can fail on a rule that is really written for per-document access. If the rule uses `resource.data` in a way that doesn’t support `list`, switch the test to `getDoc` or rewrite the rule intentionally. ## Firestore diff --git a/docs/progress.md b/docs/progress.md index 6fae7761..5c8d1a2c 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -2,6 +2,32 @@ ## Current +### PR #63 CodeRabbit follow-up fixes (2026-04-24) + +- Status: DONE locally - resolved the remaining review comments on schema validation, inbox materialization, and Firestore rules +- Scope: + - shared validators: derived responder FCM flag, widened persisted report type enum, runtime lookup expiry check + - inbox trigger: removed redundant exact-location guard, added `report_sms_consent.municipalityId`, strengthened geohash regression coverage + - Firestore rules: report messages, report sharing events, field mode sessions, and report notes authorization/data-consistency fixes +- Verification: + - `pnpm --filter @bantayog/shared-validators test` - PASS + - `firebase emulators:exec --only firestore,database,storage "pnpm --filter @bantayog/functions exec vitest run src/__tests__/triggers/process-inbox-item.test.ts src/__tests__/rules/report-sharing.rules.test.ts src/__tests__/rules/report-notes.rules.test.ts src/__tests__/rules/report-messages.rules.test.ts src/__tests__/rules/field-mode-sessions.rules.test.ts"` - PASS + - `pnpm --filter @bantayog/shared-validators typecheck` - PASS + - `pnpm --filter @bantayog/functions lint` - PASS + +### Phase 5 PRE-B - Schema + Rules Foundation (2026-04-24) + +- Status: DONE locally - schema amendments, rules additions, and inbox materialization updates landed +- Scope: + - shared validators: coordination, responders, users, reports, and export updates + - Firestore rules: field mode sessions, report notes, report messages, command channel map-key lookup, report sharing events, and shared report reads + - inbox trigger: `report_ops.reportType`, optional `locationGeohash`, and explicit `exactLocation` payload support derived from the inbox item +- Verification: + - `pnpm --filter @bantayog/shared-validators exec vitest run src/coordination.test.ts src/responders.test.ts src/users.test.ts src/reports.test.ts` - PASS + - `firebase emulators:exec --only firestore,database,storage "pnpm --filter @bantayog/functions exec vitest run src/__tests__/rules/field-mode-sessions.rules.test.ts src/__tests__/rules/report-notes.rules.test.ts src/__tests__/rules/report-messages.rules.test.ts src/__tests__/rules/coordination.rules.test.ts src/__tests__/rules/report-sharing.rules.test.ts"` - PASS + - `firebase emulators:exec --only firestore "pnpm --filter @bantayog/functions exec vitest run src/__tests__/triggers/process-inbox-item.test.ts"` - PASS + - `npx turbo run lint typecheck` - PASS + ### Refactor Audit 2026-04-23 — Implementation Continuation **Branch:** `refactor/audit-2026-04-23-continuation` diff --git a/docs/reviews/2026-04-24-pr63-phase5-pre-b-review.md b/docs/reviews/2026-04-24-pr63-phase5-pre-b-review.md new file mode 100644 index 00000000..3448b02b --- /dev/null +++ b/docs/reviews/2026-04-24-pr63-phase5-pre-b-review.md @@ -0,0 +1,270 @@ +# PR #63 Review — phase5 pre-b schema and rules + +**PR:** [#63 `[codex] phase5 pre-b schema and rules`](https://github.com/Exc1D/bantayog-alert/pull/63) +**Branch:** `codex/phase5-pre-b-schema-pr` +**Base:** `main` +**Commit:** `ad8cc8f` +**Files Changed:** 56 +**Review Date:** 2026-04-24 +**Reviewers:** code-reviewer, pr-test-analyzer, silent-failure-hunter, type-design-analyzer, comment-analyzer (automated) + +--- + +## Executive Summary + +This PR lands Phase 5 PRE-B schema amendments, Firestore rules additions, inbox trigger updates, and shared validator extensions. The changes are additive, well-scoped, and follow existing patterns. No critical code bugs or security regressions were found. However, there are **6 critical test-coverage gaps**, **2 high-severity silent-failure risks in the trigger**, and **4 type-design/comment clarity issues** that should be addressed before merge. + +--- + +## Critical Issues (Must Fix Before Merge) + +### 1. Silent discard of malformed `exactLocation` — no logging + +- **File:** `functions/src/triggers/process-inbox-item.ts:92` +- **Agent:** silent-failure-hunter (Severity: HIGH) +- **Issue:** The `isExactLocation` type guard silently drops invalid `exactLocation` values without logging. If `payload.exactLocation` is malformed (e.g., `lat` is a string, NaN, or has extra fields), the code falls back to `undefined` and proceeds. This masks data quality issues upstream. +- **Fix:** Log a warning when `payload.exactLocation` is present but fails the type guard: + + ```typescript + const exactLocation = isExactLocation(payload.exactLocation) ? payload.exactLocation : undefined + if (payload.exactLocation && !exactLocation) { + log({ + severity: 'WARN', + code: 'INBOX_EXACT_LOCATION_MALFORMED', + message: `exactLocation malformed for inbox ${inboxId}`, + data: { inboxId, received: payload.exactLocation }, + }) + } + ``` + +### 2. Missing `location_missing` moderation path test + +- **File:** `functions/src/__tests__/triggers/process-inbox-item.test.ts` +- **Agent:** pr-test-analyzer (Rating: 9/10) +- **Issue:** The test covers `out_of_jurisdiction` (lat 0,0) but there is no test for `location_missing` when `publicLocation` is absent from the payload. This is a distinct code path at `process-inbox-item.ts:100` that writes a different `reason` to `moderation_incidents`. +- **Fix:** Add a test case that omits `publicLocation` from the inbox payload and asserts the `moderation_incidents` doc has `reason: 'location_missing'`. + +### 3. No SMS salt-missing error path tested + +- **File:** `functions/src/__tests__/triggers/process-inbox-item.test.ts` +- **Agent:** pr-test-analyzer (Rating: 8/10) +- **Issue:** When `SMS_MSISDN_HASH_SALT` is absent, the trigger logs an ERROR and skips enqueue (`process-inbox-item.ts:220-225`). No test verifies this graceful degradation. +- **Fix:** Add a test that unsets `SMS_MSISDN_HASH_SALT` and asserts no SMS is enqueued and no unhandled exception is thrown. + +### 4. Missing `pending_media` not-found handling test + +- **File:** `functions/src/__tests__/triggers/process-inbox-item.test.ts` +- **Agent:** pr-test-analyzer (Rating: 8/10) +- **Issue:** If a `pendingMediaIds` entry references a non-existent doc, the code silently skips it (`process-inbox-item.ts:134`). No test exercises this branch; a regression could cause a crash if the `continue` were accidentally removed. +- **Fix:** Add a test with a `pendingMediaIds` array containing a fake ID and assert the report materializes without media (and ideally with a logged warning). + +### 5. No report-sharing events read-rules coverage + +- **File:** `functions/src/__tests__/rules/report-sharing.rules.test.ts` +- **Agent:** pr-test-analyzer (Rating: 8/10) +- **Issue:** The test file seeds events and tests writes, but never tests `getDocs` or `getDoc` on `report_sharing/{r}/events` with different roles. The rules restrict reads to `isActivePrivileged() && (isMuniAdmin || isAgencyAdmin || isSuperadmin)`. +- **Fix:** Add read tests for muni-admin, agency-admin, superadmin (positive) and citizen/unauthed (negative). + +### 6. Missing `agency_assistance_requests` cross-role negative cases + +- **File:** `functions/src/__tests__/rules/coordination.rules.test.ts` +- **Agent:** pr-test-analyzer (Rating: 8/10) +- **Issue:** Only muni-admin positive and generic callable-only negatives are tested. Missing: agency-admin read for matching `targetAgencyId`, superadmin read, and non-matching agency-admin denial. +- **Fix:** Expand the describe block to cover all role combinations. + +--- + +## Important Issues (Should Fix) + +### 7. `reportOpsDocSchema.reportType` is `.optional()` but written unconditionally + +- **File:** `packages/shared-validators/src/reports.ts:123` +- **Agent:** code-reviewer (Confidence: 82) +- **Issue:** `reportType` in `reportOpsDocSchema` is `.optional()`, but `processInboxItemCore` writes it unconditionally. If a future code path writes `report_ops` without `reportType`, the schema allows it silently. The ops-specific enum also excludes 5 types that are valid in `reportDocSchema`. +- **Fix:** Either remove `.optional()` from `reportOpsDocSchema.reportType` (if all write paths guarantee it), or add a code comment explaining why it must remain optional. + +### 8. `commandChannelMessageDocSchema.authorRole` excludes `'responder'` + +- **File:** `packages/shared-validators/src/coordination.ts:47` +- **Agent:** code-reviewer (Confidence: 80) +- **Issue:** The `authorRole` enum is `['municipal_admin', 'agency_admin', 'provincial_superadmin']`. Responders are participants in threads (via `participantUids`) but cannot author messages. If this is intentional (responders are read-only), it should be documented. If responders should post updates, the schema will reject them. +- **Fix:** Add a comment above the schema clarifying the design intent. + +### 9. Silent skip of missing `pending_media` docs without logging + +- **File:** `functions/src/triggers/process-inbox-item.ts:132-140` +- **Agent:** silent-failure-hunter (Severity: MEDIUM) +- **Issue:** The loop over `pendingMediaIds` silently skips entries where `pendingSnap.exists` is false. No log entry records which upload IDs were referenced but missing. +- **Fix:** Log a warning for each missing `pending_media` doc, including the inbox ID and the missing upload ID. + +### 10. `messages` read rule: `get()` on `report_ops` without `exists()` guard + +- **File:** `infra/firebase/firestore.rules:102-109` +- **Agent:** silent-failure-hunter (Severity: MEDIUM) +- **Issue:** The rule reads `get(/databases/$(database)/documents/report_ops/$(reportId)).data.agencyIds` without verifying the doc exists. Accessing `.data` on a non-existent resource evaluates to `null`; the `in` operator on `null` behaves unpredictably. +- **Fix:** Guard with `exists(...)` before accessing `.data.agencyIds`, or restructure to avoid the nested `get()` without existence check. + +### 11. `report_notes` create rule: `get()` on `report_ops` without `exists()` guard + +- **File:** `infra/firebase/firestore.rules:157-166` +- **Agent:** silent-failure-hunter (Severity: MEDIUM) +- **Issue:** Same pattern as #10. The create rule reads `get(...report_ops/$(request.resource.data.reportId)).data.municipalityId` without verifying existence. +- **Fix:** Add `exists()` guard or document the intended behavior explicitly. + +--- + +## Type Design Analysis + +### Overall Ratings + +| File | Encapsulation | Invariant Expression | Invariant Usefulness | Invariant Enforcement | +| ----------------- | ------------- | -------------------- | -------------------- | --------------------- | +| `coordination.ts` | 8/10 | 6/10 | 7/10 | 7/10 | +| `reports.ts` | 8/10 | 7/10 | 8/10 | 8/10 | +| `responders.ts` | 7/10 | **4/10** | 7/10 | **2/10** | +| `users.ts` | 8/10 | 8/10 | 8/10 | 9/10 | + +### Specific Findings + +#### `coordination.ts` + +- Temporal ordering (`expiresAt > createdAt`) is enforced via `.refine()` on three schemas — good. +- Lifecycle coupling is implicit: `respondedBy` without `respondedAt` is semantically incoherent but not enforced. +- State machine gap: `massAlertRequestDocSchema.status` has 8 states but no cross-field rules (e.g., `sentAt` should be required when `status === 'sent'`). +- `commandChannelThreadDocSchema.reportId` changed from optional to required — **breaking change** for existing threads without `reportId`. + +#### `reports.ts` + +- `reportOpsReportTypeSchema` (6 values) is a subset of `reportDocSchema.reportType` (11 values). This is intentional but implicit. **Recommendation:** Add a test asserting the subset relationship, or derive the subset from the superset. +- `locationGeohash` length (6) and `exactLocation` lat/lng bounds are well-enforced. +- `reportSharingEventDocSchema` has no `reportId` field — invariant enforced only by Firestore path conventions. + +#### `responders.ts` — Weakest type in the PR + +- `fcmTokens` (array) and `hasFcmToken` (boolean) are denormalized. The invariant `hasFcmToken === (fcmTokens.length > 0)` is **completely implicit** with no enforcement. +- **Recommendation:** Add `.refine((d) => d.hasFcmToken === (d.fcmTokens.length > 0))` or, better, a `.transform()` that derives `hasFcmToken` from `fcmTokens` on parse. + +#### `users.ts` + +- `reportSmsConsentDocSchema.smsConsent` is `z.literal(true)` — excellent, prevents accidental `false` writes. +- `phone` uses `.min(1)` instead of `msisdnPhSchema`. If Philippine numbers only, use the shared validator for consistency. + +--- + +## Comment / Documentation Issues + +### Critical Comment Inaccuracies + +1. **`process-inbox-item.ts:142`** — "pending_media docs are write-once by onMediaFinalize and only deleted here, so reads outside the transaction are safe by design." + - **This is false.** The code reads `pending_media` docs _before_ the transaction, then deletes them _inside_ the transaction. If `onMediaFinalize` writes between the pre-transaction read and the transaction delete, the delete overwrites fresh data. + - **Fix:** Change comment to: "Reads outside the transaction are a known race; we accept it because pending_media is append-only and deletion is best-effort." + +2. **`process-inbox-item.ts:216`** — "smsConsent check is intentional — presence of contact.phone implies smsConsent=true (schema enforces contact.smsConsent as z.literal(true))." + - **Partially misleading.** `inboxPayloadSchema` does enforce it, but this code path only checks `payload.contact?.phone` — it does not validate `payload.contact` against that schema here. + - **Fix:** Clarify that the _payload schema_ guarantees it, not that this code path validates it. + +### Improvement Opportunities + +3. **`coordination.ts:22`** — `agencyAssistanceRequestDocSchema` has `.refine((d) => d.expiresAt > d.createdAt)` but no comment explaining the business rule. +4. **`reports.ts:123`** — No comment explains why ops uses a narrowed enum. Add: "Ops-facing schemas use a restricted enum to prevent rules/test drift." +5. **`firestore.rules:6-11`** — Header claims "names and role literals match spec §5.7 exactly" but the file contains Phase 5 PRE-B extensions. Update header. +6. **`docs/learnings.md:19`** — Great learning about ops-specific enums, but doesn't reference `reportOpsReportTypeSchema`. Cross-reference would help. + +### Recommended Removals + +7. **`reports.ts`** — Redundant `// schemaName — description` comments on every export (lines 13, 22, 85, 97, 138, 150, 161, 173, 185, 200, 225). These add no value and will rot. +8. **`users.ts:26`** — `// reportSmsConsentDocSchema — contacts document` is **factually incorrect**. This is the SMS consent document, not the general contacts document. + +### Positive Findings + +- `docs/learnings.md:16-21` — New learnings about `createTestEnv()`, strict schema transitional fields, ops-specific enums, `z.uuid()` vs `z.string().uuid()`, and collection query vs per-document rules are all accurate and tied to real bugs. +- `docs/progress.md:5-16` — Phase 5 PRE-B entry is precise, lists exact verification commands, and doesn't overclaim. + +--- + +## Additional Test Quality Findings + +| # | Issue | File | Rating | +| --- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ------ | +| 12 | No `command_channel_messages` write-rules test (callable-only writes not verified) | `coordination.rules.test.ts` | 7 | +| 13 | Missing `report_notes` agency_admin and superadmin positive cases | `report-notes.rules.test.ts` | 7 | +| 14 | No `field_mode_sessions` update/delete rule tests | `field-mode-sessions.rules.test.ts` | 6 | +| 15 | Brittle `ngeohash` regex assertion — doesn't verify hash correctness for coordinates | `process-inbox-item.test.ts:175` | 6 | +| 16 | Missing `massAlertRequestDocSchema` boundary tests (body max length, negative reach) | `coordination.test.ts` | 6 | +| 17 | No `reportOpsDocSchema` `duplicateClusterId` or `hazardZoneIdList` validation | `reports.test.ts` | 5 | +| 18 | Schema tests use `not.toThrow()` without asserting parsed output (could mask default-value bugs) | `coordination.test.ts`, `reports.test.ts` | 5 | +| 19 | Rules tests use `beforeAll` for seeding without per-test cleanup — cross-test pollution risk | `coordination.rules.test.ts`, `report-sharing.rules.test.ts` | 5 | + +--- + +## Strengths + +- **Additive and safe:** All changes are additive; no breaking changes to existing collections (except `commandChannelThreadDocSchema.reportId` becoming required). +- **Well-tested patterns:** All new schemas have unit tests; all new rules have rules tests. +- **Rules in sync:** Both `firestore.rules` and `firestore.rules.template` updated identically. +- **Minimal dependencies:** Only `@types/ngeohash` and `ngeohash` added — necessary for geohash feature. +- **Security posture maintained:** Default-deny; least-privilege (`isActivePrivileged`, `adminOf` gating). +- **Idempotency tested:** Both materialization and SMS enqueue paths have idempotency coverage. +- **Participant map-key lookup:** `command_channel_threads/messages` rules thoroughly tested with positive and negative cases. +- **Cross-collection validation:** `report_notes` correctly tests the `report_ops` lookup for municipality validation. +- **Prior learning applied:** `reportSmsConsentDocSchema` now requires `municipalityId`, fixing prior schema/trigger drift. + +--- + +## Recommended Action Plan + +### Phase 1 — Before Merge (Critical + Important) + +1. [ ] Add warning log for malformed `exactLocation` at `process-inbox-item.ts:92` +2. [ ] Add `location_missing` test case to `process-inbox-item.test.ts` +3. [ ] Add SMS salt-missing graceful degradation test +4. [ ] Add `pending_media` not-found test +5. [ ] Add report-sharing events read-rules tests +6. [ ] Add agency-assistance cross-role negative tests +7. [ ] Add warning log for missing `pending_media` docs +8. [ ] Decide on `reportOpsDocSchema.reportType` optionality (remove `.optional()` or document) +9. [ ] Add design comment for `authorRole` responder exclusion +10. [ ] Add `exists()` guards in `messages` and `report_notes` rules (or document intended behavior) + +### Phase 2 — Polish (Type Design + Comments) + +11. [ ] Add `.refine()` or `.transform()` for `hasFcmToken`/`fcmTokens` consistency in `responders.ts` +12. [ ] Add state-dependent refinements on `massAlertRequestDocSchema` (e.g., `sentAt` required when `status === 'sent'`) +13. [ ] Add subset test for `reportOpsReportTypeSchema` vs `reportDocSchema.reportType` +14. [ ] Fix misleading comments in `process-inbox-item.ts:142` and `:216` +15. [ ] Remove redundant schema name comments in `reports.ts` +16. [ ] Fix incorrect comment in `users.ts:26` +17. [ ] Update `firestore.rules` header to acknowledge Phase 5 PRE-B extensions + +### Phase 3 — Test Quality (Optional, Can Defer) + +18. [ ] Add `command_channel_messages` write-rules negative test +19. [ ] Add `report_notes` agency_admin/superadmin positive cases +20. [ ] Add `field_mode_sessions` update/delete rule tests +21. [ ] Strengthen `ngeohash` assertion to verify actual coordinate hash +22. [ ] Add boundary tests for `massAlertRequestDocSchema` +23. [ ] Add validation tests for `duplicateClusterId` and `hazardZoneIdList` +24. [ ] Assert parsed output shape in schema tests (not just `not.toThrow()`) +25. [ ] Consider per-test cleanup in rules tests to prevent cross-test pollution + +--- + +## Verification Commands (from progress.md) + +```bash +# Shared validators +pnpm --filter @bantayog/shared-validators exec vitest run src/coordination.test.ts src/responders.test.ts src/users.test.ts src/reports.test.ts + +# Firestore rules +firebase emulators:exec --only firestore,database,storage "pnpm --filter @bantayog/functions exec vitest run src/__tests__/rules/field-mode-sessions.rules.test.ts src/__tests__/rules/report-notes.rules.test.ts src/__tests__/rules/report-messages.rules.test.ts src/__tests__/rules/coordination.rules.test.ts src/__tests__/rules/report-sharing.rules.test.ts" + +# Inbox trigger +firebase emulators:exec --only firestore "pnpm --filter @bantayog/functions exec vitest run src/__tests__/triggers/process-inbox-item.test.ts" + +# Lint + typecheck +npx turbo run lint typecheck +``` + +--- + +_Review generated by automated PR review toolkit (5 specialized agents)._ diff --git a/functions/package.json b/functions/package.json index ff4431d7..ee8ddb5c 100644 --- a/functions/package.json +++ b/functions/package.json @@ -35,11 +35,13 @@ "file-type": "^22.0.1", "firebase-admin": "^13.8.0", "firebase-functions": "^7.2.5", + "ngeohash": "^0.6.3", "sharp": "^0.34.5", "zod": "^4.3.6" }, "devDependencies": { "@firebase/rules-unit-testing": "^5.0.0", + "@types/ngeohash": "^0.6.8", "@types/node": "^20.12.0", "firebase": "^12.0.0", "firebase-functions-test": "^3.3.0", diff --git a/functions/src/__tests__/rules/coordination.rules.test.ts b/functions/src/__tests__/rules/coordination.rules.test.ts index 4929b6ef..2421c266 100644 --- a/functions/src/__tests__/rules/coordination.rules.test.ts +++ b/functions/src/__tests__/rules/coordination.rules.test.ts @@ -1,5 +1,5 @@ import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing' -import { collection, doc, getDocs, setDoc, addDoc } from 'firebase/firestore' +import { collection, doc, getDoc, getDocs, setDoc, addDoc } from 'firebase/firestore' import { afterAll, beforeAll, describe, it } from 'vitest' import { authed, createTestEnv } from '../helpers/rules-harness.js' import { seedActiveAccount, staffClaims, ts } from '../helpers/seed-factories.js' @@ -13,6 +13,27 @@ beforeAll(async () => { role: 'municipal_admin', municipalityId: 'daet', }) + await seedActiveAccount(env, { + uid: 'other-admin', + role: 'municipal_admin', + municipalityId: 'mercedes', + }) + await seedActiveAccount(env, { + uid: 'bfp-daet-agency', + role: 'agency_admin', + municipalityId: 'daet', + agencyId: 'bfp-daet', + }) + await seedActiveAccount(env, { + uid: 'pnp-daet-agency', + role: 'agency_admin', + municipalityId: 'daet', + agencyId: 'pnp-daet', + }) + await seedActiveAccount(env, { + uid: 'super-admin', + role: 'provincial_superadmin', + }) }) afterAll(async () => { @@ -134,20 +155,183 @@ describe('coordination collections rules', () => { }) it('muni admin can read own municipality requests', async () => { - const unauthed = env.unauthenticatedContext().firestore() - await setDoc(doc(unauthed, 'agency_assistance_requests/req-1'), { - requestedByMunicipality: 'daet', - targetAgencyId: 'bfp-daet', - dispatchId: 'd-1', - requestType: 'BFP', - requestedAt: ts, + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), 'agency_assistance_requests', 'req-1'), { + requestedByMunicipality: 'daet', + targetAgencyId: 'bfp-daet', + dispatchId: 'd-1', + requestType: 'BFP', + requestedAt: ts, + }) + }) + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'agency_assistance_requests', 'req-1'))) + }) + + it('agency admin reads request matching their agencyId (positive)', async () => { + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), 'agency_assistance_requests', 'req-agency-match'), { + requestedByMunicipality: 'daet', + targetAgencyId: 'bfp-daet', + requestType: 'BFP', + requestedAt: ts, + }) + }) + const db = authed( + env, + 'bfp-daet-agency', + staffClaims({ role: 'agency_admin', municipalityId: 'daet', agencyId: 'bfp-daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'agency_assistance_requests', 'req-agency-match'))) + }) + + it('agency admin denied when agencyId does not match targetAgencyId (negative)', async () => { + const db = authed( + env, + 'pnp-daet-agency', + staffClaims({ role: 'agency_admin', municipalityId: 'daet', agencyId: 'pnp-daet' }), + ) + // req-agency-match has targetAgencyId: 'bfp-daet', pnp-daet should be denied + await assertFails(getDoc(doc(db, 'agency_assistance_requests', 'req-agency-match'))) + }) + + it('superadmin reads any agency assistance request (positive)', async () => { + const db = authed(env, 'super-admin', staffClaims({ role: 'provincial_superadmin' })) + await assertSucceeds(getDoc(doc(db, 'agency_assistance_requests', 'req-agency-match'))) + }) + + it('citizen read denied (negative)', async () => { + const db = authed(env, 'citizen-1', staffClaims({ role: 'citizen' })) + await assertFails(getDoc(doc(db, 'agency_assistance_requests', 'req-agency-match'))) + }) + }) + + describe('command_channel_threads/messages participant lookup', () => { + beforeAll(async () => { + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), 'command_channel_threads', 'thread-1'), { + threadId: 'thread-1', + reportId: 'report-1', + threadType: 'agency_assistance', + subject: 'Need help', + participantUids: { 'daet-admin': true }, + createdBy: 'daet-admin', + createdAt: ts, + updatedAt: ts, + schemaVersion: 1, + }) + + await setDoc(doc(ctx.firestore(), 'command_channel_messages', 'msg-1'), { + threadId: 'thread-1', + authorUid: 'daet-admin', + authorRole: 'municipal_admin', + body: 'hello', + createdAt: ts, + schemaVersion: 1, + }) }) + }) + + it('allows participant to read thread', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'command_channel_threads', 'thread-1'))) + }) + + it('denies non-participant from reading thread', async () => { + const db = authed( + env, + 'other-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'mercedes' }), + ) + await assertFails(getDoc(doc(db, 'command_channel_threads', 'thread-1'))) + }) + + it('allows participant to read message through parent thread lookup', async () => { const db = authed( env, 'daet-admin', staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), ) - await assertSucceeds(getDocs(collection(db, 'agency_assistance_requests'))) + await assertSucceeds(getDoc(doc(db, 'command_channel_messages', 'msg-1'))) }) + + it('denies non-participant from reading message', async () => { + const db = authed( + env, + 'other-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'mercedes' }), + ) + await assertFails(getDoc(doc(db, 'command_channel_messages', 'msg-1'))) + }) + }) +}) + +describe('command_channel_threads/messages — participant map key lookup', () => { + beforeAll(async () => { + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), 'command_channel_threads', 'thread-2'), { + threadId: 'thread-2', + reportId: 'report-1', + threadType: 'agency_assistance', + subject: 'Need help', + participantUids: { 'daet-admin': true }, + createdBy: 'daet-admin', + createdAt: ts, + updatedAt: ts, + schemaVersion: 1, + }) + + await setDoc(doc(ctx.firestore(), 'command_channel_messages', 'msg-2'), { + threadId: 'thread-2', + message: 'hello', + sentBy: 'daet-admin', + sentAt: ts, + schemaVersion: 1, + }) + }) + }) + + it('allows participant to read thread', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'command_channel_threads', 'thread-2'))) + }) + + it('denies non-participant from reading thread', async () => { + const db = authed( + env, + 'other-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'mercedes' }), + ) + await assertFails(getDoc(doc(db, 'command_channel_threads', 'thread-2'))) + }) + + it('allows participant to read a message when parent thread participantUids contains uid', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'command_channel_messages', 'msg-2'))) + }) + + it('denies non-participant from reading a message', async () => { + const db = authed( + env, + 'other-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'mercedes' }), + ) + await assertFails(getDoc(doc(db, 'command_channel_messages', 'msg-2'))) }) }) diff --git a/functions/src/__tests__/rules/field-mode-sessions.rules.test.ts b/functions/src/__tests__/rules/field-mode-sessions.rules.test.ts new file mode 100644 index 00000000..cb78accd --- /dev/null +++ b/functions/src/__tests__/rules/field-mode-sessions.rules.test.ts @@ -0,0 +1,97 @@ +import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing' +import { doc, getDoc, setDoc } from 'firebase/firestore' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { authed, createTestEnv, unauthed } from '../helpers/rules-harness.js' +import { seedActiveAccount, staffClaims, ts } from '../helpers/seed-factories.js' + +let env: Awaited> + +const sessionData = { + uid: 'daet-admin', + municipalityId: 'daet', + enteredAt: ts, + expiresAt: ts + 43200000, + isActive: true, + schemaVersion: 1, +} + +beforeAll(async () => { + env = await createTestEnv('field-mode-sessions-rules-test') + await seedActiveAccount(env, { + uid: 'daet-admin', + role: 'municipal_admin', + municipalityId: 'daet', + }) + await seedActiveAccount(env, { + uid: 'other-admin', + role: 'municipal_admin', + municipalityId: 'mercedes', + }) + await seedActiveAccount(env, { uid: 'superadmin', role: 'provincial_superadmin' }) + + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), 'field_mode_sessions', 'daet-admin'), sessionData) + }) +}) + +afterAll(async () => { + await env.cleanup() +}) + +describe('field_mode_sessions rules', () => { + it('allows owner to read their own session', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'field_mode_sessions', 'daet-admin'))) + }) + + it('allows owner to write their own session', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(setDoc(doc(db, 'field_mode_sessions', 'daet-admin'), sessionData)) + }) + + it('denies writes when embedded uid does not match the path', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertFails( + setDoc(doc(db, 'field_mode_sessions', 'daet-admin'), { + ...sessionData, + uid: 'other-admin', + }), + ) + }) + + it('denies other user reading another user session', async () => { + const db = authed( + env, + 'other-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'mercedes' }), + ) + await assertFails(getDoc(doc(db, 'field_mode_sessions', 'daet-admin'))) + }) + + it('denies unauthenticated reads', async () => { + const db = unauthed(env) + await assertFails(getDoc(doc(db, 'field_mode_sessions', 'daet-admin'))) + }) + + it('denies superadmin writes to field_mode_sessions', async () => { + const db = authed(env, 'superadmin', staffClaims({ role: 'provincial_superadmin' })) + await assertFails(setDoc(doc(db, 'field_mode_sessions', 'daet-admin'), sessionData)) + }) + + it('allows superadmin reads', async () => { + const db = authed(env, 'superadmin', staffClaims({ role: 'provincial_superadmin' })) + await assertSucceeds(getDoc(doc(db, 'field_mode_sessions', 'daet-admin'))) + }) +}) diff --git a/functions/src/__tests__/rules/report-messages.rules.test.ts b/functions/src/__tests__/rules/report-messages.rules.test.ts new file mode 100644 index 00000000..e588dfd2 --- /dev/null +++ b/functions/src/__tests__/rules/report-messages.rules.test.ts @@ -0,0 +1,112 @@ +import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing' +import { addDoc, collection, doc, getDoc, setDoc } from 'firebase/firestore' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { authed, createTestEnv, unauthed } from '../helpers/rules-harness.js' +import { seedActiveAccount, seedReport, staffClaims, ts } from '../helpers/seed-factories.js' + +let env: Awaited> + +beforeAll(async () => { + env = await createTestEnv('report-messages-rules-test') + await seedActiveAccount(env, { + uid: 'daet-admin', + role: 'municipal_admin', + municipalityId: 'daet', + }) + await seedActiveAccount(env, { + uid: 'bfp-admin', + role: 'agency_admin', + municipalityId: 'daet', + agencyId: 'bfp-daet', + }) + await seedReport(env, 'report-1', { + municipalityId: 'daet', + opsOverrides: { municipalityId: 'daet', agencyIds: ['bfp-daet'] }, + }) + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), 'reports', 'report-1', 'messages', 'msg-1'), { + authorUid: 'daet-admin', + body: 'Seed message', + createdAt: ts, + schemaVersion: 1, + }) + }) +}) + +afterAll(async () => { + await env.cleanup() +}) + +describe('reports/messages rules', () => { + it('allows muni admin to read a message', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'reports', 'report-1', 'messages', 'msg-1'))) + }) + + it('allows agency admin to read a message when report_ops agencyIds includes their agency', async () => { + const db = authed( + env, + 'bfp-admin', + staffClaims({ role: 'agency_admin', municipalityId: 'daet', agencyId: 'bfp-daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'reports', 'report-1', 'messages', 'msg-1'))) + }) + + it('allows muni admin to write a message', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds( + addDoc(collection(db, 'reports', 'report-1', 'messages'), { + authorUid: 'daet-admin', + body: 'En route.', + createdAt: ts, + schemaVersion: 1, + }), + ) + }) + + it('denies muni admin from writing to another report municipality', async () => { + await seedReport(env, 'report-2', { + municipalityId: 'mercedes', + opsOverrides: { municipalityId: 'mercedes' }, + }) + + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertFails( + addDoc(collection(db, 'reports', 'report-2', 'messages'), { + authorUid: 'daet-admin', + body: 'Out of scope.', + createdAt: ts, + schemaVersion: 1, + }), + ) + }) + + it('denies citizen writes to messages', async () => { + const db = authed(env, 'citizen-1', staffClaims({ role: 'citizen' })) + await assertFails( + addDoc(collection(db, 'reports', 'report-1', 'messages'), { + authorUid: 'citizen-1', + body: 'hi', + createdAt: ts, + schemaVersion: 1, + }), + ) + }) + + it('denies unauthenticated reads', async () => { + const db = unauthed(env) + await assertFails(getDoc(doc(db, 'reports', 'report-1', 'messages', 'msg-1'))) + }) +}) diff --git a/functions/src/__tests__/rules/report-notes.rules.test.ts b/functions/src/__tests__/rules/report-notes.rules.test.ts new file mode 100644 index 00000000..2fe7a9cc --- /dev/null +++ b/functions/src/__tests__/rules/report-notes.rules.test.ts @@ -0,0 +1,105 @@ +import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing' +import { addDoc, collection, getDocs } from 'firebase/firestore' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { authed, createTestEnv, unauthed } from '../helpers/rules-harness.js' +import { seedActiveAccount, seedReport, staffClaims, ts } from '../helpers/seed-factories.js' + +let env: Awaited> + +beforeAll(async () => { + env = await createTestEnv('report-notes-rules-test') + await seedActiveAccount(env, { + uid: 'daet-admin', + role: 'municipal_admin', + municipalityId: 'daet', + }) + await seedActiveAccount(env, { + uid: 'other-admin', + role: 'municipal_admin', + municipalityId: 'mercedes', + }) + await seedReport(env, 'report-daet', { + municipalityId: 'daet', + opsOverrides: { municipalityId: 'daet' }, + }) + await seedReport(env, 'report-mercedes', { + municipalityId: 'mercedes', + opsOverrides: { municipalityId: 'mercedes' }, + }) + await env.withSecurityRulesDisabled(async (ctx) => { + await addDoc(collection(ctx.firestore(), 'report_notes'), { + reportId: 'report-daet', + authorUid: 'daet-admin', + body: 'Seed note', + createdAt: ts, + schemaVersion: 1, + }) + }) +}) + +afterAll(async () => { + await env.cleanup() +}) + +const validNote = { + reportId: 'report-daet', + authorUid: 'daet-admin', + body: 'Situation is stable.', + createdAt: ts, + schemaVersion: 1, +} + +describe('report_notes rules', () => { + it('allows muni admin to write note with matching authorUid and municipality', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(addDoc(collection(db, 'report_notes'), validNote)) + }) + + it('denies muni admin writing note with mismatched authorUid', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertFails( + addDoc(collection(db, 'report_notes'), { ...validNote, authorUid: 'other-admin' }), + ) + }) + + it('denies muni admin writing note for a report in a different municipality', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertFails( + addDoc(collection(db, 'report_notes'), { + ...validNote, + reportId: 'report-mercedes', + }), + ) + }) + + it('denies citizen writes', async () => { + const db = authed(env, 'citizen-1', staffClaims({ role: 'citizen' })) + await assertFails(addDoc(collection(db, 'report_notes'), validNote)) + }) + + it('denies unauthenticated reads', async () => { + const db = unauthed(env) + await assertFails(getDocs(collection(db, 'report_notes'))) + }) + + it('allows muni admin to read notes', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDocs(collection(db, 'report_notes'))) + }) +}) diff --git a/functions/src/__tests__/rules/report-sharing.rules.test.ts b/functions/src/__tests__/rules/report-sharing.rules.test.ts index 6058f2ac..227e3f53 100644 --- a/functions/src/__tests__/rules/report-sharing.rules.test.ts +++ b/functions/src/__tests__/rules/report-sharing.rules.test.ts @@ -1,6 +1,6 @@ import { assertFails, assertSucceeds } from '@firebase/rules-unit-testing' -import { doc, getDoc } from 'firebase/firestore' -import { afterAll, beforeAll, describe, it } from 'vitest' +import { addDoc, collection, doc, getDoc, getDocs, setDoc } from 'firebase/firestore' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { authed, createTestEnv, unauthed } from '../helpers/rules-harness.js' import { seedActiveAccount, staffClaims } from '../helpers/seed-factories.js' @@ -18,6 +18,12 @@ beforeAll(async () => { role: 'municipal_admin', municipalityId: 'mercedes', }) + await seedActiveAccount(env, { + uid: 'mercedes-agency', + role: 'agency_admin', + municipalityId: 'mercedes', + agencyId: 'bfp-mercedes', + }) await seedActiveAccount(env, { uid: 'libman-admin', role: 'municipal_admin', @@ -31,19 +37,14 @@ beforeAll(async () => { // Seed sharing doc owned by daet, shared with mercedes await env.withSecurityRulesDisabled(async (ctx) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const db: any = ctx.firestore() - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ - await db - .collection('report_sharing') - .doc('r-share-1') - .set({ - ownerMunicipalityId: 'daet', - sharedWith: ['mercedes'], - reportId: 'r-share-1', - createdAt: 1713350400000, - schemaVersion: 1, - }) + await setDoc(doc(ctx.firestore(), 'report_sharing', 'r-share-1'), { + ownerMunicipalityId: 'daet', + sharedWith: ['mercedes'], + reportId: 'r-share-1', + createdAt: 1713350400000, + updatedAt: 1713350400000, + schemaVersion: 1, + }) }) }) @@ -70,6 +71,15 @@ describe('report_sharing rules', () => { await assertSucceeds(getDoc(doc(db, 'report_sharing/r-share-1'))) }) + it('active agency admin whose municipality is shared reads (positive)', async () => { + const db = authed( + env, + 'mercedes-agency', + staffClaims({ role: 'agency_admin', municipalityId: 'mercedes', agencyId: 'bfp-mercedes' }), + ) + await assertSucceeds(getDoc(doc(db, 'report_sharing/r-share-1'))) + }) + it('non-recipient admin fails (negative)', async () => { const db = authed( env, @@ -97,7 +107,6 @@ describe('report_sharing rules', () => { 'daet-admin', staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), ) - const { setDoc } = await import('firebase/firestore') await assertFails( setDoc(doc(db, 'report_sharing/new'), { ownerMunicipalityId: 'daet', @@ -111,3 +120,119 @@ describe('report_sharing rules', () => { await assertFails(getDoc(doc(db, 'report_sharing/r-share-1'))) }) }) + +const validEvent = { + targetMunicipalityId: 'mercedes', + sharedBy: 'daet-admin', + sharedAt: 1713350400000, + sharedReason: 'Border incident', + source: 'manual', + schemaVersion: 1, +} + +describe('report_sharing/events rules', () => { + it('allows muni admin to write event to subcollection', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds( + addDoc(collection(db, 'report_sharing', 'r-share-1', 'events'), validEvent), + ) + }) + + it('denies a different municipality admin from writing the share event', async () => { + const db = authed( + env, + 'mercedes-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'mercedes' }), + ) + await assertFails( + addDoc(collection(db, 'report_sharing', 'r-share-1', 'events'), { + ...validEvent, + sharedBy: 'mercedes-admin', + targetMunicipalityId: 'daet', + }), + ) + }) + + it('a second share appends a second event without overwriting first', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds( + addDoc(collection(db, 'report_sharing', 'r-share-1', 'events'), { + ...validEvent, + targetMunicipalityId: 'labo', + }), + ) + + await env.withSecurityRulesDisabled(async (ctx) => { + const snap = await getDocs( + collection(ctx.firestore(), 'report_sharing', 'r-share-1', 'events'), + ) + expect(snap.size).toBeGreaterThanOrEqual(2) + }) + }) + + it('denies citizen writes to events subcollection', async () => { + const db = authed(env, 'citizen-1', staffClaims({ role: 'citizen' })) + await assertFails(addDoc(collection(db, 'report_sharing', 'r-share-1', 'events'), validEvent)) + }) +}) + +describe('report_sharing/events rules — reads', () => { + const seededEventId = 'seeded-evt-1' + + beforeAll(async () => { + await env.withSecurityRulesDisabled(async (ctx) => { + await setDoc( + doc(ctx.firestore(), 'report_sharing', 'r-share-1', 'events', seededEventId), + validEvent, + ) + }) + }) + + it('allows muni admin to read events subcollection (positive)', async () => { + const db = authed( + env, + 'daet-admin', + staffClaims({ role: 'municipal_admin', municipalityId: 'daet' }), + ) + await assertSucceeds(getDoc(doc(db, 'report_sharing', 'r-share-1', 'events', seededEventId))) + }) + + it('allows agency admin to read events subcollection (positive)', async () => { + const db = authed( + env, + 'mercedes-agency', + staffClaims({ role: 'agency_admin', municipalityId: 'mercedes', agencyId: 'bfp-mercedes' }), + ) + await assertSucceeds(getDoc(doc(db, 'report_sharing', 'r-share-1', 'events', seededEventId))) + }) + + it('allows superadmin to read events subcollection (positive)', async () => { + const db = authed( + env, + 'super-1', + staffClaims({ + role: 'provincial_superadmin', + permittedMunicipalityIds: ['daet', 'mercedes'], + }), + ) + await assertSucceeds(getDoc(doc(db, 'report_sharing', 'r-share-1', 'events', seededEventId))) + }) + + it('denies citizen reads on events subcollection (negative)', async () => { + const db = authed(env, 'citizen-1', staffClaims({ role: 'citizen' })) + await assertFails(getDoc(doc(db, 'report_sharing', 'r-share-1', 'events', seededEventId))) + }) + + it('denies unauthenticated reads on events subcollection (negative)', async () => { + const db = unauthed(env) + await assertFails(getDoc(doc(db, 'report_sharing', 'r-share-1', 'events', seededEventId))) + }) +}) diff --git a/functions/src/__tests__/triggers/process-inbox-item.test.ts b/functions/src/__tests__/triggers/process-inbox-item.test.ts index a7d05039..cbadd211 100644 --- a/functions/src/__tests__/triggers/process-inbox-item.test.ts +++ b/functions/src/__tests__/triggers/process-inbox-item.test.ts @@ -96,6 +96,7 @@ describe('processInboxItemCore', () => { const opsSnap = await getDoc(doc(ctx.firestore(), 'report_ops', result.reportId)) expect(opsSnap.exists()).toBe(true) + expect(opsSnap.data()?.reportType).toBe('flood') const lookupSnap = await getDoc(doc(ctx.firestore(), 'report_lookup', 'a1b2c3d4')) expect(lookupSnap.exists()).toBe(true) @@ -142,6 +143,99 @@ describe('processInboxItemCore', () => { }) }) + it('writes 6-char locationGeohash onto report_ops when exactLocation present', async () => { + await env!.withSecurityRulesDisabled(async (ctx) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db = ctx.firestore() as any + await setDoc(doc(ctx.firestore(), 'report_inbox', 'ibx-geohash'), { + reporterUid: 'citizen-geo', + clientCreatedAt: 1713350400000, + idempotencyKey: 'idem-geo', + publicRef: 'geo00123', + secretHash: 'a'.repeat(64), + correlationId: '99999999-9999-4999-8999-999999999999', + payload: { + reportType: 'fire', + description: 'structure fire', + severity: 'high', + source: 'web', + publicLocation: { lat: 14.11, lng: 122.95 }, + exactLocation: { lat: 14.11, lng: 122.95 }, + }, + }) + + const result = await processInboxItemCore({ + db, + inboxId: 'ibx-geohash', + now: () => 1713350401000, + }) + + const opsSnap = await getDoc(doc(ctx.firestore(), 'report_ops', result.reportId)) + expect(opsSnap.exists()).toBe(true) + const firstGeohash = opsSnap.data()?.locationGeohash + expect(firstGeohash).toMatch(/^[a-z0-9]{6}$/) + + await setDoc(doc(ctx.firestore(), 'report_inbox', 'ibx-geohash-2'), { + reporterUid: 'citizen-geo', + clientCreatedAt: 1713350400000, + idempotencyKey: 'idem-geo-2', + publicRef: 'geo00124', + secretHash: 'c'.repeat(64), + correlationId: '88888888-8888-4888-8888-888888888888', + payload: { + reportType: 'fire', + description: 'structure fire', + severity: 'high', + source: 'web', + publicLocation: { lat: 14.11, lng: 122.95 }, + exactLocation: { lat: 14.12, lng: 122.96 }, + }, + }) + + const secondResult = await processInboxItemCore({ + db, + inboxId: 'ibx-geohash-2', + now: () => 1713350402000, + }) + const secondOpsSnap = await getDoc(doc(ctx.firestore(), 'report_ops', secondResult.reportId)) + const secondGeohash = secondOpsSnap.data()?.locationGeohash + expect(secondGeohash).toMatch(/^[a-z0-9]{6}$/) + expect(secondGeohash).not.toBe(firstGeohash) + }) + }) + + it('omits locationGeohash from report_ops when exactLocation is absent', async () => { + await env!.withSecurityRulesDisabled(async (ctx) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db = ctx.firestore() as any + await setDoc(doc(ctx.firestore(), 'report_inbox', 'ibx-noloc'), { + reporterUid: 'citizen-noloc', + clientCreatedAt: 1713350400000, + idempotencyKey: 'idem-noloc', + publicRef: 'noloc123', + secretHash: 'b'.repeat(64), + correlationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + payload: { + reportType: 'flood', + description: 'sms flood report', + severity: 'medium', + source: 'sms', + publicLocation: { lat: 14.11, lng: 122.95 }, + }, + }) + + const result = await processInboxItemCore({ + db, + inboxId: 'ibx-noloc', + now: () => 1713350401000, + }) + + const opsSnap = await getDoc(doc(ctx.firestore(), 'report_ops', result.reportId)) + expect(opsSnap.exists()).toBe(true) + expect(opsSnap.data()?.locationGeohash).toBeUndefined() + }) + }) + it('moves pending_media references into reports/{id}/media', async () => { await env!.withSecurityRulesDisabled(async (ctx) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -244,6 +338,113 @@ describe('processInboxItemCore', () => { }) }) + it('writes moderation_incident with reason location_missing when publicLocation is absent', async () => { + await env!.withSecurityRulesDisabled(async (ctx) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db = ctx.firestore() as any + await setDoc(doc(ctx.firestore(), 'report_inbox', 'ibx-nopublic'), { + reporterUid: 'citizen-1', + clientCreatedAt: 1713350400000, + idempotencyKey: 'idem-nopublic', + publicRef: 'nopub123', + secretHash: 'f'.repeat(64), + correlationId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + payload: { + reportType: 'flood', + description: 'no location provided', + severity: 'high', + source: 'sms', + // publicLocation intentionally absent + }, + }) + + await expect( + processInboxItemCore({ db, inboxId: 'ibx-nopublic', now: () => 1713350401000 }), + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }) + + const incidentSnap = await getDoc( + doc(ctx.firestore(), 'moderation_incidents', 'ibx-nopublic'), + ) + expect(incidentSnap.exists()).toBe(true) + expect(incidentSnap.data()?.reason).toBe('location_missing') + }) + }) + + it('skips SMS enqueue without throwing when SMS_MSISDN_HASH_SALT is unset', async () => { + await env!.withSecurityRulesDisabled(async (ctx) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db = ctx.firestore() as any + const savedSalt = process.env.SMS_MSISDN_HASH_SALT + try { + delete process.env.SMS_MSISDN_HASH_SALT + await setDoc(doc(ctx.firestore(), 'report_inbox', 'ibx-nosalt'), { + reporterUid: 'citizen-salt', + clientCreatedAt: 1713350400000, + idempotencyKey: 'idem-nosalt', + publicRef: 'nosalt01', + secretHash: 'f'.repeat(64), + correlationId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', + payload: { + reportType: 'flood', + description: 'salt missing test', + severity: 'low', + source: 'web', + publicLocation: { lat: 14.11, lng: 122.95 }, + contact: { phone: '+639171234567', smsConsent: true }, + }, + }) + + const result = await processInboxItemCore({ + db, + inboxId: 'ibx-nosalt', + now: () => 1713350401000, + }) + expect(result.materialized).toBe(true) + + const outboxQ = await getDocs(collection(ctx.firestore(), 'sms_outbox')) + expect(outboxQ.size).toBe(0) + } finally { + process.env.SMS_MSISDN_HASH_SALT = savedSalt + } + }) + }) + + it('materializes report without media when pendingMediaIds references a missing doc', async () => { + await env!.withSecurityRulesDisabled(async (ctx) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db = ctx.firestore() as any + // Do NOT seed a pending_media doc for 'ghost-upload-id' + await setDoc(doc(ctx.firestore(), 'report_inbox', 'ibx-ghostmedia'), { + reporterUid: 'citizen-ghost', + clientCreatedAt: 1713350400000, + idempotencyKey: 'idem-ghostmedia', + publicRef: 'ghost123', + secretHash: 'f'.repeat(64), + correlationId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', + payload: { + reportType: 'fire', + description: 'ghost media test', + severity: 'medium', + source: 'web', + publicLocation: { lat: 14.11, lng: 122.95 }, + pendingMediaIds: ['ghost-upload-id'], + }, + }) + + const result = await processInboxItemCore({ + db, + inboxId: 'ibx-ghostmedia', + now: () => 1713350401000, + }) + expect(result.materialized).toBe(true) + + const mediaDocs = await getDocs( + collection(ctx.firestore(), 'reports', result.reportId, 'media'), + ) + expect(mediaDocs.size).toBe(0) + }) + }) + it('throws NOT_FOUND when inbox doc does not exist', async () => { await env!.withSecurityRulesDisabled(async (ctx) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/functions/src/triggers/process-inbox-item.ts b/functions/src/triggers/process-inbox-item.ts index de16d53c..7335af12 100644 --- a/functions/src/triggers/process-inbox-item.ts +++ b/functions/src/triggers/process-inbox-item.ts @@ -1,5 +1,6 @@ import { randomUUID } from 'node:crypto' import type { Firestore } from 'firebase-admin/firestore' +import ngeohash from 'ngeohash' import { BantayogError, BantayogErrorCode, @@ -77,6 +78,7 @@ export async function processInboxItemCore( ) } const payload = payloadResult.data + const exactLocation = payload.exactLocation let geo: Awaited> | null = null if (payload.publicLocation) { @@ -123,11 +125,18 @@ export async function processInboxItemCore( uploadId, pendingSnap.data() as { storagePath: string; mimeType: string; strippedAt: number }, ) + } else { + log({ + severity: 'WARNING', + code: 'INBOX_PENDING_MEDIA_MISSING', + message: `pending_media doc not found: ${uploadId} (inbox ${inboxId})`, + data: { inboxId, uploadId }, + }) } } - // pending_media docs are write-once by onMediaFinalize and only deleted here, - // so reads outside the transaction are safe by design. + // Reads outside the transaction are a known race; we accept it because + // pending_media is append-only and deletion is best-effort. await db.runTransaction(async (tx) => { const lookupRef = db.collection('report_lookup').doc(inbox.publicRef) @@ -174,6 +183,10 @@ export async function processInboxItemCore( agencyIds: [], activeResponderCount: 0, requiresLocationFollowUp: false, + reportType: payload.reportType, + ...(exactLocation + ? { locationGeohash: ngeohash.encode(exactLocation.lat, exactLocation.lng, 6) } + : {}), visibility: { scope: 'municipality', sharedWith: [] }, updatedAt: createdAt, schemaVersion: 1, @@ -196,8 +209,9 @@ export async function processInboxItemCore( schemaVersion: 1, }) - // smsConsent check is intentional — presence of contact.phone implies smsConsent=true - // (schema enforces contact.smsConsent as z.literal(true)) + // The inboxPayloadSchema enforces contact.smsConsent as z.literal(true), so + // presence of contact.phone here means the payload schema already validated consent. + // This code path does not re-validate — it relies on upstream schema enforcement. if (payload.contact?.phone) { const salt = process.env.SMS_MSISDN_HASH_SALT if (!salt) { @@ -223,6 +237,7 @@ export async function processInboxItemCore( phone: payload.contact.phone, locale: muniLocale, smsConsent: true, + municipalityId: geo.municipalityId, createdAt, schemaVersion: 1, }) diff --git a/infra/firebase/firestore.rules b/infra/firebase/firestore.rules index 32287eb3..0f3fcbb2 100644 --- a/infra/firebase/firestore.rules +++ b/infra/firebase/firestore.rules @@ -85,7 +85,6 @@ service cloud.firestore { // - system triggers: processInboxItem, dispatchMirrorToReport // - callables only: verifyReport, dispatchResponder, cancelDispatch, closeReport // Responders NEVER write reports.status directly. - deny update: if isResponder(); allow update: if adminOf(resource.data.municipalityId) && request.resource.data.diff(resource.data) .affectedKeys() @@ -102,9 +101,21 @@ service cloud.firestore { } match /messages/{m} { allow read: if isActivePrivileged() - && (isMuniAdmin() || isSuperadmin() - || (isResponder() && exists(/databases/$(database)/documents/dispatches/$(reportId + '_' + uid())))); - allow write: if false; + && (adminOf(get(/databases/$(database)/documents/reports/$(reportId)).data.municipalityId) + || (isAgencyAdmin() + && exists(/databases/$(database)/documents/report_ops/$(reportId)) + && myAgency() in get(/databases/$(database)/documents/report_ops/$(reportId)).data.agencyIds)); + allow create: if isActivePrivileged() + && request.auth.uid == request.resource.data.authorUid + && ( + adminOf(get(/databases/$(database)/documents/reports/$(reportId)).data.municipalityId) + || ( + isAgencyAdmin() + && exists(/databases/$(database)/documents/report_ops/$(reportId)) + && myAgency() in get(/databases/$(database)/documents/report_ops/$(reportId)).data.agencyIds + ) + ); + allow update, delete: if false; } match /field_notes/{n} { allow read: if isActivePrivileged() @@ -127,8 +138,18 @@ service cloud.firestore { match /report_sharing/{r} { allow read: if adminOf(resource.data.ownerMunicipalityId) - || (isSuperadmin() && resource.data.sharedWith.hasAny([myMunicipality()])); + || (isActivePrivileged() && resource.data.sharedWith.hasAny([myMunicipality()])); allow write: if false; + + match /events/{eventId} { + allow read: if isActivePrivileged() + && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()); + allow create: if isActivePrivileged() + && request.auth.uid == request.resource.data.sharedBy + && exists(/databases/$(database)/documents/report_sharing/$(r)) + && adminOf(get(/databases/$(database)/documents/report_sharing/$(r)).data.ownerMunicipalityId); + allow update, delete: if false; + } } match /report_contacts/{r} { @@ -136,6 +157,35 @@ service cloud.firestore { allow write: if false; } + match /field_mode_sessions/{sessionUid} { + allow read: if isAuthed() && (request.auth.uid == sessionUid || isSuperadmin()); + allow write: if isAuthed() + && request.auth.uid == sessionUid + && request.resource.data.uid == sessionUid + && isActivePrivileged() + && !isSuperadmin(); + } + + match /report_notes/{n} { + allow read: if isActivePrivileged() + && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()); + allow create: if isActivePrivileged() + && request.auth.uid == request.resource.data.authorUid + && exists(/databases/$(database)/documents/report_ops/$(request.resource.data.reportId)) + && ( + adminOf( + get(/databases/$(database)/documents/report_ops/$(request.resource.data.reportId)) + .data.municipalityId + ) + || ( + isAgencyAdmin() + && myAgency() in get(/databases/$(database)/documents/report_ops/$(request.resource.data.reportId)) + .data.agencyIds + ) + ); + allow update, delete: if false; + } + match /report_lookup/{publicRef} { allow read: if isAuthed(); allow write: if false; @@ -329,14 +379,15 @@ service cloud.firestore { match /command_channel_threads/{threadId} { allow read: if isActivePrivileged() && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()) - && request.auth.uid in resource.data.participantUids; + && resource.data.participantUids[request.auth.uid] == true; allow write: if false; } match /command_channel_messages/{messageId} { allow read: if isActivePrivileged() && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()) - && request.auth.uid in get(/databases/$(database)/documents/command_channel_threads/$(resource.data.threadId)).data.participantUids; + && get(/databases/$(database)/documents/command_channel_threads/$(resource.data.threadId)) + .data.participantUids[request.auth.uid] == true; allow write: if false; } diff --git a/infra/firebase/firestore.rules.template b/infra/firebase/firestore.rules.template index 11b312dd..43c4688f 100644 --- a/infra/firebase/firestore.rules.template +++ b/infra/firebase/firestore.rules.template @@ -66,7 +66,6 @@ service cloud.firestore { // - system triggers: processInboxItem, dispatchMirrorToReport // - callables only: verifyReport, dispatchResponder, cancelDispatch, closeReport // Responders NEVER write reports.status directly. - deny update: if isResponder(); allow update: if adminOf(resource.data.municipalityId) && request.resource.data.diff(resource.data) .affectedKeys() @@ -83,9 +82,21 @@ service cloud.firestore { } match /messages/{m} { allow read: if isActivePrivileged() - && (isMuniAdmin() || isSuperadmin() - || (isResponder() && exists(/databases/$(database)/documents/dispatches/$(reportId + '_' + uid())))); - allow write: if false; + && (adminOf(get(/databases/$(database)/documents/reports/$(reportId)).data.municipalityId) + || (isAgencyAdmin() + && exists(/databases/$(database)/documents/report_ops/$(reportId)) + && myAgency() in get(/databases/$(database)/documents/report_ops/$(reportId)).data.agencyIds)); + allow create: if isActivePrivileged() + && request.auth.uid == request.resource.data.authorUid + && ( + adminOf(get(/databases/$(database)/documents/reports/$(reportId)).data.municipalityId) + || ( + isAgencyAdmin() + && exists(/databases/$(database)/documents/report_ops/$(reportId)) + && myAgency() in get(/databases/$(database)/documents/report_ops/$(reportId)).data.agencyIds + ) + ); + allow update, delete: if false; } match /field_notes/{n} { allow read: if isActivePrivileged() @@ -108,8 +119,18 @@ service cloud.firestore { match /report_sharing/{r} { allow read: if adminOf(resource.data.ownerMunicipalityId) - || (isSuperadmin() && resource.data.sharedWith.hasAny([myMunicipality()])); + || (isActivePrivileged() && resource.data.sharedWith.hasAny([myMunicipality()])); allow write: if false; + + match /events/{eventId} { + allow read: if isActivePrivileged() + && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()); + allow create: if isActivePrivileged() + && request.auth.uid == request.resource.data.sharedBy + && exists(/databases/$(database)/documents/report_sharing/$(r)) + && adminOf(get(/databases/$(database)/documents/report_sharing/$(r)).data.ownerMunicipalityId); + allow update, delete: if false; + } } match /report_contacts/{r} { @@ -117,6 +138,35 @@ service cloud.firestore { allow write: if false; } + match /field_mode_sessions/{sessionUid} { + allow read: if isAuthed() && (request.auth.uid == sessionUid || isSuperadmin()); + allow write: if isAuthed() + && request.auth.uid == sessionUid + && request.resource.data.uid == sessionUid + && isActivePrivileged() + && !isSuperadmin(); + } + + match /report_notes/{n} { + allow read: if isActivePrivileged() + && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()); + allow create: if isActivePrivileged() + && request.auth.uid == request.resource.data.authorUid + && exists(/databases/$(database)/documents/report_ops/$(request.resource.data.reportId)) + && ( + adminOf( + get(/databases/$(database)/documents/report_ops/$(request.resource.data.reportId)) + .data.municipalityId + ) + || ( + isAgencyAdmin() + && myAgency() in get(/databases/$(database)/documents/report_ops/$(request.resource.data.reportId)) + .data.agencyIds + ) + ); + allow update, delete: if false; + } + match /report_lookup/{publicRef} { allow read: if isAuthed(); allow write: if false; @@ -310,14 +360,15 @@ service cloud.firestore { match /command_channel_threads/{threadId} { allow read: if isActivePrivileged() && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()) - && request.auth.uid in resource.data.participantUids; + && resource.data.participantUids[request.auth.uid] == true; allow write: if false; } match /command_channel_messages/{messageId} { allow read: if isActivePrivileged() && (isMuniAdmin() || isAgencyAdmin() || isSuperadmin()) - && request.auth.uid in get(/databases/$(database)/documents/command_channel_threads/$(resource.data.threadId)).data.participantUids; + && get(/databases/$(database)/documents/command_channel_threads/$(resource.data.threadId)) + .data.participantUids[request.auth.uid] == true; allow write: if false; } diff --git a/packages/shared-validators/lib/coordination.d.ts b/packages/shared-validators/lib/coordination.d.ts index cc7c64be..0362c7e9 100644 --- a/packages/shared-validators/lib/coordination.d.ts +++ b/packages/shared-validators/lib/coordination.d.ts @@ -28,16 +28,24 @@ export declare const agencyAssistanceRequestDocSchema: z.ZodObject<{ fulfilledByDispatchIds: z.ZodArray; createdAt: z.ZodNumber; respondedAt: z.ZodOptional; + respondedBy: z.ZodOptional; + escalatedAt: z.ZodOptional; expiresAt: z.ZodNumber; }, z.core.$strict>; export declare const commandChannelThreadDocSchema: z.ZodObject<{ threadId: z.ZodString; - reportId: z.ZodOptional; + reportId: z.ZodString; + threadType: z.ZodEnum<{ + agency_assistance: "agency_assistance"; + border_share: "border_share"; + }>; + assistanceRequestId: z.ZodOptional; subject: z.ZodString; participantUids: z.ZodRecord>; createdBy: z.ZodString; createdAt: z.ZodNumber; updatedAt: z.ZodNumber; + lastMessageAt: z.ZodOptional; closedAt: z.ZodOptional; schemaVersion: z.ZodNumber; }, z.core.$strict>; @@ -50,6 +58,7 @@ export declare const commandChannelMessageDocSchema: z.ZodObject<{ provincial_superadmin: "provincial_superadmin"; }>; body: z.ZodString; + idempotencyKey: z.ZodOptional; createdAt: z.ZodNumber; schemaVersion: z.ZodNumber; }, z.core.$strict>; @@ -70,7 +79,10 @@ export declare const massAlertRequestDocSchema: z.ZodObject<{ targetGeometryRef: z.ZodOptional; estimatedReach: z.ZodNumber; status: z.ZodEnum<{ + declined: "declined"; queued: "queued"; + sent: "sent"; + pending_ndrrmc_review: "pending_ndrrmc_review"; submitted_to_pdrrmo: "submitted_to_pdrrmo"; forwarded_to_ndrrmc: "forwarded_to_ndrrmc"; acknowledged_by_ndrrmc: "acknowledged_by_ndrrmc"; @@ -78,13 +90,16 @@ export declare const massAlertRequestDocSchema: z.ZodObject<{ }>; createdAt: z.ZodNumber; forwardedAt: z.ZodOptional; + forwardMethod: z.ZodOptional; + ndrrrcRecipient: z.ZodOptional; acknowledgedAt: z.ZodOptional; cancelledAt: z.ZodOptional; + sentAt: z.ZodOptional; schemaVersion: z.ZodNumber; }, z.core.$strict>; export declare const shiftHandoffDocSchema: z.ZodObject<{ fromUid: z.ZodString; - toUid: z.ZodString; + toUid: z.ZodOptional; municipalityId: z.ZodString; activeIncidentSnapshot: z.ZodArray; notes: z.ZodString; @@ -95,6 +110,7 @@ export declare const shiftHandoffDocSchema: z.ZodObject<{ }>; createdAt: z.ZodNumber; acceptedAt: z.ZodOptional; + escalatedAt: z.ZodOptional; expiresAt: z.ZodNumber; schemaVersion: z.ZodNumber; }, z.core.$strict>; @@ -107,10 +123,20 @@ export declare const breakglassEventDocSchema: z.ZodObject<{ correlationId: z.ZodString; schemaVersion: z.ZodNumber; }, z.core.$strict>; +export declare const fieldModeSessionDocSchema: z.ZodObject<{ + uid: z.ZodString; + municipalityId: z.ZodString; + enteredAt: z.ZodNumber; + expiresAt: z.ZodNumber; + exitedAt: z.ZodOptional; + isActive: z.ZodBoolean; + schemaVersion: z.ZodNumber; +}, z.core.$strict>; export type AgencyAssistanceRequestDoc = z.infer; export type CommandChannelThreadDoc = z.infer; export type CommandChannelMessageDoc = z.infer; export type MassAlertRequestDoc = z.infer; export type ShiftHandoffDoc = z.infer; export type BreakglassEventDoc = z.infer; +export type FieldModeSessionDoc = z.infer; //# sourceMappingURL=coordination.d.ts.map \ No newline at end of file diff --git a/packages/shared-validators/lib/coordination.d.ts.map b/packages/shared-validators/lib/coordination.d.ts.map index f04b2a48..9fd9d84f 100644 --- a/packages/shared-validators/lib/coordination.d.ts.map +++ b/packages/shared-validators/lib/coordination.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"coordination.d.ts","sourceRoot":"","sources":["../src/coordination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmBzC,CAAA;AAEJ,eAAO,MAAM,6BAA6B;;;;;;;;;;kBAY/B,CAAA;AAEX,eAAO,MAAM,8BAA8B;;;;;;;;;;;kBAShC,CAAA;AAEX,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAsB3B,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;kBAgB9B,CAAA;AAEJ,eAAO,MAAM,wBAAwB;;;;;;;;kBAU1B,CAAA;AAEX,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AACzF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACnF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAA;AACrF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AACnE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"coordination.d.ts","sourceRoot":"","sources":["../src/coordination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAuBzC,CAAA;AAEJ,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;kBAe/B,CAAA;AAEX,eAAO,MAAM,8BAA8B;;;;;;;;;;;;kBAYhC,CAAA;AAEX,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4B3B,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;kBAiB9B,CAAA;AAEJ,eAAO,MAAM,wBAAwB;;;;;;;;kBAU1B,CAAA;AAEX,eAAO,MAAM,yBAAyB;;;;;;;;kBAalC,CAAA;AAEJ,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AACzF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AACnF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAA;AACrF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AACnE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/coordination.js b/packages/shared-validators/lib/coordination.js index eee4d3a7..9bc5f462 100644 --- a/packages/shared-validators/lib/coordination.js +++ b/packages/shared-validators/lib/coordination.js @@ -13,21 +13,28 @@ export const agencyAssistanceRequestDocSchema = z fulfilledByDispatchIds: z.array(z.string()), createdAt: z.number().int(), respondedAt: z.number().int().optional(), + respondedBy: z.string().optional(), + escalatedAt: z.number().int().optional(), expiresAt: z.number().int(), }) .strict() + // Assistance windows must have a positive duration — expiresAt is set by the + // requesting municipality and must exceed the request creation timestamp. .refine((d) => d.expiresAt > d.createdAt, { message: 'expiresAt must be after createdAt', }); export const commandChannelThreadDocSchema = z .object({ threadId: z.string().min(1), - reportId: z.string().optional(), + reportId: z.string().min(1), + threadType: z.enum(['agency_assistance', 'border_share']), + assistanceRequestId: z.string().min(1).optional(), subject: z.string().max(200), participantUids: z.record(z.string(), z.literal(true)), createdBy: z.string().min(1), createdAt: z.number().int(), updatedAt: z.number().int(), + lastMessageAt: z.number().int().optional(), closedAt: z.number().int().optional(), schemaVersion: z.number().int().positive(), }) @@ -36,8 +43,11 @@ export const commandChannelMessageDocSchema = z .object({ threadId: z.string().min(1), authorUid: z.string().min(1), + // Responders appear in participantUids but cannot author messages; + // command channel posts are admin/agency/superadmin only. authorRole: z.enum(['municipal_admin', 'agency_admin', 'provincial_superadmin']), body: z.string().max(2000), + idempotencyKey: z.uuid().optional(), createdAt: z.number().int(), schemaVersion: z.number().int().positive(), }) @@ -53,28 +63,35 @@ export const massAlertRequestDocSchema = z estimatedReach: z.number().int().nonnegative(), status: z.enum([ 'queued', + 'sent', + 'pending_ndrrmc_review', 'submitted_to_pdrrmo', 'forwarded_to_ndrrmc', 'acknowledged_by_ndrrmc', + 'declined', 'cancelled', ]), createdAt: z.number().int(), forwardedAt: z.number().int().optional(), + forwardMethod: z.string().optional(), + ndrrrcRecipient: z.string().optional(), acknowledgedAt: z.number().int().optional(), cancelledAt: z.number().int().optional(), + sentAt: z.number().int().optional(), schemaVersion: z.number().int().positive(), }) .strict(); export const shiftHandoffDocSchema = z .object({ fromUid: z.string().min(1), - toUid: z.string().min(1), + toUid: z.string().min(1).optional(), municipalityId: z.string().min(1), activeIncidentSnapshot: z.array(z.string()), notes: z.string().max(2000), status: z.enum(['pending', 'accepted', 'expired']), createdAt: z.number().int(), acceptedAt: z.number().int().optional(), + escalatedAt: z.number().int().optional(), expiresAt: z.number().int(), schemaVersion: z.number().int().positive(), }) @@ -93,4 +110,18 @@ export const breakglassEventDocSchema = z schemaVersion: z.number().int().positive(), }) .strict(); +export const fieldModeSessionDocSchema = z + .object({ + uid: z.string().min(1), + municipalityId: z.string().min(1), + enteredAt: z.number().int(), + expiresAt: z.number().int(), + exitedAt: z.number().int().optional(), + isActive: z.boolean(), + schemaVersion: z.number().int().positive(), +}) + .strict() + .refine((d) => d.expiresAt > d.enteredAt, { + message: 'expiresAt must be after enteredAt', +}); //# sourceMappingURL=coordination.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/coordination.js.map b/packages/shared-validators/lib/coordination.js.map index 61702c57..f705815e 100644 --- a/packages/shared-validators/lib/coordination.js.map +++ b/packages/shared-validators/lib/coordination.js.map @@ -1 +1 @@ -{"version":3,"file":"coordination.js","sourceRoot":"","sources":["../src/coordination.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"coordination.js","sourceRoot":"","sources":["../src/coordination.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/shared-validators/lib/coordination.test.js b/packages/shared-validators/lib/coordination.test.js index da7078ed..f5159dd7 100644 --- a/packages/shared-validators/lib/coordination.test.js +++ b/packages/shared-validators/lib/coordination.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { shiftHandoffDocSchema, massAlertRequestDocSchema, commandChannelThreadDocSchema, commandChannelMessageDocSchema, agencyAssistanceRequestDocSchema, } from './coordination'; +import { shiftHandoffDocSchema, massAlertRequestDocSchema, commandChannelThreadDocSchema, commandChannelMessageDocSchema, agencyAssistanceRequestDocSchema, fieldModeSessionDocSchema, } from './coordination'; describe('Coordination Schemas', () => { describe('shiftHandoffDocSchema', () => { it('accepts valid shift handoff document', () => { @@ -17,6 +17,20 @@ describe('Coordination Schemas', () => { }; expect(() => shiftHandoffDocSchema.parse(validDoc)).not.toThrow(); }); + it('accepts a handoff without toUid', () => { + const validDoc = { + fromUid: 'responder-1', + municipalityId: 'daet', + activeIncidentSnapshot: ['incident-1', 'incident-2'], + notes: 'Shift change normal', + status: 'pending', + createdAt: 1713350400000, + escalatedAt: 1713350405000, + expiresAt: 1713436800000, + schemaVersion: 1, + }; + expect(() => shiftHandoffDocSchema.parse(validDoc)).not.toThrow(); + }); it('rejects invalid status literal', () => { const invalidDoc = { fromUid: 'responder-1', @@ -59,6 +73,9 @@ describe('Coordination Schemas', () => { status: 'queued', createdAt: 1713350400000, forwardedAt: 1713350401000, + forwardMethod: 'sms', + ndrrrcRecipient: 'NDRRMC-ops', + sentAt: 1713350402000, schemaVersion: 1, }; expect(() => massAlertRequestDocSchema.parse(validDoc)).not.toThrow(); @@ -92,11 +109,30 @@ describe('Coordination Schemas', () => { }; expect(() => massAlertRequestDocSchema.parse(docWithExtraKey)).toThrow(); }); + it('accepts the expanded review statuses', () => { + for (const status of ['sent', 'pending_ndrrmc_review', 'declined']) { + const validDoc = { + requestedByMunicipality: 'Daet', + requestedByUid: 'admin-1', + severity: 'high', + body: 'Evacuation alert for Barangay X', + targetType: 'municipality', + estimatedReach: 5000, + status, + createdAt: 1713350400000, + schemaVersion: 1, + }; + expect(() => massAlertRequestDocSchema.parse(validDoc)).not.toThrow(); + } + }); }); describe('commandChannelThreadDocSchema', () => { it('accepts valid command channel thread document', () => { const validDoc = { threadId: 'thread-123', + reportId: 'report-123', + threadType: 'agency_assistance', + assistanceRequestId: 'request-123', subject: 'Emergency response coordination', participantUids: { 'admin-1': true, 'responder-1': true }, createdBy: 'admin-1', @@ -116,6 +152,19 @@ describe('Coordination Schemas', () => { }; expect(() => commandChannelThreadDocSchema.parse(incompleteDoc)).toThrow(); }); + it('rejects a thread without reportId', () => { + const incompleteDoc = { + threadId: 'thread-123', + threadType: 'agency_assistance', + subject: 'Emergency response coordination', + participantUids: { 'admin-1': true }, + createdBy: 'admin-1', + createdAt: 1713350400000, + updatedAt: 1713350401000, + schemaVersion: 1, + }; + expect(() => commandChannelThreadDocSchema.parse(incompleteDoc)).toThrow(); + }); it('rejects unknown keys via strict mode', () => { const docWithExtraKey = { threadId: 'thread-123', @@ -137,6 +186,7 @@ describe('Coordination Schemas', () => { authorUid: 'admin-1', authorRole: 'municipal_admin', body: 'Proceed to location immediately', + idempotencyKey: '11111111-1111-4111-8111-111111111111', createdAt: 1713350400000, schemaVersion: 1, }; @@ -227,6 +277,50 @@ describe('Coordination Schemas', () => { }; expect(() => agencyAssistanceRequestDocSchema.parse(docWithExtraKey)).toThrow(); }); + it('accepts respondedBy and escalatedAt fields', () => { + const validDoc = { + reportId: 'report-123', + requestedByMunicipalId: 'daet', + requestedByMunicipality: 'Daet', + targetAgencyId: 'bfp', + requestType: 'BFP', + message: 'Requesting assistance for flood response', + priority: 'urgent', + status: 'accepted', + declinedReason: undefined, + fulfilledByDispatchIds: [], + createdAt: 1713350400000, + respondedAt: 1713350401000, + respondedBy: 'admin-1', + escalatedAt: 1713350402000, + expiresAt: 1713436800000, + }; + expect(() => agencyAssistanceRequestDocSchema.parse(validDoc)).not.toThrow(); + }); + }); + describe('fieldModeSessionDocSchema', () => { + it('accepts valid field mode session document', () => { + const validDoc = { + uid: 'admin-1', + municipalityId: 'daet', + enteredAt: 1713350400000, + expiresAt: 1713393600000, + isActive: true, + schemaVersion: 1, + }; + expect(() => fieldModeSessionDocSchema.parse(validDoc)).not.toThrow(); + }); + it('rejects when expiresAt is not after enteredAt', () => { + const invalidDoc = { + uid: 'admin-1', + municipalityId: 'daet', + enteredAt: 1713350400000, + expiresAt: 1713350399999, + isActive: true, + schemaVersion: 1, + }; + expect(() => fieldModeSessionDocSchema.parse(invalidDoc)).toThrow(); + }); }); }); //# sourceMappingURL=coordination.test.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/coordination.test.js.map b/packages/shared-validators/lib/coordination.test.js.map index 9722d635..8fe96a3e 100644 --- a/packages/shared-validators/lib/coordination.test.js.map +++ b/packages/shared-validators/lib/coordination.test.js.map @@ -1 +1 @@ -{"version":3,"file":"coordination.test.js","sourceRoot":"","sources":["../src/coordination.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"coordination.test.js","sourceRoot":"","sources":["../src/coordination.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/shared-validators/lib/index.d.ts b/packages/shared-validators/lib/index.d.ts index d2deb545..dddd9dff 100644 --- a/packages/shared-validators/lib/index.d.ts +++ b/packages/shared-validators/lib/index.d.ts @@ -3,8 +3,8 @@ export { normalizeMsisdn, msisdnPhSchema, hashMsisdn, MsisdnInvalidError } from export { activeAccountSchema, claimRevocationSchema, setStaffClaimsInputSchema, suspendStaffAccountInputSchema, } from './auth.js'; export { minAppVersionSchema } from './config.js'; export { alertSchema } from './alerts.js'; -export { reportDocSchema, reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, inboxPayloadSchema, hazardTagSchema, } from './reports.js'; -export type { ReportDoc, ReportPrivateDoc, ReportOpsDoc, ReportSharingDoc, ReportContactsDoc, ReportLookupDoc, ReportInboxDoc, InboxPayload, HazardTag, } from './reports.js'; +export { reportDocSchema, reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, reportNoteDocSchema, reportSharingEventDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, inboxPayloadSchema, hazardTagSchema, } from './reports.js'; +export type { ReportDoc, ReportPrivateDoc, ReportOpsDoc, ReportSharingDoc, ReportNoteDoc, ReportSharingEventDoc, ReportContactsDoc, ReportLookupDoc, ReportInboxDoc, InboxPayload, HazardTag, } from './reports.js'; export { dispatchDocSchema, dispatchStatusSchema, advanceDispatchRequestSchema, } from './dispatches.js'; export type { DispatchDoc, AdvanceDispatchRequest, AdvanceDispatchTarget } from './dispatches.js'; export { reportEventSchema, dispatchEventSchema } from './events.js'; @@ -14,13 +14,14 @@ export type { AgencyDoc } from './agencies.js'; export { responderDocSchema } from './responders.js'; export type { ResponderDoc } from './responders.js'; export { userDocSchema } from './users.js'; -export type { UserDoc } from './users.js'; +export { reportSmsConsentDocSchema } from './users.js'; +export type { UserDoc, ReportSmsConsentDoc } from './users.js'; export { smsInboxDocSchema, smsOutboxDocSchema, smsSessionDocSchema, smsProviderHealthDocSchema, smsProviderIdSchema, smsReportInboxFieldsSchema, } from './sms.js'; export type { SmsInboxDoc, SmsOutboxDoc, SmsSessionDoc, SmsProviderHealthDoc, SmsReportInboxFields, } from './sms.js'; export { detectEncoding } from './sms-encoding.js'; export type { SmsEncoding, EncodingResult } from './sms-encoding.js'; -export { agencyAssistanceRequestDocSchema, commandChannelThreadDocSchema, commandChannelMessageDocSchema, massAlertRequestDocSchema, shiftHandoffDocSchema, breakglassEventDocSchema, } from './coordination.js'; -export type { AgencyAssistanceRequestDoc, CommandChannelThreadDoc, CommandChannelMessageDoc, MassAlertRequestDoc, ShiftHandoffDoc, BreakglassEventDoc, } from './coordination.js'; +export { agencyAssistanceRequestDocSchema, commandChannelThreadDocSchema, commandChannelMessageDocSchema, massAlertRequestDocSchema, shiftHandoffDocSchema, breakglassEventDocSchema, fieldModeSessionDocSchema, } from './coordination.js'; +export type { AgencyAssistanceRequestDoc, CommandChannelThreadDoc, CommandChannelMessageDoc, MassAlertRequestDoc, ShiftHandoffDoc, BreakglassEventDoc, FieldModeSessionDoc, } from './coordination.js'; export { hazardZoneDocSchema, hazardZoneHistoryDocSchema, hazardSignalDocSchema } from './hazard.js'; export type { HazardZoneDoc, HazardZoneHistoryDoc, HazardSignalDoc } from './hazard.js'; export { incidentResponseEventSchema } from './incident-response.js'; diff --git a/packages/shared-validators/lib/index.d.ts.map b/packages/shared-validators/lib/index.d.ts.map index a40f9ab7..0ff626fd 100644 --- a/packages/shared-validators/lib/index.d.ts.map +++ b/packages/shared-validators/lib/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC7F,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,GAChB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,SAAS,GACV,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,WAAW,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACjG,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACpE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AACtD,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA;AACjB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,8BAA8B,EAC9B,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACpG,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACvF,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAA;AACpE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAA;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5E,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACrE,OAAO,EAAE,qBAAqB,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAA;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qCAAqC,CAAA;AAC5C,YAAY,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAA;AACrE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,aAAa,EACb,aAAa,EACb,sBAAsB,GACvB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACxE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/index.js b/packages/shared-validators/lib/index.js index 2655d93c..f33952bd 100644 --- a/packages/shared-validators/lib/index.js +++ b/packages/shared-validators/lib/index.js @@ -3,15 +3,16 @@ export { normalizeMsisdn, msisdnPhSchema, hashMsisdn, MsisdnInvalidError } from export { activeAccountSchema, claimRevocationSchema, setStaffClaimsInputSchema, suspendStaffAccountInputSchema, } from './auth.js'; export { minAppVersionSchema } from './config.js'; export { alertSchema } from './alerts.js'; -export { reportDocSchema, reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, inboxPayloadSchema, hazardTagSchema, } from './reports.js'; +export { reportDocSchema, reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, reportNoteDocSchema, reportSharingEventDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, inboxPayloadSchema, hazardTagSchema, } from './reports.js'; export { dispatchDocSchema, dispatchStatusSchema, advanceDispatchRequestSchema, } from './dispatches.js'; export { reportEventSchema, dispatchEventSchema } from './events.js'; export { agencyDocSchema } from './agencies.js'; export { responderDocSchema } from './responders.js'; export { userDocSchema } from './users.js'; +export { reportSmsConsentDocSchema } from './users.js'; export { smsInboxDocSchema, smsOutboxDocSchema, smsSessionDocSchema, smsProviderHealthDocSchema, smsProviderIdSchema, smsReportInboxFieldsSchema, } from './sms.js'; export { detectEncoding } from './sms-encoding.js'; -export { agencyAssistanceRequestDocSchema, commandChannelThreadDocSchema, commandChannelMessageDocSchema, massAlertRequestDocSchema, shiftHandoffDocSchema, breakglassEventDocSchema, } from './coordination.js'; +export { agencyAssistanceRequestDocSchema, commandChannelThreadDocSchema, commandChannelMessageDocSchema, massAlertRequestDocSchema, shiftHandoffDocSchema, breakglassEventDocSchema, fieldModeSessionDocSchema, } from './coordination.js'; export { hazardZoneDocSchema, hazardZoneHistoryDocSchema, hazardSignalDocSchema } from './hazard.js'; export { incidentResponseEventSchema } from './incident-response.js'; export { moderationIncidentDocSchema } from './moderation.js'; diff --git a/packages/shared-validators/lib/index.js.map b/packages/shared-validators/lib/index.js.map index 3a0782b1..21d51e2c 100644 --- a/packages/shared-validators/lib/index.js.map +++ b/packages/shared-validators/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC7F,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,GAChB,MAAM,cAAc,CAAA;AAYrB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA;AAQjB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,8BAA8B,EAC9B,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,mBAAmB,CAAA;AAS1B,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAA;AAEpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5E,OAAO,EAAE,qBAAqB,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAA;AAE3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qCAAqC,CAAA;AAG5C,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,aAAa,EACb,aAAa,EACb,sBAAsB,GACvB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC7F,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,GAChB,MAAM,cAAc,CAAA;AAcrB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAEtD,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,UAAU,CAAA;AAQjB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,8BAA8B,EAC9B,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAA;AAU1B,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAA;AAEpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5E,OAAO,EAAE,qBAAqB,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAA;AAE3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qCAAqC,CAAA;AAG5C,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,aAAa,EACb,aAAa,EACb,sBAAsB,GACvB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/moderation.d.ts b/packages/shared-validators/lib/moderation.d.ts index 74fa9739..325c7f01 100644 --- a/packages/shared-validators/lib/moderation.d.ts +++ b/packages/shared-validators/lib/moderation.d.ts @@ -10,8 +10,8 @@ export declare const moderationIncidentDocSchema: z.ZodObject<{ app_check_failed: "app_check_failed"; }>; source: z.ZodEnum<{ - web: "web"; sms: "sms"; + web: "web"; responder_witness: "responder_witness"; }>; flaggedBy: z.ZodEnum<{ diff --git a/packages/shared-validators/lib/reports.d.ts b/packages/shared-validators/lib/reports.d.ts index c6bbff4a..9f72fbdf 100644 --- a/packages/shared-validators/lib/reports.d.ts +++ b/packages/shared-validators/lib/reports.d.ts @@ -72,8 +72,8 @@ export declare const reportDocSchema: z.ZodObject<{ sharedWith: z.ZodDefault>; }, z.core.$strict>; source: z.ZodEnum<{ - web: "web"; sms: "sms"; + web: "web"; responder_witness: "responder_witness"; }>; hasPhotoAndGPS: z.ZodDefault; @@ -117,6 +117,22 @@ export declare const reportOpsDocSchema: z.ZodObject<{ agencyIds: z.ZodDefault>; activeResponderCount: z.ZodDefault; requiresLocationFollowUp: z.ZodDefault; + reportType: z.ZodOptional>; + locationGeohash: z.ZodOptional; + duplicateClusterId: z.ZodOptional; + hazardZoneIdList: z.ZodOptional>; visibility: z.ZodObject<{ scope: z.ZodEnum<{ provincial: "provincial"; @@ -136,6 +152,24 @@ export declare const reportSharingDocSchema: z.ZodObject<{ updatedAt: z.ZodNumber; schemaVersion: z.ZodNumber; }, z.core.$strict>; +export declare const reportNoteDocSchema: z.ZodObject<{ + reportId: z.ZodString; + authorUid: z.ZodString; + body: z.ZodString; + createdAt: z.ZodNumber; + schemaVersion: z.ZodNumber; +}, z.core.$strict>; +export declare const reportSharingEventDocSchema: z.ZodObject<{ + targetMunicipalityId: z.ZodString; + sharedBy: z.ZodString; + sharedAt: z.ZodNumber; + sharedReason: z.ZodOptional; + source: z.ZodEnum<{ + manual: "manual"; + auto: "auto"; + }>; + schemaVersion: z.ZodNumber; +}, z.core.$strict>; export declare const reportContactsDocSchema: z.ZodObject<{ reportId: z.ZodString; reporterUid: z.ZodString; @@ -167,6 +201,8 @@ export type ReportDoc = z.infer; export type ReportPrivateDoc = z.infer; export type ReportOpsDoc = z.infer; export type ReportSharingDoc = z.infer; +export type ReportNoteDoc = z.infer; +export type ReportSharingEventDoc = z.infer; export type ReportContactsDoc = z.infer; export type ReportLookupDoc = z.infer; export type ReportInboxDoc = z.infer; @@ -179,8 +215,8 @@ export declare const inboxPayloadSchema: z.ZodObject<{ high: "high"; }>; source: z.ZodEnum<{ - web: "web"; sms: "sms"; + web: "web"; responder_witness: "responder_witness"; }>; clientDraftRef: z.ZodOptional; @@ -188,6 +224,10 @@ export declare const inboxPayloadSchema: z.ZodObject<{ lat: z.ZodNumber; lng: z.ZodNumber; }, z.core.$strict>>; + exactLocation: z.ZodOptional>; pendingMediaIds: z.ZodOptional>; municipalityId: z.ZodOptional; barangayId: z.ZodOptional; diff --git a/packages/shared-validators/lib/reports.d.ts.map b/packages/shared-validators/lib/reports.d.ts.map index 236d661f..527d3010 100644 --- a/packages/shared-validators/lib/reports.d.ts.map +++ b/packages/shared-validators/lib/reports.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,eAAe;;;;;;;;kBAMjB,CAAA;AAGX,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4DjB,CAAA;AAGX,eAAO,MAAM,sBAAsB;;;;;;;kBASxB,CAAA;AAGX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkCpB,CAAA;AAGX,eAAO,MAAM,sBAAsB;;;;;;;kBASxB,CAAA;AAGX,eAAO,MAAM,uBAAuB;;;;;;;kBASzB,CAAA;AAGX,eAAO,MAAM,qBAAqB;;;;;;;kBAYvB,CAAA;AAGX,eAAO,MAAM,oBAAoB;;;;;;;;;kBAWtB,CAAA;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AACrE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAGjE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0BpB,CAAA;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,eAAO,MAAM,eAAe;;;;;;;;kBAMjB,CAAA;AAEX,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA4DjB,CAAA;AAEX,eAAO,MAAM,sBAAsB;;;;;;;kBASxB,CAAA;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwCpB,CAAA;AAEX,eAAO,MAAM,sBAAsB;;;;;;;kBASxB,CAAA;AAEX,eAAO,MAAM,mBAAmB;;;;;;kBAQrB,CAAA;AAEX,eAAO,MAAM,2BAA2B;;;;;;;;;;kBAS7B,CAAA;AAEX,eAAO,MAAM,uBAAuB;;;;;;;kBASzB,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;kBAa9B,CAAA;AAEJ,eAAO,MAAM,oBAAoB;;;;;;;;;kBAWtB,CAAA;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AACrE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AACrE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC/D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAC/E,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAEjE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiCpB,CAAA;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/reports.js b/packages/shared-validators/lib/reports.js index d3c21875..383640da 100644 --- a/packages/shared-validators/lib/reports.js +++ b/packages/shared-validators/lib/reports.js @@ -1,6 +1,18 @@ import { z } from 'zod'; import { msisdnPhSchema } from './msisdn.js'; -// hazard tag schema +const reportOpsReportTypeSchema = z.enum([ + 'flood', + 'fire', + 'earthquake', + 'typhoon', + 'landslide', + 'storm_surge', + 'medical', + 'accident', + 'structural', + 'security', + 'other', +]); export const hazardTagSchema = z .object({ hazardZoneId: z.string().min(1), @@ -8,7 +20,6 @@ export const hazardTagSchema = z hazardType: z.enum(['flood', 'landslide', 'storm_surge']), }) .strict(); -// reportDocSchema — public report document export const reportDocSchema = z .object({ municipalityId: z.string().min(1), @@ -70,7 +81,6 @@ export const reportDocSchema = z correlationId: z.uuid(), }) .strict(); -// reportPrivateDocSchema — private report document export const reportPrivateDocSchema = z .object({ municipalityId: z.string().min(1), @@ -81,7 +91,6 @@ export const reportPrivateDocSchema = z schemaVersion: z.number().int().positive(), }) .strict(); -// reportOpsDocSchema — operations document export const reportOpsDocSchema = z .object({ municipalityId: z.string().min(1), @@ -107,6 +116,12 @@ export const reportOpsDocSchema = z agencyIds: z.array(z.string()).default([]), activeResponderCount: z.number().int().nonnegative().default(0), requiresLocationFollowUp: z.boolean().default(false), + // processInboxItemCore always writes reportType; .optional() preserves + // backward-compat for report_ops docs written before Phase 5 PRE-B. + reportType: reportOpsReportTypeSchema.optional(), + locationGeohash: z.string().length(6).optional(), + duplicateClusterId: z.string().optional(), + hazardZoneIdList: z.array(z.string()).optional(), visibility: z .object({ scope: z.enum(['municipality', 'shared', 'provincial']), @@ -117,7 +132,6 @@ export const reportOpsDocSchema = z schemaVersion: z.number().int().positive(), }) .strict(); -// reportSharingDocSchema — sharing document export const reportSharingDocSchema = z .object({ ownerMunicipalityId: z.string().min(1), @@ -128,7 +142,25 @@ export const reportSharingDocSchema = z schemaVersion: z.number().int().positive(), }) .strict(); -// reportContactsDocSchema — contacts document +export const reportNoteDocSchema = z + .object({ + reportId: z.string().min(1), + authorUid: z.string().min(1), + body: z.string().max(2000), + createdAt: z.number().int(), + schemaVersion: z.number().int().positive(), +}) + .strict(); +export const reportSharingEventDocSchema = z + .object({ + targetMunicipalityId: z.string().min(1), + sharedBy: z.string().min(1), + sharedAt: z.number().int(), + sharedReason: z.string().max(500).optional(), + source: z.enum(['manual', 'auto']), + schemaVersion: z.number().int().positive(), +}) + .strict(); export const reportContactsDocSchema = z .object({ reportId: z.string().min(1), @@ -139,21 +171,20 @@ export const reportContactsDocSchema = z schemaVersion: z.number().int().positive(), }) .strict(); -// reportLookupDocSchema — lookup document export const reportLookupDocSchema = z .object({ publicTrackingRef: z.string().regex(/^[a-z0-9]{8}$/), reportId: z.string().min(1), tokenHash: z.string().regex(/^[a-f0-9]{64}$/), - expiresAt: z - .number() - .int() - .max(Date.now() + 365 * 24 * 60 * 60 * 1000), + expiresAt: z.number().int(), createdAt: z.number().int(), schemaVersion: z.number().int().positive(), }) - .strict(); -// reportInboxDocSchema — inbox document + .strict() + .refine((d) => d.expiresAt <= Date.now() + 365 * 24 * 60 * 60 * 1000, { + path: ['expiresAt'], + message: 'expiresAt must be within 365 days of validation', +}); export const reportInboxDocSchema = z .object({ reporterUid: z.string().min(1), @@ -166,7 +197,6 @@ export const reportInboxDocSchema = z processedAt: z.number().int().optional(), }) .strict(); -// inboxPayloadSchema — validated payload inside report_inbox docs export const inboxPayloadSchema = z .object({ reportType: z.string().min(1).max(32), @@ -181,6 +211,13 @@ export const inboxPayloadSchema = z }) .strict() .optional(), + exactLocation: z + .object({ + lat: z.number().min(-90).max(90), + lng: z.number().min(-180).max(180), + }) + .strict() + .optional(), pendingMediaIds: z.array(z.string().min(1)).max(20).optional(), municipalityId: z.string().min(1).optional(), barangayId: z.string().min(1).optional(), diff --git a/packages/shared-validators/lib/reports.js.map b/packages/shared-validators/lib/reports.js.map index 4cf70835..658cb94e 100644 --- a/packages/shared-validators/lib/reports.js.map +++ b/packages/shared-validators/lib/reports.js.map @@ -1 +1 @@ -{"version":3,"file":"reports.js","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"reports.js","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/shared-validators/lib/reports.test.js b/packages/shared-validators/lib/reports.test.js index 6b270e6a..5387b8aa 100644 --- a/packages/shared-validators/lib/reports.test.js +++ b/packages/shared-validators/lib/reports.test.js @@ -1,5 +1,5 @@ -import { describe, expect, it } from 'vitest'; -import { reportDocSchema, reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, hazardTagSchema, inboxPayloadSchema, } from './reports.js'; +import { describe, expect, it, vi } from 'vitest'; +import { reportDocSchema, reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, reportNoteDocSchema, reportSharingEventDocSchema, hazardTagSchema, inboxPayloadSchema, } from './reports.js'; const ts = 1713350400000; describe('reportDocSchema', () => { it('accepts a canonical verified report', () => { @@ -95,7 +95,9 @@ describe('reportOpsDocSchema', () => { expect(reportOpsDocSchema.parse({ municipalityId: 'daet', status: 'verified', + reportType: 'flood', severity: 'high', + locationGeohash: 'w7hfm2', createdAt: ts, agencyIds: [], activeResponderCount: 0, @@ -105,6 +107,36 @@ describe('reportOpsDocSchema', () => { schemaVersion: 1, })).toMatchObject({ status: 'verified' }); }); + it('rejects report types outside the ops report enum', () => { + expect(() => reportOpsDocSchema.parse({ + municipalityId: 'daet', + status: 'verified', + reportType: 'volcanic', + severity: 'high', + createdAt: ts, + agencyIds: [], + activeResponderCount: 0, + requiresLocationFollowUp: false, + visibility: { scope: 'municipality', sharedWith: [] }, + updatedAt: ts, + schemaVersion: 1, + })).toThrow(); + }); + it('accepts persisted ops report types like medical', () => { + expect(reportOpsDocSchema.parse({ + municipalityId: 'daet', + status: 'verified', + reportType: 'medical', + severity: 'high', + createdAt: ts, + agencyIds: [], + activeResponderCount: 0, + requiresLocationFollowUp: false, + visibility: { scope: 'municipality', sharedWith: [] }, + updatedAt: ts, + schemaVersion: 1, + })).toMatchObject({ reportType: 'medical' }); + }); }); describe('reportSharingDocSchema', () => { it('accepts a sharing config', () => { @@ -151,6 +183,22 @@ describe('reportLookupDocSchema', () => { schemaVersion: 1, })).toMatchObject({ publicTrackingRef: 'a1b2c3d4' }); }); + it('rejects expiresAt beyond a year at validation time', () => { + const spy = vi.spyOn(Date, 'now').mockReturnValue(ts); + try { + expect(() => reportLookupDocSchema.parse({ + publicTrackingRef: 'a1b2c3d4', + reportId: 'r-1', + tokenHash: 'f'.repeat(64), + expiresAt: ts + 366 * 24 * 60 * 60 * 1000, + createdAt: ts, + schemaVersion: 1, + })).toThrow(/expiresAt/); + } + finally { + spy.mockRestore(); + } + }); }); describe('reportInboxDocSchema', () => { it('accepts an inbox item', () => { @@ -286,6 +334,12 @@ describe('inboxPayloadSchema contact extension', () => { contact: { phone: '+639171234567', smsConsent: true }, })).not.toThrow(); }); + it('accepts exactLocation for geohash materialization', () => { + expect(() => inboxPayloadSchema.parse({ + ...basePayload, + exactLocation: { lat: 14.6, lng: 121.0 }, + })).not.toThrow(); + }); it('rejects contact with smsConsent=false (consent must be literal true)', () => { expect(() => inboxPayloadSchema.parse({ ...basePayload, @@ -305,4 +359,71 @@ describe('inboxPayloadSchema contact extension', () => { })).toThrow(); }); }); +describe('reportOpsDocSchema PRE-B deltas', () => { + const base = { + municipalityId: 'daet', + status: 'verified', + severity: 'high', + createdAt: 1713350400000, + agencyIds: [], + activeResponderCount: 0, + requiresLocationFollowUp: false, + visibility: { scope: 'municipality', sharedWith: [] }, + updatedAt: 1713350400000, + schemaVersion: 1, + }; + it('accepts reportType, locationGeohash, duplicateClusterId, and hazardZoneIdList', () => { + expect(reportOpsDocSchema.parse({ + ...base, + reportType: 'flood', + locationGeohash: 'w7hfm2', + duplicateClusterId: 'cluster-uuid-1', + hazardZoneIdList: ['hz-1', 'hz-2'], + })).toMatchObject({ reportType: 'flood', locationGeohash: 'w7hfm2' }); + }); + it('still accepts the old shape when the new fields are absent', () => { + expect(() => reportOpsDocSchema.parse(base)).not.toThrow(); + }); +}); +describe('reportNoteDocSchema', () => { + it('parses a valid report note', () => { + expect(reportNoteDocSchema.parse({ + reportId: 'r1', + authorUid: 'uid-1', + body: 'Situation is stable now.', + createdAt: 1713350400000, + schemaVersion: 1, + })).toMatchObject({ reportId: 'r1' }); + }); + it('rejects body over 2000 chars', () => { + expect(() => reportNoteDocSchema.parse({ + reportId: 'r1', + authorUid: 'uid-1', + body: 'x'.repeat(2001), + createdAt: 1713350400000, + schemaVersion: 1, + })).toThrow(); + }); +}); +describe('reportSharingEventDocSchema', () => { + it('parses a manual share event', () => { + expect(reportSharingEventDocSchema.parse({ + targetMunicipalityId: 'mercedes', + sharedBy: 'uid-1', + sharedAt: 1713350400000, + sharedReason: 'Border incident', + source: 'manual', + schemaVersion: 1, + })).toMatchObject({ source: 'manual' }); + }); + it('parses an auto share event without reason', () => { + expect(reportSharingEventDocSchema.parse({ + targetMunicipalityId: 'mercedes', + sharedBy: 'system', + sharedAt: 1713350400000, + source: 'auto', + schemaVersion: 1, + })).toMatchObject({ targetMunicipalityId: 'mercedes' }); + }); +}); //# sourceMappingURL=reports.test.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/reports.test.js.map b/packages/shared-validators/lib/reports.test.js.map index 9c28057b..ae0e2161 100644 --- a/packages/shared-validators/lib/reports.test.js.map +++ b/packages/shared-validators/lib/reports.test.js.map @@ -1 +1 @@ -{"version":3,"file":"reports.test.js","sourceRoot":"","sources":["../src/reports.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"reports.test.js","sourceRoot":"","sources":["../src/reports.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.d.ts b/packages/shared-validators/lib/responders.d.ts index 006bbd00..14031e2a 100644 --- a/packages/shared-validators/lib/responders.d.ts +++ b/packages/shared-validators/lib/responders.d.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -export declare const responderDocSchema: z.ZodObject<{ +export declare const responderDocSchema: z.ZodPipe; isActive: z.ZodBoolean; + fcmTokens: z.ZodDefault>; + hasFcmToken: z.ZodOptional; lastTelemetryAt: z.ZodOptional; schemaVersion: z.ZodNumber; createdAt: z.ZodNumber; updatedAt: z.ZodNumber; -}, z.core.$strict>; +}, z.core.$strict>, z.ZodTransform<{ + hasFcmToken: boolean; + uid: string; + agencyId: string; + municipalityId: string; + displayCode: string; + specialisations: string[]; + availabilityStatus: "on_duty" | "off_duty" | "on_break" | "unavailable"; + isActive: boolean; + fcmTokens: string[]; + schemaVersion: number; + createdAt: number; + updatedAt: number; + lastTelemetryAt?: number | undefined; +}, { + uid: string; + agencyId: string; + municipalityId: string; + displayCode: string; + specialisations: string[]; + availabilityStatus: "on_duty" | "off_duty" | "on_break" | "unavailable"; + isActive: boolean; + fcmTokens: string[]; + schemaVersion: number; + createdAt: number; + updatedAt: number; + hasFcmToken?: boolean | undefined; + lastTelemetryAt?: number | undefined; +}>>; export type ResponderDoc = z.infer; //# sourceMappingURL=responders.d.ts.map \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.d.ts.map b/packages/shared-validators/lib/responders.d.ts.map index 6991716d..5c1fc8fe 100644 --- a/packages/shared-validators/lib/responders.d.ts.map +++ b/packages/shared-validators/lib/responders.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"responders.d.ts","sourceRoot":"","sources":["../src/responders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;kBAcpB,CAAA;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"responders.d.ts","sourceRoot":"","sources":["../src/responders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBqC,CAAA;AAEpE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.js b/packages/shared-validators/lib/responders.js index 52fc3cf1..99042d94 100644 --- a/packages/shared-validators/lib/responders.js +++ b/packages/shared-validators/lib/responders.js @@ -8,10 +8,13 @@ export const responderDocSchema = z specialisations: z.array(z.string()).default([]), availabilityStatus: z.enum(['on_duty', 'off_duty', 'on_break', 'unavailable']), isActive: z.boolean(), + fcmTokens: z.array(z.string().trim().min(1)).default([]), + hasFcmToken: z.boolean().optional(), lastTelemetryAt: z.number().int().optional(), schemaVersion: z.number().int().positive(), createdAt: z.number().int(), updatedAt: z.number().int(), }) - .strict(); + .strict() + .transform((d) => ({ ...d, hasFcmToken: d.fcmTokens.length > 0 })); //# sourceMappingURL=responders.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.js.map b/packages/shared-validators/lib/responders.js.map index 82c993aa..4601bdfb 100644 --- a/packages/shared-validators/lib/responders.js.map +++ b/packages/shared-validators/lib/responders.js.map @@ -1 +1 @@ -{"version":3,"file":"responders.js","sourceRoot":"","sources":["../src/responders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KAChC,MAAM,CAAC;IACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAChD,kBAAkB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IAC9E,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC5C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC1C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CAC5B,CAAC;KACD,MAAM,EAAE,CAAA"} \ No newline at end of file +{"version":3,"file":"responders.js","sourceRoot":"","sources":["../src/responders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KAChC,MAAM,CAAC;IACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAChD,kBAAkB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IAC9E,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACxD,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACnC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC5C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC1C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CAC5B,CAAC;KACD,MAAM,EAAE;KACR,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.test.d.ts b/packages/shared-validators/lib/responders.test.d.ts new file mode 100644 index 00000000..e84951b6 --- /dev/null +++ b/packages/shared-validators/lib/responders.test.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=responders.test.d.ts.map \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.test.d.ts.map b/packages/shared-validators/lib/responders.test.d.ts.map new file mode 100644 index 00000000..5877319d --- /dev/null +++ b/packages/shared-validators/lib/responders.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"responders.test.d.ts","sourceRoot":"","sources":["../src/responders.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.test.js b/packages/shared-validators/lib/responders.test.js new file mode 100644 index 00000000..aa404fa6 --- /dev/null +++ b/packages/shared-validators/lib/responders.test.js @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest'; +import { responderDocSchema } from './responders'; +const ts = 1713350400000; +describe('responderDocSchema PRE-B deltas', () => { + it('accepts hasFcmToken and fcmTokens fields', () => { + const result = responderDocSchema.parse({ + uid: 'resp-1', + agencyId: 'bfp-daet', + municipalityId: 'daet', + displayCode: 'BFP-01', + specialisations: [], + availabilityStatus: 'on_duty', + isActive: true, + schemaVersion: 1, + createdAt: ts, + updatedAt: ts, + fcmTokens: ['token-abc'], + hasFcmToken: true, + }); + expect(result.hasFcmToken).toBe(true); + expect(result.fcmTokens).toEqual(['token-abc']); + }); + it('defaults hasFcmToken to false when absent', () => { + const result = responderDocSchema.parse({ + uid: 'resp-1', + agencyId: 'bfp-daet', + municipalityId: 'daet', + displayCode: 'BFP-01', + specialisations: [], + availabilityStatus: 'on_duty', + isActive: true, + schemaVersion: 1, + createdAt: ts, + updatedAt: ts, + }); + expect(result.hasFcmToken).toBe(false); + expect(result.fcmTokens).toEqual([]); + }); + it('derives hasFcmToken from non-empty fcmTokens when omitted', () => { + const result = responderDocSchema.parse({ + uid: 'resp-1', + agencyId: 'bfp-daet', + municipalityId: 'daet', + displayCode: 'BFP-01', + specialisations: [], + availabilityStatus: 'on_duty', + isActive: true, + schemaVersion: 1, + createdAt: ts, + updatedAt: ts, + fcmTokens: ['token-abc'], + }); + expect(result.hasFcmToken).toBe(true); + expect(result.fcmTokens).toEqual(['token-abc']); + }); +}); +//# sourceMappingURL=responders.test.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/responders.test.js.map b/packages/shared-validators/lib/responders.test.js.map new file mode 100644 index 00000000..356a4807 --- /dev/null +++ b/packages/shared-validators/lib/responders.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"responders.test.js","sourceRoot":"","sources":["../src/responders.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAEjD,MAAM,EAAE,GAAG,aAAa,CAAA;AAExB,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC;YACtC,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,UAAU;YACpB,cAAc,EAAE,MAAM;YACtB,WAAW,EAAE,QAAQ;YACrB,eAAe,EAAE,EAAE;YACnB,kBAAkB,EAAE,SAAS;YAC7B,QAAQ,EAAE,IAAI;YACd,aAAa,EAAE,CAAC;YAChB,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,WAAW,CAAC;YACxB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACrC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC;YACtC,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,UAAU;YACpB,cAAc,EAAE,MAAM;YACtB,WAAW,EAAE,QAAQ;YACrB,eAAe,EAAE,EAAE;YACnB,kBAAkB,EAAE,SAAS;YAC7B,QAAQ,EAAE,IAAI;YACd,aAAa,EAAE,CAAC;YAChB,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,EAAE;SACd,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACtC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC;YACtC,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,UAAU;YACpB,cAAc,EAAE,MAAM;YACtB,WAAW,EAAE,QAAQ;YACrB,eAAe,EAAE,EAAE;YACnB,kBAAkB,EAAE,SAAS;YAC7B,QAAQ,EAAE,IAAI;YACd,aAAa,EAAE,CAAC;YAChB,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,WAAW,CAAC;SACzB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACrC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/sms.d.ts b/packages/shared-validators/lib/sms.d.ts index 43d7e025..164bf847 100644 --- a/packages/shared-validators/lib/sms.d.ts +++ b/packages/shared-validators/lib/sms.d.ts @@ -147,4 +147,4 @@ export declare const smsReportInboxFieldsSchema: z.ZodObject<{ requiresLocationFollowUp: z.ZodLiteral; }, z.core.$strict>; export type SmsReportInboxFields = z.infer; -//# sourceMappingURL=sms.d.ts.map \ No newline at end of file +//# sourceMappingURL=sms.d.ts.map diff --git a/packages/shared-validators/lib/users.d.ts b/packages/shared-validators/lib/users.d.ts index a4916a6a..b27f1dd9 100644 --- a/packages/shared-validators/lib/users.d.ts +++ b/packages/shared-validators/lib/users.d.ts @@ -19,5 +19,16 @@ export declare const userDocSchema: z.ZodObject<{ createdAt: z.ZodNumber; updatedAt: z.ZodNumber; }, z.core.$strict>; +export declare const reportSmsConsentDocSchema: z.ZodObject<{ + reportId: z.ZodString; + phone: z.ZodString; + locale: z.ZodString; + smsConsent: z.ZodLiteral; + municipalityId: z.ZodString; + followUpConsent: z.ZodDefault; + createdAt: z.ZodNumber; + schemaVersion: z.ZodNumber; +}, z.core.$strict>; export type UserDoc = z.infer; +export type ReportSmsConsentDoc = z.infer; //# sourceMappingURL=users.d.ts.map \ No newline at end of file diff --git a/packages/shared-validators/lib/users.d.ts.map b/packages/shared-validators/lib/users.d.ts.map index 854f172a..69944765 100644 --- a/packages/shared-validators/lib/users.d.ts.map +++ b/packages/shared-validators/lib/users.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../src/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;kBAqBf,CAAA;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../src/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;kBAqBf,CAAA;AAEX,eAAO,MAAM,yBAAyB;;;;;;;;;kBAW3B,CAAA;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AACnD,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/users.js b/packages/shared-validators/lib/users.js index b7789368..97d4d164 100644 --- a/packages/shared-validators/lib/users.js +++ b/packages/shared-validators/lib/users.js @@ -21,4 +21,16 @@ export const userDocSchema = z updatedAt: z.number().int(), }) .strict(); +export const reportSmsConsentDocSchema = z + .object({ + reportId: z.string().min(1), + phone: z.string().min(1), + locale: z.string().min(1), + smsConsent: z.literal(true), + municipalityId: z.string().min(1), + followUpConsent: z.boolean().default(false), + createdAt: z.number().int(), + schemaVersion: z.number().int().positive(), +}) + .strict(); //# sourceMappingURL=users.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/users.js.map b/packages/shared-validators/lib/users.js.map index 531bb7ca..f5aa77ea 100644 --- a/packages/shared-validators/lib/users.js.map +++ b/packages/shared-validators/lib/users.js.map @@ -1 +1 @@ -{"version":3,"file":"users.js","sourceRoot":"","sources":["../src/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,MAAM,CAAC;IACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;QACX,SAAS;QACT,WAAW;QACX,iBAAiB;QACjB,cAAc;QACd,uBAAuB;KACxB,CAAC;IACF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC1C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CAC5B,CAAC;KACD,MAAM,EAAE,CAAA"} \ No newline at end of file +{"version":3,"file":"users.js","sourceRoot":"","sources":["../src/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,MAAM,CAAC;IACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;QACX,SAAS;QACT,WAAW;QACX,iBAAiB;QACjB,cAAc;QACd,uBAAuB;KACxB,CAAC;IACF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC1C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CAC5B,CAAC;KACD,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC;KACvC,MAAM,CAAC;IACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC3B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CAC3C,CAAC;KACD,MAAM,EAAE,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/lib/users.test.d.ts b/packages/shared-validators/lib/users.test.d.ts new file mode 100644 index 00000000..9be0cf81 --- /dev/null +++ b/packages/shared-validators/lib/users.test.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=users.test.d.ts.map \ No newline at end of file diff --git a/packages/shared-validators/lib/users.test.d.ts.map b/packages/shared-validators/lib/users.test.d.ts.map new file mode 100644 index 00000000..bffebe96 --- /dev/null +++ b/packages/shared-validators/lib/users.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"users.test.d.ts","sourceRoot":"","sources":["../src/users.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/shared-validators/lib/users.test.js b/packages/shared-validators/lib/users.test.js new file mode 100644 index 00000000..fe3a2df2 --- /dev/null +++ b/packages/shared-validators/lib/users.test.js @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import { reportSmsConsentDocSchema } from './users'; +const ts = 1713350400000; +describe('reportSmsConsentDocSchema', () => { + it('parses a full consent doc', () => { + expect(reportSmsConsentDocSchema.parse({ + reportId: 'r1', + phone: '+63 912 345 6789', + locale: 'tl', + smsConsent: true, + municipalityId: 'daet', + followUpConsent: true, + createdAt: ts, + schemaVersion: 1, + })).toMatchObject({ municipalityId: 'daet', followUpConsent: true }); + }); + it('defaults followUpConsent to false', () => { + const result = reportSmsConsentDocSchema.parse({ + reportId: 'r1', + phone: '+63 912 345 6789', + locale: 'tl', + smsConsent: true, + municipalityId: 'daet', + createdAt: ts, + schemaVersion: 1, + }); + expect(result.followUpConsent).toBe(false); + }); + it('rejects when municipalityId is absent', () => { + expect(() => reportSmsConsentDocSchema.parse({ + reportId: 'r1', + phone: '+63 912 345 6789', + locale: 'tl', + smsConsent: true, + createdAt: ts, + schemaVersion: 1, + })).toThrow(); + }); +}); +//# sourceMappingURL=users.test.js.map \ No newline at end of file diff --git a/packages/shared-validators/lib/users.test.js.map b/packages/shared-validators/lib/users.test.js.map new file mode 100644 index 00000000..685bc164 --- /dev/null +++ b/packages/shared-validators/lib/users.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.test.js","sourceRoot":"","sources":["../src/users.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAEnD,MAAM,EAAE,GAAG,aAAa,CAAA;AAExB,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CACJ,yBAAyB,CAAC,KAAK,CAAC;YAC9B,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,kBAAkB;YACzB,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI;YAChB,cAAc,EAAE,MAAM;YACtB,eAAe,EAAE,IAAI;YACrB,SAAS,EAAE,EAAE;YACb,aAAa,EAAE,CAAC;SACjB,CAAC,CACH,CAAC,aAAa,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,MAAM,GAAG,yBAAyB,CAAC,KAAK,CAAC;YAC7C,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,kBAAkB;YACzB,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI;YAChB,cAAc,EAAE,MAAM;YACtB,SAAS,EAAE,EAAE;YACb,aAAa,EAAE,CAAC;SACjB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,GAAG,EAAE,CACV,yBAAyB,CAAC,KAAK,CAAC;YAC9B,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,kBAAkB;YACzB,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI;YAChB,SAAS,EAAE,EAAE;YACb,aAAa,EAAE,CAAC;SACjB,CAAC,CACH,CAAC,OAAO,EAAE,CAAA;IACb,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/packages/shared-validators/src/coordination.test.ts b/packages/shared-validators/src/coordination.test.ts index 4502fc9b..e72113dc 100644 --- a/packages/shared-validators/src/coordination.test.ts +++ b/packages/shared-validators/src/coordination.test.ts @@ -5,6 +5,7 @@ import { commandChannelThreadDocSchema, commandChannelMessageDocSchema, agencyAssistanceRequestDocSchema, + fieldModeSessionDocSchema, } from './coordination' describe('Coordination Schemas', () => { @@ -25,6 +26,21 @@ describe('Coordination Schemas', () => { expect(() => shiftHandoffDocSchema.parse(validDoc)).not.toThrow() }) + it('accepts a handoff without toUid', () => { + const validDoc = { + fromUid: 'responder-1', + municipalityId: 'daet', + activeIncidentSnapshot: ['incident-1', 'incident-2'], + notes: 'Shift change normal', + status: 'pending' as const, + createdAt: 1713350400000, + escalatedAt: 1713350405000, + expiresAt: 1713436800000, + schemaVersion: 1, + } + expect(() => shiftHandoffDocSchema.parse(validDoc)).not.toThrow() + }) + it('rejects invalid status literal', () => { const invalidDoc = { fromUid: 'responder-1', @@ -69,6 +85,9 @@ describe('Coordination Schemas', () => { status: 'queued' as const, createdAt: 1713350400000, forwardedAt: 1713350401000, + forwardMethod: 'sms', + ndrrrcRecipient: 'NDRRMC-ops', + sentAt: 1713350402000, schemaVersion: 1, } expect(() => massAlertRequestDocSchema.parse(validDoc)).not.toThrow() @@ -104,12 +123,32 @@ describe('Coordination Schemas', () => { } expect(() => massAlertRequestDocSchema.parse(docWithExtraKey)).toThrow() }) + + it('accepts the expanded review statuses', () => { + for (const status of ['sent', 'pending_ndrrmc_review', 'declined'] as const) { + const validDoc = { + requestedByMunicipality: 'Daet', + requestedByUid: 'admin-1', + severity: 'high' as const, + body: 'Evacuation alert for Barangay X', + targetType: 'municipality' as const, + estimatedReach: 5000, + status, + createdAt: 1713350400000, + schemaVersion: 1, + } + expect(() => massAlertRequestDocSchema.parse(validDoc)).not.toThrow() + } + }) }) describe('commandChannelThreadDocSchema', () => { it('accepts valid command channel thread document', () => { const validDoc = { threadId: 'thread-123', + reportId: 'report-123', + threadType: 'agency_assistance' as const, + assistanceRequestId: 'request-123', subject: 'Emergency response coordination', participantUids: { 'admin-1': true, 'responder-1': true }, createdBy: 'admin-1', @@ -131,6 +170,20 @@ describe('Coordination Schemas', () => { expect(() => commandChannelThreadDocSchema.parse(incompleteDoc)).toThrow() }) + it('rejects a thread without reportId', () => { + const incompleteDoc = { + threadId: 'thread-123', + threadType: 'agency_assistance' as const, + subject: 'Emergency response coordination', + participantUids: { 'admin-1': true }, + createdBy: 'admin-1', + createdAt: 1713350400000, + updatedAt: 1713350401000, + schemaVersion: 1, + } + expect(() => commandChannelThreadDocSchema.parse(incompleteDoc)).toThrow() + }) + it('rejects unknown keys via strict mode', () => { const docWithExtraKey = { threadId: 'thread-123', @@ -153,6 +206,7 @@ describe('Coordination Schemas', () => { authorUid: 'admin-1', authorRole: 'municipal_admin' as const, body: 'Proceed to location immediately', + idempotencyKey: '11111111-1111-4111-8111-111111111111', createdAt: 1713350400000, schemaVersion: 1, } @@ -249,5 +303,52 @@ describe('Coordination Schemas', () => { } expect(() => agencyAssistanceRequestDocSchema.parse(docWithExtraKey)).toThrow() }) + + it('accepts respondedBy and escalatedAt fields', () => { + const validDoc = { + reportId: 'report-123', + requestedByMunicipalId: 'daet', + requestedByMunicipality: 'Daet', + targetAgencyId: 'bfp', + requestType: 'BFP' as const, + message: 'Requesting assistance for flood response', + priority: 'urgent' as const, + status: 'accepted' as const, + declinedReason: undefined, + fulfilledByDispatchIds: [], + createdAt: 1713350400000, + respondedAt: 1713350401000, + respondedBy: 'admin-1', + escalatedAt: 1713350402000, + expiresAt: 1713436800000, + } + expect(() => agencyAssistanceRequestDocSchema.parse(validDoc)).not.toThrow() + }) + }) + + describe('fieldModeSessionDocSchema', () => { + it('accepts valid field mode session document', () => { + const validDoc = { + uid: 'admin-1', + municipalityId: 'daet', + enteredAt: 1713350400000, + expiresAt: 1713393600000, + isActive: true, + schemaVersion: 1, + } + expect(() => fieldModeSessionDocSchema.parse(validDoc)).not.toThrow() + }) + + it('rejects when expiresAt is not after enteredAt', () => { + const invalidDoc = { + uid: 'admin-1', + municipalityId: 'daet', + enteredAt: 1713350400000, + expiresAt: 1713350399999, + isActive: true, + schemaVersion: 1, + } + expect(() => fieldModeSessionDocSchema.parse(invalidDoc)).toThrow() + }) }) }) diff --git a/packages/shared-validators/src/coordination.ts b/packages/shared-validators/src/coordination.ts index c49d43bd..a444b54b 100644 --- a/packages/shared-validators/src/coordination.ts +++ b/packages/shared-validators/src/coordination.ts @@ -14,9 +14,13 @@ export const agencyAssistanceRequestDocSchema = z fulfilledByDispatchIds: z.array(z.string()), createdAt: z.number().int(), respondedAt: z.number().int().optional(), + respondedBy: z.string().optional(), + escalatedAt: z.number().int().optional(), expiresAt: z.number().int(), }) .strict() + // Assistance windows must have a positive duration — expiresAt is set by the + // requesting municipality and must exceed the request creation timestamp. .refine((d) => d.expiresAt > d.createdAt, { message: 'expiresAt must be after createdAt', }) @@ -24,12 +28,15 @@ export const agencyAssistanceRequestDocSchema = z export const commandChannelThreadDocSchema = z .object({ threadId: z.string().min(1), - reportId: z.string().optional(), + reportId: z.string().min(1), + threadType: z.enum(['agency_assistance', 'border_share']), + assistanceRequestId: z.string().min(1).optional(), subject: z.string().max(200), participantUids: z.record(z.string(), z.literal(true)), createdBy: z.string().min(1), createdAt: z.number().int(), updatedAt: z.number().int(), + lastMessageAt: z.number().int().optional(), closedAt: z.number().int().optional(), schemaVersion: z.number().int().positive(), }) @@ -39,8 +46,11 @@ export const commandChannelMessageDocSchema = z .object({ threadId: z.string().min(1), authorUid: z.string().min(1), + // Responders appear in participantUids but cannot author messages; + // command channel posts are admin/agency/superadmin only. authorRole: z.enum(['municipal_admin', 'agency_admin', 'provincial_superadmin']), body: z.string().max(2000), + idempotencyKey: z.uuid().optional(), createdAt: z.number().int(), schemaVersion: z.number().int().positive(), }) @@ -57,15 +67,21 @@ export const massAlertRequestDocSchema = z estimatedReach: z.number().int().nonnegative(), status: z.enum([ 'queued', + 'sent', + 'pending_ndrrmc_review', 'submitted_to_pdrrmo', 'forwarded_to_ndrrmc', 'acknowledged_by_ndrrmc', + 'declined', 'cancelled', ]), createdAt: z.number().int(), forwardedAt: z.number().int().optional(), + forwardMethod: z.string().optional(), + ndrrrcRecipient: z.string().optional(), acknowledgedAt: z.number().int().optional(), cancelledAt: z.number().int().optional(), + sentAt: z.number().int().optional(), schemaVersion: z.number().int().positive(), }) .strict() @@ -73,13 +89,14 @@ export const massAlertRequestDocSchema = z export const shiftHandoffDocSchema = z .object({ fromUid: z.string().min(1), - toUid: z.string().min(1), + toUid: z.string().min(1).optional(), municipalityId: z.string().min(1), activeIncidentSnapshot: z.array(z.string()), notes: z.string().max(2000), status: z.enum(['pending', 'accepted', 'expired']), createdAt: z.number().int(), acceptedAt: z.number().int().optional(), + escalatedAt: z.number().int().optional(), expiresAt: z.number().int(), schemaVersion: z.number().int().positive(), }) @@ -100,9 +117,25 @@ export const breakglassEventDocSchema = z }) .strict() +export const fieldModeSessionDocSchema = z + .object({ + uid: z.string().min(1), + municipalityId: z.string().min(1), + enteredAt: z.number().int(), + expiresAt: z.number().int(), + exitedAt: z.number().int().optional(), + isActive: z.boolean(), + schemaVersion: z.number().int().positive(), + }) + .strict() + .refine((d) => d.expiresAt > d.enteredAt, { + message: 'expiresAt must be after enteredAt', + }) + export type AgencyAssistanceRequestDoc = z.infer export type CommandChannelThreadDoc = z.infer export type CommandChannelMessageDoc = z.infer export type MassAlertRequestDoc = z.infer export type ShiftHandoffDoc = z.infer export type BreakglassEventDoc = z.infer +export type FieldModeSessionDoc = z.infer diff --git a/packages/shared-validators/src/index.ts b/packages/shared-validators/src/index.ts index 17cbfff0..5c8aa071 100644 --- a/packages/shared-validators/src/index.ts +++ b/packages/shared-validators/src/index.ts @@ -13,6 +13,8 @@ export { reportPrivateDocSchema, reportOpsDocSchema, reportSharingDocSchema, + reportNoteDocSchema, + reportSharingEventDocSchema, reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, @@ -24,6 +26,8 @@ export type { ReportPrivateDoc, ReportOpsDoc, ReportSharingDoc, + ReportNoteDoc, + ReportSharingEventDoc, ReportContactsDoc, ReportLookupDoc, ReportInboxDoc, @@ -43,7 +47,8 @@ export type { AgencyDoc } from './agencies.js' export { responderDocSchema } from './responders.js' export type { ResponderDoc } from './responders.js' export { userDocSchema } from './users.js' -export type { UserDoc } from './users.js' +export { reportSmsConsentDocSchema } from './users.js' +export type { UserDoc, ReportSmsConsentDoc } from './users.js' export { smsInboxDocSchema, smsOutboxDocSchema, @@ -68,6 +73,7 @@ export { massAlertRequestDocSchema, shiftHandoffDocSchema, breakglassEventDocSchema, + fieldModeSessionDocSchema, } from './coordination.js' export type { AgencyAssistanceRequestDoc, @@ -76,6 +82,7 @@ export type { MassAlertRequestDoc, ShiftHandoffDoc, BreakglassEventDoc, + FieldModeSessionDoc, } from './coordination.js' export { hazardZoneDocSchema, hazardZoneHistoryDocSchema, hazardSignalDocSchema } from './hazard.js' export type { HazardZoneDoc, HazardZoneHistoryDoc, HazardSignalDoc } from './hazard.js' diff --git a/packages/shared-validators/src/reports.test.ts b/packages/shared-validators/src/reports.test.ts index 94476e79..8a96c7d5 100644 --- a/packages/shared-validators/src/reports.test.ts +++ b/packages/shared-validators/src/reports.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { reportDocSchema, reportPrivateDocSchema, @@ -7,6 +7,8 @@ import { reportContactsDocSchema, reportLookupDocSchema, reportInboxDocSchema, + reportNoteDocSchema, + reportSharingEventDocSchema, hazardTagSchema, inboxPayloadSchema, } from './reports.js' @@ -123,7 +125,9 @@ describe('reportOpsDocSchema', () => { reportOpsDocSchema.parse({ municipalityId: 'daet', status: 'verified', + reportType: 'flood', severity: 'high', + locationGeohash: 'w7hfm2', createdAt: ts, agencyIds: [], activeResponderCount: 0, @@ -134,6 +138,42 @@ describe('reportOpsDocSchema', () => { }), ).toMatchObject({ status: 'verified' }) }) + + it('rejects report types outside the ops report enum', () => { + expect(() => + reportOpsDocSchema.parse({ + municipalityId: 'daet', + status: 'verified', + reportType: 'volcanic', + severity: 'high', + createdAt: ts, + agencyIds: [], + activeResponderCount: 0, + requiresLocationFollowUp: false, + visibility: { scope: 'municipality', sharedWith: [] }, + updatedAt: ts, + schemaVersion: 1, + }), + ).toThrow() + }) + + it('accepts persisted ops report types like medical', () => { + expect( + reportOpsDocSchema.parse({ + municipalityId: 'daet', + status: 'verified', + reportType: 'medical', + severity: 'high', + createdAt: ts, + agencyIds: [], + activeResponderCount: 0, + requiresLocationFollowUp: false, + visibility: { scope: 'municipality', sharedWith: [] }, + updatedAt: ts, + schemaVersion: 1, + }), + ).toMatchObject({ reportType: 'medical' }) + }) }) describe('reportSharingDocSchema', () => { @@ -192,6 +232,24 @@ describe('reportLookupDocSchema', () => { }), ).toMatchObject({ publicTrackingRef: 'a1b2c3d4' }) }) + + it('rejects expiresAt beyond a year at validation time', () => { + const spy = vi.spyOn(Date, 'now').mockReturnValue(ts) + try { + expect(() => + reportLookupDocSchema.parse({ + publicTrackingRef: 'a1b2c3d4', + reportId: 'r-1', + tokenHash: 'f'.repeat(64), + expiresAt: ts + 366 * 24 * 60 * 60 * 1000, + createdAt: ts, + schemaVersion: 1, + }), + ).toThrow(/expiresAt/) + } finally { + spy.mockRestore() + } + }) }) describe('reportInboxDocSchema', () => { @@ -359,6 +417,15 @@ describe('inboxPayloadSchema contact extension', () => { ).not.toThrow() }) + it('accepts exactLocation for geohash materialization', () => { + expect(() => + inboxPayloadSchema.parse({ + ...basePayload, + exactLocation: { lat: 14.6, lng: 121.0 }, + }), + ).not.toThrow() + }) + it('rejects contact with smsConsent=false (consent must be literal true)', () => { expect(() => inboxPayloadSchema.parse({ @@ -386,3 +453,87 @@ describe('inboxPayloadSchema contact extension', () => { ).toThrow() }) }) + +describe('reportOpsDocSchema PRE-B deltas', () => { + const base = { + municipalityId: 'daet', + status: 'verified' as const, + severity: 'high' as const, + createdAt: 1713350400000, + agencyIds: [], + activeResponderCount: 0, + requiresLocationFollowUp: false, + visibility: { scope: 'municipality' as const, sharedWith: [] }, + updatedAt: 1713350400000, + schemaVersion: 1, + } + + it('accepts reportType, locationGeohash, duplicateClusterId, and hazardZoneIdList', () => { + expect( + reportOpsDocSchema.parse({ + ...base, + reportType: 'flood', + locationGeohash: 'w7hfm2', + duplicateClusterId: 'cluster-uuid-1', + hazardZoneIdList: ['hz-1', 'hz-2'], + }), + ).toMatchObject({ reportType: 'flood', locationGeohash: 'w7hfm2' }) + }) + + it('still accepts the old shape when the new fields are absent', () => { + expect(() => reportOpsDocSchema.parse(base)).not.toThrow() + }) +}) + +describe('reportNoteDocSchema', () => { + it('parses a valid report note', () => { + expect( + reportNoteDocSchema.parse({ + reportId: 'r1', + authorUid: 'uid-1', + body: 'Situation is stable now.', + createdAt: 1713350400000, + schemaVersion: 1, + }), + ).toMatchObject({ reportId: 'r1' }) + }) + + it('rejects body over 2000 chars', () => { + expect(() => + reportNoteDocSchema.parse({ + reportId: 'r1', + authorUid: 'uid-1', + body: 'x'.repeat(2001), + createdAt: 1713350400000, + schemaVersion: 1, + }), + ).toThrow() + }) +}) + +describe('reportSharingEventDocSchema', () => { + it('parses a manual share event', () => { + expect( + reportSharingEventDocSchema.parse({ + targetMunicipalityId: 'mercedes', + sharedBy: 'uid-1', + sharedAt: 1713350400000, + sharedReason: 'Border incident', + source: 'manual', + schemaVersion: 1, + }), + ).toMatchObject({ source: 'manual' }) + }) + + it('parses an auto share event without reason', () => { + expect( + reportSharingEventDocSchema.parse({ + targetMunicipalityId: 'mercedes', + sharedBy: 'system', + sharedAt: 1713350400000, + source: 'auto', + schemaVersion: 1, + }), + ).toMatchObject({ targetMunicipalityId: 'mercedes' }) + }) +}) diff --git a/packages/shared-validators/src/reports.ts b/packages/shared-validators/src/reports.ts index f1850416..ca0bcc9e 100644 --- a/packages/shared-validators/src/reports.ts +++ b/packages/shared-validators/src/reports.ts @@ -1,7 +1,20 @@ import { z } from 'zod' import { msisdnPhSchema } from './msisdn.js' -// hazard tag schema +const reportOpsReportTypeSchema = z.enum([ + 'flood', + 'fire', + 'earthquake', + 'typhoon', + 'landslide', + 'storm_surge', + 'medical', + 'accident', + 'structural', + 'security', + 'other', +]) + export const hazardTagSchema = z .object({ hazardZoneId: z.string().min(1), @@ -10,7 +23,6 @@ export const hazardTagSchema = z }) .strict() -// reportDocSchema — public report document export const reportDocSchema = z .object({ municipalityId: z.string().min(1), @@ -73,7 +85,6 @@ export const reportDocSchema = z }) .strict() -// reportPrivateDocSchema — private report document export const reportPrivateDocSchema = z .object({ municipalityId: z.string().min(1), @@ -85,7 +96,6 @@ export const reportPrivateDocSchema = z }) .strict() -// reportOpsDocSchema — operations document export const reportOpsDocSchema = z .object({ municipalityId: z.string().min(1), @@ -111,6 +121,12 @@ export const reportOpsDocSchema = z agencyIds: z.array(z.string()).default([]), activeResponderCount: z.number().int().nonnegative().default(0), requiresLocationFollowUp: z.boolean().default(false), + // processInboxItemCore always writes reportType; .optional() preserves + // backward-compat for report_ops docs written before Phase 5 PRE-B. + reportType: reportOpsReportTypeSchema.optional(), + locationGeohash: z.string().length(6).optional(), + duplicateClusterId: z.string().optional(), + hazardZoneIdList: z.array(z.string()).optional(), visibility: z .object({ scope: z.enum(['municipality', 'shared', 'provincial']), @@ -122,7 +138,6 @@ export const reportOpsDocSchema = z }) .strict() -// reportSharingDocSchema — sharing document export const reportSharingDocSchema = z .object({ ownerMunicipalityId: z.string().min(1), @@ -134,7 +149,27 @@ export const reportSharingDocSchema = z }) .strict() -// reportContactsDocSchema — contacts document +export const reportNoteDocSchema = z + .object({ + reportId: z.string().min(1), + authorUid: z.string().min(1), + body: z.string().max(2000), + createdAt: z.number().int(), + schemaVersion: z.number().int().positive(), + }) + .strict() + +export const reportSharingEventDocSchema = z + .object({ + targetMunicipalityId: z.string().min(1), + sharedBy: z.string().min(1), + sharedAt: z.number().int(), + sharedReason: z.string().max(500).optional(), + source: z.enum(['manual', 'auto']), + schemaVersion: z.number().int().positive(), + }) + .strict() + export const reportContactsDocSchema = z .object({ reportId: z.string().min(1), @@ -146,22 +181,21 @@ export const reportContactsDocSchema = z }) .strict() -// reportLookupDocSchema — lookup document export const reportLookupDocSchema = z .object({ publicTrackingRef: z.string().regex(/^[a-z0-9]{8}$/), reportId: z.string().min(1), tokenHash: z.string().regex(/^[a-f0-9]{64}$/), - expiresAt: z - .number() - .int() - .max(Date.now() + 365 * 24 * 60 * 60 * 1000), + expiresAt: z.number().int(), createdAt: z.number().int(), schemaVersion: z.number().int().positive(), }) .strict() + .refine((d) => d.expiresAt <= Date.now() + 365 * 24 * 60 * 60 * 1000, { + path: ['expiresAt'], + message: 'expiresAt must be within 365 days of validation', + }) -// reportInboxDocSchema — inbox document export const reportInboxDocSchema = z .object({ reporterUid: z.string().min(1), @@ -180,11 +214,12 @@ export type ReportDoc = z.infer export type ReportPrivateDoc = z.infer export type ReportOpsDoc = z.infer export type ReportSharingDoc = z.infer +export type ReportNoteDoc = z.infer +export type ReportSharingEventDoc = z.infer export type ReportContactsDoc = z.infer export type ReportLookupDoc = z.infer export type ReportInboxDoc = z.infer -// inboxPayloadSchema — validated payload inside report_inbox docs export const inboxPayloadSchema = z .object({ reportType: z.string().min(1).max(32), @@ -199,6 +234,13 @@ export const inboxPayloadSchema = z }) .strict() .optional(), + exactLocation: z + .object({ + lat: z.number().min(-90).max(90), + lng: z.number().min(-180).max(180), + }) + .strict() + .optional(), pendingMediaIds: z.array(z.string().min(1)).max(20).optional(), municipalityId: z.string().min(1).optional(), barangayId: z.string().min(1).optional(), diff --git a/packages/shared-validators/src/responders.test.ts b/packages/shared-validators/src/responders.test.ts new file mode 100644 index 00000000..fa1a3646 --- /dev/null +++ b/packages/shared-validators/src/responders.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest' +import { responderDocSchema } from './responders' + +const ts = 1713350400000 + +describe('responderDocSchema PRE-B deltas', () => { + it('accepts hasFcmToken and fcmTokens fields', () => { + const result = responderDocSchema.parse({ + uid: 'resp-1', + agencyId: 'bfp-daet', + municipalityId: 'daet', + displayCode: 'BFP-01', + specialisations: [], + availabilityStatus: 'on_duty', + isActive: true, + schemaVersion: 1, + createdAt: ts, + updatedAt: ts, + fcmTokens: ['token-abc'], + hasFcmToken: true, + }) + expect(result.hasFcmToken).toBe(true) + expect(result.fcmTokens).toEqual(['token-abc']) + }) + + it('defaults hasFcmToken to false when absent', () => { + const result = responderDocSchema.parse({ + uid: 'resp-1', + agencyId: 'bfp-daet', + municipalityId: 'daet', + displayCode: 'BFP-01', + specialisations: [], + availabilityStatus: 'on_duty', + isActive: true, + schemaVersion: 1, + createdAt: ts, + updatedAt: ts, + }) + expect(result.hasFcmToken).toBe(false) + expect(result.fcmTokens).toEqual([]) + }) + + it('derives hasFcmToken from non-empty fcmTokens when omitted', () => { + const result = responderDocSchema.parse({ + uid: 'resp-1', + agencyId: 'bfp-daet', + municipalityId: 'daet', + displayCode: 'BFP-01', + specialisations: [], + availabilityStatus: 'on_duty', + isActive: true, + schemaVersion: 1, + createdAt: ts, + updatedAt: ts, + fcmTokens: ['token-abc'], + }) + expect(result.hasFcmToken).toBe(true) + expect(result.fcmTokens).toEqual(['token-abc']) + }) +}) diff --git a/packages/shared-validators/src/responders.ts b/packages/shared-validators/src/responders.ts index 3e367441..d0f6cdcd 100644 --- a/packages/shared-validators/src/responders.ts +++ b/packages/shared-validators/src/responders.ts @@ -9,11 +9,14 @@ export const responderDocSchema = z specialisations: z.array(z.string()).default([]), availabilityStatus: z.enum(['on_duty', 'off_duty', 'on_break', 'unavailable']), isActive: z.boolean(), + fcmTokens: z.array(z.string().trim().min(1)).default([]), + hasFcmToken: z.boolean().optional(), lastTelemetryAt: z.number().int().optional(), schemaVersion: z.number().int().positive(), createdAt: z.number().int(), updatedAt: z.number().int(), }) .strict() + .transform((d) => ({ ...d, hasFcmToken: d.fcmTokens.length > 0 })) export type ResponderDoc = z.infer diff --git a/packages/shared-validators/src/users.test.ts b/packages/shared-validators/src/users.test.ts new file mode 100644 index 00000000..9696aa8f --- /dev/null +++ b/packages/shared-validators/src/users.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest' +import { reportSmsConsentDocSchema } from './users' + +const ts = 1713350400000 + +describe('reportSmsConsentDocSchema', () => { + it('parses a full consent doc', () => { + expect( + reportSmsConsentDocSchema.parse({ + reportId: 'r1', + phone: '+63 912 345 6789', + locale: 'tl', + smsConsent: true, + municipalityId: 'daet', + followUpConsent: true, + createdAt: ts, + schemaVersion: 1, + }), + ).toMatchObject({ municipalityId: 'daet', followUpConsent: true }) + }) + + it('defaults followUpConsent to false', () => { + const result = reportSmsConsentDocSchema.parse({ + reportId: 'r1', + phone: '+63 912 345 6789', + locale: 'tl', + smsConsent: true, + municipalityId: 'daet', + createdAt: ts, + schemaVersion: 1, + }) + expect(result.followUpConsent).toBe(false) + }) + + it('rejects when municipalityId is absent', () => { + expect(() => + reportSmsConsentDocSchema.parse({ + reportId: 'r1', + phone: '+63 912 345 6789', + locale: 'tl', + smsConsent: true, + createdAt: ts, + schemaVersion: 1, + }), + ).toThrow() + }) +}) diff --git a/packages/shared-validators/src/users.ts b/packages/shared-validators/src/users.ts index 19e877fb..44b91cab 100644 --- a/packages/shared-validators/src/users.ts +++ b/packages/shared-validators/src/users.ts @@ -23,4 +23,18 @@ export const userDocSchema = z }) .strict() +export const reportSmsConsentDocSchema = z + .object({ + reportId: z.string().min(1), + phone: z.string().min(1), + locale: z.string().min(1), + smsConsent: z.literal(true), + municipalityId: z.string().min(1), + followUpConsent: z.boolean().default(false), + createdAt: z.number().int(), + schemaVersion: z.number().int().positive(), + }) + .strict() + export type UserDoc = z.infer +export type ReportSmsConsentDoc = z.infer diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be29965f..0f6e00d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -257,6 +257,9 @@ importers: firebase-functions: specifier: ^7.2.5 version: 7.2.5(firebase-admin@13.8.0)(graphql@16.13.2) + ngeohash: + specifier: ^0.6.3 + version: 0.6.3 sharp: specifier: ^0.34.5 version: 0.34.5 @@ -267,6 +270,9 @@ importers: '@firebase/rules-unit-testing': specifier: ^5.0.0 version: 5.0.0(firebase@12.12.0) + '@types/ngeohash': + specifier: ^0.6.8 + version: 0.6.8 '@types/node': specifier: ^20.12.0 version: 20.19.39 @@ -1701,6 +1707,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/ngeohash@0.6.8': + resolution: {integrity: sha512-A90x3HMwE1yXbWCnd0ztHzv8rAQPjwTzX2diYI/6OrWm/3oairDaehw5WPWJFgZ+8+J/OuF99IbipmMa2le6tQ==} + '@types/node@20.19.39': resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} @@ -3759,6 +3768,10 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + ngeohash@0.6.3: + resolution: {integrity: sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==} + engines: {node: '>=v0.2.0'} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -6427,6 +6440,8 @@ snapshots: '@types/ms@2.1.0': {} + '@types/ngeohash@0.6.8': {} + '@types/node@20.19.39': dependencies: undici-types: 6.21.0 @@ -9000,6 +9015,8 @@ snapshots: negotiator@0.6.3: {} + ngeohash@0.6.3: {} + node-domexception@1.0.0: {} node-exports-info@1.6.0: