Property Amenities
Catalog of amenities (e.g. "Wi-Fi", "Hair Dryer") grouped under amenity_groups. Each amenity carries a display name, description, an icon (S3 URL to a logo image), and an active flag. Amenities are soft-deleted; the mapping table property_amenity_mappings on each property references these rows.
📄️ List all active property amenities
Returns every amenity with `active = true` across all groups. Soft-deleted amenities are excluded. No pagination — cardinality is small (low hundreds).
📄️ Get a property amenity by id
Returns a single amenity. Also returns 404 for soft-deleted rows.
📄️ Soft-delete an amenity
Flips `active` to `false`. Existing `property_amenity_mappings` are preserved and will still resolve, but the amenity will no longer show up in listings or group queries.
📄️ List all amenities in a given group
Returns every active amenity whose `amenity_group_id` matches. The group must exist and be active — otherwise a 404 is returned rather than an empty list, to distinguish 'empty group' from 'wrong id'.
📄️ Create an amenity under the given group
Inserts a new amenity linked to `amenityGroupId`. The group must exist and be active. `active` defaults to `true` server-side and does not need to be passed. `icon` should be the S3 object URL returned by /logo-upload-url after the client uploads the image.
📄️ List all amenities mapped to a given property space
Returns every active amenity whose id appears in `property_amenity_space_mappings` for the given space. The space must exist. Ordered by amenity group then name.
📄️ Replace the amenity set mapped to a property space
Deletes every existing mapping for the given space, then inserts one row per distinct id in the request body. The space and every amenity id must exist. Passing `[]` clears all mappings for the space. Runs in a single transaction.
📄️ Get a presigned S3 URL to upload an amenity icon image
Returns a short-lived presigned PUT URL under the `property-amenities/` prefix along with the resulting public `objectUrl`. Client flow: (1) POST here, (2) PUT the image bytes to `uploadUrl` with the same `Content-Type`, (3) submit `objectUrl` as `icon` when creating/updating the amenity. URL expires after `expiresInSeconds` (default 900).
📄️ Update an amenity (and optionally move it to a different group)
Updates `name`, `description`, `icon`, and `active` on an existing amenity. Setting a different `amenityGroupId` in the path re-parents the amenity to that group. Both the amenity and the target group must exist and be active. Only rows where `active = true` can be updated; to reactivate a soft-deleted amenity, restore it directly.