# Patbond API Patbond API is a Spring Boot multi-module backend. ## Modules - `patbond-common`: stable cross-module contracts (response wrapper, shared DTOs). No web/messaging dependencies. - `patbond-user`: user service for user creation, profile lookup, and password verification. - `patbond-auth`: authentication service for registration and login. Calls `patbond-user` over HTTP via OpenFeign with a configured static URL (no service discovery in the MVP, see ADR-002). - `patbond-pet`: pet profile and health record service (M2, ADR-009). Shares the database with `patbond-user` and only reads/writes the `pet_health` schema; the Flyway migration chain stays owned by `patbond-user`. First-wave skeleton: liveness endpoint only. ## Technology Stack - Java 17 (build baseline; use JDK 17 for release builds) - Spring Boot 3.5.16 - Spring Cloud 2025.0.3 (OpenFeign only) - PostgreSQL 18 + Flyway (patbond-user owns the migration chain: `identity`/`media`/`platform`/`pet_health` schemas) - Maven (use the committed Maven Wrapper `./mvnw`) ## Build and Test ```bash ./mvnw clean test ``` Integration tests start a disposable `postgres:18` via Testcontainers, so a running Docker daemon is required (no local PostgreSQL installation or credentials are needed). If your default JDK is not 17, point `JAVA_HOME` at a JDK 17 installation first, e.g.: ```bash JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test ``` ## Run Running `patbond-user` requires a reachable PostgreSQL 18 database; Flyway applies the versioned migrations in `patbond-user/src/main/resources/db/migration` automatically on startup. Development seed data (`db/dev`, regions reference rows) is opt-in via a dev profile — see `application.yml.sample`. Each service ships a committed `application.yml.sample`; the real `application.yml` is git-ignored. First copy the samples (defaults work locally, overrides via environment variables): ```bash cp patbond-user/src/main/resources/application.yml.sample \ patbond-user/src/main/resources/application.yml cp patbond-auth/src/main/resources/application.yml.sample \ patbond-auth/src/main/resources/application.yml cp patbond-pet/src/main/resources/application.yml.sample \ patbond-pet/src/main/resources/application.yml ``` Install the shared module once, then run each service in its own terminal: ```bash ./mvnw -pl patbond-common install ./mvnw -pl patbond-user spring-boot:run ./mvnw -pl patbond-auth spring-boot:run ``` Running the services also needs an RS256 key pair for access tokens (the private key signs in `patbond-auth`, the public key verifies in `patbond-user`; neither is committed): ```bash openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt-private.pem openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem export PATBOND_JWT_PRIVATE_KEY=$PWD/jwt-private.pem # patbond-auth export PATBOND_JWT_PUBLIC_KEY=$PWD/jwt-public.pem # patbond-user ``` Tests do not require this copy step — `patbond-auth/src/test/resources/application.yml` keeps `./mvnw clean test` self-contained on a clean checkout. Smoke check: ```bash curl -X POST http://127.0.0.1:8081/api/v1/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"demo_user","password":"secret123"}' ``` ### Configuration | Environment variable | Default | Used by | | --- | --- | --- | | `PATBOND_USER_PORT` | `8082` | patbond-user | | `PATBOND_AUTH_PORT` | `8081` | patbond-auth | | `PATBOND_USER_SERVICE_URL` | `http://127.0.0.1:8082` | patbond-auth (Feign target for patbond-user) | | `PATBOND_DB_URL` | `jdbc:postgresql://127.0.0.1:5432/patbond` | patbond-user | | `PATBOND_DB_USER` | `patbond` | patbond-user | | `PATBOND_DB_PASSWORD` | `patbond` | patbond-user | | `PATBOND_INTERNAL_TOKEN` | `dev-only-internal-token` | both (shared secret for `/internal/**`; inject a strong random value outside local dev) | | `PATBOND_JWT_PRIVATE_KEY` | _(none, required)_ | patbond-auth (RS256 private key: PEM path or inline PEM) | | `PATBOND_JWT_PUBLIC_KEY` | _(none)_ | patbond-user (RS256 public key: PEM path or inline PEM) | | `PATBOND_ACCESS_TTL` | `15m` | patbond-auth (access token lifetime, ADR-003) | | `PATBOND_REFRESH_TTL` | `30d` | patbond-user (refresh token lifetime, ADR-003) | | `PATBOND_LOGIN_LOCK_MAX_FAILURES` | `5` | patbond-user (login failures before lockout) | | `PATBOND_LOGIN_LOCK_WINDOW` | `15m` | patbond-user (failure counting window) | | `PATBOND_LOGIN_LOCK_DURATION` | `15m` | patbond-user (lock duration) | Machine-specific values live in the git-ignored `application.yml` (copied from the committed `.sample`); never commit secrets to the samples. ## Docker Compose MVP 编排(ADR-007):`postgres:18`(数据落 volume)+ 两个无状态应用容器。 配置与本机运行同一套约定 —— 容器内挂载 `application.yml.sample` 作为配置, `PATBOND_*` 环境变量注入实际值。 ```bash # 1. 生成 RS256 密钥对与 .env(DB 口令、内部令牌;产物被 .gitignore 忽略) ./deploy/init-secrets.sh # 2. 构建可执行 jar JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -DskipTests package # 3. 启动(首次会构建镜像) docker compose up -d --build # 冒烟 curl -s -X POST http://127.0.0.1:8081/api/v1/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"demo_user","password":"secret123"}' # 停止(-v 同时删除数据库数据) docker compose down ``` 注意:数据库端口不对宿主机发布;`8082`(user)在 MVP 阶段直连暴露以提供 `/api/v1/me`,`/internal/**` 由服务间令牌保护,规模化阶段应改由网关统一入口。 ## Services ### Auth Service - Application name: `patbond-auth` - Default port: `8081` - Register: `POST /api/v1/auth/register` - Login: `POST /api/v1/auth/login` - Refresh (rotates the refresh token): `POST /api/v1/auth/refresh` - Logout (revokes the current session): `POST /api/v1/auth/logout` ### User Service - Application name: `patbond-user` - Default port: `8082` - Current user profile: `GET /api/v1/me` (Bearer access token, verified locally with the RS256 public key) - Internal (require `X-Internal-Token`): - Create user: `POST /internal/users` - Verify password: `POST /internal/users/verify-password` - Get user by id: `GET /internal/users/{id}` - Get user by username: `GET /internal/users/by-username/{username}` - Sessions: `POST /internal/sessions`, `POST /internal/sessions/refresh`, `POST /internal/sessions/revoke` > Note: user data is persisted in PostgreSQL (`identity.users` / > `identity.user_credentials`, bcrypt password hashes, UUIDv7 ids generated in > the application). Refresh sessions live in `identity.auth_sessions` (SHA-256 > digests only, rotation on every refresh, token-family revocation on reuse, > ADR-003). Errors follow the `{code, message, data}` envelope with stable > business codes and matching HTTP statuses; the public contract is documented > in `patbond-doc/docs/api/openapi.yaml`.