Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ repos:
# Catches the same class of issue as ruff's W292
# ("no newline at end of file"), but for every text file in
# the repo -- not only Python.
exclude: ^services/(py-genai-helper/generated|spring-.*/src/generated)/
- id: trailing-whitespace
# Preserve intentional two-space line breaks in markdown.
args: [--markdown-linebreak-ext=md]
exclude: ^services/(py-genai-helper/generated|spring-.*/src/generated)/
- id: check-yaml
# --allow-multiple-documents is defensive for the planned
# Helm charts under infra/; current YAML files are all
Expand All @@ -54,9 +56,11 @@ repos:
name: ruff (lint + autofix)
args: [--fix]
files: ^services/py-genai-helper/.*\.py$
exclude: ^services/py-genai-helper/generated/
- id: ruff-format
name: ruff-format
files: ^services/py-genai-helper/.*\.py$
exclude: ^services/py-genai-helper/generated/

# ------------------------------------------------------------------
# Local hooks -- delegate to the project's own toolchain (pnpm,
Expand Down Expand Up @@ -95,3 +99,11 @@ repos:
files: ^services/spring-.*/.*\.java$
pass_filenames: false
stages: [pre-push]

- id: openapi-codegen
name: openapi codegen (all services)
language: system
entry: scripts/hooks/run-codegen.sh
files: ^api/openapi\.yaml$
pass_filenames: false
stages: [pre-commit]
1 change: 1 addition & 0 deletions .spectral.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends: ["spectral:oas"]
82 changes: 58 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,66 @@ Club organizers get an all-in-one tool for managing members, automating billing,
```
repo/
├── api/ # Single source of truth for API contracts
│ ├── openapi.yaml # Versioned OpenAPI spec
│ └── scripts/ # Code-gen helper scripts
│ ├── openapi.yaml # Versioned OpenAPI spec (OpenAPI 3.0.3)
│ └── scripts/ # Code-gen scripts (gen-all.sh, gen-spring.sh, …)
├── docs/ # Project documentation
├── services/
│ ├── spring-*/ # Java 21, Spring Boot 3 microservices
│ └── py-*/ # Python 3.12, Flask + LangChain GenAI service
├── web-client/ # React single-page application
│ │ └── src/generated/ # ⚠ Generated — do not edit by hand
│ └── py-genai-helper/ # Python 3.12, Flask + LangChain GenAI service
│ └── generated/ # ⚠ Generated — do not edit by hand
├── web-client/ # React SPA (Vite, TypeScript)
│ └── src/api.ts # ⚠ Generated — do not edit by hand
├── infra/ # docker-compose, Traefik config, Helm/Terraform
└── .github/workflows/ # CI/CD pipelines
```

## Architecture

All services sit behind a **Traefik** reverse proxy that handles routing and authentication (OAuth2). The Spring Boot services and the GenAI service share **PostgreSQL** databases. The web client communicates with the backend services via REST APIs defined in the OpenAPI spec. The GenAI service can call external LLM APIs (like OpenAI) or use local models to generate personalized feedback based on member data and trainer notes.

| Service | Port | Stack |
All services run in Docker and are exposed through a single **Traefik** reverse
proxy on port **80**. Traefik routes requests by path prefix and strips the full
prefix before forwarding, so each service receives only the resource path (e.g.
`GET /api/v1/organization/sports` → organization-service receives `GET /sports`).
The Spring Boot services and the GenAI service share a **PostgreSQL** database.

| Service | External route | Internal port | Stack |
|---|---|---|---|
| Organization Service | `/api/v1/organization/…` | 8080 | Java 21, Spring Boot 3 |
| Member Service | `/api/v1/members/…` | 8080 | Java 21, Spring Boot 3 |
| Event Service | `/api/v1/events/…` | 8080 | Java 21, Spring Boot 3 |
| Feedback Service | `/api/v1/feedback/…` | 8080 | Java 21, Spring Boot 3 |
| Finance Service | `/api/v1/finance/…` | 8080 | Java 21, Spring Boot 3 |
| Letter Service | `/api/v1/letters/…` | 8080 | Java 21, Spring Boot 3 |
| GenAI Service | `/api/v1/helper/…` | 5000 | Python 3.12, Flask, LangChain |
| Web Client | `/` | 8080 | React, Vite |
| Swagger UI | `/docs` | 8080 | swaggerapi/swagger-ui |
| Traefik dashboard | `http://localhost:8080` | — | Traefik v3 |
| PostgreSQL | internal only | 5432 | postgres:15 |

## Code Generation

`api/openapi.yaml` is the single source of truth. Three generators derive code
from it that **must never be edited by hand**:

| Generator | Tool | Output |
|---|---|---|
| Organization Service | 8001 | Java 21, Spring Boot 3 |
| Member Service | 8002 | Java 21, Spring Boot 3 |
| Event Service | 8003 | Java 21, Spring Boot 3 |
| Feedback Service | 8004 | Java 21, Spring Boot 3 |
| Finance Service | 8005 | Java 21, Spring Boot 3 |
| Letter Service | 8006 | Java 21, Spring Boot 3 |
| GenAI Service | 5000 | Python 3.12, Flask, LangChain |
| Web Client | 3000 | React, Vite |
| PostgreSQL | 5432 | — |
| Traefik | 80/443 | — |
| Spring Boot API interfaces + models | `openapitools/openapi-generator-cli:v7.14.0` (Docker) | `services/spring-*/src/generated/java/` |
| Pydantic v2 models | `datamodel-code-generator` (pip) | `services/py-genai-helper/generated/models.py` |
| TypeScript types | `openapi-typescript` (pnpm devDep) | `web-client/src/api.ts` |

Run all generators at once:

```bash
./api/scripts/gen-all.sh
```

The `openapi-codegen` pre-commit hook runs this automatically whenever
`api/openapi.yaml` is staged. If any generated file changes, the hook re-stages
the output and aborts so you can review the diff before re-committing.

Prerequisites: **Docker** (Spring generator), **`datamodel-code-generator`**
(`pip install datamodel-code-generator`), **`pnpm`** (already a devDependency
in `web-client/`).

## Developer Setup

Expand All @@ -54,21 +87,22 @@ checks locally that CI gates on (ruff, eslint, end-of-file fixer, pnpm lockfile
sync, etc.). One-time setup per developer:

```bash
pip install pre-commit # or: pipx install pre-commit
pre-commit install # installs the pre-commit git hook
pre-commit install --hook-type pre-push # installs the pre-push hook
pre-commit run --all-files # optional one-time clean-up pass
pip install pre-commit datamodel-code-generator # or: pipx install pre-commit
pre-commit install # installs the pre-commit git hook
pre-commit install --hook-type pre-push # installs the pre-push hook
pre-commit run --all-files # optional one-time clean-up pass
```

What runs when:

| Stage | Hooks |
|---|---|
| `pre-commit` (every commit) | end-of-file-fixer, trailing-whitespace, check-yaml/json, merge-conflict guard, large-file guard, **ruff** (lint + format, py-genai-helper), **eslint --fix** (web-client), **pnpm-lock-sync** (regenerates `web-client/pnpm-lock.yaml` when `package.json` changes) |
| `pre-commit` (every commit) | end-of-file-fixer, trailing-whitespace, check-yaml/json, merge-conflict guard, large-file guard, **ruff** (lint + format, py-genai-helper), **eslint --fix** (web-client), **pnpm-lock-sync** (regenerates `web-client/pnpm-lock.yaml` when `package.json` changes), **openapi-codegen** (regenerates all generated sources when `api/openapi.yaml` changes) |
| `pre-push` (only on push) | **Spectral** lint of `api/openapi.yaml` (if changed), **Checkstyle** for all Spring services (if Java sources changed) |

Auto-fixing hooks (ruff, eslint, npm-lock-sync, end-of-file-fixer, etc.) will
modify files and **abort the commit** so you can re-stage and re-commit.
Auto-fixing hooks (ruff, eslint, pnpm-lock-sync, openapi-codegen,
end-of-file-fixer, etc.) will modify files and **abort the commit** so you can
re-stage and re-commit.

Bypass (emergencies only -- CI will still gate):

Expand Down
Loading
Loading