# ADR-007: Configuration and Secrets Management

**Date**: 2025-12-12

**Status**: Accepted

**Implemented**: 2026-01-09

## Context

The application currently accesses environment variables directly via `process.env`. This can lead to missing variables at runtime, inconsistent naming, and a lack of type safety. It is difficult to know at a glance which environment variables are required for the application to run.

## Decision

We will introduce a centralized, schema-validated configuration service. We will use a library like `zod` to define a schema for all required environment variables. A singleton service will parse `process.env` at application startup, validate it against the schema, and provide a type-safe configuration object to the rest of the app. The application will fail fast on startup if any required configuration is missing or invalid.

## Consequences

**Positive**: Improves application reliability and developer experience by catching configuration errors at startup rather than at runtime. Provides a single source of truth for all required configuration.
**Negative**: Adds a small amount of boilerplate for defining the configuration schema. Requires a one-time effort to refactor all `process.env` access points to use the new configuration service.

## Implementation Status

### What's Implemented

- ✅ **Centralized Configuration Schema** - Zod-based validation in `src/config/env.ts`
- ✅ **Type-Safe Access** - Full TypeScript types for all configuration
- ✅ **Fail-Fast Startup** - Clear error messages for missing/invalid config
- ✅ **Environment Helpers** - `isProduction`, `isTest`, `isDevelopment` exports
- ✅ **Service Configuration Helpers** - `isSmtpConfigured`, `isAiConfigured`, etc.

### Migration Status

- ⏳ Gradual migration of `process.env` access to `config.*` in progress
- Legacy `process.env` access still works during transition

## Implementation Details

### Configuration Schema

The configuration is organized into logical groups:

```typescript
import { config, isProduction, isTest } from './config/env';

// Database
config.database.host; // DB_HOST
config.database.port; // DB_PORT (default: 5432)
config.database.user; // DB_USER
config.database.password; // DB_PASSWORD
config.database.name; // DB_NAME

// Redis
config.redis.url; // REDIS_URL
config.redis.password; // REDIS_PASSWORD (optional)

// Authentication
config.auth.jwtSecret; // JWT_SECRET (min 32 chars)
config.auth.jwtSecretPrevious; // JWT_SECRET_PREVIOUS (for rotation)

// SMTP (all optional - email degrades gracefully)
config.smtp.host; // SMTP_HOST
config.smtp.port; // SMTP_PORT (default: 587)
config.smtp.user; // SMTP_USER
config.smtp.pass; // SMTP_PASS
config.smtp.secure; // SMTP_SECURE (default: false)
config.smtp.fromEmail; // SMTP_FROM_EMAIL

// AI Services
config.ai.geminiApiKey; // GEMINI_API_KEY
config.ai.geminiRpm; // GEMINI_RPM (default: 5)
config.ai.priceQualityThreshold; // AI_PRICE_QUALITY_THRESHOLD (default: 0.5)

// Google Services
config.google.mapsApiKey; // GOOGLE_MAPS_API_KEY (optional)
config.google.clientId; // GOOGLE_CLIENT_ID (optional)
config.google.clientSecret; // GOOGLE_CLIENT_SECRET (optional)

// Worker Configuration
config.worker.concurrency; // WORKER_CONCURRENCY (default: 1)
config.worker.lockDuration; // WORKER_LOCK_DURATION (default: 30000)
config.worker.emailConcurrency; // EMAIL_WORKER_CONCURRENCY (default: 10)
config.worker.analyticsConcurrency; // ANALYTICS_WORKER_CONCURRENCY (default: 1)
config.worker.cleanupConcurrency; // CLEANUP_WORKER_CONCURRENCY (default: 10)
config.worker.weeklyAnalyticsConcurrency; // WEEKLY_ANALYTICS_WORKER_CONCURRENCY (default: 1)

// Server
config.server.nodeEnv; // NODE_ENV (development/production/test)
config.server.port; // PORT (default: 3001)
config.server.frontendUrl; // FRONTEND_URL
config.server.baseUrl; // BASE_URL
config.server.storagePath; // STORAGE_PATH (default: /var/www/.../flyer-images)
```

### Convenience Helpers

```typescript
import { isProduction, isTest, isDevelopment, isSmtpConfigured } from './config/env';

// Environment checks
if (isProduction) {
  // Production-only logic
}

// Service availability checks
if (isSmtpConfigured) {
  await sendEmail(...);
} else {
  logger.warn('Email not configured, skipping notification');
}
```

### Fail-Fast Error Messages

When configuration is invalid, the application exits with a clear error:

```text
╔════════════════════════════════════════════════════════════════╗
║           CONFIGURATION ERROR - APPLICATION STARTUP            ║
╚════════════════════════════════════════════════════════════════╝

The following environment variables are missing or invalid:

  - database.host: DB_HOST is required
  - auth.jwtSecret: JWT_SECRET must be at least 32 characters for security

Please check your .env file or environment configuration.
See ADR-007 for the complete list of required environment variables.
```

### Usage Example

```typescript
// Before (direct process.env access)
const pool = new Pool({
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT || '5432', 10),
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});

// After (type-safe config access)
import { config } from './config/env';

const pool = new Pool({
  host: config.database.host,
  port: config.database.port,
  user: config.database.user,
  password: config.database.password,
  database: config.database.name,
});
```

## Required Environment Variables

### Critical (Application will not start without these)

| Variable      | Description                                           |
| ------------- | ----------------------------------------------------- |
| `DB_HOST`     | PostgreSQL database host                              |
| `DB_USER`     | PostgreSQL database user                              |
| `DB_PASSWORD` | PostgreSQL database password                          |
| `DB_NAME`     | PostgreSQL database name                              |
| `REDIS_URL`   | Redis connection URL (e.g., `redis://localhost:6379`) |
| `JWT_SECRET`  | JWT signing secret (minimum 32 characters)            |

### Optional with Defaults

| Variable                     | Default                   | Description                     |
| ---------------------------- | ------------------------- | ------------------------------- |
| `DB_PORT`                    | 5432                      | PostgreSQL port                 |
| `PORT`                       | 3001                      | Server HTTP port                |
| `NODE_ENV`                   | development               | Environment mode                |
| `STORAGE_PATH`               | /var/www/.../flyer-images | File upload directory           |
| `SMTP_PORT`                  | 587                       | SMTP server port                |
| `SMTP_SECURE`                | false                     | Use TLS for SMTP                |
| `GEMINI_RPM`                 | 5                         | Gemini API requests per minute  |
| `AI_PRICE_QUALITY_THRESHOLD` | 0.5                       | AI extraction quality threshold |
| `WORKER_CONCURRENCY`         | 1                         | Flyer processing concurrency    |
| `WORKER_LOCK_DURATION`       | 30000                     | Worker lock duration (ms)       |

### Optional (Feature-specific)

| Variable              | Description                                 |
| --------------------- | ------------------------------------------- |
| `GEMINI_API_KEY`      | Google Gemini API key (enables AI features) |
| `GOOGLE_MAPS_API_KEY` | Google Maps API key (enables geocoding)     |
| `SMTP_HOST`           | SMTP server (enables email notifications)   |
| `SMTP_USER`           | SMTP authentication username                |
| `SMTP_PASS`           | SMTP authentication password                |
| `SMTP_FROM_EMAIL`     | Sender email address                        |
| `FRONTEND_URL`        | Frontend URL for email links                |
| `JWT_SECRET_PREVIOUS` | Previous JWT secret for rotation (ADR-029)  |

## Key Files

- `src/config/env.ts` - Configuration schema and validation
- `.env.example` - Template for required environment variables

## Migration Guide

To migrate existing `process.env` usage:

1. Import the config:

   ```typescript
   import { config, isProduction } from '../config/env';
   ```

2. Replace direct access:

   ```typescript
   // Before
   process.env.DB_HOST;
   process.env.NODE_ENV === 'production';
   parseInt(process.env.PORT || '3001', 10);

   // After
   config.database.host;
   isProduction;
   config.server.port;
   ```

3. Use service helpers for optional features:

   ```typescript
   import { isSmtpConfigured, isAiConfigured } from '../config/env';

   if (isSmtpConfigured) {
     // Email is available
   }
   ```
