# Development

## Prerequisites

- **JDK 21** (Temurin recommended)
- **MongoDB** reachable at `MONGODB_HOST:MONGODB_PORT`
- Maven is not required — use the bundled wrapper (`./mvnw`)

## Setup

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

`http://localhost:8080/swagger-ui.html` gives you an interactive client; use the
**Authorize** button to supply a JWT.

## Common commands

```bash
./mvnw clean verify                   # unit tests + coverage gate — run before pushing
./mvnw test                           # unit tests only
./mvnw clean package                  # build the jar
./mvnw spring-boot:run                # run locally
./mvnw test -Dtest=JwtServiceTest     # a single test class
```

Helper scripts in [`scripts/`](../scripts):

| Script | Purpose |
| --- | --- |
| `run-parser.sh` | Preflight checks then start the application |
| `smoke-mongodb.sh` | Verify MongoDB reachability and print a connection string |
| `smoke-openai-api.sh` | Verify `OPENAI_API_KEY` works against the OpenAI API |
| `smoke-research-api.sh` | Exercise the report endpoints against a running instance |

## Testing

Two tiers, separated by an environment variable:

**Unit tests** run by default. They mock collaborators and never touch the network.
This is what CI and `./mvnw verify` execute.

**Integration tests** boot the full Spring context and need real MongoDB plus valid
credentials. They are annotated:

```java
@SpringBootTest
@EnabledIfEnvironmentVariable(named = "RUN_INTEGRATION_TESTS", matches = "true")
```

so they are skipped unless you opt in:

```bash
RUN_INTEGRATION_TESTS=true ./mvnw verify
```

Surefire additionally excludes the `integration` JUnit tag (see `pom.xml`).

### Coverage gate

`verify` fails if line coverage drops below **80%** on any of:

- `PostingTaskService`
- `JwtService`
- `SocialMediaCredentialsService`

Report: `target/site/jacoco/index.html`. To extend the gate to another class, add it to
the JaCoCo `qg01-coverage-check` rule in `pom.xml`.

### Test credentials

Tests must never contain real credentials. Use placeholders that resolve from the
environment:

```java
@TestPropertySource(properties = {
        "spring.data.mongodb.host=${MONGODB_HOST:localhost}",
        "spring.data.mongodb.username=${MONGODB_USERNAME:}"
})
```

## Conventions

- **Java 21**, Lombok for boilerplate (excluded from the packaged jar).
- Constructor injection, not field injection — see `ParserManagerService`.
- Controllers stay thin: validate, delegate to a service, return a DTO. No business
  logic and no repository access from a controller.
- Never return a `model` document directly from a controller — map it to a `dto`.
- Throw domain exceptions from `exception/` and let `GlobalExceptionHandler` render
  them; do not build error responses by hand.
- Long-running AI calls belong on an async executor (`AsyncConfig`), not the request
  thread.
- Existing code mixes Russian and English comments. New comments should be in English.

## Adding an RSS source

`ParserManagerService` discovers parsers through Spring, so no registry needs editing:

1. Add `rss.<source>.url` to `application.properties`.
2. Implement `ParserService` — return a unique `getSourceName()` and implement
   `parseAndSaveRssFeed()`. Model it on `KursivParserService`.
3. Annotate the scheduled entry point with `@Scheduled`, matching the cadence of
   comparable sources (5 min for high-volume, 30 min otherwise).
4. Add a unit test alongside `KursivParserServiceTest`.
5. Add a log level line in `application.properties`.
6. Document the source in [rss-parsers.md](rss-parsers.md).

## Adding an endpoint

1. Define request/response DTOs in `dto/` with Bean Validation annotations.
2. Add the handler to the relevant controller; extract the caller with
   `jwtService.extractUserIdFromHeader(authHeader)`.
3. Put the logic in a service; add a repository method if persistence is needed.
4. Add unit tests for the service.
5. Document it under [`docs/api/`](api/README.md) and update that index.

## Adding configuration

See [configuration.md](configuration.md#adding-a-new-configuration-value). In short:
environment placeholder, no default for secrets, and update `.env.example`.

## Before you push

```bash
./mvnw clean verify
git status                            # nothing unexpected staged
```

Pushing to `main` deploys to production — [deployment.md](deployment.md).
