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.
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.
Internally it performs:
- Create
.envfrom.env.example(skipped if it exists) - Verify Docker is running
docker compose up -d postgres(PostgreSQL 17)- Wait for readiness via
pg_isready bun installbun run db:push
To run the steps individually:
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
| 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
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.
src/api/** (React Query hooks and types) is regenerated from http://localhost:8080/openapi.json. Never edit the generated files by hand.
4. Run
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.
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:
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: