https://github.com/budget-buddy-org/budget-buddy-api
API for Budget Buddy App
https://github.com/budget-buddy-org/budget-buddy-api
java openapi-specification rest-api semantic-release spring-boot
Last synced: about 2 months ago
JSON representation
API for Budget Buddy App
- Host: GitHub
- URL: https://github.com/budget-buddy-org/budget-buddy-api
- Owner: budget-buddy-org
- License: apache-2.0
- Created: 2026-03-01T18:57:58.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-05-03T19:24:16.000Z (about 2 months ago)
- Last Synced: 2026-05-03T19:27:51.923Z (about 2 months ago)
- Topics: java, openapi-specification, rest-api, semantic-release, spring-boot
- Language: Java
- Homepage:
- Size: 544 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Budget Buddy API
[](https://github.com/budget-buddy-org/budget-buddy-api/actions/workflows/ci.yaml)
[](https://github.com/budget-buddy-org/budget-buddy-api/releases)
REST API for personal budget management. Built with Spring Boot 4 and PostgreSQL.
## Tech Stack
- **Java 25** / Spring Boot 4.0.5
- **Spring Data JDBC** + PostgreSQL
- **Spring Security** — stateless OIDC resource server with JWT validation
- **Liquibase** — database migrations
- **Budget Buddy Contracts** — external OpenAPI-based contracts
- **Testcontainers** — integration tests
- **Docker** — containerized deployment
## Prerequisites
- Java 25
- Docker & Docker Compose
- GitHub Packages authentication (see [Building](#building))
## Running Locally
The project uses Spring Docker Compose — PostgreSQL starts automatically when you run the app.
```bash
# Clone the repository
git clone https://github.com/budget-buddy-org/budget-buddy-api.git
cd budget-buddy-api
# Set OIDC configuration (required for JWT validation)
export OIDC_ISSUER_URI=https://
export OIDC_AUDIENCES=https://
# Run with dev profile
./gradlew bootRun --args='--spring.profiles.active=dev'
```
The API will be available at `http://localhost:8080`.
## Running with Docker Compose
```bash
# Copy and fill in environment variables
cp .env.example .env
# Start all services
docker compose up -d
# View logs
docker compose logs -f app
```
### Environment Variables
| Variable | Description |
|--------------------------|------------------------------------------------------------------------|
| `BUDGET_BUDDY_API_IMAGE` | Docker image (e.g. `ghcr.io/username/budget-buddy-api:latest`) |
| `SPRING_PROFILES_ACTIVE` | Spring profile (`prod`) |
| `DB_NAME` | PostgreSQL database name |
| `DB_USER` | PostgreSQL username |
| `DB_PASSWORD` | PostgreSQL password |
| `OIDC_ISSUER_URI` | OIDC issuer URI for JWT validation (e.g. `https://`) |
| `OIDC_AUDIENCES` | Expected JWT audience(s) (e.g. `https://`) |
## API
### Authentication
Authentication is handled by an external OIDC provider. The API is a stateless resource server that validates JWTs using the JWKS endpoint discovered from `OIDC_ISSUER_URI`.
- **Issuer & audience validation** — JWTs are verified against the configured issuer and must contain an expected audience (`OIDC_AUDIENCES`).
- **Multi-issuer support** — The same `sub` claim from different issuers is treated as separate users (composite unique constraint on `oidc_subject + oidc_issuer`).
- **JIT provisioning** — On first authenticated request, a local user is automatically created by mapping the JWT `sub` and `iss` claims to a local user record.
### Categories
| Method | Endpoint | Description |
|----------|-------------------------------|----------------------------------|
| `GET` | `/v1/categories` | List categories |
| `POST` | `/v1/categories` | Create category |
| `GET` | `/v1/categories/{categoryId}` | Get category |
| `PUT` | `/v1/categories/{categoryId}` | Replace category (full update) |
| `PATCH` | `/v1/categories/{categoryId}` | Update category (partial update) |
| `DELETE` | `/v1/categories/{categoryId}` | Delete category |
### Transactions
| Method | Endpoint | Description |
|----------|------------------------------------|-------------------------------------|
| `GET` | `/v1/transactions` | List transactions (with filters) |
| `POST` | `/v1/transactions` | Create transaction |
| `GET` | `/v1/transactions/{transactionId}` | Get transaction |
| `PUT` | `/v1/transactions/{transactionId}` | Replace transaction (full update) |
| `PATCH` | `/v1/transactions/{transactionId}` | Update transaction (partial update) |
| `DELETE` | `/v1/transactions/{transactionId}` | Delete transaction |
All endpoints require `Authorization: Bearer ` (JWT issued by the OIDC provider).
## Building
To build the project, you need to provide GitHub credentials to access the `budget-buddy-contracts` package. You can set `GITHUB_ACTOR` and `GITHUB_TOKEN` environment variables, or provide them via `gradle.properties`:
```bash
# Build and run tests
./gradlew build -Pgpr.user=YOUR_USERNAME -Pgpr.key=YOUR_TOKEN
```
### Build Docker image
```bash
./gradlew bootBuildImage --imageName=ghcr.io/your-username/budget-buddy-api:latest
```
## Tests
```bash
./gradlew test # Run all tests (unit + integration)
./gradlew integrationTest # Run integration tests (requires Docker)
./gradlew check # Run all tests + quality checks
```
## Health Check
```
GET /actuator/health
```
### PATCH Behavior
The API supports JSON Merge Patch semantics for fields that can be cleared:
- **Provided value**: Updates the field to the new value.
- **Explicit `null`**: Sets the field to `null` in the database.
- **Omitted field**: Leaves the existing value unchanged.
This is implemented using MapStruct with `JsonNullable` support in the `BaseEntityMapper`.
### Error Responses (RFC 9457)
The API uses standardized Problem Details for HTTP APIs (RFC 9457) for all error responses.
When `type` is `about:blank`, the `title` field corresponds to the standard HTTP status phrase.
For validation errors (`400 Bad Request`), the response is extended with an `errors` array containing field-level details:
```json
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "One or more fields are invalid",
"instance": "/v1/transactions",
"errors": [
{ "field": "amount", "message": "must be greater than 0" }
]
}
```