# Configuration

All configuration lives in `src/main/resources/application.properties`. Values fall
into two groups:

- **Non-secret defaults** — model names, timeouts, retry policy, RSS URLs, log levels.
  These are literal values in the properties file and are safe to read in a review.
- **Secrets and environment-specific endpoints** — resolved from environment variables
  via `${VAR}` placeholders.

[`.env.example`](../.env.example) is the authoritative list of every variable, marked
`REQUIRED` or `OPTIONAL`.

## Secret handling

**No credential is ever committed.** Required secrets are declared *without* a
fallback:

```properties
openai.api.key=${OPENAI_API_KEY}
```

If the variable is absent, Spring throws
`IllegalArgumentException: Could not resolve placeholder 'OPENAI_API_KEY'` and the
application refuses to start.

This is intentional. The alternative — an empty default — produces a service that
boots happily and then fails every downstream call with an opaque `401`, usually in
production, usually at 3am. A startup failure names the missing variable immediately.

Optional values keep a default after the colon:

```properties
minio.bucket-name=${MINIO_BUCKET_NAME:konturai}
```

### Adding a new configuration value

1. If it is a credential, endpoint, or anything that differs per environment, use a
   `${VAR}` placeholder — no default for required secrets.
2. Add it to `.env.example` with a `REQUIRED`/`OPTIONAL` marker and a one-line comment.
3. Add it to `.env.production` on the server before merging.

Never write a real value into `application.properties`, a test's
`@TestPropertySource`, a shell script, or documentation.

## The Google service-account key

`NanoBananaImageGenerationService` and `GeminiVideoGenerationService` authenticate to
Vertex AI by loading `keys/google-key.json` from the classpath:

```java
ClassPathResource resource = new ClassPathResource("keys/google-key.json");
```

The file must exist at `src/main/resources/keys/google-key.json` at **build** time, so
it is baked into the jar. It is gitignored and must be supplied out of band:

```bash
cp /secure/path/google-key.json src/main/resources/keys/google-key.json
```

On the deployment host the file must be present in the checkout before the Docker
image is built, since the `Dockerfile` uses `COPY . .`.

If the file is missing the application still starts — image and video generation log
an error and return `null`, while every other feature keeps working.

## Local development

```bash
cp .env.example .env
$EDITOR .env
set -a && source .env && set +a
./mvnw spring-boot:run
```

For a fully local setup you need MongoDB on `localhost:27017`. Point `MINIO_ENDPOINT`
at a local MinIO container, or leave the storage-backed endpoints unused.

An alternative to `.env` is `src/main/resources/application-local.properties` (also
gitignored), activated with `--spring.profiles.active=local`.

## Configuration reference by area

| Area | Prefix | Notes |
| --- | --- | --- |
| MongoDB | `spring.data.mongodb.*` | 30s connect/socket/server-selection timeouts |
| Object storage | `minio.*` | Stores generated reports and images |
| Security | `security.jwt.*`, `encryption.*` | JWT secret must match the issuing auth service |
| OpenAI | `openai.*` | `gpt-4o` for text, `gpt-4o-mini` for cheaper calls; retry with exponential backoff, max 3 concurrent requests |
| Ollama | `ollama.*` | Self-hosted; very long timeouts (5h) for large generations |
| Vertex AI | `google.*` | Imagen 3 for images, Veo 3 for video; 20s rate-limit spacing |
| Serper | `serper.*` | Google search results for research reports |
| Facebook | `facebook.*` | Posting, plus the hot-leads collector and call-centre sync |
| RSS | `rss.*` | Five sources — see [rss-parsers.md](rss-parsers.md) |
| Schedulers | `parser.scheduler.enabled`, `posting.scheduler.enabled` | Parser scheduler is **off** by default |
| CORS | `cors.*` | Bound by `CorsProperties`, applied in `WebCorsConfig` |

## Rotating a credential

1. Issue the new credential with the provider.
2. Update `.env.production` on the deployment host.
3. Restart the container (`docker compose up -d` — see [deployment.md](deployment.md)).
4. Revoke the old credential.

Because secrets are never baked into the image, rotation requires no rebuild.
