Internal admin UI for the Mobility Database Operations API
(functions-python/operations_api in
MobilityData/mobility-feed-api).
It provides CRUD over every Operations API endpoint and is restricted to
MobilityData staff signing in with their Google accounts.
Internal tool — not intended for the general public.
- Next.js (App Router) + TypeScript + Yarn
- MUI (Material UI) + @tanstack/react-query
- openapi-typescript (types generated from the OpenAPI spec) + openapi-fetch (typed client)
- Google Identity Services (GIS) for authentication
- Jest + Testing Library for tests
The stack follows the MobilityData/mobilitydatabase-web pattern, with one
deliberate difference: authentication uses GIS directly instead of Firebase
(see below).
The Operations API accepts an Authorization: Bearer <token> where the token is
a Google OAuth 2.0 access token whose audience equals the API's
GOOGLE_CLIENT_ID (it validates the token against Google's tokeninfo
endpoint). The API itself does not filter by email domain.
Therefore:
- The app signs users in with GIS using
NEXT_PUBLIC_GOOGLE_CLIENT_ID, which must be the same OAuth client as the API'sGOOGLE_CLIENT_ID. GIS mints an access token under that client, so the audience matches by construction. - "MobilityData staff only" is enforced by configuring that OAuth client as
Internal user type in the mobilitydata.org Google Workspace — only org
members can obtain a token. The app additionally verifies the signed-in email
ends in
@mobilitydata.organd blocks anyone else client-side. - For local development against an API started with
LOCAL_ENV=True, setNEXT_PUBLIC_LOCAL_ENV=trueto skip real sign-in. - Guard rail:
NEXT_PUBLIC_LOCAL_ENVis aNEXT_PUBLIC_var, so its value is baked into the client bundle at build time — if it were evertruein a production build, every visitor would land in the app with no Google sign-in at all.next.config.jsrefuses to build or start whenNEXT_PUBLIC_LOCAL_ENV=trueandNODE_ENV=productionare set together, so a misconfigured deploy fails loudly instead of shipping an unauthenticated app.
# 1. Configure env
cp .env.rename_me .env.local # then fill in the values
# 2. Install
yarn install
# 3. Generate the typed API client from the pinned OpenAPI spec
yarn generate:api-types
# 4. Run
yarn dev # http://localhost:3000See .env.rename_me. Client-exposed vars must be prefixed with
NEXT_PUBLIC_.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_OPERATIONS_API_URL |
Base URL of the deployed Operations API |
NEXT_PUBLIC_GOOGLE_CLIENT_ID |
OAuth client ID — must equal the API's GOOGLE_CLIENT_ID |
NEXT_PUBLIC_LOCAL_ENV |
true to skip sign-in against a LOCAL_ENV=True API |
NEXT_PUBLIC_ALLOWED_EMAIL_DOMAIN |
Allowed email domain (default mobilitydata.org) |
OPERATIONS_SPEC_REF |
Build-time only: git ref of the spec to generate types from |
The Operations API sends no CORS headers and its auth middleware rejects an
unauthenticated preflight, so a browser at http://localhost:3000 cannot call
the dev Cloud Function cross-origin. Use the built-in same-origin dev proxy:
the browser calls localhost:3000, and Next.js forwards /v1/operations/* to
dev server-side (passing the Authorization: Bearer header through).
-
Get the dev Cloud Function URL:
gcloud functions describe operations-api --gen2 --region=northamerica-northeast1 \ --project=mobility-feeds-dev --format='value(serviceConfig.uri)' -
In
.env.local:NEXT_PUBLIC_LOCAL_ENV=false NEXT_PUBLIC_OPERATIONS_API_URL= # empty → requests go through the proxy OPERATIONS_API_PROXY_TARGET=https://<dev-operations-api-url> NEXT_PUBLIC_GOOGLE_CLIENT_ID=59033768865-bbf5v4rkut7n7i59vi162a3snhh3r1gc.apps.googleusercontent.com
-
One-time GCP setup: in the GCP Console, add
http://localhost:3000to the dev OAuth client's Authorized JavaScript origins (Google sign-in is rejected otherwise). -
yarn dev, sign in with your@mobilitydata.orgaccount, and the app will read/write the dev database through the proxy.
Restart
yarn devafter changing.env.local— Next.js reads env andnext.config.jsat startup.For a real deployment (not localhost), the Operations API will need CORS added for the web app's origin, or the app must be served through an equivalent proxy.
The app is spec-first. yarn generate:api-types:
- Runs
scripts/fetch-spec.mjs, which downloadsdocs/OperationsAPI.yamlfrom a pinned ref ofMobilityData/mobility-feed-api(OPERATIONS_SPEC_REF) intospec/OperationsAPI.yaml. - Runs
openapi-typescriptto regeneratesrc/lib/api/generated.ts.
To adopt a newer API contract, bump OPERATIONS_SPEC_REF and re-run the command.
Never hand-edit src/lib/api/generated.ts.
All 19 Operations API endpoints are exposed:
- Feeds — list (search / status / type filters), GTFS & GTFS-RT detail, availability history, create & edit for both feed types.
- Licenses — list, detail (with rules), match-a-URL tool, propagate tool (dry-run preview → confirmed apply).
- Users — search, detail, per-user feature-flag editor.
- Feature flags — list, create, edit, delete.
src/
app/ # App Router pages (feeds, licenses, users, feature-flags)
components/ # Shared UI + per-domain components
lib/
api/ # generated types, typed client, react-query hooks
auth/ # GIS wrapper, AuthProvider, token store, config
theme.ts
__tests__/ # Jest + Testing Library specs
scripts/fetch-spec.mjs # spec fetcher used by generate:api-types
| Command | Description |
|---|---|
yarn dev |
Run the dev server |
yarn build / yarn start |
Production build / serve |
yarn generate:api-types |
Fetch the spec and regenerate the typed client |
yarn lint |
ESLint |
yarn test |
Jest |
yarn format |
Prettier |
Not configured yet. The app is a standard Next.js App Router project and can be deployed to Vercel or Firebase Hosting; add the environment variables above to the hosting provider. Ensure the deployed origin is added to the OAuth client's Authorized JavaScript origins.
- In
mobility-feed-api, start the Operations API:scripts/api-operations-start.sh(listens on:8081withLOCAL_ENV=True). - Here, set
NEXT_PUBLIC_OPERATIONS_API_URL=http://localhost:8081andNEXT_PUBLIC_LOCAL_ENV=true, thenyarn dev. - Exercise each screen (feeds list/detail/availability/create/edit, licenses list/detail/match/propagate, users search/detail + flag editor, feature-flag CRUD).
- Real-auth check: point the app at a deployed dev API with a real
NEXT_PUBLIC_GOOGLE_CLIENT_ID, sign in with a@mobilitydata.orgaccount, and confirm a non-org account is blocked.