Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Store Locations

The retail-location database behind the store locator — managing it, importing it, and keeping coordinates and photos current.

Store Locations

Every shop, market and restaurant that carries the salsa lives in RetailLocation. It is the data behind the public store locator and the "find us locally" pages.

FieldNotes
businessName, address, city, state, zipCodeUnique on (businessName, address)
phone, websiteOptional contact details
latitude, longitudeDecimal(10,7) — set by geocoding, not by hand
googlePlacesIdLinks the row to a Google Places record
photoUrl + LocationPhoto[]A primary photo plus a gallery
countyUsed for regional grouping
isActiveHides a location without deleting it
sortOrderManual ordering within a city

Indexed on city, state, (state, city) and isActive — the locator's query shapes.

Managing them

RoutePurpose
/admin/locationsList, search, activate/deactivate
/admin/locations/newAdd one
/admin/locations/[id]/editEdit one

Access is gated by the LOCATIONS permission category (locations:read is part of the default STAFF set; writing is not).

Bulk tooling

Run from the storefront workspace:

npm run locations:import         --workspace @jose-madrid/storefront  # from a markdown source list
npm run locations:geocode        --workspace @jose-madrid/storefront  # backfill lat/long
npm run locations:photos         --workspace @jose-madrid/storefront  # generate photos
npm run locations:update-photos  --workspace @jose-madrid/storefront  # refresh existing photos
npm run locations:cache-photos   --workspace @jose-madrid/storefront  # store one real photo per location in Blob
npm run locations:verify         --workspace @jose-madrid/storefront  # sanity-check the set

Prefer these over ad-hoc scripts — they handle the unique constraint and the coordinate precision correctly.

locations:cache-photos is how locations get their storefront photo. The site never calls Google for location photos at runtime (each call was billed, see getLocationImageUrl), so this script fetches each photo once: the Google Places photo when the Place's address matches the stored one, otherwise Street View of the address. It converts it to WebP, uploads it to Blob at site/images/locations/<id>.webp and saves that URL as photoUrl and in the gallery. It is a dry run without --apply, skips locations that already have a Blob photo unless --force, and writes every photo plus report.json to .location-photos/ so each one can be checked. Locations with no street address are skipped rather than given a city-wide Street View shot, and a Google key, quota or server error fails the location instead of being reported as missing imagery; the command exits non-zero if any location failed. It needs GOOGLE_PLACES_API_KEY, DATABASE_URL and BLOB_READ_WRITE_TOKEN, from the environment or apps/storefront/.env.local. Re-running the retail-location seed keeps a cached Blob photo. With --from-site <origin> it reads the locations from the public /api/locations API instead of the database, leaves the database alone, and records each blobUrl and placeId in report.json for saving separately.

Query helpers live in lib/locations/ (query.ts, db-query.ts, shared.ts, import.ts).

Google dependencies

Geocoding and photos come from Google. See Google Places and Google Maps for the API keys and which APIs must be enabled.

A location with no latitude/longitude will not appear on the map even though it shows in list views. After importing, run the geocode backfill and then locations:verify.

Search indexing

Location pages are public URLs and belong in the sitemap. app/sitemap.ts builds them from Prisma, and SeoConfiguration.locationTitleTemplate / locationDescTemplate supply their metadata. See SEO.

How is this guide?

Edit on GitHub

Last updated on

On this page