# Vehicle photos

How a car's gallery is filled from the template's uploads, how one photo is the cover the garage and the profile show, and the Free plan's 50 photos per car.

## What it does [#what-it-does]

A vehicle photo (RF26, US05) is an image in the Organization's private storage in one car's gallery. The bytes travel by the template's direct upload ([Documents and uploads](/docs/features/documents)); a client only gets 15-minute signed links, with the photo id as the image cache key.

- `vehiclePhoto.add` puts up to 20 uploaded images at the end of the gallery, in the order given. Each must be an image of the Active Organization (`BAD_REQUEST`). A file already in the gallery is skipped, so a replay adds nothing (RNF04). The first photo of a car without a cover becomes the cover.
- At most one photo is the cover (a unique index). `vehiclePhoto.setCover` moves it; `garage.list` and `vehicle.profile` show it. Deleting the cover makes the first remaining photo the cover. Deleting a photo leaves its file in Files.
- A car holds 50 photos on Free and 100 on Plus (one read of the gallery). Going over answers `CONFLICT`; the list returns the `plan` and its `limit`, so the app says why before it asks. A deployment without billing is Plus.
- Writes to one gallery take an advisory lock, so the limit, the positions and the one cover hold under concurrent adds.
- A cover's dominant colour is read once by the `coverColors` job and seeds the Car colour theme ([Appearance](/docs/features/appearance)). A [Cinematic Studio](/docs/features/studio) picture is a photo with `ai_edited` and a link to its original.
- Every photo gets a list preview once, from the `photoPreviews` job: the picture upright (EXIF orientation applied), at most 384 px on its long side, as a PNG under `orgs/<org>/previews/<photoId>.png` (`vehicle_photo.preview_key`, `preview_checked_at`). A JPEG is read at 1/8 scale like the colour, so a Worker never holds the 12 MP original. `vehiclePhoto.list` and `moment.list` sign it as `previewUrl` (null until made, or for a small or unreadable picture: read at 1/8, a JPEG under about 2048 px on its long side comes out under 256 px and gets none, so the seed's 1200 px photos never have one); the gallery strip and Moments rows show it, the hero and garage card keep the original.

| Procedure                                           | Failures                                                                        |
| --------------------------------------------------- | ------------------------------------------------------------------------------- |
| `vehiclePhoto.list` `{ vehicleId }` (gallery order) | `NOT_FOUND` (car)                                                               |
| `vehiclePhoto.add` `{ vehicleId, fileIds (1–20) }`  | `INPUT_VALIDATION`, `NOT_FOUND` (car), `BAD_REQUEST` (file), `CONFLICT` (limit) |
| `vehiclePhoto.setCover` `{ id }`                    | `NOT_FOUND`                                                                     |
| `vehiclePhoto.delete` `{ id }`                      | `NOT_FOUND`                                                                     |

All are for Members of the Organization; a non-Member gets `FORBIDDEN`. Photos hold no money or personal data, so no `audit_log` event is written.

## In the app [#in-the-app]

M-Galeria (`features/vehicle-photo/vehicle-photo.screen.tsx`, route `/gallery/<vehicleId>`) opens from M-Veiculo's Gallery pill. The top bar has the car's name and, on iOS, Share (the photo itself, downloaded to the cache, never its link). Gallery shows the selected photo with its Cover badge and a delete button (confirmed), the thumbnail strip with the selected one outlined and an add tile, and "Set as cover". Add opens a sheet: several photos from the library (in the order tapped, at most what the plan still holds) or one from the camera. At the limit the add tile gives way to the plan's note. Specs lists make, model, trim, year, fuel, color, plate and VIN; color, plate and VIN open an edit sheet, and the owner can show or hide the plate and VIN. `autofolio://dev/M-Galeria` opens the seeded M3 with three photos.

## Key files [#key-files]

- [`packages/api/src/features/vehicle-photo/vehicle-photo.service.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/vehicle-photo/vehicle-photo.service.ts)
- [`apps/mobile/src/features/vehicle-photo/vehicle-photo.hooks.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/mobile/src/features/vehicle-photo/vehicle-photo.hooks.ts)
- [`apps/mobile/src/features/vehicle-photo/vehicle-photo.screen.tsx`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/mobile/src/features/vehicle-photo/vehicle-photo.screen.tsx)

