Skip to content

[Assets] 2 - Attach Map Point photos #313

Description

@KacperKozak

Parent

What to build

Replace the parked device-local Map Point media prototype with server-backed Hosted Asset photos. Riders can pick or capture a photo, keep seeing its normalized local preview while upload or attachment is pending, retry recoverable failures, and later see resized public photos on Map Points loaded from the server.

Map Points remain server-owned with no offline Map Point store. Shared Asset infrastructure owns normalization, local files, multipart transfer, Device Token auth, background retry, and remote Asset deletion. This slice owns Map Point attachment, UI state, and cleanup after replacement, removal, or Map Point deletion.

Server contract

  • A Map Point carries at most four photos.
  • Photo order is the server-assigned attachment order. This slice has no reorder route.
  • Attach through POST /map-points/:id/photos with { "assetId": "..." }.
  • Detach through DELETE /map-points/:id/photos/:assetId.
  • Nearby, create, patch, and attach responses expose photos as ordered { assetId, url } references.
  • Signed-out nearby reads receive the same public photo references without authentication.
  • Attachment requires the same Account to own the Map Point and the Asset, and the Asset must belong to the map-points group.
  • Missing, wrong-group, and wrong-owner Assets return 404 without exposing ownership. Duplicate attachment and the four-photo cap return documented 409 errors.
  • Attachment references never delete Asset bytes. Deleting bytes while a live Map Point still references them returns 409 asset-in-use.
  • Deleting a Map Point removes its photo references but does not delete Hosted Assets.

Local and UI contract

  • A picked or captured photo immediately gets a normalized local preview and durable Asset job owned by the Map Point identity.
  • UI renders stable normalizing, pending or uploading, attached, retryable failure, permanent failure, and deleting states.
  • Closing the sheet or killing JS does not discard work.
  • An uploaded but unattached Asset retains its ID and retries only attachment. It never intentionally uploads the bytes again.
  • Only owner-facing UI may add, replace, remove, or retry photos. Public readers never receive mutation controls.
  • Public UI requests only supported bounded variants and never constructs bucket keys or presigned URLs.
  • After attachment is durable and the remote URL is usable, release the normalized local file. Map Points have no offline media store.

Replacement and cleanup

  • Replacement orders upload new, attach new, confirm the returned Map Point references it, detach old, then delete the old Hosted Asset.
  • Removal orders detach, confirm success, then enqueue remote Asset deletion.
  • Map Point deletion first persists cleanup jobs containing every attached Asset ID. It then deletes the Map Point and marks those Asset deletes eligible only after the server confirms deletion. This prevents both leaked Assets and deletion of bytes still referenced by a live Map Point.
  • A failed Map Point delete keeps its cleanup jobs blocked. A failed Asset delete keeps its job durable and follows [Assets] 1 - Normalize and upload Map Point photos #312 retry classification.

Likely files

  • src/modules/map-points/hooks/useMapPointMedia.ts - replace the in-memory picker and camera controller with durable native Asset state.
  • src/modules/map-points/store/mapPointPhotoFiles.ts - remove the raw local-copy prototype after shared normalization owns pending files.
  • src/modules/map-points/components/MapPointMediaPreview.tsx - render local and remote previews plus transfer states.
  • src/modules/map-points/components/MapTargetSheetEditBody.tsx - owner add, remove, replace, and retry intents.
  • src/modules/map-points/constants/mapPoints.ts - remove MAP_POINT_MEDIA_ENABLED once the server-backed path is complete.
  • modules/vescape-core/ios/mappoints/MapPointApi.swift - iOS attachment and read decoding through the existing API boundary.
  • modules/vescape-core/android/src/main/java/expo/modules/vescapecore/mappoints/MapPointApi.kt - Android parity peer.
  • modules/vescape-core/src/index.ts - Map Point photo and mutation contracts mirrored from the server.

Acceptance criteria

  • Owner can pick or capture a supported photo and immediately sees its normalized local preview.
  • Upload uses the shared [Assets] 1 - Normalize and upload Map Point photos #312 pipeline and POST /assets/map-points with Device Token auth.
  • Pending upload and attachment state survives sheet dismissal, JS death, process restart, and offline periods.
  • Uploaded but unattached work retries attachment with the existing Asset ID.
  • Native clients strictly parse the ordered { assetId, url } photo contract and enforce the four-photo bound.
  • Signed-out nearby reads render bounded public photos.
  • Owner and public controls follow existing ownedByMe semantics.
  • Replacement never deletes the old Asset until the new attachment is durable.
  • Removal never deletes Asset bytes before detachment is durable.
  • Map Point deletion durably records every Asset ID before server deletion and deletes those Assets only after the Map Point is gone.
  • Successful attachment releases the normalized local file without breaking the remote preview.
  • Missing, wrong-group, wrong-owner, duplicate, over-capacity, and deleted-Map-Point responses leave consistent local state and a useful rider-facing result.
  • The parked raw photo and video prototype needs no migration because it never shipped enabled. Remove its obsolete storage code.
  • Tests cover owner and public reads, local pending preview, process restart, offline retry, upload-success and attach-failure, replacement ordering, removal ordering, Map Point deletion cleanup, Account mismatch, four-photo capacity, resized URL selection, and strict response parsing.
  • Reusable media transfer states have component showcase previews.
  • TS, Swift, Kotlin, and server contracts stay linked by @parity.

Blocked by

Explicit exclusions

  • Video upload or playback.
  • Offline Map Point creation.
  • Media moderation, reporting, quotas, or deduplication.
  • Private reads or arbitrary Asset groups.
  • Migration of parked prototype files.
  • vescape-web Map Point consumption.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:assetsHosted media, image normalization, upload state, and remote Asset referencesarea:mapMaps, routes, GPS displayarea:nativeTouches native side (modules/vesc-ble, Swift/Kotlin)area:serverVescape backend APIs, relay behavior, server policy, and deployment-facing contractscomplexity:highCritical paths, subtle correctness, native pipelines. Use opus.ready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions