Repository navigation
feat(reports): postbox-data problem reporting, admin review & OSM pipeline - #117
Merged
Merged
Conversation
…eline Add a facility for players to report problems with postbox data and for designated admins to action them: - Report a missing postbox from the Claim/Nearby empty state (captures live GPS as the authoritative location). - Report a wrong/missing cypher from the History screen's postbox detail. - Up to 3 optional geotagged photos per report, uploaded to Cloud Storage; EXIF GPS / capture time extracted client-side for reviewer verification. - In-app admin review queue (Pending/Accepted/Rejected) gated on the `admin` Firebase Auth custom claim; accept/reject with a final-cypher picker. - Accepting a report updates the Firestore postbox entry, retroactively re-scores every affected claim plus the users' aggregates and leaderboards when the points value changes, and generates an osmChange (.osc) file + an openstreetmap.org/edit deep-link for a human to push to OSM. Backend: new `submitReport`/`reviewReport` callables (`functions/src/reports.ts`), `_recomputeScores.ts` for retroactive re-scoring, `KNOWN_MONARCHS`/`pointsForMonarch` in `_getPoints.ts`, `reports` Firestore rules, `storage.rules`, and a `set_admin.js` CLI to grant/revoke the admin claim. Flutter: `lib/reports/` (repository + 3 screens + shared widgets), `lib/admin/` (access helper + review screen), wired into Claim/Nearby empty states, History (map/list toggle + report action), and the Home overflow menu. New deps: firebase_storage, image_picker, exif, url_launcher; iOS Info.plist camera/photo usage strings. Tests: 262 TS passing (adds buildOsmChange, parsePhotos, pointsForMonarch, maxDailyFromClaims, submitReport/reviewReport auth & validation); 105 Dart passing; `flutter analyze` clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Postbox data imported from OpenStreetMap is often wrong or incomplete (a real box missing entirely, or a wrong/missing royal cypher). This adds an end-to-end feedback loop so players can flag it and designated admins can action it.
Features
report_photos/{uid}/; EXIF GPS / capture time extracted client-side for reviewer verification (best-effort — browsers strip it, iOS needsrequestFullMetadata+ camera location on, so the livegeolocatorfix is always authoritative).reportscollection, visible only to users with theadminFirebase Auth custom claim; cards show location (with a "Map" link), current vs suggested cypher, note, and photo thumbnails (tap → full view with the photo's EXIF GPS vs the reported location); Accept (with a final-cypher picker + ref/note) and Reject actions.postbox/{id}entry (or createspostbox/manual_{reportId}withsource: 'user_report'for a missing box), retroactively re-scores every affected claim plus the users' aggregates and all leaderboards when the points value changes, and generates anosmChange.oscfile + anopenstreetmap.org/editdeep-link for a human to push to OSM (deliberately not automated, per OSM's automated-edit policy).Backend (
functions/)src/reports.ts—submitReport/reviewReportcallables (wired intoindex.ts) + the pure, unit-testedbuildOsmChange.reviewReportis gated onrequest.auth.token.admin === true.src/_recomputeScores.ts—repointClaimsForPostbox(batched claim rewrite with an audit trail),recomputeUserAggregates(re-deriveslifetimePoints/uniquePostboxesClaimed/maxDailyPointsfrom claims, re-sums daily/weekly/monthly via the existingupdateUserLeaderboards, refreshesleaderboards/lifetime+ the box's-county stats/leaderboard),rescoreAfterCypherChangeorchestrator. Streaks are intentionally not recomputed (they depend on claim dates, not points)._getPoints.ts—KNOWN_MONARCHS,pointsForMonarch.types.ts—ReportDoc/ReportPhoto;PostboxDoccorrection/source fields.firestore.rules—reports/{id}readable by the reporter or an admin, no client writes.storage.rules(new, registered infirebase.json) —report_photos/{uid}/{file}owner-write + owner/admin-read (≤10 MB, images only);osm_changesets/{file}admin-read-only, server-write only.set_admin.js(new CLI) —node set_admin.js <uid> [--remove]grants/revokes theadmincustom claim (mirrorsimport_postboxes.js).Flutter (
lib/)reports/—report_repository.dart(pick photos, extract EXIF, upload to Storage, callsubmitReport),report_missing_postbox_screen.dart,report_cypher_screen.dart,my_reports_screen.dart,report_form_widgets.dart(sharedCypherPicker+PhotoPickerField).admin/—admin_access.dart(AdminAccess.isAdmin()reads the cached custom claim),admin_reports_screen.dart.ViewToggle; "My reports" (always) and "Admin · Reports" (admins only) in the app-bar overflow menu; aJamesMessages.reportSentline.firebase_storage,image_picker,exif,url_launcher; iOSInfo.plistgainedNSCameraUsageDescription/NSPhotoLibraryUsageDescription.Tests / verification
functions: build + lint clean; 262 TS tests passing (addsbuildOsmChange,parsePhotos,pointsForMonarch,maxDailyFromClaims, andsubmitReport/reviewReportauth & validation).flutter analyzeclean; 105 Dart tests passing.Operational notes / follow-ups
node functions/set_admin.js <uid> --project the-postbox-game(the user re-authenticates afterwards).reviewReport, bounded by a box's distinct claimants; could move to an asyncrescoreJobstrigger if a box ever has very many claimants.postbox/manual_{reportId}box will collide with the same OSM node once it appears in a future import — add dedup toimport_postboxes.jsas a follow-up.submitReport.🤖 Generated with Claude Code