Skip to content

Setup

Prerequisites

Tool Version
Bun v1.x or later
Docker / Docker Compose Required to run the Postgres container
Node.js v20 or later (for frontend type definitions)
Google Cloud project Required to issue an OAuth client ID / secret

Repository layout

The project consists of two independent repositories. The expected layout places both side by side under one working directory.

arrange/
โ”œโ”€โ”€ heineken-interview-arrange-backend/
โ”œโ”€โ”€ heineken-interview-arrange-frontend/
โ””โ”€โ”€ heineken-interview-arrange-docs/

Backend

1. Clone the repository

git clone <backend-repository-url> heineken-interview-arrange-backend
cd heineken-interview-arrange-backend

2. One-shot setup

Creates .env, starts Postgres, installs dependencies and pushes the schema.

bun run setup

Internally it performs:

  1. Create .env from .env.example (skipped if it exists)
  2. Verify Docker is running
  3. docker compose up -d postgres (PostgreSQL 17)
  4. Wait for readiness via pg_isready
  5. bun install
  6. bun run db:push

To run the steps individually:

cp .env.example .env
bun run docker:up
bun install
bun run db:push

3. Configure environment variables

Of the values in .env, the three Google OAuth entries must be replaced with your own.

Variable Required Description Example
NODE_ENV - Runtime mode development
PORT - Listening port (default 8080) 8080
HOST - Listening host (default 0.0.0.0) 0.0.0.0
APP_BASE_URL โœ“ The backend's own URL http://localhost:8080
FRONTEND_URL - Frontend URL (default http://localhost:3000) http://localhost:3000
ALLOWED_ORIGINS โœ“ CORS allow-list (comma separated) http://localhost:3000
DATABASE_URL โœ“ DB connection string postgresql://user:password@localhost:5432/interview_arrangement
GOOGLE_CLIENT_ID โœ“ OAuth client ID xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET โœ“ OAuth client secret -
GOOGLE_REDIRECT_URI โœ“ OAuth callback URL http://localhost:8080/api/v1/auth/google/callback
ALLOWED_EMAIL_DOMAINS โœ“ Email domains allowed to sign in (comma separated) 4digit.jp
ALLOWED_SURVEY_IDS - Restrict which surveys this environment may read (comma separated). Unset means no limit 389867
GOOGLE_SERVICE_ACCOUNT_EMAIL - Service account for Calendar operations -
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY - Its private key "-----BEGIN PRIVATE KEY-----\n...\n"
GMAIL_SENDER_EMAIL - Sender address noreply@example.com
EMAIL_ENABLED - Whether real emails are sent (default false) true
JWT_SECRET โœ“ JWT signing key (32 characters minimum) -
JWT_EXPIRES_IN - Access token lifetime (default 1h) 1h
JWT_REFRESH_EXPIRES_IN - Refresh token lifetime (default 7d) 7d
PROTOTYPE_MODE - Disable external side effects (default true) true
LOG_LEVEL - Log level (default info) debug
LOG_SQL_QUERIES - Log SQL queries (default false) false
SCHEDULING_TOKEN_TTL_DAYS - Scheduling token lifetime in days (default 14) 14
DATABRICKS_HOST - Workspace URL https://dbc-xxxx.cloud.databricks.com
DATABRICKS_WAREHOUSE_ID - SQL Warehouse ID -
DATABRICKS_CLIENT_ID - Service principal application ID -
DATABRICKS_CLIENT_SECRET - Service principal OAuth secret -
DATABRICKS_TOKEN - Local shortcut: a CLI-minted token used instead -
DATABRICKS_CATALOG - Defaults to cs cs
DATABRICKS_SCHEMA_DM - Schema holding the per-survey tables, defaults to cs_dm cs_dm
DATABRICKS_SCHEMA_DWH - Schema used for metadata aggregation, defaults to cs_dwh cs_dwh

Environment variables are validated at startup by the Zod schema in src/config/env.ts. When adding a variable, update both src/config/env.ts and .env.example.

ALLOWED_EMAIL_DOMAINS is required

Startup fails without it. Making it optional would leave a hole open whenever someone forgets to set it, so it is deliberately fail-closed.

The Databricks variables are read straight from process.env

lib/databricks-client.ts does not go through getEnvConfig(), because the batch scripts run without the app's required variables. The definitions in env.ts exist for the API path. Locally the credentials can be skipped with DATABRICKS_TOKEN=$(databricks auth token -p <profile> | jq -r .access_token).

PROTOTYPE_MODE

While PROTOTYPE_MODE=true (the default), email sending and Google Calendar writes are disabled. In code, do not read the env var directly โ€” use isEmailEnabled() / isCalendarEnabled() / isPrototypeMode(). dev keeps the default; stg sets it to false so both actually happen.

ALLOWED_SURVEY_IDS rejects an empty value

Unset means no restriction (local and dev). Set but empty is a startup error: an environment that must be restricted should fail rather than quietly fall back to exposing every survey. Use isSurveyAllowed() / getAllowedSurveyIds() in code.

It applies to the list and detail endpoints in services/survey.ts (anything else returns 404) and to what batch/databricks-meta-sync.ts caches โ€” the metadata aggregation itself gets cheaper because it filters with WHERE survey_id IN (...).

4. Run

bun run dev
URL Contents
http://localhost:8080/health Health check
http://localhost:8080/docs Swagger UI
http://localhost:8080/openapi.json OpenAPI definition (Orval's input)

Frontend

1. Install dependencies

cd heineken-interview-arrange-frontend
bun install

2. Configure environment variables

Variable Description Example
NEXT_PUBLIC_API_URL Backend base URL. Falls back to http://localhost:8080 http://localhost:8080

3. Generate the API client

Run this while the backend is running โ€” Orval fails otherwise.

bun run generate-api

src/api/** (React Query hooks and types) is regenerated from http://localhost:8080/openapi.json. Never edit the generated files by hand.

4. Run

bun run dev

Open http://localhost:3000.

Workflow When Changing the API

graph LR
  A[Edit routes/schemas in backend] --> B[Start backend with bun run dev]
  B --> C[Run bun run generate-api in frontend]
  C --> D[Update screen code]

Common Errors

Invalid environment variables:

Cause: Zod validation in src/config/env.ts failed โ€” a required variable is missing or malformed (JWT_SECRET under 32 characters, a value that is not a URL, etc.).

Fix: Correct the variables listed in the error message in .env.

cp .env.example .env

Orval generation fails

Cause: The backend is not running, or /openapi.json is not served.

Fix:

# Start the backend in another terminal
cd ../heineken-interview-arrange-backend && bun run dev

# Verify connectivity, then retry
curl http://localhost:8080/openapi.json | head

Postgres will not start under docker compose

Cause: Port 5432 is already taken by an existing PostgreSQL instance.

Fix:

lsof -i :5432
bun run docker:down && bun run docker:up

Schema changes are not reflected in the DB

Cause: In development, db:push is the primary path. Generating migration files (db:generate) alone does not change the DB.

Fix:

bun run db:push
bun run db:studio   # Inspect with Drizzle Studio