chore: remove sample portfolio module
This commit is contained in:
@@ -178,13 +178,6 @@ gates:
|
|||||||
workflow: object-storage-qualification.yml
|
workflow: object-storage-qualification.yml
|
||||||
job: minio-managed-contract
|
job: minio-managed-contract
|
||||||
execution: explicit
|
execution: explicit
|
||||||
- id: poster-image-migration
|
|
||||||
release_blocking: true
|
|
||||||
mechanism: gradle-custom-task
|
|
||||||
ref: posterImageMigrationTest
|
|
||||||
workflow: object-storage-qualification.yml
|
|
||||||
job: poster-image-v7-migration
|
|
||||||
execution: explicit
|
|
||||||
- id: object-storage-minio-managed-fault
|
- id: object-storage-minio-managed-fault
|
||||||
release_blocking: conditional
|
release_blocking: conditional
|
||||||
mechanism: gradle-custom-task
|
mechanism: gradle-custom-task
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ fi
|
|||||||
readonly REPO_ROOT
|
readonly REPO_ROOT
|
||||||
readonly MATRIX="${REPO_ROOT}/.github/ci-gate-matrix.yml"
|
readonly MATRIX="${REPO_ROOT}/.github/ci-gate-matrix.yml"
|
||||||
# Deliberately a literal: a gate silently appearing or disappearing is the drift this lint exists to
|
# Deliberately a literal: a gate silently appearing or disappearing is the drift this lint exists to
|
||||||
# catch, so growing the matrix is an explicit edit here. 38 as of the HTTP Client platform hardening,
|
# catch, so growing the matrix is an explicit edit here. 37 after removing the sample-only Poster
|
||||||
|
# migration gate; the HTTP Client platform hardening
|
||||||
# which registered httpclient-spring62-runtime as a delegated-pending control — the 6.2 *runtime*
|
# which registered httpclient-spring62-runtime as a delegated-pending control — the 6.2 *runtime*
|
||||||
# claim, distinct from the API-surface scan that was standing in for it.
|
# claim, distinct from the API-surface scan that was standing in for it.
|
||||||
readonly EXPECTED_GATE_COUNT=38
|
readonly EXPECTED_GATE_COUNT=37
|
||||||
|
|
||||||
if [[ ! -f "${MATRIX}" ]]; then
|
if [[ ! -f "${MATRIX}" ]]; then
|
||||||
printf '::error::gate-matrix-lint: missing %s\n' "${MATRIX}" >&2
|
printf '::error::gate-matrix-lint: missing %s\n' "${MATRIX}" >&2
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ readonly EXPECTED_WORKFLOW_LOCK=(
|
|||||||
'ad84000efc438ee7439517b8f85819e62b13dab0aa4f94066c2905060f3bb581 .github/workflows/httpclient-release.yml'
|
'ad84000efc438ee7439517b8f85819e62b13dab0aa4f94066c2905060f3bb581 .github/workflows/httpclient-release.yml'
|
||||||
'59cb3a0ffc687a15eefe96bc5e3a70d42be78e1cc85d2e7f7880dac6124ca4c7 .github/workflows/jpa-r2-evidence.yml'
|
'59cb3a0ffc687a15eefe96bc5e3a70d42be78e1cc85d2e7f7880dac6124ca4c7 .github/workflows/jpa-r2-evidence.yml'
|
||||||
'5be7e931db749029d89787da042d6d7cf8e683d60698bd8a2993c29db26355fb .github/workflows/link-check.yml'
|
'5be7e931db749029d89787da042d6d7cf8e683d60698bd8a2993c29db26355fb .github/workflows/link-check.yml'
|
||||||
'64245586cd5936f1a5647b57f2cd9acd316f96fd75f713b1890decb812e7d5fe .github/workflows/object-storage-qualification.yml'
|
'2f1c46cc8b7b39890e5debe90f78d19b9347ed53f499b282539dae88aefdd574 .github/workflows/object-storage-qualification.yml'
|
||||||
'cbc104ea486c746229895e804e3be7716e056a02cce0588c537bce9f442f8b38 .github/workflows/redis-sdk-topology.yml'
|
'cbc104ea486c746229895e804e3be7716e056a02cce0588c537bce9f442f8b38 .github/workflows/redis-sdk-topology.yml'
|
||||||
)
|
)
|
||||||
readonly EXPECTED_WRAPPER_PROPERTIES=(
|
readonly EXPECTED_WRAPPER_PROPERTIES=(
|
||||||
|
|||||||
@@ -19,26 +19,6 @@ env:
|
|||||||
TESTCONTAINERS_REUSE_ENABLE: "false"
|
TESTCONTAINERS_REUSE_ENABLE: "false"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
poster-image-v7-migration:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
|
|
||||||
- name: Validate Gradle wrapper
|
|
||||||
id: gradle-wrapper-validation
|
|
||||||
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
|
|
||||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
|
|
||||||
with:
|
|
||||||
distribution: temurin
|
|
||||||
java-version: "21.0.11+10"
|
|
||||||
cache: gradle
|
|
||||||
cache-dependency-path: |
|
|
||||||
src/**/*.gradle
|
|
||||||
src/**/gradle-wrapper.properties
|
|
||||||
src/**/gradle.lockfile
|
|
||||||
- name: Run non-skipping Poster image migration qualification
|
|
||||||
working-directory: src
|
|
||||||
run: ./gradlew :sample-portfolio:posterImageMigrationTest --no-daemon --stacktrace
|
|
||||||
|
|
||||||
minio-managed-contract:
|
minio-managed-contract:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
이 저장소는 단순한 예제 블로그 애플리케이션이 아니라, Java 21 + Spring Boot 4.0.0 + Gradle 멀티모듈 기반의 Clean Architecture 템플릿이다.
|
이 저장소는 단순한 예제 블로그 애플리케이션이 아니라, Java 21 + Spring Boot 4.0.0 + Gradle 멀티모듈 기반의 Clean Architecture 템플릿이다.
|
||||||
|
|
||||||
기본 패키지는 `dev.caskeleton`이며, 예시 도메인은 production 모듈이 아니라 `sample-portfolio` 모듈(WorkLog 엔지니어링 작업 기록 게시판)에 격리한다. 새 프로젝트를 시작할 때는 도메인 이름, 패키지, 엔티티, 유스케이스를 교체할 수 있지만, 모듈 경계와 의존성 방향은 유지해야 한다.
|
기본 패키지는 `dev.caskeleton`이며 구체적인 예시 도메인은 제거되어 있다. 새 프로젝트를 시작할 때는 production 모듈에 도메인, 엔티티, 유스케이스를 추가할 수 있지만, 모듈 경계와 의존성 방향은 유지해야 한다.
|
||||||
|
|
||||||
## Prime Directive
|
## Prime Directive
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ root `CLAUDE.md`는 이 목록의 동기화된 요약이다. 두 문서가 어
|
|||||||
|
|
||||||
## Gradle 정책 권위
|
## Gradle 정책 권위
|
||||||
|
|
||||||
- `src/config/architecture/modules.json`: 정확히 19개 leaf의 ID, repository-relative 소스 경로,
|
- `src/config/architecture/modules.json`: 정확히 18개 leaf의 ID, repository-relative 소스 경로,
|
||||||
Gradle path, 허용 production project dependency edge, 두 composition root의 실제 runtime
|
Gradle path, 허용 production project dependency edge, production composition root의 실제 runtime
|
||||||
membership
|
membership
|
||||||
- `src/settings.gradle`: registry를 fail-closed로 검증하고 등록된 Gradle project를 include/mapping
|
- `src/settings.gradle`: registry를 fail-closed로 검증하고 등록된 Gradle project를 include/mapping
|
||||||
- `src/build.gradle`: 같은 registry를 읽는 `verifyCleanArchitectureDependencies`와 그 밖의
|
- `src/build.gradle`: 같은 registry를 읽는 `verifyCleanArchitectureDependencies`와 그 밖의
|
||||||
@@ -102,7 +102,7 @@ commit 정책은 모든 플랫폼에서 `human-only`이며 agent는 stage/commit
|
|||||||
|
|
||||||
## 모듈 책임
|
## 모듈 책임
|
||||||
|
|
||||||
19개 leaf 모듈의 ID, 실제 소스 경로, Gradle path, 허용 production 의존성, runtime membership은
|
18개 leaf 모듈의 ID, 실제 소스 경로, Gradle path, 허용 production 의존성, runtime membership은
|
||||||
`src/config/architecture/modules.json`이 SSOT다. focused test는 소유 leaf의 `gradle_path`에서
|
`src/config/architecture/modules.json`이 SSOT다. focused test는 소유 leaf의 `gradle_path`에서
|
||||||
파생한다. 이 문서는 leaf 목록을 복제하지 않고 family 책임만 정의한다. 작업 파일에서는 가장 가까운
|
파생한다. 이 문서는 leaf 목록을 복제하지 않고 family 책임만 정의한다. 작업 파일에서는 가장 가까운
|
||||||
`src/**/CLAUDE.md`를 함께 읽는다.
|
`src/**/CLAUDE.md`를 함께 읽는다.
|
||||||
@@ -118,7 +118,6 @@ commit 정책은 모든 플랫폼에서 `human-only`이며 agent는 stage/commit
|
|||||||
file server, HTTP client, identifier 능력을 port 뒤에서 구현한다. adapter 간 허용 edge는
|
file server, HTTP client, identifier 능력을 port 뒤에서 구현한다. adapter 간 허용 edge는
|
||||||
registry만 따른다.
|
registry만 따른다.
|
||||||
- `shared-contract`: skeleton-wide 운영 계약. business/domain 개념 저장 금지.
|
- `shared-contract`: skeleton-wide 운영 계약. business/domain 개념 저장 금지.
|
||||||
- `sample-portfolio`: 샘플/fixture consumer. production leaf가 의존하면 안 된다.
|
|
||||||
- `app-bootstrap`: Spring Boot entrypoint와 composition root. 비즈니스 유스케이스 금지.
|
- `app-bootstrap`: Spring Boot entrypoint와 composition root. 비즈니스 유스케이스 금지.
|
||||||
|
|
||||||
## 의존성 방향
|
## 의존성 방향
|
||||||
@@ -129,7 +128,6 @@ family 수준 기본 방향:
|
|||||||
app-bootstrap -> adapter:inbound:* -> application-core -> domain-core
|
app-bootstrap -> adapter:inbound:* -> application-core -> domain-core
|
||||||
app-bootstrap -> adapter:outbound:* -> application-core -> domain-core
|
app-bootstrap -> adapter:outbound:* -> application-core -> domain-core
|
||||||
runtime modules -> shared-contract
|
runtime modules -> shared-contract
|
||||||
sample-portfolio -> registered runtime leaves (fixture consumer only)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
개별 edge는 `src/config/architecture/modules.json`의 `allowed_dependencies`가 유일한 목록이다.
|
개별 edge는 `src/config/architecture/modules.json`의 `allowed_dependencies`가 유일한 목록이다.
|
||||||
@@ -182,7 +180,7 @@ cd src
|
|||||||
```
|
```
|
||||||
|
|
||||||
소유 leaf의 정확한 Gradle path는 `src/config/architecture/modules.json`에서 읽고 focused test
|
소유 leaf의 정확한 Gradle path는 `src/config/architecture/modules.json`에서 읽고 focused test
|
||||||
명령을 파생한다. root 문서에 19개 명령 목록을 복제하지 않는다.
|
명령을 파생한다. root 문서에 18개 명령 목록을 복제하지 않는다.
|
||||||
|
|
||||||
## 설정과 런타임
|
## 설정과 런타임
|
||||||
|
|
||||||
@@ -208,7 +206,7 @@ Docker/runtime 규칙:
|
|||||||
1. `src/settings.gradle`의 `rootProject.name`을 새 프로젝트명으로 바꾼다.
|
1. `src/settings.gradle`의 `rootProject.name`을 새 프로젝트명으로 바꾼다.
|
||||||
2. Java package `dev.caskeleton`을 새 organization/project package로 바꾼다.
|
2. Java package `dev.caskeleton`을 새 organization/project package로 바꾼다.
|
||||||
3. `CaSkeletonApplication` 이름을 새 애플리케이션 이름으로 바꾼다.
|
3. `CaSkeletonApplication` 이름을 새 애플리케이션 이름으로 바꾼다.
|
||||||
4. production 모듈에는 목표 도메인의 entity, repository port, use case, adapter만 추가하고, 예시 코드는 `sample-portfolio`에 격리한다.
|
4. production 모듈에는 목표 도메인의 entity, repository port, use case, adapter만 추가한다.
|
||||||
5. 모듈 이름과 경계는 유지한다.
|
5. 모듈 이름과 경계는 유지한다.
|
||||||
6. Docker image/application 이름을 새 프로젝트 기준으로 수정한다.
|
6. Docker image/application 이름을 새 프로젝트 기준으로 수정한다.
|
||||||
7. `.env`, `.env.local`, `application.yml`의 예시 값을 새 런타임 요구사항에 맞춘다.
|
7. `.env`, `.env.local`, `application.yml`의 예시 값을 새 런타임 요구사항에 맞춘다.
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ If this summary drifts from `AGENTS.md`, `AGENTS.md` wins and this summary must
|
|||||||
|
|
||||||
## Gradle policy authorities
|
## Gradle policy authorities
|
||||||
|
|
||||||
- `src/config/architecture/modules.json`: exactly 19 leaf identities, repository-relative source
|
- `src/config/architecture/modules.json`: exactly 18 leaf identities, repository-relative source
|
||||||
paths, Gradle paths, allowed production project dependency edges, and the exact runtime
|
paths, Gradle paths, allowed production project dependency edges, and the exact runtime
|
||||||
memberships of both composition roots.
|
membership of the production composition root.
|
||||||
- `src/settings.gradle`: fail-closed registry validation, project inclusion, and directory mapping.
|
- `src/settings.gradle`: fail-closed registry validation, project inclusion, and directory mapping.
|
||||||
- `src/build.gradle`: `verifyCleanArchitectureDependencies` and the other architecture-wide
|
- `src/build.gradle`: `verifyCleanArchitectureDependencies` and the other architecture-wide
|
||||||
verification tasks.
|
verification tasks.
|
||||||
@@ -43,7 +43,7 @@ count.
|
|||||||
|
|
||||||
## Module families
|
## Module families
|
||||||
|
|
||||||
`src/config/architecture/modules.json` owns the complete 19-leaf list. Root guidance summarizes
|
`src/config/architecture/modules.json` owns the complete 18-leaf list. Root guidance summarizes
|
||||||
families; the nearest `src/**/CLAUDE.md` owns local rules.
|
families; the nearest `src/**/CLAUDE.md` owns local rules.
|
||||||
|
|
||||||
| Family | Responsibility | Stable dependency direction |
|
| Family | Responsibility | Stable dependency direction |
|
||||||
@@ -54,7 +54,6 @@ families; the nearest `src/**/CLAUDE.md` owns local rules.
|
|||||||
| `adapter:outbound:persistence-*` | JPA/PostgreSQL and MongoDB persistence adapters | application/domain/shared contracts as registered |
|
| `adapter:outbound:persistence-*` | JPA/PostgreSQL and MongoDB persistence adapters | application/domain/shared contracts as registered |
|
||||||
| `adapter:outbound:*` | support, messaging, cache, notification, storage, file, HTTP client, identifier capabilities | application/domain/shared and registered support edge |
|
| `adapter:outbound:*` | support, messaging, cache, notification, storage, file, HTTP client, identifier capabilities | application/domain/shared and registered support edge |
|
||||||
| `shared-contract` | Skeleton-wide operational contracts | Java stdlib only |
|
| `shared-contract` | Skeleton-wide operational contracts | Java stdlib only |
|
||||||
| `sample-portfolio` | Fixture/reference consumer | registered runtime leaves; never a production dependency |
|
|
||||||
| `app-bootstrap` | Spring Boot entrypoint and composition root | registered runtime leaves |
|
| `app-bootstrap` | Spring Boot entrypoint and composition root | registered runtime leaves |
|
||||||
|
|
||||||
Never infer an individual leaf's Gradle path, allowed dependency, or test command from this table.
|
Never infer an individual leaf's Gradle path, allowed dependency, or test command from this table.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Initialized from `clean-architecture-backend-template` revision
|
|||||||
`0a6dd0e419620683de48f69b5c6d22d9964b6f44` as a tracked snapshot. Template
|
`0a6dd0e419620683de48f69b5c6d22d9964b6f44` as a tracked snapshot. Template
|
||||||
updates are applied explicitly and recorded in `template.lock.json`.
|
updates are applied explicitly and recorded in `template.lock.json`.
|
||||||
|
|
||||||
ca-skeleton은 Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 기반의 Clean Architecture 백엔드 템플릿입니다. fork해서 도메인·패키지·엔티티·유스케이스만 교체하면 새 서비스를 시작할 수 있고, 모듈 경계와 의존 방향은 그대로 유지합니다. 기본 패키지는 `dev.caskeleton`이며, 예시 도메인은 production 모듈이 아니라 `sample-portfolio` 모듈(WorkLog 작업 기록 게시판)에 격리합니다.
|
ca-skeleton은 Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 기반의 Clean Architecture 백엔드 템플릿입니다. fork해서 도메인·패키지·엔티티·유스케이스를 추가하면 새 서비스를 시작할 수 있고, 모듈 경계와 의존 방향은 그대로 유지합니다. 기본 패키지는 `dev.caskeleton`이며, 구체적인 예시 도메인은 제거되어 있습니다.
|
||||||
|
|
||||||
이 문서는 전체 구조와 첫 실행만 다룹니다. 모듈별 상세 규칙과 설계 근거는 각 모듈의 README와 `CLAUDE.md`가, 빌드·환경 변수 상세는 [src/README.md](src/README.md)가 소유합니다.
|
이 문서는 전체 구조와 첫 실행만 다룹니다. 모듈별 상세 규칙과 설계 근거는 각 모듈의 README와 `CLAUDE.md`가, 빌드·환경 변수 상세는 [src/README.md](src/README.md)가 소유합니다.
|
||||||
|
|
||||||
@@ -16,7 +16,6 @@ ca-skeleton은 Java 21, Spring Boot 4.0.0, Gradle 멀티모듈 기반의 Clean A
|
|||||||
app-bootstrap -> adapter:inbound:* -> application-core -> domain-core
|
app-bootstrap -> adapter:inbound:* -> application-core -> domain-core
|
||||||
app-bootstrap -> adapter:outbound:* -> application-core -> domain-core
|
app-bootstrap -> adapter:outbound:* -> application-core -> domain-core
|
||||||
모든 런타임 모듈 -> shared-contract
|
모든 런타임 모듈 -> shared-contract
|
||||||
sample-portfolio -> 등록된 런타임 리프 (fixture 소비자 전용)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
family 수준의 책임은 다음과 같습니다.
|
family 수준의 책임은 다음과 같습니다.
|
||||||
@@ -30,9 +29,8 @@ family 수준의 책임은 다음과 같습니다.
|
|||||||
| `adapter:outbound:*` | support(공유 베이스)·messaging·cache·notification·object storage·file·HTTP client·identifier 능력을 port 뒤에서 구현. 외부 연동 어댑터(messaging·cache·notification·HTTP client)는 기본 비활성 |
|
| `adapter:outbound:*` | support(공유 베이스)·messaging·cache·notification·object storage·file·HTTP client·identifier 능력을 port 뒤에서 구현. 외부 연동 어댑터(messaging·cache·notification·HTTP client)는 기본 비활성 |
|
||||||
| `shared-contract` | skeleton 전역 운영 계약. business/domain 개념 저장 금지 |
|
| `shared-contract` | skeleton 전역 운영 계약. business/domain 개념 저장 금지 |
|
||||||
| `app-bootstrap` | Spring Boot entrypoint와 composition root |
|
| `app-bootstrap` | Spring Boot entrypoint와 composition root |
|
||||||
| `sample-portfolio` | WorkLog 예시 도메인(fixture/reference). production이 의존하지 않음 |
|
|
||||||
|
|
||||||
정확한 19개 leaf 목록과 각 leaf의 Gradle path·소스 경로·허용 production 의존 edge는
|
정확한 18개 leaf 목록과 각 leaf의 Gradle path·소스 경로·허용 production 의존 edge는
|
||||||
[src/config/architecture/modules.json](src/config/architecture/modules.json)이 SSOT입니다. focused
|
[src/config/architecture/modules.json](src/config/architecture/modules.json)이 SSOT입니다. focused
|
||||||
test는 해당 Gradle path에서 `./gradlew <gradle-path>:test --console=plain` 형태로 파생하며, root
|
test는 해당 Gradle path에서 `./gradlew <gradle-path>:test --console=plain` 형태로 파생하며, root
|
||||||
문서나 기억에서 개별 leaf edge를 추론하지 않습니다.
|
문서나 기억에서 개별 leaf edge를 추론하지 않습니다.
|
||||||
@@ -46,7 +44,7 @@ cd src
|
|||||||
./gradlew bootstrap
|
./gradlew bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
`bootstrap`은 compile 검사, PostgreSQL Compose 기동, 애플리케이션 이미지 build·기동(startup Flyway 포함), sample 격리 검증, `GET /api/healthcheck` HTTP smoke를 순서대로 실행합니다. 각 단계가 별도 Gradle task라 실패 단계가 task 이름으로 드러납니다. 기동을 확인하려면 health endpoint를 호출합니다.
|
`bootstrap`은 compile 검사, PostgreSQL Compose 기동, 애플리케이션 이미지 build·기동(startup Flyway 포함), `GET /api/healthcheck` HTTP smoke를 순서대로 실행합니다. 각 단계가 별도 Gradle task라 실패 단계가 task 이름으로 드러납니다. 기동을 확인하려면 health endpoint를 호출합니다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsS http://localhost:8080/api/healthcheck
|
curl -fsS http://localhost:8080/api/healthcheck
|
||||||
@@ -91,10 +89,10 @@ cd src
|
|||||||
|
|
||||||
애플리케이션 이름 등 나머지 rename 단계는 위 체크리스트를 따릅니다.
|
애플리케이션 이름 등 나머지 rename 단계는 위 체크리스트를 따릅니다.
|
||||||
|
|
||||||
3. `CaSkeletonApplication`을 새 애플리케이션 이름으로 바꾸고, 목표 도메인의 엔티티·repository port·유스케이스·adapter를 production 모듈에 추가합니다. 예시 코드는 `sample-portfolio`에만 둡니다.
|
3. `CaSkeletonApplication`을 새 애플리케이션 이름으로 바꾸고, 목표 도메인의 엔티티·repository port·유스케이스·adapter를 production 모듈에 추가합니다.
|
||||||
4. 모듈 이름과 경계는 그대로 유지합니다.
|
4. 모듈 이름과 경계는 그대로 유지합니다.
|
||||||
|
|
||||||
검증은 sample-on과 sample-off를 모두 통과시킵니다.
|
검증은 전체 테스트와 sample-off 재유입 방지 계약을 모두 통과시킵니다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd src
|
cd src
|
||||||
@@ -102,7 +100,7 @@ cd src
|
|||||||
./gradlew :app-bootstrap:sampleOffTest
|
./gradlew :app-bootstrap:sampleOffTest
|
||||||
```
|
```
|
||||||
|
|
||||||
`sample-portfolio`는 템플릿이 유지하는 fixture/reference 모듈이라 production 모듈이 의존하지 않고, runtime에 sample bean이나 endpoint를 넣지 않습니다. 다운스트림 fork에서 fixture가 더 필요 없을 때만 sample-off 테스트를 통과시킨 뒤 정리합니다.
|
`sampleOffTest`는 삭제된 샘플 타입이 production bootstrap classpath에 다시 들어오지 않는지 검증합니다.
|
||||||
|
|
||||||
## 아키텍처 규칙과 검증
|
## 아키텍처 규칙과 검증
|
||||||
|
|
||||||
@@ -129,7 +127,7 @@ cd src
|
|||||||
## 더 알아보기
|
## 더 알아보기
|
||||||
|
|
||||||
- 빌드·검증 게이트·환경 변수 상세: [src/README.md](src/README.md)
|
- 빌드·검증 게이트·환경 변수 상세: [src/README.md](src/README.md)
|
||||||
- 모듈 레지스트리(19개 leaf SSOT): [src/config/architecture/modules.json](src/config/architecture/modules.json)
|
- 모듈 레지스트리(18개 leaf SSOT): [src/config/architecture/modules.json](src/config/architecture/modules.json)
|
||||||
- 에이전트·기여자 작업 규칙: [AGENTS.md](AGENTS.md) · [CLAUDE.md](CLAUDE.md)
|
- 에이전트·기여자 작업 규칙: [AGENTS.md](AGENTS.md) · [CLAUDE.md](CLAUDE.md)
|
||||||
- 빌드·릴리스 공급망 파이프라인은 현재 Mode B 복구 범위에 포함되지 않았다. 현재 저장소가
|
- 빌드·릴리스 공급망 파이프라인은 현재 Mode B 복구 범위에 포함되지 않았다. 현재 저장소가
|
||||||
제공하는 canonical workflow는 품질·의존성 취약점·링크 검사이며, release/publish 자동화는 별도
|
제공하는 canonical workflow는 품질·의존성 취약점·링크 검사이며, release/publish 자동화는 별도
|
||||||
|
|||||||
@@ -3063,6 +3063,11 @@ env_keys:
|
|||||||
required_test: cache-contract:redis-soft-hard-ttl-order
|
required_test: cache-contract:redis-soft-hard-ttl-order
|
||||||
|
|
||||||
- name: APP_CACHE_REDIS_TTL_JITTER
|
- name: APP_CACHE_REDIS_TTL_JITTER
|
||||||
|
# DEPRECATED 2026-08-13: removing the installed sample portfolio also removed the only
|
||||||
|
# application.yml binding for this key; app-bootstrap has no TTL-jitter consumer. The row is
|
||||||
|
# retained so deployments still setting it can be warned until its scheduled removal.
|
||||||
|
deprecated_orphaned: true
|
||||||
|
removal_deadline: 2026-11-30
|
||||||
type: float
|
type: float
|
||||||
default: 0.10
|
default: 0.10
|
||||||
allowed_values: null
|
allowed_values: null
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
# syntax=docker/dockerfile:1.7-labs@sha256:b99fecfe00268a8b556fad7d9c37ee25d716ae08a5d7320e6d51c4dd83246894
|
|
||||||
# =============================================================================
|
|
||||||
# sample-portfolio standalone demo image — twin of src/Dockerfile.
|
|
||||||
#
|
|
||||||
# Builds and runs the REFERENCE app (SamplePortfolioApplication), not the
|
|
||||||
# production composition root (CaSkeletonApplication) that src/Dockerfile builds.
|
|
||||||
# The sample is the demo-friendly entrypoint: its application.yml self-provides
|
|
||||||
# defaults for every env placeholder, so the only external dependency it needs
|
|
||||||
# to boot is a reachable PostgreSQL (datasource + Flyway sample migrations).
|
|
||||||
#
|
|
||||||
# Build (no build-args required — this is a disposable demo, not a release artifact):
|
|
||||||
# docker build -f src/Dockerfile.sample src/ -t ca-sample:local
|
|
||||||
#
|
|
||||||
# Run (point APP_DATASOURCE_URL at a reachable Postgres; localhost default shown):
|
|
||||||
# docker run --rm -p 8080:8080 -p 9001:9001 \
|
|
||||||
# -e APP_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/ca_skeleton \
|
|
||||||
# ca-sample:local
|
|
||||||
#
|
|
||||||
# The multi-stage build, --parents descriptor glob, STRICT lock verification,
|
|
||||||
# non-root user, read-only-root-fs writable mounts, and JVM container ergonomics
|
|
||||||
# are all identical to src/Dockerfile — only the bootJar target differs. Keep the
|
|
||||||
# two files' builder stages in sync.
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Demo defaults so the image builds with zero build-args. Two format constraints from the root
|
|
||||||
# build.gradle configuration guard (feature-build-release-supply-chain-contract D1/D9):
|
|
||||||
# - RELEASE_VERSION must be bare MAJOR.MINOR.PATCH — no pre-release/build suffix (build.gradle L21).
|
|
||||||
# The "-sample" marker therefore lives only on BUILD_VERSION, which is a label, not a gradle prop.
|
|
||||||
# - GIT_SHA must be 7-40 hex chars (build.gradle L35); 0000000 is the placeholder.
|
|
||||||
ARG RELEASE_VERSION=0.0.0
|
|
||||||
ARG BUILD_VERSION=0.0.0-sample
|
|
||||||
ARG GIT_SHA=0000000
|
|
||||||
ARG SOURCE_URL=https://example.invalid/ca-tmpl-sample
|
|
||||||
|
|
||||||
# ---- Stage 1: builder -------------------------------------------------------
|
|
||||||
# Uses the full JDK only in the build stage, never in the final image.
|
|
||||||
FROM eclipse-temurin:21-jdk-jammy@sha256:801b7e1a9c4befaf82bf9a2a58025ef43a7694bbc84779187ad0524d84742772 AS builder
|
|
||||||
|
|
||||||
ARG RELEASE_VERSION
|
|
||||||
ARG GIT_SHA
|
|
||||||
|
|
||||||
WORKDIR /build/src
|
|
||||||
|
|
||||||
# Copy the Gradle wrapper and every module's build descriptor + dependency lockfile FIRST,
|
|
||||||
# so the expensive dependency-resolution layer is cached and only re-runs when a build.gradle
|
|
||||||
# or gradle.lockfile changes (D8). `--parents` preserves each file's directory structure, so a
|
|
||||||
# single structure-preserving glob replaces a per-module COPY list: new modules are picked up
|
|
||||||
# automatically and this stage never drifts out of sync with settings.gradle.
|
|
||||||
# (Requires the labs Dockerfile frontend — see the `# syntax` directive at the top of this file.)
|
|
||||||
COPY gradlew ./
|
|
||||||
COPY gradle/ gradle/
|
|
||||||
COPY config/ ./config/
|
|
||||||
COPY --parents settings.gradle build.gradle **/build.gradle **/gradle.lockfile ./
|
|
||||||
|
|
||||||
# Resolve every module configuration in STRICT mode (no --write-locks in a demo build either).
|
|
||||||
RUN test -n "${RELEASE_VERSION}" \
|
|
||||||
&& test -n "${GIT_SHA}" \
|
|
||||||
&& ./gradlew verifyDependencyLocks --no-daemon --quiet \
|
|
||||||
-PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"
|
|
||||||
|
|
||||||
# Copy full source and stage the executable sample JAR at Gradle's declared Docker output path.
|
|
||||||
COPY . .
|
|
||||||
RUN ./gradlew :sample-portfolio:stageDockerJar --no-daemon -x test \
|
|
||||||
-PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"
|
|
||||||
|
|
||||||
# ---- Stage 2: runtime image -------------------------------------------------
|
|
||||||
# JRE-only slim image (no full JDK in the demo image either).
|
|
||||||
FROM eclipse-temurin:21-jre-jammy@sha256:199aebeb3adcde4910695cdebfe782ada38dadb6cc8013159b58d3724451befd AS runtime
|
|
||||||
|
|
||||||
ARG BUILD_VERSION
|
|
||||||
ARG GIT_SHA
|
|
||||||
ARG SOURCE_URL
|
|
||||||
|
|
||||||
# OCI image labels. Unlike the release image (src/Dockerfile), the demo image does NOT hard-fail
|
|
||||||
# on missing metadata — the ARG defaults above keep it buildable with no build-args.
|
|
||||||
LABEL org.opencontainers.image.title="caskeleton-sample" \
|
|
||||||
org.opencontainers.image.description="ca-tmpl sample-portfolio reference/demo application" \
|
|
||||||
org.opencontainers.image.source="${SOURCE_URL}" \
|
|
||||||
org.opencontainers.image.revision="${GIT_SHA}" \
|
|
||||||
org.opencontainers.image.version="${BUILD_VERSION}"
|
|
||||||
|
|
||||||
# ---- Locale / timezone ------------------------------------------------------
|
|
||||||
ENV TZ=UTC \
|
|
||||||
LANG=C.UTF-8 \
|
|
||||||
LC_ALL=C.UTF-8
|
|
||||||
|
|
||||||
# ---- Writable HOME under read-only root fs ----------------------------------
|
|
||||||
ENV HOME=/tmp
|
|
||||||
|
|
||||||
# ---- JVM ergonomics ---------------------------------------------------------
|
|
||||||
# Identical to src/Dockerfile: container-aware heap, fail-fast on OOM, heap dump to a
|
|
||||||
# writable mount, and Tomcat temp redirected to /tmp for a read-only root filesystem.
|
|
||||||
ENV JAVA_TOOL_OPTIONS="\
|
|
||||||
-XX:MaxRAMPercentage=75 \
|
|
||||||
-XX:+UseContainerSupport \
|
|
||||||
-XX:+ExitOnOutOfMemoryError \
|
|
||||||
-XX:+HeapDumpOnOutOfMemoryError \
|
|
||||||
-XX:HeapDumpPath=/var/tmp/heap \
|
|
||||||
-Dserver.tomcat.basedir=/tmp"
|
|
||||||
|
|
||||||
# ---- Filesystem layout (read-only root filesystem) --------------------------
|
|
||||||
# At runtime /var/tmp/heap and /tmp MUST be writable mounts (tmpfs/emptyDir).
|
|
||||||
RUN mkdir -p /var/tmp/heap && chmod 1777 /var/tmp/heap
|
|
||||||
|
|
||||||
# ---- Non-root user ----------------------------------------------------------
|
|
||||||
RUN groupadd --system --gid 1000 app \
|
|
||||||
&& useradd --system --uid 1000 --gid app --no-create-home --shell /usr/sbin/nologin app
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/application.jar app.jar
|
|
||||||
|
|
||||||
USER app
|
|
||||||
|
|
||||||
# ---- Ports ------------------------------------------------------------------
|
|
||||||
# 8080 — application HTTP port
|
|
||||||
# 9001 — management / actuator port
|
|
||||||
EXPOSE 8080 9001
|
|
||||||
|
|
||||||
# ---- Health check -----------------------------------------------------------
|
|
||||||
# Actuator readiness probe on the management port (9001). Ignored by Kubernetes,
|
|
||||||
# which uses its own probes — kept for docker/compose parity with src/Dockerfile.
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
|
||||||
CMD wget --no-verbose --tries=1 --spider \
|
|
||||||
http://localhost:9001/actuator/health/readiness || exit 1
|
|
||||||
|
|
||||||
# ---- Entrypoint -------------------------------------------------------------
|
|
||||||
# mainClass (SamplePortfolioApplication) is baked into the bootJar manifest.
|
|
||||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
|
||||||
+5
-9
@@ -17,7 +17,7 @@
|
|||||||
| 게이트 | 하는 일 |
|
| 게이트 | 하는 일 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `verifyCleanArchitectureDependencies` | 모듈 간 의존 방향이 허용된 범위 안에 있는지 검사 |
|
| `verifyCleanArchitectureDependencies` | 모듈 간 의존 방향이 허용된 범위 안에 있는지 검사 |
|
||||||
| `verifyRuntimeModuleMembership` | registry의 두 composition root membership과 실제 main project dependency가 정확히 일치하는지 검사 |
|
| `verifyRuntimeModuleMembership` | registry의 production composition root membership과 실제 main project dependency가 정확히 일치하는지 검사 |
|
||||||
| `verifyEnvKeys` | `env-keys.yaml` ↔ `application.yml` ↔ `src/.env` 가 어긋나지 않는지 검사 |
|
| `verifyEnvKeys` | `env-keys.yaml` ↔ `application.yml` ↔ `src/.env` 가 어긋나지 않는지 검사 |
|
||||||
| `verifyOneTypePerFile` | 파일당 public 최상위 타입 1개, 파일명 == 타입명인지 검사 |
|
| `verifyOneTypePerFile` | 파일당 public 최상위 타입 1개, 파일명 == 타입명인지 검사 |
|
||||||
| `verifyTrivyignore` | `.trivyignore.yaml` 의 Trivy suppression 이 사유·만료일을 갖추고 만료/기한초과가 아닌지 검사 |
|
| `verifyTrivyignore` | `.trivyignore.yaml` 의 Trivy suppression 이 사유·만료일을 갖추고 만료/기한초과가 아닌지 검사 |
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
### Local bootstrap
|
### Local bootstrap
|
||||||
|
|
||||||
`./gradlew bootstrap`은 `bootstrapCompile` → `bootstrapDependencies` →
|
`./gradlew bootstrap`은 `bootstrapCompile` → `bootstrapDependencies` →
|
||||||
`bootstrapMigrateAndStart` → `bootstrapSampleContract` → `bootstrapSmoke`를 순서대로 실행합니다.
|
`bootstrapMigrateAndStart` → `bootstrapSmoke`를 순서대로 실행합니다.
|
||||||
DB와 app lifecycle은 저장소 루트의 base/local Compose 조합이 소유하며, app startup Flyway가
|
DB와 app lifecycle은 저장소 루트의 base/local Compose 조합이 소유하며, app startup Flyway가
|
||||||
끝나 public health endpoint가 준비되어야 다음 단계로 넘어갑니다. `src/.env`는 env 설정의
|
끝나 public health endpoint가 준비되어야 다음 단계로 넘어갑니다. `src/.env`는 env 설정의
|
||||||
SSOT이고 bootstrap이 별도 env template을 만들지 않습니다.
|
SSOT이고 bootstrap이 별도 env template을 만들지 않습니다.
|
||||||
@@ -88,10 +88,10 @@ vendor/build나 container base image까지 byte-for-byte 같음을 주장하지
|
|||||||
### `verifyRuntimeModuleMembership`
|
### `verifyRuntimeModuleMembership`
|
||||||
|
|
||||||
- **하는 일.** 같은 registry의 `runtime_compositions`와 각 leaf의 `runtime_memberships`를 읽어
|
- **하는 일.** 같은 registry의 `runtime_compositions`와 각 leaf의 `runtime_memberships`를 읽어
|
||||||
`app-bootstrap`/`sample-portfolio`의 실제 `api`/`implementation`/`compileOnly`/`runtimeOnly`
|
`app-bootstrap`의 실제 `api`/`implementation`/`compileOnly`/`runtimeOnly`
|
||||||
project dependency와 정확히 대조합니다.
|
project dependency와 정확히 대조합니다.
|
||||||
- **opt-in의 의미.** membership이 빈 GraphQL/gRPC/WebSocket/Mongo leaf는 독립 빌드 대상이지만 두
|
- **opt-in의 의미.** membership이 빈 GraphQL/gRPC/WebSocket/Mongo leaf는 독립 빌드 대상이지만
|
||||||
shipped runtime에는 없습니다. app-bootstrap의 `conditionalTransportTest` test-only classpath는
|
production runtime에는 없습니다. app-bootstrap의 `conditionalTransportTest` test-only classpath는
|
||||||
실제 채택 전에 세 inbound transport를 함께 qualification하기 위한 evidence composition입니다.
|
실제 채택 전에 세 inbound transport를 함께 qualification하기 위한 evidence composition입니다.
|
||||||
- **변경 규칙.** production edge를 추가하거나 제거할 때 `allowed_dependencies`,
|
- **변경 규칙.** production edge를 추가하거나 제거할 때 `allowed_dependencies`,
|
||||||
`runtime_memberships`, 실제 Gradle dependency를 같은 변경에서 갱신하지 않으면 `check`가 실패합니다.
|
`runtime_memberships`, 실제 Gradle dependency를 같은 변경에서 갱신하지 않으면 `check`가 실패합니다.
|
||||||
@@ -282,10 +282,6 @@ ca-skeleton:
|
|||||||
- 기존 `APP_OUTBOUND_HTTP_*`와 `app.outbound.http.*`는 canonical 설정이 아닙니다. 루트 `src/.env`,
|
- 기존 `APP_OUTBOUND_HTTP_*`와 `app.outbound.http.*`는 canonical 설정이 아닙니다. 루트 `src/.env`,
|
||||||
`app-bootstrap`의 application YAML, env-key registry에서 제거됐으며 canonical composition에
|
`app-bootstrap`의 application YAML, env-key registry에서 제거됐으며 canonical composition에
|
||||||
입력하면 상태와 무관하게 기동을 거부합니다.
|
입력하면 상태와 무관하게 기동을 거부합니다.
|
||||||
- 다만 `sample-portfolio`의 application YAML에는 legacy facade를 시연하기 위해 15개 키가 남아
|
|
||||||
있습니다. 이 모듈은 fixture/reference consumer이고 production 의존성이 아니며, 그 YAML은
|
|
||||||
`verifyEnvKeys`가 검사하는 세 파일에 포함되지 않습니다. "제거됐다"는 문장이 저장소 전체를
|
|
||||||
가리킨다고 읽히지 않도록 범위를 명시합니다.
|
|
||||||
- legacy JDK facade가 필요한 fork만 canonical composition 밖에서
|
- legacy JDK facade가 필요한 fork만 canonical composition 밖에서
|
||||||
`OutboundHttpSettings.bindLegacy(Binder)`와 legacy configuration을 명시적으로 import합니다.
|
`OutboundHttpSettings.bindLegacy(Binder)`와 legacy configuration을 명시적으로 import합니다.
|
||||||
timeout/retry/CB/response-size 설정은 그 migration API 내부 계약일 뿐 canonical provider
|
timeout/retry/CB/response-size 설정은 그 migration API 내부 계약일 뿐 canonical provider
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Package root: `dev.caskeleton.adapter.inbound.graphql`.
|
|||||||
자동 합성/바인딩하도록 얹는 얇은 계층이다.
|
자동 합성/바인딩하도록 얹는 얇은 계층이다.
|
||||||
- feature-agnostic: `classpath:graphql/**` 스키마와 모든 `@Controller` `@QueryMapping`/
|
- feature-agnostic: `classpath:graphql/**` 스키마와 모든 `@Controller` `@QueryMapping`/
|
||||||
`@MutationMapping` 을 generic 하게 합성한다. **WorkLog 등 구체 기능을 이름으로 알지 않는다.**
|
`@MutationMapping` 을 generic 하게 합성한다. **WorkLog 등 구체 기능을 이름으로 알지 않는다.**
|
||||||
- classpath opt-in: 현재 `app-bootstrap`/`sample-portfolio` production runtime 은 이 leaf 를
|
- classpath opt-in: 현재 `app-bootstrap` production runtime 은 이 leaf 를
|
||||||
의존하지 않는다. 실제 채택 시 composition root 가 GraphQL leaf 와 인증/인가·CORS 정책,
|
의존하지 않는다. 실제 채택 시 composition root 가 GraphQL leaf 와 인증/인가·CORS 정책,
|
||||||
GraphiQL/introspection 운영 설정을 함께 명시해야 한다.
|
GraphiQL/introspection 운영 설정을 함께 명시해야 한다.
|
||||||
|
|
||||||
|
|||||||
@@ -19,18 +19,17 @@ Spring for GraphQL 은 schema-first 다. 빈 스키마로는 부팅이 실패하
|
|||||||
|
|
||||||
## 기능(feature)은 어떻게 기여하는가 — machinery/feature 분리
|
## 기능(feature)은 어떻게 기여하는가 — machinery/feature 분리
|
||||||
|
|
||||||
스켈레톤은 **WorkLog 를 이름으로 알지 못한다.** 향후 composition root 가 이 모듈을 classpath 에
|
스켈레톤은 구체적인 제품 기능을 이름으로 알지 못한다. 향후 composition root 가 이 모듈을 classpath 에
|
||||||
명시적으로 채택하고 feature 를 추가하면 Spring for GraphQL 이 다음 두 축으로 합성할 수 있다:
|
명시적으로 채택하고 feature 를 추가하면 Spring for GraphQL 이 다음 두 축으로 합성할 수 있다:
|
||||||
|
|
||||||
- **스키마**: `classpath:graphql/**/*.graphqls` 를 전부 병합한다. sample 모듈의
|
- **스키마**: `classpath:graphql/**/*.graphqls` 를 전부 병합한다. 제품 모듈의 feature 스키마는
|
||||||
향후 `worklog.graphqls` 같은 feature 스키마는 스켈레톤의 `skeleton.graphqls` 와 합쳐진다.
|
스켈레톤의 `skeleton.graphqls` 와 합쳐진다.
|
||||||
- **resolver(핸들러)**: 컨텍스트의 모든 `@Controller` 의 `@QueryMapping`/`@MutationMapping`
|
- **resolver(핸들러)**: 컨텍스트의 모든 `@Controller` 의 `@QueryMapping`/`@MutationMapping`
|
||||||
메서드를 바인딩한다. 향후 feature 의 GraphQL controller 는 스켈레톤을 수정하지 않고 등록할 수
|
메서드를 바인딩한다. 향후 feature 의 GraphQL controller 는 스켈레톤을 수정하지 않고 등록할 수
|
||||||
있다.
|
있다.
|
||||||
|
|
||||||
현재 `app-bootstrap` 과 `sample-portfolio` 의 production runtime 은 이 leaf 를 의존하지 않는다.
|
현재 `app-bootstrap` production runtime 은 이 leaf 를 의존하지 않는다. 즉 이 모듈은
|
||||||
즉 이 모듈은 **classpath opt-in** 이며, 현재 sample 에 feature GraphQL 스키마/controller 가 있다는
|
**classpath opt-in** 이다. leaf 자체는 최소 health 스키마로 독립 기동할 수 있다.
|
||||||
뜻이 아니다. leaf 자체는 최소 health 스키마로 독립 기동할 수 있다.
|
|
||||||
|
|
||||||
## 에러 매핑 — web `GlobalExceptionHandler` / gRPC 인터셉터의 GraphQL 형제
|
## 에러 매핑 — web `GlobalExceptionHandler` / gRPC 인터셉터의 GraphQL 형제
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ also drives the runtime patterns reference implementations must follow.
|
|||||||
`Optional<T>` wrappers on request records.
|
`Optional<T>` wrappers on request records.
|
||||||
- **B3 — Mapper-internal failures.** Map record canonical-constructor
|
- **B3 — Mapper-internal failures.** Map record canonical-constructor
|
||||||
`IllegalArgumentException`, MapStruct generated NPE, ACL normalization
|
`IllegalArgumentException`, MapStruct generated NPE, ACL normalization
|
||||||
failures, etc. by throwing `MappingException` (sample implementation in
|
failures, etc. by throwing `MappingException`; the global handler routes it to
|
||||||
`sample-portfolio`); the global handler routes it to `MAPPING_FAILED` (HTTP 400),
|
`MAPPING_FAILED` (HTTP 400),
|
||||||
never to `BAD_PARAMETER` or `INTERNAL_ERROR`. Plain `IllegalArgumentException`
|
never to `BAD_PARAMETER` or `INTERNAL_ERROR`. Plain `IllegalArgumentException`
|
||||||
remains `BAD_PARAMETER` for non-mapper callers.
|
remains `BAD_PARAMETER` for non-mapper callers.
|
||||||
- **B4 — Validation layering.** Class-level Bean Validation constraints belong
|
- **B4 — Validation layering.** Class-level Bean Validation constraints belong
|
||||||
@@ -93,16 +93,13 @@ Domain `@RestControllerAdvice` in a consuming module must be annotated
|
|||||||
`@Order(Ordered.HIGHEST_PRECEDENCE)` (or otherwise ordered ahead of this
|
`@Order(Ordered.HIGHEST_PRECEDENCE)` (or otherwise ordered ahead of this
|
||||||
module's base `GlobalExceptionHandler`), because the base handler's catch-all
|
module's base `GlobalExceptionHandler`), because the base handler's catch-all
|
||||||
`@ExceptionHandler(Exception.class)` would otherwise resolve domain exceptions
|
`@ExceptionHandler(Exception.class)` would otherwise resolve domain exceptions
|
||||||
to `INTERNAL_ERROR`. See `sample-portfolio`'s `DomainExceptionHandler` for the
|
to `INTERNAL_ERROR`.
|
||||||
pattern.
|
|
||||||
|
|
||||||
The base operational handler (`error/GlobalExceptionHandler`), the error-code
|
The base operational handler (`error/GlobalExceptionHandler`), the error-code
|
||||||
contract (`dev.caskeleton.shared.error.ApiErrorCode` + `OperationalError`), the
|
contract (`dev.caskeleton.shared.error.ApiErrorCode` + `OperationalError`), the
|
||||||
`error/ErrorResponseFactory`, and the `envelope/EnvelopeBodyAdvice` now live in
|
`error/ErrorResponseFactory`, and the `envelope/EnvelopeBodyAdvice` now live in
|
||||||
production modules (`adapter:inbound:web` / `shared-contract`), so the running application
|
production modules (`adapter:inbound:web` / `shared-contract`). Domain-specific exception
|
||||||
provides them without depending on `sample-portfolio`. Domain-specific exception
|
handlers and error codes live in the consuming module.
|
||||||
handlers and error codes live in the consuming module (see sample's
|
|
||||||
`DomainExceptionHandler` / `PortfolioErrorCode`).
|
|
||||||
|
|
||||||
## Schema / serialization contract
|
## Schema / serialization contract
|
||||||
|
|
||||||
|
|||||||
@@ -225,8 +225,7 @@ commit-aware response wrapper 계약을 구현한다. 또한 이 모듈은 HTML
|
|||||||
### EnvelopeBodyAdvice
|
### EnvelopeBodyAdvice
|
||||||
- 컨트롤러는 도메인/DTO 타입을 반환하고, 이 advice 가 와이어 형태를 항상
|
- 컨트롤러는 도메인/DTO 타입을 반환하고, 이 advice 가 와이어 형태를 항상
|
||||||
`{success, data | error, traceId}` 로 보장한다.
|
`{success, data | error, traceId}` 로 보장한다.
|
||||||
- 위치: sample 모듈이 아니라 adapter-web. 실행 앱은 adapter-web 에 의존하지만 sample-portfolio 에는 의존하지
|
- 위치: adapter-web. 실행 앱이 이 모듈을 의존하므로 응답 래핑이 실제 runtime에 적용된다.
|
||||||
않으므로, 응답 래핑이 실제로 동작하려면 여기 있어야 한다.
|
|
||||||
|
|
||||||
### CacheControlFilter
|
### CacheControlFilter
|
||||||
- 스켈레톤 기본 HTTP 캐시 정책.
|
- 스켈레톤 기본 HTTP 캐시 정책.
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ The broadcaster sends only when exactly one projector matches. Zero or duplicate
|
|||||||
projection failures, invalid event types, null fields, and over-limit maps are dropped. The wire
|
projection failures, invalid event types, null fields, and over-limit maps are dropped. The wire
|
||||||
envelope contains only `type`, bounded string `fields`, and `occurredAt`.
|
envelope contains only `type`, bounded string `fields`, and `occurredAt`.
|
||||||
|
|
||||||
No current `sample-portfolio` feature publishes a WebSocket event or contributes a projector.
|
No current production feature publishes a WebSocket event or contributes a projector.
|
||||||
That is a future adoption example, not an existing runtime feature.
|
That is a future adoption example, not an existing runtime feature.
|
||||||
|
|
||||||
## Evidence and limits
|
## Evidence and limits
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ live in [CLAUDE.md](CLAUDE.md); this document records why the P1 boundary has th
|
|||||||
|
|
||||||
## Opt-in instead of accidental exposure
|
## Opt-in instead of accidental exposure
|
||||||
|
|
||||||
The leaf is built and tested but is not part of `app-bootstrap` or `sample-portfolio` production
|
The leaf is built and tested but is not part of `app-bootstrap` production runtime membership.
|
||||||
runtime membership. Even after a future composition adds it, `ca-skeleton.websocket.enabled=false`
|
Even after a future composition adds it, `ca-skeleton.websocket.enabled=false`
|
||||||
keeps its configuration and broadcaster absent. Enabling requires explicit non-wildcard origins,
|
keeps its configuration and broadcaster absent. Enabling requires explicit non-wildcard origins,
|
||||||
so merely adding the artifact cannot expose a wildcard STOMP broker.
|
so merely adding the artifact cannot expose a wildcard STOMP broker.
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Package root: `dev.caskeleton.adapter.outbound.fileserver`. Driven (outbound) ad
|
|||||||
|
|
||||||
## Forbidden
|
## Forbidden
|
||||||
|
|
||||||
- Inbound adapters, sibling outbound adapters, persistence, `app-bootstrap`, `sample-portfolio`
|
- Inbound adapters, sibling outbound adapters, persistence, or `app-bootstrap`
|
||||||
(ArchUnit `OUTBOUND_ADAPTERS_*` family rules).
|
(ArchUnit `OUTBOUND_ADAPTERS_*` family rules).
|
||||||
- Leaking filesystem, stream, framework, or provider types across `FilePublicationPort`.
|
- Leaking filesystem, stream, framework, or provider types across `FilePublicationPort`.
|
||||||
- Advertising `FILE_AND_DIRECTORY_SYNC` as physical device/controller/replica/site power-loss
|
- Advertising `FILE_AND_DIRECTORY_SYNC` as physical device/controller/replica/site power-loss
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ build choices) live in [README.md](README.md).
|
|||||||
- Persistence or web technology (JPA/Hibernate/Spring Data/Spring Web) — ArchUnit
|
- Persistence or web technology (JPA/Hibernate/Spring Data/Spring Web) — ArchUnit
|
||||||
`identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap`;
|
`identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap`;
|
||||||
`.claude/hooks/ca_import_gate.py` G4 가 쓰기 시점에 차단.
|
`.claude/hooks/ca_import_gate.py` G4 가 쓰기 시점에 차단.
|
||||||
- inbound adapters, persistence adapters, other outbound leaves, `app-bootstrap`,
|
- inbound adapters, persistence adapters, other outbound leaves, or `app-bootstrap`.
|
||||||
`sample-portfolio`.
|
|
||||||
- External IO (HTTP / messaging / cache / DB) — that belongs in `adapter-outbound`.
|
- External IO (HTTP / messaging / cache / DB) — that belongs in `adapter-outbound`.
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ readiness live in [README.md](README.md) and
|
|||||||
|
|
||||||
## Forbidden
|
## Forbidden
|
||||||
|
|
||||||
- Inbound adapters, sibling outbound adapters, persistence, app-bootstrap, or sample-portfolio
|
- Inbound adapters, sibling outbound adapters, persistence, or app-bootstrap
|
||||||
dependencies.
|
dependencies.
|
||||||
- Provider keys, paths, locators, SDK types, Spring types, or control-record types leaking into
|
- Provider keys, paths, locators, SDK types, Spring types, or control-record types leaking into
|
||||||
application-core.
|
application-core.
|
||||||
|
|||||||
@@ -68,10 +68,9 @@ Adapters that serve one optional capability carry that capability's switch, unli
|
|||||||
module. The `fileserver` package is the current case: every `Jpa*` adapter there is annotated
|
module. The `fileserver` package is the current case: every `Jpa*` adapter there is annotated
|
||||||
`@ConditionalOnProperty(prefix = "app.fileserver-platform", name = "enabled", havingValue = "true")`.
|
`@ConditionalOnProperty(prefix = "app.fileserver-platform", name = "enabled", havingValue = "true")`.
|
||||||
|
|
||||||
Without the gate a composition root that merely includes this module builds those beans, and each of
|
Without the gate a composition root that merely includes this module builds those beans, although
|
||||||
them needs collaborators only the Fileserver configuration supplies — which is how `sample-portfolio`
|
their collaborators exist only when Fileserver configuration is selected. A store for a capability
|
||||||
came to fail on a `FileStateMachine` it has no use for. A store for a capability nobody enabled
|
nobody enabled should not exist.
|
||||||
should not exist.
|
|
||||||
|
|
||||||
## Allowed
|
## Allowed
|
||||||
|
|
||||||
@@ -174,7 +173,7 @@ framework-neutral `shared.error.PersistenceFailureException` carrying one of the
|
|||||||
Audit metadata (`created_at` / `updated_at` / `created_by` / `updated_by`, D3) is an
|
Audit metadata (`created_at` / `updated_at` / `created_by` / `updated_by`, D3) is an
|
||||||
infrastructure concern that must never reach `domain-core` (D2). It lives only on the
|
infrastructure concern that must never reach `domain-core` (D2). It lives only on the
|
||||||
`audit/AuditableEntity` `@MappedSuperclass`; a domain aggregate persistence entity opts in
|
`audit/AuditableEntity` `@MappedSuperclass`; a domain aggregate persistence entity opts in
|
||||||
by extending it (D6 — e.g. the sample `WorkLogEntity`). The domain aggregate itself carries
|
by extending it (D6). The domain aggregate itself carries
|
||||||
zero audit fields, enforced by ArchUnit `domain_is_pure` (no `jakarta.persistence..`) plus
|
zero audit fields, enforced by ArchUnit `domain_is_pure` (no `jakarta.persistence..`) plus
|
||||||
`domain_entities_do_not_carry_audit_fields` (no `createdAt`/`updatedAt`/`createdBy`/`updatedBy`
|
`domain_entities_do_not_carry_audit_fields` (no `createdAt`/`updatedAt`/`createdBy`/`updatedBy`
|
||||||
fields under `..domain..`).
|
fields under `..domain..`).
|
||||||
|
|||||||
+1
-2
@@ -11,8 +11,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
* (feature-persistence-auditing-contract D1/D3/D4/D5).
|
* (feature-persistence-auditing-contract D1/D3/D4/D5).
|
||||||
*
|
*
|
||||||
* <p>Exercised through a tiny concrete subclass because {@link AuditableEntity} is a
|
* <p>Exercised through a tiny concrete subclass because {@link AuditableEntity} is a
|
||||||
* {@code @MappedSuperclass} (JPA cannot instantiate it standalone, and the production subclass
|
* {@code @MappedSuperclass} (JPA cannot instantiate it standalone).
|
||||||
* {@code WorkLogEntity} lives in the sample module).
|
|
||||||
*/
|
*/
|
||||||
class AuditableEntityTest {
|
class AuditableEntityTest {
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Data MongoDB infrastructure. Design rationale lives in [README.md](README.md).
|
|||||||
|
|
||||||
## Forbidden
|
## Forbidden
|
||||||
|
|
||||||
- Inbound adapters, sibling outbound adapters, `app-bootstrap`, `sample-portfolio` (ArchUnit
|
- Inbound adapters, sibling outbound adapters, or `app-bootstrap` (ArchUnit
|
||||||
`OUTBOUND_ADAPTERS_*` family rules).
|
`OUTBOUND_ADAPTERS_*` family rules).
|
||||||
- Shipping placeholder `Example*` document, repository, record, or adapter types in production.
|
- Shipping placeholder `Example*` document, repository, record, or adapter types in production.
|
||||||
- Adding idempotency/outbox/lock on Mongo without a separately approved contract.
|
- Adding idempotency/outbox/lock on Mongo without a separately approved contract.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Package root: `dev.caskeleton.bootstrap`.
|
|||||||
## Allowed
|
## Allowed
|
||||||
|
|
||||||
- Runtime leaves whose dependency edge is explicitly allowed and whose `runtime_memberships`
|
- Runtime leaves whose dependency edge is explicitly allowed and whose `runtime_memberships`
|
||||||
includes `app-bootstrap` in `src/config/architecture/modules.json`; do not duplicate the 19-leaf
|
includes `app-bootstrap` in `src/config/architecture/modules.json`; do not duplicate the 18-leaf
|
||||||
dependency list here.
|
dependency list here.
|
||||||
- Spring Boot startup/runtime dependencies.
|
- Spring Boot startup/runtime dependencies.
|
||||||
- ArchUnit in tests.
|
- ArchUnit in tests.
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
이 모듈은 비즈니스 로직을 담지 않는다. Spring Boot 기동, 런타임 설정 바인딩, 기본 runtime
|
이 모듈은 비즈니스 로직을 담지 않는다. Spring Boot 기동, 런타임 설정 바인딩, 기본 runtime
|
||||||
모듈 간 최종 와이어링, 그리고 composition classpath를 대상으로 한 중앙 아키텍처 테스트만 둔다.
|
모듈 간 최종 와이어링, 그리고 composition classpath를 대상으로 한 중앙 아키텍처 테스트만 둔다.
|
||||||
19개 leaf 전체의 프로젝트 edge와 두 composition root의 실제 runtime membership은 JSON registry를
|
18개 leaf 전체의 프로젝트 edge와 production composition root의 실제 runtime membership은 JSON registry를
|
||||||
읽는 Gradle gate가 별도로 검사한다. 허용/금지 의존, 책임 범위, 테스트 명령 같은 **모듈 규칙**의
|
읽는 Gradle gate가 별도로 검사한다. 허용/금지 의존, 책임 범위, 테스트 명령 같은 **모듈 규칙**의
|
||||||
SSOT 는 [CLAUDE.md](CLAUDE.md) 다.
|
SSOT 는 [CLAUDE.md](CLAUDE.md) 다.
|
||||||
|
|
||||||
기본 composition은 `build.gradle`의 main project dependency와 registry의
|
기본 composition은 `build.gradle`의 main project dependency와 registry의
|
||||||
`runtime_memberships=["app-bootstrap"]`가 정확히 일치해야 한다. GraphQL, gRPC, WebSocket,
|
`runtime_memberships=["app-bootstrap"]`가 정확히 일치해야 한다. GraphQL, gRPC, WebSocket,
|
||||||
MongoDB leaf의 membership은 비어 있어 두 shipped runtime에 포함되지 않는다. object storage는
|
MongoDB와 object storage leaf의 membership은 비어 있어 production runtime에 포함되지 않는다.
|
||||||
sample runtime에만 있고 file server는 기본 app runtime에 이미 포함된다.
|
file server는 기본 app runtime에 이미 포함된다.
|
||||||
|
|
||||||
`conditionalTransportTest`는 GraphQL/gRPC/WebSocket 세 leaf를 test-only classpath에서 함께
|
`conditionalTransportTest`는 GraphQL/gRPC/WebSocket 세 leaf를 test-only classpath에서 함께
|
||||||
resolve하는 명시적 qualification composition이다. main `implementation`/`runtimeOnly` edge가 아니므로
|
resolve하는 명시적 qualification composition이다. main `implementation`/`runtimeOnly` edge가 아니므로
|
||||||
@@ -870,10 +870,6 @@ ArchUnit 규칙이 **금지**하는 타입을 fixture 가 일부러 import 해
|
|||||||
- `spring-cloud-context` — `@RefreshScope` 금지(no-refresh-scope) fixture 용. Spring Cloud 는
|
- `spring-cloud-context` — `@RefreshScope` 금지(no-refresh-scope) fixture 용. Spring Cloud 는
|
||||||
production 에 없고 규칙은 애너테이션을 FQN 문자열로 참조한다. BOM 이 spring-cloud 좌표를 관리하지
|
production 에 없고 규칙은 애너테이션을 FQN 문자열로 참조한다. BOM 이 spring-cloud 좌표를 관리하지
|
||||||
않아 버전을 명시 고정한다.
|
않아 버전을 명시 고정한다.
|
||||||
- **`testImplementation project(':sample-portfolio')`** — ArchUnit 이 템플릿 reference 구현을 분석하려고
|
|
||||||
test 경로에만 둔다. **production 은 절대 sample-portfolio 에 의존 금지** —
|
|
||||||
`production_code_does_not_depend_on_sample_portfolio` 규칙이 강제한다.
|
|
||||||
|
|
||||||
### 빌드 설정
|
### 빌드 설정
|
||||||
- **test JVM UTC 고정(`-Duser.timezone=UTC`)** — `RuntimeHealthLifecycleContractTest`가
|
- **test JVM UTC 고정(`-Duser.timezone=UTC`)** — `RuntimeHealthLifecycleContractTest`가
|
||||||
`TimeZone.getDefault().getID() == "UTC"`를 단언해, 호스트 로케일과 무관하게 타임스탬프 산술이
|
`TimeZone.getDefault().getID() == "UTC"`를 단언해, 호스트 로케일과 무관하게 타임스탬프 산술이
|
||||||
|
|||||||
@@ -4,25 +4,6 @@ apply plugin: 'org.springframework.boot'
|
|||||||
|
|
||||||
apply from: "${rootProject.projectDir}/gradle/strict-qualification-test.gradle"
|
apply from: "${rootProject.projectDir}/gradle/strict-qualification-test.gradle"
|
||||||
|
|
||||||
// The sample fixture (sampleFixture -> sample-portfolio -> objectstorage) pulls software.amazon.awssdk:s3
|
|
||||||
// onto the sample-on test classpath; its version is managed by the AWS SDK v2 BOM (NOT the Spring Boot
|
|
||||||
// BOM). Import that BOM at this module's scope so the transitive s3 dependency resolves for the ArchUnit
|
|
||||||
// sample-on analysis. AWS-SDK version SSOT = ext.awsSdkVersion.
|
|
||||||
dependencyManagement {
|
|
||||||
imports {
|
|
||||||
mavenBom "software.amazon.awssdk:bom:${awsSdkVersion}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// feature-sample-removal-adoption-contract D7 — ordinary tests may inspect the sample fixture,
|
|
||||||
// while sampleOffTest compiles the same core suite without the sample project on either classpath.
|
|
||||||
configurations {
|
|
||||||
sampleFixture {
|
|
||||||
canBeConsumed = false
|
|
||||||
canBeResolved = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
sampleOffTest {
|
sampleOffTest {
|
||||||
java.srcDirs = sourceSets.test.java.srcDirs
|
java.srcDirs = sourceSets.test.java.srcDirs
|
||||||
@@ -44,8 +25,6 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
testCompileClasspath.extendsFrom sampleFixture
|
|
||||||
testRuntimeClasspath.extendsFrom sampleFixture
|
|
||||||
sampleOffTestImplementation.extendsFrom testImplementation
|
sampleOffTestImplementation.extendsFrom testImplementation
|
||||||
sampleOffTestCompileOnly.extendsFrom testCompileOnly
|
sampleOffTestCompileOnly.extendsFrom testCompileOnly
|
||||||
sampleOffTestRuntimeOnly.extendsFrom testRuntimeOnly
|
sampleOffTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||||
@@ -115,8 +94,6 @@ dependencies {
|
|||||||
testImplementation 'org.springframework.boot:spring-boot-starter-json'
|
testImplementation 'org.springframework.boot:spring-boot-starter-json'
|
||||||
// test-only: parses checked-in Messaging capability registries for the fail-closed drift gate.
|
// test-only: parses checked-in Messaging capability registries for the fail-closed drift gate.
|
||||||
testImplementation 'org.yaml:snakeyaml'
|
testImplementation 'org.yaml:snakeyaml'
|
||||||
// sample-on only: ArchUnit analyses the reference impl. sampleOffTest intentionally omits it.
|
|
||||||
sampleFixture project(':sample-portfolio')
|
|
||||||
// test-only: ArchUnit violation fixtures intentionally import forbidden types. See README.
|
// test-only: ArchUnit violation fixtures intentionally import forbidden types. See README.
|
||||||
testCompileOnly 'org.springframework:spring-tx'
|
testCompileOnly 'org.springframework:spring-tx'
|
||||||
// test-only: streaming/websocket violation fixtures import these forbidden packages. See README.
|
// test-only: streaming/websocket violation fixtures import these forbidden packages. See README.
|
||||||
@@ -213,21 +190,6 @@ tasks.named('check') {
|
|||||||
dependsOn tasks.named('functionalTest')
|
dependsOn tasks.named('functionalTest')
|
||||||
}
|
}
|
||||||
|
|
||||||
// feature-developer-experience-contract D2/D7 delegation: the DX entrypoint verifies production
|
|
||||||
// isolation and the sample-on build contract. sample-off is the separate sampleOffTest task.
|
|
||||||
tasks.register('bootstrapSampleContract', Test) {
|
|
||||||
group = 'developer experience'
|
|
||||||
description = 'Runs the delegated sample production-isolation/build contract for bootstrap.'
|
|
||||||
testClassesDirs = sourceSets.test.output.classesDirs
|
|
||||||
classpath = sourceSets.test.runtimeClasspath
|
|
||||||
useJUnitPlatform()
|
|
||||||
filter {
|
|
||||||
includeTestsMatching 'dev.caskeleton.bootstrap.contract.SampleRemovalSmokeContractTest'
|
|
||||||
}
|
|
||||||
outputs.upToDateWhen { false }
|
|
||||||
jvmArgs '-Duser.timezone=UTC'
|
|
||||||
}
|
|
||||||
|
|
||||||
bootJar {
|
bootJar {
|
||||||
mainClass = 'dev.caskeleton.bootstrap.CaSkeletonApplication'
|
mainClass = 'dev.caskeleton.bootstrap.CaSkeletonApplication'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,29 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
aopalliance:aopalliance:1.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
aopalliance:aopalliance:1.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
ch.qos.logback:logback-classic:1.5.21=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
ch.qos.logback:logback-classic:1.5.21=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
ch.qos.logback:logback-classic:1.5.34=sampleFixture
|
|
||||||
ch.qos.logback:logback-core:1.5.21=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
ch.qos.logback:logback-core:1.5.21=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
ch.qos.logback:logback-core:1.5.34=sampleFixture
|
|
||||||
com.approvaltests:approvaltests-util:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.approvaltests:approvaltests-util:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.approvaltests:approvaltests:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.approvaltests:approvaltests:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.ethlo.time:itu:1.14.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.ethlo.time:itu:1.14.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.21=sampleFixture
|
|
||||||
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson.core:jackson-core:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.4=sampleFixture
|
|
||||||
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.fasterxml.jackson:jackson-bom:2.21.4=sampleFixture
|
|
||||||
com.fasterxml:classmate:1.7.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.fasterxml:classmate:1.7.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.fasterxml:classmate:1.7.3=sampleFixture
|
|
||||||
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
com.github.docker-java:docker-java-api:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.github.docker-java:docker-java-api:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.github.docker-java:docker-java-transport-zerodep:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.github.docker-java:docker-java-transport-zerodep:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.github.docker-java:docker-java-transport:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.github.docker-java:docker-java-transport:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.github.f4b6a3:uuid-creator:6.1.1=sampleFixture,testRuntimeClasspath
|
|
||||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
|
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
|
||||||
com.github.spotbugs:spotbugs-annotations:4.8.6=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
com.github.spotbugs:spotbugs-annotations:4.8.6=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
com.github.spotbugs:spotbugs:4.10.2=spotbugs
|
com.github.spotbugs:spotbugs:4.10.2=spotbugs
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
||||||
com.google.android:annotations:4.1.1.4=conditionalTransportTestRuntimeClasspath
|
com.google.android:annotations:4.1.1.4=conditionalTransportTestRuntimeClasspath
|
||||||
com.google.api.grpc:proto-google-common-protos:2.41.0=conditionalTransportTestRuntimeClasspath
|
com.google.api.grpc:proto-google-common-protos:2.41.0=conditionalTransportTestRuntimeClasspath
|
||||||
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
@@ -72,24 +59,19 @@ com.networknt:json-schema-validator:3.0.2=productionRuntimeClasspath,runtimeClas
|
|||||||
com.nimbusds:content-type:2.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.nimbusds:content-type:2.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.nimbusds:lang-tag:1.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.nimbusds:lang-tag:1.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.nimbusds:nimbus-jose-jwt:9.37.4=sampleFixture
|
|
||||||
com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
|
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
|
||||||
com.squareup.okhttp3:okhttp-jvm:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.squareup.okhttp3:okhttp-jvm:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.squareup.okhttp3:okhttp:4.12.0=sampleFixture
|
|
||||||
com.squareup.okhttp3:okhttp:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.squareup.okhttp3:okhttp:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.squareup.okio:okio-jvm:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.squareup.okio:okio-jvm:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.squareup.okio:okio-jvm:3.6.0=sampleFixture
|
|
||||||
com.squareup.okio:okio:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.squareup.okio:okio:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.squareup.okio:okio:3.6.0=sampleFixture
|
com.sun.istack:istack-commons-runtime:4.1.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.sun.istack:istack-commons-runtime:4.1.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
|
||||||
com.tngtech.archunit:archunit-junit5-api:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.tngtech.archunit:archunit-junit5-api:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.tngtech.archunit:archunit-junit5-engine:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
com.tngtech.archunit:archunit-junit5-engine:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
com.tngtech.archunit:archunit-junit5:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.tngtech.archunit:archunit-junit5:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.tngtech.archunit:archunit:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.tngtech.archunit:archunit:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
com.zaxxer:HikariCP:6.3.3=sampleFixture
|
|
||||||
com.zaxxer:HikariCP:7.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
com.zaxxer:HikariCP:7.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
commons-beanutils:commons-beanutils:1.11.0=checkstyle
|
commons-beanutils:commons-beanutils:1.11.0=checkstyle
|
||||||
commons-codec:commons-codec:1.19.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
commons-codec:commons-codec:1.19.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
@@ -98,16 +80,16 @@ commons-io:commons-io:2.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeC
|
|||||||
commons-io:commons-io:2.21.0=spotbugs
|
commons-io:commons-io:2.21.0=spotbugs
|
||||||
commons-logging:commons-logging:1.3.5=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
commons-logging:commons-logging:1.3.5=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
info.picocli:picocli:4.7.7=checkstyle
|
info.picocli:picocli:4.7.7=checkstyle
|
||||||
io.github.cdimascio:dotenv-java:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.cdimascio:dotenv-java:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
io.github.resilience4j:resilience4j-bulkhead:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-bulkhead:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.resilience4j:resilience4j-core:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-core:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.resilience4j:resilience4j-micrometer:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-micrometer:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.resilience4j:resilience4j-ratelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-ratelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.resilience4j:resilience4j-retry:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-retry:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.github.resilience4j:resilience4j-timelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.github.resilience4j:resilience4j-timelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.grpc:grpc-api:1.68.1=conditionalTransportTestRuntimeClasspath
|
io.grpc:grpc-api:1.68.1=conditionalTransportTestRuntimeClasspath
|
||||||
io.grpc:grpc-context:1.68.1=conditionalTransportTestRuntimeClasspath
|
io.grpc:grpc-context:1.68.1=conditionalTransportTestRuntimeClasspath
|
||||||
io.grpc:grpc-core:1.68.1=conditionalTransportTestRuntimeClasspath
|
io.grpc:grpc-core:1.68.1=conditionalTransportTestRuntimeClasspath
|
||||||
@@ -118,21 +100,13 @@ io.grpc:grpc-services:1.68.1=conditionalTransportTestRuntimeClasspath
|
|||||||
io.grpc:grpc-stub:1.68.1=conditionalTransportTestRuntimeClasspath
|
io.grpc:grpc-stub:1.68.1=conditionalTransportTestRuntimeClasspath
|
||||||
io.grpc:grpc-util:1.68.1=conditionalTransportTestRuntimeClasspath
|
io.grpc:grpc-util:1.68.1=conditionalTransportTestRuntimeClasspath
|
||||||
io.lettuce:lettuce-core:6.8.1.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.lettuce:lettuce-core:6.8.1.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.micrometer:context-propagation:1.1.4=sampleFixture
|
|
||||||
io.micrometer:context-propagation:1.2.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:context-propagation:1.2.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-commons:1.15.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-commons:1.16.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-commons:1.16.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-core:1.15.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-core:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-core:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-jakarta9:1.15.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-jakarta9:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-jakarta9:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-observation:1.15.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-observation:1.16.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-observation:1.16.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-registry-prometheus:1.15.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-registry-prometheus:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-registry-prometheus:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-tracing-bridge-otel:1.5.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-tracing-bridge-otel:1.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-tracing-bridge-otel:1.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.micrometer:micrometer-tracing:1.5.12=sampleFixture
|
|
||||||
io.micrometer:micrometer-tracing:1.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.micrometer:micrometer-tracing:1.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-buffer:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-buffer:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-codec-base:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-codec-base:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
@@ -142,11 +116,8 @@ io.netty:netty-codec-dns:4.2.17.Final=productionRuntimeClasspath,runtimeClasspat
|
|||||||
io.netty:netty-codec-http2:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-codec-http2:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-codec-http3:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-codec-http3:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-codec-http:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-codec-http:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-codec-marshalling:4.2.17.Final=testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-native-quic:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-codec-native-quic:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-codec-protobuf:4.2.17.Final=testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-socks:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-codec-socks:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-codec:4.2.17.Final=testRuntimeClasspath
|
|
||||||
io.netty:netty-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-handler-proxy:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-handler-proxy:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-handler:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-handler:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
@@ -158,113 +129,78 @@ io.netty:netty-transport-classes-epoll:4.2.17.Final=productionRuntimeClasspath,r
|
|||||||
io.netty:netty-transport-native-epoll:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-transport-native-epoll:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-transport-native-unix-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-transport-native-unix-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.netty:netty-transport:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.netty:netty-transport:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry.semconv:opentelemetry-semconv:1.32.0=sampleFixture
|
|
||||||
io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-api:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-context:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-exporter-common:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-exporter-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-exporter-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-exporter-otlp-common:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-exporter-otlp:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-extension-trace-propagators:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-sdk-common:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-sdk-logs:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-sdk-metrics:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-sdk-trace:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.opentelemetry:opentelemetry-sdk:1.49.0=sampleFixture
|
|
||||||
io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.perfmark:perfmark-api:0.27.0=conditionalTransportTestRuntimeClasspath
|
io.perfmark:perfmark-api:0.27.0=conditionalTransportTestRuntimeClasspath
|
||||||
io.projectreactor.netty:reactor-netty-core:1.3.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.projectreactor.netty:reactor-netty-core:1.3.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.projectreactor.netty:reactor-netty-http:1.3.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.projectreactor.netty:reactor-netty-http:1.3.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.projectreactor:reactor-core:3.7.19=sampleFixture
|
|
||||||
io.projectreactor:reactor-core:3.8.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.projectreactor:reactor-core:3.8.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.prometheus:prometheus-metrics-config:1.3.10=sampleFixture
|
|
||||||
io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.prometheus:prometheus-metrics-core:1.3.10=sampleFixture
|
|
||||||
io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.prometheus:prometheus-metrics-exposition-formats:1.3.10=sampleFixture
|
|
||||||
io.prometheus:prometheus-metrics-exposition-formats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.prometheus:prometheus-metrics-exposition-formats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.prometheus:prometheus-metrics-exposition-textformats:1.3.10=sampleFixture
|
|
||||||
io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.prometheus:prometheus-metrics-model:1.3.10=sampleFixture
|
|
||||||
io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.prometheus:prometheus-metrics-tracer-common:1.3.10=sampleFixture
|
|
||||||
io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
io.smallrye:jandex:3.2.0=sampleFixture
|
|
||||||
io.swagger.core.v3:swagger-annotations-jakarta:2.2.29=sampleFixture
|
|
||||||
io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.swagger.core.v3:swagger-core-jakarta:2.2.29=sampleFixture
|
|
||||||
io.swagger.core.v3:swagger-core-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.swagger.core.v3:swagger-core-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
io.swagger.core.v3:swagger-models-jakarta:2.2.29=sampleFixture
|
|
||||||
io.swagger.core.v3:swagger-models-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
io.swagger.core.v3:swagger-models-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
jakarta.activation:jakarta.activation-api:2.1.4=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
jakarta.activation:jakarta.activation-api:2.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
jakarta.annotation:jakarta.annotation-api:2.1.1=sampleFixture
|
|
||||||
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
jakarta.inject:jakarta.inject-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
jakarta.inject:jakarta.inject-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
jakarta.persistence:jakarta.persistence-api:3.1.0=sampleFixture
|
|
||||||
jakarta.persistence:jakarta.persistence-api:3.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
jakarta.persistence:jakarta.persistence-api:3.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
jakarta.transaction:jakarta.transaction-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
jakarta.transaction:jakarta.transaction-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
jakarta.validation:jakarta.validation-api:3.0.2=sampleFixture
|
|
||||||
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
jakarta.websocket:jakarta.websocket-api:2.2.0=sampleOffTestCompileClasspath,testCompileClasspath
|
jakarta.websocket:jakarta.websocket-api:2.2.0=sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=sampleOffTestCompileClasspath,testCompileClasspath
|
jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=sampleFixture
|
|
||||||
javax.inject:javax.inject:1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
javax.inject:javax.inject:1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
jaxen:jaxen:2.0.0=spotbugs
|
jaxen:jaxen:2.0.0=spotbugs
|
||||||
me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.bytebuddy:byte-buddy-agent:1.17.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
net.bytebuddy:byte-buddy-agent:1.17.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.bytebuddy:byte-buddy:1.17.8=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
net.bytebuddy:byte-buddy:1.17.8=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.java.dev.jna:jna:5.18.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
net.java.dev.jna:jna:5.18.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.minidev:accessors-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
net.minidev:accessors-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.minidev:json-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
net.minidev:json-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
|
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
|
||||||
org.antlr:antlr4-runtime:4.13.2=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.antlr:antlr4-runtime:4.13.2=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.bcel:bcel:6.12.0=spotbugs
|
org.apache.bcel:bcel:6.12.0=spotbugs
|
||||||
org.apache.commons:commons-compress:1.28.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.apache.commons:commons-compress:1.28.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.commons:commons-lang3:3.20.0=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
org.apache.commons:commons-lang3:3.20.0=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.commons:commons-text:1.15.0=spotbugs
|
org.apache.commons:commons-text:1.15.0=spotbugs
|
||||||
org.apache.commons:commons-text:1.3=checkstyle
|
org.apache.commons:commons-text:1.3=checkstyle
|
||||||
org.apache.httpcomponents.client5:httpclient5:5.5.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.apache.httpcomponents.client5:httpclient5:5.5.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.apache.httpcomponents.core5:httpcore5:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.apache.httpcomponents.core5:httpcore5:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.apache.httpcomponents:httpclient:4.5.13=checkstyle,testRuntimeClasspath
|
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
|
||||||
org.apache.httpcomponents:httpcore:4.4.16=checkstyle,testRuntimeClasspath
|
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
|
||||||
org.apache.kafka:kafka-clients:4.1.1=sampleOffTestCompileClasspath,testCompileClasspath
|
org.apache.kafka:kafka-clients:4.1.1=sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
org.apache.logging.log4j:log4j-api:2.24.3=sampleFixture
|
|
||||||
org.apache.logging.log4j:log4j-api:2.25.2=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
org.apache.logging.log4j:log4j-api:2.25.2=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.logging.log4j:log4j-core:2.25.2=spotbugs
|
org.apache.logging.log4j:log4j-core:2.25.2=spotbugs
|
||||||
org.apache.logging.log4j:log4j-to-slf4j:2.24.3=sampleFixture
|
|
||||||
org.apache.logging.log4j:log4j-to-slf4j:2.25.2=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.apache.logging.log4j:log4j-to-slf4j:2.25.2=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
|
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
|
||||||
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
|
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
|
||||||
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
|
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
|
||||||
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
|
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
|
||||||
org.apache.tomcat.embed:tomcat-embed-core:10.1.55=sampleFixture
|
|
||||||
org.apache.tomcat.embed:tomcat-embed-core:11.0.14=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.apache.tomcat.embed:tomcat-embed-core:11.0.14=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.tomcat.embed:tomcat-embed-el:10.1.55=sampleFixture
|
|
||||||
org.apache.tomcat.embed:tomcat-embed-el:11.0.14=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.apache.tomcat.embed:tomcat-embed-el:11.0.14=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.tomcat.embed:tomcat-embed-websocket:10.1.55=sampleFixture
|
|
||||||
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.14=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.14=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.apache.xbean:xbean-reflect:3.7=checkstyle
|
org.apache.xbean:xbean-reflect:3.7=checkstyle
|
||||||
org.apiguardian:apiguardian-api:1.1.2=conditionalTransportTestCompileClasspath,functionalTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
org.apiguardian:apiguardian-api:1.1.2=conditionalTransportTestCompileClasspath,functionalTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
org.aspectj:aspectjweaver:1.9.25=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.aspectj:aspectjweaver:1.9.25=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.aspectj:aspectjweaver:1.9.25.1=sampleFixture
|
|
||||||
org.assertj:assertj-core:3.27.6=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.assertj:assertj-core:3.27.6=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.awaitility:awaitility:4.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.awaitility:awaitility:4.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.checkerframework:checker-qual:3.42.0=conditionalTransportTestRuntimeClasspath
|
org.checkerframework:checker-qual:3.42.0=conditionalTransportTestRuntimeClasspath
|
||||||
@@ -275,7 +211,7 @@ org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
|
|||||||
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
|
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
|
||||||
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
|
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
|
||||||
org.dom4j:dom4j:2.2.0=spotbugs
|
org.dom4j:dom4j:2.2.0=spotbugs
|
||||||
org.eclipse.angus:angus-activation:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.eclipse.angus:angus-activation:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.eclipse.jetty.compression:jetty-compression-common:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.eclipse.jetty.compression:jetty-compression-common:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.eclipse.jetty.compression:jetty-compression-gzip:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.eclipse.jetty.compression:jetty-compression-gzip:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.eclipse.jetty:jetty-alpn-client:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.eclipse.jetty:jetty-alpn-client:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
@@ -284,32 +220,19 @@ org.eclipse.jetty:jetty-http:12.1.4=productionRuntimeClasspath,runtimeClasspath,
|
|||||||
org.eclipse.jetty:jetty-io:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.eclipse.jetty:jetty-io:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.eclipse.jetty:jetty-util:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.eclipse.jetty:jetty-util:12.1.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.flywaydb:flyway-core:11.14.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.flywaydb:flyway-core:11.14.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.flywaydb:flyway-core:11.7.2=sampleFixture
|
|
||||||
org.flywaydb:flyway-database-postgresql:11.14.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.flywaydb:flyway-database-postgresql:11.14.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.flywaydb:flyway-database-postgresql:11.7.2=sampleFixture
|
|
||||||
org.glassfish.jaxb:jaxb-core:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.glassfish.jaxb:jaxb-core:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.glassfish.jaxb:jaxb-core:4.0.9=sampleFixture
|
|
||||||
org.glassfish.jaxb:jaxb-runtime:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.glassfish.jaxb:jaxb-runtime:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.glassfish.jaxb:jaxb-runtime:4.0.9=sampleFixture
|
|
||||||
org.glassfish.jaxb:txw2:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.glassfish.jaxb:txw2:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.glassfish.jaxb:txw2:4.0.9=sampleFixture
|
|
||||||
org.hamcrest:hamcrest:3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.hamcrest:hamcrest:3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.hibernate.common:hibernate-commons-annotations:7.0.3.Final=sampleFixture
|
|
||||||
org.hibernate.models:hibernate-models:1.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.hibernate.models:hibernate-models:1.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.hibernate.orm:hibernate-core:6.6.53.Final=sampleFixture
|
|
||||||
org.hibernate.orm:hibernate-core:7.1.8.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.hibernate.orm:hibernate-core:7.1.8.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.hibernate.validator:hibernate-validator:8.0.3.Final=sampleFixture
|
|
||||||
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||||
org.jboss.logging:jboss-logging:3.6.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.jboss.logging:jboss-logging:3.6.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.jboss.logging:jboss-logging:3.6.3.Final=sampleFixture
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.25=sampleFixture
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.25=sampleFixture
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.25=sampleFixture
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:1.9.25=sampleFixture
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.jetbrains:annotations:13.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture
|
org.jetbrains:annotations:13.0=productionRuntimeClasspath,runtimeClasspath
|
||||||
org.jetbrains:annotations:17.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.jetbrains:annotations:17.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,conditionalTransportTestAnnotationProcessor,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,functionalTestCompileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,conditionalTransportTestAnnotationProcessor,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,functionalTestCompileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||||
org.junit.jupiter:junit-jupiter-api:6.0.1=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.junit.jupiter:junit-jupiter-api:6.0.1=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
@@ -322,11 +245,11 @@ org.junit.platform:junit-platform-launcher:6.0.1=conditionalTransportTestRuntime
|
|||||||
org.junit.platform:junit-platform-testkit:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.junit.platform:junit-platform-testkit:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.junit:junit-bom:6.0.1=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.junit:junit-bom:6.0.1=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.junit:junit-bom:6.1.0=spotbugs
|
org.junit:junit-bom:6.1.0=spotbugs
|
||||||
org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.mockito:mockito-core:5.20.0=mockitoAgent,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.mockito:mockito-core:5.20.0=mockitoAgent,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.mockito:mockito-junit-jupiter:5.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.mockito:mockito-junit-jupiter:5.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.objenesis:objenesis:3.3=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.objenesis:objenesis:3.3=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.openapitools:jackson-databind-nullable:0.2.6=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.openapitools:jackson-databind-nullable:0.2.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.opentest4j:opentest4j:1.3.0=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.opentest4j:opentest4j:1.3.0=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
@@ -339,26 +262,18 @@ org.ow2.asm:asm-util:9.10.1=spotbugs
|
|||||||
org.ow2.asm:asm:9.10.1=spotbugs
|
org.ow2.asm:asm:9.10.1=spotbugs
|
||||||
org.ow2.asm:asm:9.7.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.ow2.asm:asm:9.7.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.pcollections:pcollections:4.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
org.pcollections:pcollections:4.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||||
org.postgresql:postgresql:42.7.11=sampleFixture
|
|
||||||
org.postgresql:postgresql:42.7.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.postgresql:postgresql:42.7.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.reactivestreams:reactive-streams:1.0.4=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.reactivestreams:reactive-streams:1.0.4=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.reflections:reflections:0.10.2=checkstyle
|
org.reflections:reflections:0.10.2=checkstyle
|
||||||
org.rnorth.duct-tape:duct-tape:1.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.rnorth.duct-tape:duct-tape:1.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.skyscreamer:jsonassert:1.5.3=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.skyscreamer:jsonassert:1.5.3=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.slf4j:jul-to-slf4j:2.0.18=sampleFixture
|
|
||||||
org.slf4j:slf4j-api:2.0.17=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
|
org.slf4j:slf4j-api:2.0.17=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
|
||||||
org.slf4j:slf4j-api:2.0.18=sampleFixture
|
|
||||||
org.slf4j:slf4j-simple:2.0.17=checkstyle,spotbugsSlf4j
|
org.slf4j:slf4j-simple:2.0.17=checkstyle,spotbugsSlf4j
|
||||||
org.springdoc:springdoc-openapi-starter-common:2.8.6=sampleFixture
|
|
||||||
org.springdoc:springdoc-openapi-starter-common:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springdoc:springdoc-openapi-starter-common:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6=sampleFixture
|
|
||||||
org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-actuator:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-autoconfigure:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-configuration-processor:4.0.0=annotationProcessor
|
org.springframework.boot:spring-boot-configuration-processor:4.0.0=annotationProcessor
|
||||||
org.springframework.boot:spring-boot-data-commons:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-data-commons:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
@@ -384,37 +299,26 @@ org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.0=produ
|
|||||||
org.springframework.boot:spring-boot-security:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-security:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-servlet:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-servlet:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-sql:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-sql:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-actuator:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-data-jpa:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-data-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-data-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-flyway:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-flyway:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-graphql:4.0.0=conditionalTransportTestRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-graphql:4.0.0=conditionalTransportTestRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-jackson-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-jackson-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-jackson:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-jackson:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-jdbc:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-jdbc:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-jdbc:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-json:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-json:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-json:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-logging:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-logging:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-logging:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-security:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-security:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-security:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-tomcat:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-tomcat:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-tomcat:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-validation:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-validation:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-validation:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-web:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter-web:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-web:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-webmvc:4.0.0=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-webmvc:4.0.0=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter-websocket:4.0.0=conditionalTransportTestRuntimeClasspath
|
org.springframework.boot:spring-boot-starter-websocket:4.0.0=conditionalTransportTestRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-starter:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot-starter:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-starter:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-test-autoconfigure:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-test-autoconfigure:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
@@ -425,62 +329,36 @@ org.springframework.boot:spring-boot-web-server:4.0.0=conditionalTransportTestRu
|
|||||||
org.springframework.boot:spring-boot-webmvc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-webmvc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-webmvc:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot-webmvc:4.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot-websocket:4.0.0=conditionalTransportTestRuntimeClasspath
|
org.springframework.boot:spring-boot-websocket:4.0.0=conditionalTransportTestRuntimeClasspath
|
||||||
org.springframework.boot:spring-boot:3.5.16=sampleFixture
|
|
||||||
org.springframework.boot:spring-boot:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.boot:spring-boot:4.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.cloud:spring-cloud-context:4.1.4=sampleOffTestCompileClasspath,testCompileClasspath
|
org.springframework.cloud:spring-cloud-context:4.1.4=sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
org.springframework.data:spring-data-commons:3.5.13=sampleFixture
|
|
||||||
org.springframework.data:spring-data-commons:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.data:spring-data-commons:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.data:spring-data-jpa:3.5.13=sampleFixture
|
|
||||||
org.springframework.data:spring-data-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.data:spring-data-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.graphql:spring-graphql:2.0.0=conditionalTransportTestRuntimeClasspath
|
org.springframework.graphql:spring-graphql:2.0.0=conditionalTransportTestRuntimeClasspath
|
||||||
org.springframework.integration:spring-integration-core:6.5.10=sampleFixture
|
|
||||||
org.springframework.integration:spring-integration-core:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.integration:spring-integration-core:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.integration:spring-integration-jdbc:6.5.10=sampleFixture
|
|
||||||
org.springframework.integration:spring-integration-jdbc:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.integration:spring-integration-jdbc:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.retry:spring-retry:2.0.13=sampleFixture
|
|
||||||
org.springframework.security:spring-security-config:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-config:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-config:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-core:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-core:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-core:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-crypto:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-crypto:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-crypto:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-oauth2-client:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-oauth2-client:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-oauth2-core:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-oauth2-core:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-oauth2-core:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-oauth2-jose:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-oauth2-jose:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-oauth2-jose:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-oauth2-resource-server:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-oauth2-resource-server:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-oauth2-resource-server:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-test:7.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-test:7.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.security:spring-security-web:6.5.11=sampleFixture
|
|
||||||
org.springframework.security:spring-security-web:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework.security:spring-security-web:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework.session:spring-session-core:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework.session:spring-session-core:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-aop:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-aop:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-aop:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-aspects:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-aspects:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-aspects:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-beans:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-beans:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-beans:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-context:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-context:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-context:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-core:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-core:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-core:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-expression:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-expression:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-expression:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-jcl:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-jdbc:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-jdbc:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-jdbc:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-messaging:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-messaging:7.0.1=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-messaging:7.0.1=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-orm:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-orm:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-orm:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-test:7.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-test:7.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-tx:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-tx:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-tx:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-web:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-web:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-web:7.0.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-webflux:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
org.springframework:spring-webflux:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-webmvc:6.2.19=sampleFixture
|
|
||||||
org.springframework:spring-webmvc:7.0.1=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.springframework:spring-webmvc:7.0.1=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.springframework:spring-websocket:7.0.1=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
org.springframework:spring-websocket:7.0.1=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||||
org.testcontainers:testcontainers-database-commons:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.testcontainers:testcontainers-database-commons:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
@@ -490,39 +368,8 @@ org.testcontainers:testcontainers-postgresql:2.0.2=sampleOffTestCompileClasspath
|
|||||||
org.testcontainers:testcontainers:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.testcontainers:testcontainers:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
|
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
|
||||||
org.xmlunit:xmlunit-core:2.10.4=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.xmlunit:xmlunit-core:2.10.4=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
org.yaml:snakeyaml:2.4=sampleFixture
|
|
||||||
org.yaml:snakeyaml:2.5=compileClasspath,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
org.yaml:snakeyaml:2.5=compileClasspath,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
redis.clients.authentication:redis-authx-core:0.1.1-beta2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
redis.clients.authentication:redis-authx-core:0.1.1-beta2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||||
software.amazon.awssdk:annotations:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:apache-client:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:arns:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:auth:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:aws-core:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:aws-query-protocol:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:aws-xml-protocol:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:checksums-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:checksums:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:crt-core:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:endpoints-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth-aws-eventstream:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth-aws:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-client-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:identity-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:json-utils:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:metrics-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:netty-nio-client:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:profiles:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:protocol-core:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:regions:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:retries-spi:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:retries:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:s3:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:sdk-core:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:third-party-jackson-core:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:utils:2.30.0=testRuntimeClasspath
|
|
||||||
software.amazon.eventstream:eventstream:1.0.1=testRuntimeClasspath
|
|
||||||
tools.jackson.core:jackson-core:3.0.2=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
tools.jackson.core:jackson-core:3.0.2=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
tools.jackson.core:jackson-databind:3.0.2=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
tools.jackson.core:jackson-databind:3.0.2=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
tools.jackson:jackson-bom:3.0.2=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
tools.jackson:jackson-bom:3.0.2=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
|
|||||||
@@ -502,11 +502,8 @@ ca-skeleton:
|
|||||||
# alternative). Keys are RAW IdP role names (no ROLE_ prefix — that prefix only
|
# alternative). Keys are RAW IdP role names (no ROLE_ prefix — that prefix only
|
||||||
# exists on Spring authorities, not on the principal's raw role set), looked up
|
# exists on Spring authorities, not on the principal's raw role set), looked up
|
||||||
# case-insensitively. Values are explicitly enumerated `resource:action` permissions
|
# case-insensitively. Values are explicitly enumerated `resource:action` permissions
|
||||||
# (no wildcards — least-privilege, OWASP-AUTHZ-C4). The values below are the
|
# (no wildcards — least-privilege, OWASP-AUTHZ-C4). Product-specific roles belong here.
|
||||||
# sample-portfolio demonstration (§5): `user` may read/write, only `admin` may close.
|
role-permissions: {}
|
||||||
role-permissions:
|
|
||||||
user: worklog:read,worklog:write
|
|
||||||
admin: worklog:read,worklog:write,worklog:close
|
|
||||||
cors:
|
cors:
|
||||||
# true | false
|
# true | false
|
||||||
enabled: ${APP_SECURITY_CORS_ENABLED}
|
enabled: ${APP_SECURITY_CORS_ENABLED}
|
||||||
|
|||||||
+2
-34
@@ -233,38 +233,6 @@ class CleanArchitectureTest {
|
|||||||
+ "outbound adapters")
|
+ "outbound adapters")
|
||||||
.allowEmptyShould(true);
|
.allowEmptyShould(true);
|
||||||
|
|
||||||
@ArchTest
|
|
||||||
static final ArchRule SAMPLE_APPLICATION_HAS_NO_SLF4J =
|
|
||||||
noClasses()
|
|
||||||
.that()
|
|
||||||
.resideInAPackage("dev.caskeleton.sample.portfolio.application..")
|
|
||||||
.should()
|
|
||||||
.dependOnClassesThat()
|
|
||||||
.resideInAnyPackage("org.slf4j..")
|
|
||||||
.as(
|
|
||||||
"SAMPLE_APPLICATION_HAS_NO_SLF4J: sample application collaborators read "
|
|
||||||
+ "correlation context through application-core CorrelationIdPort; inbound "
|
|
||||||
+ "adapters own MDC")
|
|
||||||
.allowEmptyShould(true);
|
|
||||||
|
|
||||||
@ArchTest
|
|
||||||
static final ArchRule SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN =
|
|
||||||
noClasses()
|
|
||||||
.that()
|
|
||||||
.resideInAPackage("dev.caskeleton.sample.portfolio.application..")
|
|
||||||
.and()
|
|
||||||
.doNotHaveFullyQualifiedName(
|
|
||||||
"dev.caskeleton.sample.portfolio.application.poster.UploadPosterImageUseCase")
|
|
||||||
.should()
|
|
||||||
.dependOnClassesThat()
|
|
||||||
.resideInAPackage("dev.caskeleton.application.storage")
|
|
||||||
.as(
|
|
||||||
"Only the characterized UploadPosterImageUseCase may temporarily import the legacy "
|
|
||||||
+ "ObjectStoragePort/StoredObject contract. New sample business code must use "
|
|
||||||
+ "the semantic objectstorage ports. Task 23 moves compatibility into the exact "
|
|
||||||
+ "legacy slice; Task 24 separately reviews the administrative migration seam.")
|
|
||||||
.allowEmptyShould(true);
|
|
||||||
|
|
||||||
@ArchTest
|
@ArchTest
|
||||||
static final ArchRule APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION =
|
static final ArchRule APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION =
|
||||||
noClasses()
|
noClasses()
|
||||||
@@ -1844,7 +1812,7 @@ class CleanArchitectureTest {
|
|||||||
.haveRawType(assignableTo(ResourceId.class))
|
.haveRawType(assignableTo(ResourceId.class))
|
||||||
.as(
|
.as(
|
||||||
"D17 NO_LONG_ID_PK: a domain entity 'id' field must be a ResourceId value object"
|
"D17 NO_LONG_ID_PK: a domain entity 'id' field must be a ResourceId value object"
|
||||||
+ " (e.g. WorkLogId), never Long/long/int/Integer"
|
+ " (e.g. ArticleId), never Long/long/int/Integer"
|
||||||
+ " (feature-resource-identifier-contract D17). JPA @Id UUID columns in"
|
+ " (feature-resource-identifier-contract D17). JPA @Id UUID columns in"
|
||||||
+ " ..adapter.outbound.persistence.. are out of scope — they store the UUID as"
|
+ " ..adapter.outbound.persistence.. are out of scope — they store the UUID as"
|
||||||
+ " the PostgreSQL native uuid type per D10.")
|
+ " the PostgreSQL native uuid type per D10.")
|
||||||
@@ -1863,7 +1831,7 @@ class CleanArchitectureTest {
|
|||||||
.as(
|
.as(
|
||||||
"D5/D17 NO_UUID_RANDOM_IN_CONTROLLER: controllers and the application layer "
|
"D5/D17 NO_UUID_RANDOM_IN_CONTROLLER: controllers and the application layer "
|
||||||
+ "(use cases / services) must not generate resource identifiers directly "
|
+ "(use cases / services) must not generate resource identifiers directly "
|
||||||
+ "(UUID.randomUUID / UuidCreator) — inject the domain WorkLogIdFactory port "
|
+ "(UUID.randomUUID / UuidCreator) — inject a domain identifier factory port "
|
||||||
+ "instead (feature-resource-identifier-contract D5/D17). Scope follows the "
|
+ "instead (feature-resource-identifier-contract D5/D17). Scope follows the "
|
||||||
+ "D17 decision text (controller / service / use case); request/trace-id "
|
+ "D17 decision text (controller / service / use case); request/trace-id "
|
||||||
+ "generation in web filters is a distinct concern owned by D18 / "
|
+ "generation in web filters is a distinct concern owned by D18 / "
|
||||||
|
|||||||
+8
-13
@@ -24,9 +24,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||||||
|
|
||||||
class ConditionalTransportQualificationContractTest {
|
class ConditionalTransportQualificationContractTest {
|
||||||
|
|
||||||
// 29 pre-existing controls plus the eight release-blocking HTTP Client Platform gates
|
// 28 pre-existing controls plus the nine HTTP Client Platform gates
|
||||||
// registered in .github/ci-gate-matrix.yml (design §38).
|
// registered in .github/ci-gate-matrix.yml (design §38).
|
||||||
private static final int EXPECTED_GATE_COUNT = 38;
|
private static final int EXPECTED_GATE_COUNT = 37;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filler gates a well-formed fixture needs beside its one target gate.
|
* Filler gates a well-formed fixture needs beside its one target gate.
|
||||||
@@ -74,7 +74,6 @@ class ConditionalTransportQualificationContractTest {
|
|||||||
"filesystem-vulnerability-scan",
|
"filesystem-vulnerability-scan",
|
||||||
"documentation-links",
|
"documentation-links",
|
||||||
"object-storage-minio-managed-contract",
|
"object-storage-minio-managed-contract",
|
||||||
"poster-image-migration",
|
|
||||||
"object-storage-minio-managed-fault",
|
"object-storage-minio-managed-fault",
|
||||||
"object-storage-aws-protected-qualification",
|
"object-storage-aws-protected-qualification",
|
||||||
"redis-sdk-support-matrix",
|
"redis-sdk-support-matrix",
|
||||||
@@ -474,16 +473,12 @@ class ConditionalTransportQualificationContractTest {
|
|||||||
}
|
}
|
||||||
assertThat(ids).containsExactlyInAnyOrderElementsOf(EXPECTED_GATE_IDS);
|
assertThat(ids).containsExactlyInAnyOrderElementsOf(EXPECTED_GATE_IDS);
|
||||||
|
|
||||||
Map<?, ?> posterGate =
|
String objectStorageWorkflow =
|
||||||
gates.stream()
|
Files.readString(
|
||||||
.map(Map.class::cast)
|
repositoryRoot().resolve(".github/workflows/object-storage-qualification.yml"));
|
||||||
.filter(gate -> "poster-image-migration".equals(gate.get("id")))
|
assertThat(objectStorageWorkflow)
|
||||||
.findFirst()
|
.doesNotContain("poster-image-v7-migration")
|
||||||
.orElseThrow(() -> new AssertionError("missing Poster image migration gate"));
|
.doesNotContain(":sample-portfolio:posterImageMigrationTest");
|
||||||
assertThat(requireString(posterGate, "ref")).isEqualTo("posterImageMigrationTest");
|
|
||||||
assertThat(requireString(posterGate, "workflow")).isEqualTo("object-storage-qualification.yml");
|
|
||||||
assertThat(requireString(posterGate, "job")).isEqualTo("poster-image-v7-migration");
|
|
||||||
assertThat(requireString(posterGate, "execution")).isEqualTo("explicit");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertRejectedAsNotExplicit(ScriptResult result) {
|
private static void assertRejectedAsNotExplicit(ScriptResult result) {
|
||||||
|
|||||||
+1
-4
@@ -20,10 +20,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Gates 1–9 are the base contracts verified by this test.
|
* <li>Gates 1–9 are the base contracts verified by this test.
|
||||||
* <li>Gate #10 (OpenAPI drift) lives in {@code sample-portfolio} and is verified by its own
|
* <li>Gate #10 (sample removal smoke) is {@code
|
||||||
* module test. It is not loadable from {@code app-bootstrap}'s classpath and is therefore
|
|
||||||
* intentionally excluded here.
|
|
||||||
* <li>Gate #11 (sample removal smoke) is {@code
|
|
||||||
* dev.caskeleton.bootstrap.contract.SampleRemovalSmokeContractTest}, which is included in the
|
* dev.caskeleton.bootstrap.contract.SampleRemovalSmokeContractTest}, which is included in the
|
||||||
* 9-base enumeration below as the envelope/schema gate covers it (see concern mapping).
|
* 9-base enumeration below as the envelope/schema gate covers it (see concern mapping).
|
||||||
* </ul>
|
* </ul>
|
||||||
|
|||||||
+6
-12
@@ -81,21 +81,20 @@ class DeveloperExperienceContractTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void bootstrapIsOneOrderedFiveStageGradleEntrypoint() throws IOException {
|
void bootstrapIsOneOrderedFourStageGradleEntrypoint() throws IOException {
|
||||||
String build = read("src/build.gradle");
|
String build = read("src/build.gradle");
|
||||||
|
|
||||||
assertThat(build)
|
assertThat(build)
|
||||||
.contains("tasks.register('bootstrapCompile')")
|
.contains("tasks.register('bootstrapCompile')")
|
||||||
.contains("tasks.register('bootstrapDependencies'")
|
.contains("tasks.register('bootstrapDependencies'")
|
||||||
.contains("tasks.register('bootstrapMigrateAndStart'")
|
.contains("tasks.register('bootstrapMigrateAndStart'")
|
||||||
.contains("tasks.register('bootstrapSampleContract'")
|
|
||||||
.contains("tasks.register('bootstrapSmoke'")
|
.contains("tasks.register('bootstrapSmoke'")
|
||||||
.contains("tasks.register('bootstrap')");
|
.contains("tasks.register('bootstrap')")
|
||||||
|
.doesNotContain("tasks.register('bootstrapSampleContract'");
|
||||||
assertThat(build)
|
assertThat(build)
|
||||||
.contains("bootstrapDependencies.configure { dependsOn bootstrapCompile }")
|
.contains("bootstrapDependencies.configure { dependsOn bootstrapCompile }")
|
||||||
.contains("bootstrapMigrateAndStart.configure { dependsOn bootstrapDependencies }")
|
.contains("bootstrapMigrateAndStart.configure { dependsOn bootstrapDependencies }")
|
||||||
.contains("bootstrapSampleContract.configure { dependsOn bootstrapMigrateAndStart }")
|
.contains("bootstrapSmoke.configure { dependsOn bootstrapMigrateAndStart }");
|
||||||
.contains("bootstrapSmoke.configure { dependsOn bootstrapSampleContract }");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -807,17 +806,12 @@ class DeveloperExperienceContractTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void dockerDependencyCacheStagesDeclareTheCompleteConfigurationRegistry() throws IOException {
|
void dockerDependencyCacheStagesDeclareTheCompleteConfigurationRegistry() throws IOException {
|
||||||
for (String dockerfile : new String[] {"src/Dockerfile", "src/Dockerfile.sample"}) {
|
assertDockerDependencyCacheContract("src/Dockerfile", read("src/Dockerfile"));
|
||||||
assertDockerDependencyCacheContract(dockerfile, read(dockerfile));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void dockerBuildsConsumeOnlyGradleStagedExecutableJars() throws IOException {
|
void dockerBuildsConsumeOnlyGradleStagedExecutableJars() throws IOException {
|
||||||
Map<String, String> executableModules =
|
Map<String, String> executableModules = Map.of("src/Dockerfile", "app-bootstrap");
|
||||||
Map.of(
|
|
||||||
"src/Dockerfile", "app-bootstrap",
|
|
||||||
"src/Dockerfile.sample", "sample-portfolio");
|
|
||||||
|
|
||||||
executableModules.forEach(
|
executableModules.forEach(
|
||||||
(dockerfile, module) -> {
|
(dockerfile, module) -> {
|
||||||
|
|||||||
+44
-37
@@ -10,26 +10,23 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gate #11 (sample-removal smoke) of the contract-verification suite. The skeleton ships a
|
* Gate #10 (sample-removal smoke) of the contract-verification suite. The skeleton's concrete
|
||||||
* reference domain ({@code sample-portfolio}) that remains in the template as a fixture. Production
|
* reference domain has been removed. Production code and the sample-off contract build must remain
|
||||||
* code and the sample-off contract build must not depend on it. A downstream adopter may delete the
|
* independent of it so deleted sample code cannot re-enter the build or runtime by accident.
|
||||||
* module only after the sample-off build is green.
|
|
||||||
*
|
*
|
||||||
* <p>The contract is a build/test classpath matrix, not a Spring runtime profile:
|
* <p>The contract is a build/test classpath matrix, not a Spring runtime profile:
|
||||||
*
|
*
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>production modules reference the sample only through the app-bootstrap sample fixture
|
* <li>the concrete sample module, Dockerfile, registry leaf, and build dependency are absent;
|
||||||
* configuration;
|
|
||||||
* <li>app-bootstrap core test sources compile without importing sample types;
|
* <li>app-bootstrap core test sources compile without importing sample types;
|
||||||
* <li>the sample-off source set removes the fixture from its compile/runtime classpaths;
|
* <li>the sample-off source set proves the minimal bootstrap classpath remains sample-free;
|
||||||
* <li>sample-on and sample-off are both release-blocking CI jobs.
|
* <li>sample-off remains a release-blocking CI job.
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* <p>The build-graph scan is the no-datasource proxy for "core app/context boots green without the
|
* <p>The build-graph scan is the no-datasource proxy for "core app/context boots green without the
|
||||||
@@ -39,40 +36,49 @@ import org.yaml.snakeyaml.Yaml;
|
|||||||
*/
|
*/
|
||||||
class SampleRemovalSmokeContractTest {
|
class SampleRemovalSmokeContractTest {
|
||||||
|
|
||||||
/** A Gradle dependency line: {@code <config> project(':sample-portfolio')}. */
|
|
||||||
private static final Pattern SAMPLE_DEP =
|
private static final Pattern SAMPLE_DEP =
|
||||||
Pattern.compile("^\\s*([A-Za-z]+)\\s+project\\(\\s*['\"]:sample-portfolio['\"]\\s*\\)");
|
Pattern.compile("^\\s*([A-Za-z]+)\\s+project\\(\\s*['\"]:sample-portfolio['\"]\\s*\\)");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void productionModulesReferenceSampleOnlyAsTestFixtureDependency() throws IOException {
|
void concreteSampleModuleRegistryBuildEvidenceAndDockerfileAreAbsent() throws IOException {
|
||||||
RepositoryContractResources resources = RepositoryContractResources.fromSystemProperty();
|
RepositoryContractResources resources = RepositoryContractResources.fromSystemProperty();
|
||||||
|
Path root = resources.repositoryRoot();
|
||||||
|
|
||||||
|
assertThat(root.resolve("src/sample-portfolio/build.gradle")).doesNotExist();
|
||||||
|
assertThat(root.resolve("src/sample-portfolio/src/main")).doesNotExist();
|
||||||
|
assertThat(root.resolve("src/Dockerfile.sample")).doesNotExist();
|
||||||
|
|
||||||
List<String> violations = new ArrayList<>();
|
List<String> violations = new ArrayList<>();
|
||||||
for (String sourcePath : registeredProductionSourcePaths(resources)) {
|
for (String sourcePath : registeredSourcePathsWithoutSample(resources)) {
|
||||||
Path buildFile = resources.requireTrackedFile(sourcePath + "/build.gradle");
|
Path buildFile = resources.requireTrackedFile(sourcePath + "/build.gradle");
|
||||||
for (String line : Files.readAllLines(buildFile)) {
|
for (String line : Files.readAllLines(buildFile)) {
|
||||||
Matcher m = SAMPLE_DEP.matcher(line);
|
if (SAMPLE_DEP.matcher(line).find()) {
|
||||||
if (m.find() && !m.group(1).startsWith("test") && !m.group(1).equals("sampleFixture")) {
|
violations.add(sourcePath + ": '" + line.trim() + "'");
|
||||||
violations.add(
|
|
||||||
sourcePath
|
|
||||||
+ ": '"
|
|
||||||
+ line.trim()
|
|
||||||
+ "' (configuration '"
|
|
||||||
+ m.group(1)
|
|
||||||
+ "' is not test-scoped)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(violations)
|
assertThat(violations)
|
||||||
.as(
|
.as("no registered module may retain a Gradle dependency on the deleted sample")
|
||||||
"sample-portfolio must be a test-only dependency of every production module so "
|
|
||||||
+ "the sample can be removed without breaking the production build/runtime")
|
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
|
|
||||||
|
String rootBuild = Files.readString(resources.requireTrackedFile("src/build.gradle"));
|
||||||
|
assertThat(rootBuild)
|
||||||
|
.doesNotContain(":sample-portfolio:messagingSampleContractQualificationTest")
|
||||||
|
.doesNotContain(
|
||||||
|
"sample-portfolio/src/main/resources/contracts/messaging/portfolio.worklog.reserved/");
|
||||||
|
|
||||||
|
String application =
|
||||||
|
Files.readString(
|
||||||
|
resources.requireTrackedFile("src/app-bootstrap/src/main/resources/application.yml"));
|
||||||
|
assertThat(application)
|
||||||
|
.contains("role-permissions: {}")
|
||||||
|
.doesNotContain("user: worklog:read,worklog:write")
|
||||||
|
.doesNotContain("admin: worklog:read,worklog:write,worklog:close");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> registeredProductionSourcePaths(RepositoryContractResources resources)
|
private static List<String> registeredSourcePathsWithoutSample(
|
||||||
throws IOException {
|
RepositoryContractResources resources) throws IOException {
|
||||||
Path registry = resources.requireTrackedFile("src/config/architecture/modules.json");
|
Path registry = resources.requireTrackedFile("src/config/architecture/modules.json");
|
||||||
final Map<String, Object> root;
|
final Map<String, Object> root;
|
||||||
try (InputStream input = Files.newInputStream(registry)) {
|
try (InputStream input = Files.newInputStream(registry)) {
|
||||||
@@ -81,14 +87,17 @@ class SampleRemovalSmokeContractTest {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Map<String, Object>> modules = (List<Map<String, Object>>) root.get("modules");
|
List<Map<String, Object>> modules = (List<Map<String, Object>>) root.get("modules");
|
||||||
assertThat(modules).as("architecture registry must declare its leaf modules").isNotEmpty();
|
assertThat(modules).as("architecture registry must declare its leaf modules").isNotEmpty();
|
||||||
assertThat(modules.stream().filter(row -> "sample-portfolio".equals(row.get("id"))).count())
|
assertThat(root.get("runtime_compositions"))
|
||||||
.as("architecture registry must declare exactly one sample fixture leaf")
|
.as("only the production composition remains")
|
||||||
.isEqualTo(1);
|
.isEqualTo(List.of("app-bootstrap"));
|
||||||
|
assertThat(modules).noneMatch(row -> "sample-portfolio".equals(row.get("id")));
|
||||||
|
assertThat(modules)
|
||||||
|
.allMatch(
|
||||||
|
row ->
|
||||||
|
!((List<?>) row.get("allowed_dependencies")).contains("sample-portfolio")
|
||||||
|
&& !((List<?>) row.get("runtime_memberships")).contains("sample-portfolio"));
|
||||||
|
|
||||||
return modules.stream()
|
return modules.stream().map(row -> (String) row.get("source_path")).toList();
|
||||||
.filter(row -> !"sample-portfolio".equals(row.get("id")))
|
|
||||||
.map(row -> (String) row.get("source_path"))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -117,9 +126,7 @@ class SampleRemovalSmokeContractTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assertThat(imports)
|
assertThat(imports)
|
||||||
.as(
|
.as("app-bootstrap core tests must compile without imports from the deleted sample module")
|
||||||
"app-bootstrap core tests must compile without sample-portfolio; sample-specific "
|
|
||||||
+ "contract checks belong in the sample module")
|
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +136,8 @@ class SampleRemovalSmokeContractTest {
|
|||||||
Path buildFile = resources.requireTrackedFile("src/app-bootstrap/build.gradle");
|
Path buildFile = resources.requireTrackedFile("src/app-bootstrap/build.gradle");
|
||||||
String build = Files.readString(buildFile);
|
String build = Files.readString(buildFile);
|
||||||
|
|
||||||
assertThat(build).contains("sampleFixture project(':sample-portfolio')");
|
|
||||||
assertThat(build)
|
assertThat(build)
|
||||||
|
.doesNotContain("sampleFixture")
|
||||||
.contains("java.srcDirs = sourceSets.test.java.srcDirs")
|
.contains("java.srcDirs = sourceSets.test.java.srcDirs")
|
||||||
.contains("java.srcDir 'src/sampleOffTest/java'")
|
.contains("java.srcDir 'src/sampleOffTest/java'")
|
||||||
.contains("tasks.register('sampleOffCompile')")
|
.contains("tasks.register('sampleOffCompile')")
|
||||||
|
|||||||
+7
-7
@@ -31,9 +31,9 @@ import org.junit.jupiter.api.Test;
|
|||||||
* <li><strong>Red test:</strong> loads only the violation fixture ({@code
|
* <li><strong>Red test:</strong> loads only the violation fixture ({@code
|
||||||
* PiiViolationEventFixture} — a {@code @DomainEvent} with an {@code email} field) and asserts
|
* PiiViolationEventFixture} — a {@code @DomainEvent} with an {@code email} field) and asserts
|
||||||
* the rule fires.
|
* the rule fires.
|
||||||
* <li><strong>Green test:</strong> loads all production + sample classes (excluding the
|
* <li><strong>Green test:</strong> loads all production classes (excluding the violations
|
||||||
* violations package) and asserts the rule does NOT fire — i.e., no real event class carries
|
* package) and asserts the rule does NOT fire — i.e., no real event class carries a PII field
|
||||||
* a PII field name.
|
* name.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
class EventPayloadPiiContractTest {
|
class EventPayloadPiiContractTest {
|
||||||
@@ -99,11 +99,11 @@ class EventPayloadPiiContractTest {
|
|||||||
// ---- Green test: production classes must pass the rule ----
|
// ---- Green test: production classes must pass the rule ----
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Production + sample-portfolio classes excluding the violations fixture package.
|
* Production classes excluding the violations fixture package.
|
||||||
*
|
*
|
||||||
* <p>Scans {@code dev.caskeleton} broadly (covers domain-core, application-core,
|
* <p>Scans {@code dev.caskeleton} broadly (covers domain-core, application-core, adapter-outbound
|
||||||
* adapter-outbound, sample-portfolio all on the test classpath) but excludes the {@code
|
* modules on the test classpath) but excludes the {@code violations} package which intentionally
|
||||||
* violations} package which intentionally contains broken fixtures.
|
* contains broken fixtures.
|
||||||
*/
|
*/
|
||||||
private static final JavaClasses PRODUCTION_CLASSES =
|
private static final JavaClasses PRODUCTION_CLASSES =
|
||||||
new ClassFileImporter()
|
new ClassFileImporter()
|
||||||
|
|||||||
+1
-2
@@ -88,8 +88,7 @@ class DistributedLockProviderContractTest {
|
|||||||
|
|
||||||
sharedDataSource = hikari(pg);
|
sharedDataSource = hikari(pg);
|
||||||
|
|
||||||
// Flyway migrates V1 (idempotency), V2 (work_log from sample-portfolio on
|
// Flyway migrates the production schemas before lock table support is exercised.
|
||||||
// app-bootstrap test classpath), V3 (outbox), V4 (INT_LOCK).
|
|
||||||
Flyway.configure()
|
Flyway.configure()
|
||||||
.dataSource(sharedDataSource)
|
.dataSource(sharedDataSource)
|
||||||
.locations("classpath:db/migration/postgresql")
|
.locations("classpath:db/migration/postgresql")
|
||||||
|
|||||||
+1
-4
@@ -54,10 +54,7 @@ final class OutboxContainerTestSupport {
|
|||||||
"Docker not available — skipping Testcontainers-based outbox integration test");
|
"Docker not available — skipping Testcontainers-based outbox integration test");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Runs all production Flyway migrations against the given DataSource. */
|
||||||
* Runs Flyway migrations against the given DataSource. All three migrations (V1 idempotency, V2
|
|
||||||
* work_log from sample-portfolio on app-bootstrap test classpath, V3 outbox_event) are applied.
|
|
||||||
*/
|
|
||||||
static void migrate(DataSource dataSource) {
|
static void migrate(DataSource dataSource) {
|
||||||
Flyway.configure()
|
Flyway.configure()
|
||||||
.dataSource(dataSource)
|
.dataSource(dataSource)
|
||||||
|
|||||||
+6
-11
@@ -28,20 +28,15 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
/**
|
/**
|
||||||
* Regression guard for the skeleton's operational envelope/error contract.
|
* Regression guard for the skeleton's operational envelope/error contract.
|
||||||
*
|
*
|
||||||
* <p>The audit motivation: this contract used to live only in the disposable {@code
|
* <p>The contract beans live in {@code adapter-web}, a production dependency of {@code
|
||||||
* sample-portfolio} module, which {@code app-bootstrap} does NOT depend on at runtime ({@code
|
* app-bootstrap}. This test pins that {@link CaSkeletonApplication}'s component scan ({@code
|
||||||
* bootJar} mainClass {@code CaSkeletonApplication}). The contract beans now live in {@code
|
* scanBasePackages = "dev.caskeleton"}) actually wires them into the running application, so a
|
||||||
* adapter-web} (a production dependency of {@code app-bootstrap}). This test pins that {@link
|
* future change cannot silently drop the contract from the real app again.
|
||||||
* CaSkeletonApplication}'s component scan ({@code scanBasePackages = "dev.caskeleton"}) actually
|
|
||||||
* wires them into the running application, so a future change cannot silently drop the contract
|
|
||||||
* from the real app again.
|
|
||||||
*
|
*
|
||||||
* <p>Why a {@code @WebMvcTest} slice rather than a full {@code @SpringBootTest}: a full boot of
|
* <p>Why a {@code @WebMvcTest} slice rather than a full {@code @SpringBootTest}: a full boot of
|
||||||
* {@code CaSkeletonApplication} requires the ~50 {@code ${...}} env vars wired in {@code
|
* {@code CaSkeletonApplication} requires the ~50 {@code ${...}} env vars wired in {@code
|
||||||
* application.yml} (datasource, OIDC issuer, etc.) from {@code src/.env}, and {@code
|
* application.yml} (datasource, OIDC issuer, etc.) from {@code src/.env}. The MVC slice gives a
|
||||||
* sample-portfolio} on the test classpath drags in Spring Data JPA / Security / OAuth2
|
* real, {@code CaSkeletonApplication}-anchored web context without that cost.
|
||||||
* autoconfiguration plus JPA entities and repositories that need a live datasource and JWT issuer.
|
|
||||||
* The MVC slice gives a real, {@code CaSkeletonApplication}-anchored web context without that cost.
|
|
||||||
*
|
*
|
||||||
* <p>The slice scans {@code CaSkeletonApplication}'s package for {@code @ControllerAdvice} beans,
|
* <p>The slice scans {@code CaSkeletonApplication}'s package for {@code @ControllerAdvice} beans,
|
||||||
* so it picks up the advices via the same component-scan path the running app uses. The raw-{@code
|
* so it picks up the advices via the same component-scan path the running app uses. The raw-{@code
|
||||||
|
|||||||
@@ -212,15 +212,14 @@ The read side has two equally-valid shapes; pick per read, do not force one:
|
|||||||
|
|
||||||
| Shape | Returns | When | How |
|
| Shape | Returns | When | How |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **Through-aggregate** (default for simple reads) | domain aggregate via a `*Repository` port | read shape == write aggregate **and** the aggregate is the minimal invariant boundary (no lazy collections needed) | `QueryUseCase` → repository port → `WorkLog` |
|
| **Through-aggregate** (default for simple reads) | domain aggregate via a `*Repository` port | read shape == write aggregate **and** the aggregate is the minimal invariant boundary (no lazy collections needed) | `QueryUseCase` → repository port → aggregate |
|
||||||
| **Projection (CQRS-lite)** | application-layer projection DTO via a `*QueryPort` | read shape ≠ write, or to skip aggregate hydration / lazy-collection joins | `QueryUseCase` → `*QueryPort` → `WorkLogSummary` (record); query via JPQL `SELECT new` / JdbcTemplate |
|
| **Projection (CQRS-lite)** | application-layer projection DTO via a `*QueryPort` | read shape ≠ write, or to skip aggregate hydration / lazy-collection joins | `QueryUseCase` → `*QueryPort` → summary record; query via JPQL `SELECT new` / JdbcTemplate |
|
||||||
|
|
||||||
- **D1 — purity guardrail (core, machine-enforced):** a read port whose simple name ends
|
- **D1 — purity guardrail (core, machine-enforced):** a read port whose simple name ends
|
||||||
with `QueryPort` MUST return application-layer projection DTOs only — never a domain
|
with `QueryPort` MUST return application-layer projection DTOs only — never a domain
|
||||||
aggregate, JPA entity, or web type, **including through generic type arguments**
|
aggregate, JPA entity, or web type, **including through generic type arguments**
|
||||||
(`List<DomainType>`). Enforced by ArchUnit `query_ports_do_not_leak_domain_jpa_or_web_types`.
|
(`List<DomainType>`). Enforced by ArchUnit `query_ports_do_not_leak_domain_jpa_or_web_types`.
|
||||||
Projection usage itself is **opt-in**, not a forced default; the demo lives in
|
Projection usage itself is **opt-in**, not a forced default.
|
||||||
`sample-portfolio` (`WorkLogSummaryQueryPort` / `WorkLogSummary`).
|
|
||||||
- **D3 — Strict ceremony:** every read goes through a `QueryUseCase` bean. There is no thin
|
- **D3 — Strict ceremony:** every read goes through a `QueryUseCase` bean. There is no thin
|
||||||
web→read-port path — that would bypass the mandatory `@UseCaseCapability` fitness function.
|
web→read-port path — that would bypass the mandatory `@UseCaseCapability` fitness function.
|
||||||
- **D4 — transaction:** reads default to `TransactionPort.inRead`. A no-tx (autocommit) read
|
- **D4 — transaction:** reads default to `TransactionPort.inRead`. A no-tx (autocommit) read
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import java.util.Optional;
|
|||||||
*
|
*
|
||||||
* <p><strong>Legacy compatibility only.</strong> This contract preserves caller-keyed overwrite and
|
* <p><strong>Legacy compatibility only.</strong> This contract preserves caller-keyed overwrite and
|
||||||
* whole-{@code byte[]} materialization semantics in a separate legacy namespace while stored data
|
* whole-{@code byte[]} materialization semantics in a separate legacy namespace while stored data
|
||||||
* and the sample endpoint migrate. New code must use the semantic ports under {@code
|
* and external consumers migrate. New code must use the semantic ports under {@code
|
||||||
* dev.caskeleton.application.objectstorage}. Production activation must be removed after the
|
* dev.caskeleton.application.objectstorage}. Production activation must be removed after the
|
||||||
* additive API/data migration, dual-read observation, external-consumer approval, and zero-usage
|
* additive API/data migration, dual-read observation, external-consumer approval, and zero-usage
|
||||||
* gates pass; no calendar removal date is implied here.
|
* gates pass; no calendar removal date is implied here.
|
||||||
|
|||||||
+9
-21
@@ -708,8 +708,7 @@ def writeMessagingEvidence = {
|
|||||||
schema: messagingSha256FileSet(
|
schema: messagingSha256FileSet(
|
||||||
'ca-skeleton.messaging.evidence.schema-set.v1',
|
'ca-skeleton.messaging.evidence.schema-set.v1',
|
||||||
[
|
[
|
||||||
file('shared-contract/src/main/resources/contracts/messaging/envelope/v1.schema.json'),
|
file('shared-contract/src/main/resources/contracts/messaging/envelope/v1.schema.json')
|
||||||
file('sample-portfolio/src/main/resources/contracts/messaging/portfolio.worklog.reserved/v1.schema.json')
|
|
||||||
] + fileTree(
|
] + fileTree(
|
||||||
'adapter/outbound/messaging/src/main/resources/contracts/messaging/meta/draft-2020-12'
|
'adapter/outbound/messaging/src/main/resources/contracts/messaging/meta/draft-2020-12'
|
||||||
).files.toList()),
|
).files.toList()),
|
||||||
@@ -788,7 +787,6 @@ def verifyMessagingContracts = tasks.register('verifyMessagingContracts') {
|
|||||||
dependsOn validateMessagingJsonSchemaV1EvidenceManifestSchema
|
dependsOn validateMessagingJsonSchemaV1EvidenceManifestSchema
|
||||||
dependsOn ':application-core:messagingApplicationContractQualificationTest'
|
dependsOn ':application-core:messagingApplicationContractQualificationTest'
|
||||||
dependsOn ':shared-contract:messagingSharedSchemaQualificationTest'
|
dependsOn ':shared-contract:messagingSharedSchemaQualificationTest'
|
||||||
dependsOn ':sample-portfolio:messagingSampleContractQualificationTest'
|
|
||||||
dependsOn ':adapter:outbound:messaging:messagingCompiledContractsQualificationTest'
|
dependsOn ':adapter:outbound:messaging:messagingCompiledContractsQualificationTest'
|
||||||
dependsOn ':adapter:outbound:messaging:messagingJsonSchemaV1QualificationTest'
|
dependsOn ':adapter:outbound:messaging:messagingJsonSchemaV1QualificationTest'
|
||||||
dependsOn ':adapter:outbound:messaging:verifyJsonSchemaRuntimeGraph'
|
dependsOn ':adapter:outbound:messaging:verifyJsonSchemaRuntimeGraph'
|
||||||
@@ -797,11 +795,10 @@ def verifyMessagingContracts = tasks.register('verifyMessagingContracts') {
|
|||||||
doLast {
|
doLast {
|
||||||
writeMessagingEvidence(
|
writeMessagingEvidence(
|
||||||
'verifyMessagingContracts',
|
'verifyMessagingContracts',
|
||||||
['application', 'shared', 'sample', 'compiled', 'json-schema'],
|
['application', 'shared', 'compiled', 'json-schema'],
|
||||||
[
|
[
|
||||||
':application-core:messagingApplicationContractQualificationTest',
|
':application-core:messagingApplicationContractQualificationTest',
|
||||||
':shared-contract:messagingSharedSchemaQualificationTest',
|
':shared-contract:messagingSharedSchemaQualificationTest',
|
||||||
':sample-portfolio:messagingSampleContractQualificationTest',
|
|
||||||
':adapter:outbound:messaging:messagingCompiledContractsQualificationTest',
|
':adapter:outbound:messaging:messagingCompiledContractsQualificationTest',
|
||||||
':adapter:outbound:messaging:messagingJsonSchemaV1QualificationTest',
|
':adapter:outbound:messaging:messagingJsonSchemaV1QualificationTest',
|
||||||
'verifyMessagingContracts'
|
'verifyMessagingContracts'
|
||||||
@@ -853,7 +850,7 @@ def composeCommand = ['docker', 'compose', '-f', baseComposeFile.absolutePath,
|
|||||||
|
|
||||||
def bootstrapCompile = tasks.register('bootstrapCompile') {
|
def bootstrapCompile = tasks.register('bootstrapCompile') {
|
||||||
group = 'developer experience'
|
group = 'developer experience'
|
||||||
description = 'Stage 1/5: compiles every main and test source set as a local sanity check.'
|
description = 'Stage 1/4: compiles every main and test source set as a local sanity check.'
|
||||||
dependsOn subprojects.findAll { it.childProjects.isEmpty() }.collect { it.tasks.named('compileTestJava') }
|
dependsOn subprojects.findAll { it.childProjects.isEmpty() }.collect { it.tasks.named('compileTestJava') }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,7 +873,7 @@ bootstrapDockerPreflight.configure { dependsOn bootstrapCompile }
|
|||||||
|
|
||||||
def bootstrapDependencies = tasks.register('bootstrapDependencies', Exec) {
|
def bootstrapDependencies = tasks.register('bootstrapDependencies', Exec) {
|
||||||
group = 'developer experience'
|
group = 'developer experience'
|
||||||
description = 'Stage 2/5: starts the local PostgreSQL dependency and waits for readiness.'
|
description = 'Stage 2/4: starts the local PostgreSQL dependency and waits for readiness.'
|
||||||
commandLine composeCommand + ['up', '-d', '--wait', 'db']
|
commandLine composeCommand + ['up', '-d', '--wait', 'db']
|
||||||
}
|
}
|
||||||
bootstrapDependencies.configure { dependsOn bootstrapCompile }
|
bootstrapDependencies.configure { dependsOn bootstrapCompile }
|
||||||
@@ -884,21 +881,14 @@ bootstrapDependencies.configure { dependsOn bootstrapDockerPreflight }
|
|||||||
|
|
||||||
def bootstrapMigrateAndStart = tasks.register('bootstrapMigrateAndStart', Exec) {
|
def bootstrapMigrateAndStart = tasks.register('bootstrapMigrateAndStart', Exec) {
|
||||||
group = 'developer experience'
|
group = 'developer experience'
|
||||||
description = 'Stage 3/5: builds/starts the app; startup Flyway must finish before health is ready.'
|
description = 'Stage 3/4: builds/starts the app; startup Flyway must finish before health is ready.'
|
||||||
commandLine composeCommand + ['up', '-d', '--build', '--wait', 'app']
|
commandLine composeCommand + ['up', '-d', '--build', '--wait', 'app']
|
||||||
}
|
}
|
||||||
bootstrapMigrateAndStart.configure { dependsOn bootstrapDependencies }
|
bootstrapMigrateAndStart.configure { dependsOn bootstrapDependencies }
|
||||||
|
|
||||||
def bootstrapSampleContract = tasks.register('bootstrapSampleContract') {
|
|
||||||
group = 'developer experience'
|
|
||||||
description = 'Stage 4/5: verifies the delegated sample production-isolation/build contract.'
|
|
||||||
dependsOn project(':app-bootstrap').tasks.named('bootstrapSampleContract')
|
|
||||||
}
|
|
||||||
bootstrapSampleContract.configure { dependsOn bootstrapMigrateAndStart }
|
|
||||||
|
|
||||||
def bootstrapSmoke = tasks.register('bootstrapSmoke') {
|
def bootstrapSmoke = tasks.register('bootstrapSmoke') {
|
||||||
group = 'developer experience'
|
group = 'developer experience'
|
||||||
description = 'Stage 5/5: requires HTTP 200 and status=UP from GET /api/healthcheck.'
|
description = 'Stage 4/4: requires HTTP 200 and status=UP from GET /api/healthcheck.'
|
||||||
doLast {
|
doLast {
|
||||||
URI endpoint = URI.create('http://localhost:8080/api/healthcheck')
|
URI endpoint = URI.create('http://localhost:8080/api/healthcheck')
|
||||||
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(60)
|
long deadline = System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(60)
|
||||||
@@ -929,11 +919,11 @@ def bootstrapSmoke = tasks.register('bootstrapSmoke') {
|
|||||||
"bootstrapSmoke: /api/healthcheck did not become healthy within 60s; last result: ${lastFailure}")
|
"bootstrapSmoke: /api/healthcheck did not become healthy within 60s; last result: ${lastFailure}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bootstrapSmoke.configure { dependsOn bootstrapSampleContract }
|
bootstrapSmoke.configure { dependsOn bootstrapMigrateAndStart }
|
||||||
|
|
||||||
tasks.register('bootstrap') {
|
tasks.register('bootstrap') {
|
||||||
group = 'developer experience'
|
group = 'developer experience'
|
||||||
description = 'Runs the complete five-stage local bootstrap contract.'
|
description = 'Runs the complete four-stage local bootstrap contract.'
|
||||||
dependsOn bootstrapSmoke
|
dependsOn bootstrapSmoke
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1124,8 +1114,7 @@ tasks.register('verifyCleanArchitectureDependencies') {
|
|||||||
if (moduleName != 'sample-portfolio' && actual.contains('sample-portfolio')) {
|
if (moduleName != 'sample-portfolio' && actual.contains('sample-portfolio')) {
|
||||||
throw new GradleException(
|
throw new GradleException(
|
||||||
"Module ':${moduleName}' has a forbidden production dependency on " +
|
"Module ':${moduleName}' has a forbidden production dependency on " +
|
||||||
"':sample-portfolio'. The sample module may only be consumed through " +
|
"deleted ':sample-portfolio' (negative re-entry guard)."
|
||||||
"non-production fixture configurations."
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1134,7 +1123,6 @@ tasks.register('verifyCleanArchitectureDependencies') {
|
|||||||
throw new GradleException(
|
throw new GradleException(
|
||||||
"Module ':${moduleName}' has forbidden project dependencies ${forbidden}. " +
|
"Module ':${moduleName}' has forbidden project dependencies ${forbidden}. " +
|
||||||
"Allowed dependencies are ${allowed}. " +
|
"Allowed dependencies are ${allowed}. " +
|
||||||
"Production modules must not depend on ':sample-portfolio'; " +
|
|
||||||
"all project edges must be explicitly registered."
|
"all project edges must be explicitly registered."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"runtime_compositions": [
|
"runtime_compositions": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
],
|
],
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
@@ -10,8 +9,7 @@
|
|||||||
"source_path": "src/domain-core",
|
"source_path": "src/domain-core",
|
||||||
"allowed_dependencies": [],
|
"allowed_dependencies": [],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -20,8 +18,7 @@
|
|||||||
"source_path": "src/shared-contract",
|
"source_path": "src/shared-contract",
|
||||||
"allowed_dependencies": [],
|
"allowed_dependencies": [],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -33,8 +30,7 @@
|
|||||||
"shared-contract"
|
"shared-contract"
|
||||||
],
|
],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,8 +56,7 @@
|
|||||||
"shared-contract"
|
"shared-contract"
|
||||||
],
|
],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -83,8 +78,7 @@
|
|||||||
"application-core"
|
"application-core"
|
||||||
],
|
],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -107,9 +101,7 @@
|
|||||||
"application-core",
|
"application-core",
|
||||||
"shared-contract"
|
"shared-contract"
|
||||||
],
|
],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": []
|
||||||
"sample-portfolio"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "adapter-outbound-cache-redis",
|
"id": "adapter-outbound-cache-redis",
|
||||||
@@ -177,8 +169,7 @@
|
|||||||
"shared-contract"
|
"shared-contract"
|
||||||
],
|
],
|
||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap",
|
"app-bootstrap"
|
||||||
"sample-portfolio"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -235,23 +226,6 @@
|
|||||||
"runtime_memberships": [
|
"runtime_memberships": [
|
||||||
"app-bootstrap"
|
"app-bootstrap"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sample-portfolio",
|
|
||||||
"gradle_path": ":sample-portfolio",
|
|
||||||
"source_path": "src/sample-portfolio",
|
|
||||||
"allowed_dependencies": [
|
|
||||||
"domain-core",
|
|
||||||
"application-core",
|
|
||||||
"adapter-outbound-persistence-jpa",
|
|
||||||
"adapter-outbound-identifier",
|
|
||||||
"adapter-outbound-objectstorage",
|
|
||||||
"adapter-inbound-web",
|
|
||||||
"shared-contract"
|
|
||||||
],
|
|
||||||
"runtime_memberships": [
|
|
||||||
"sample-portfolio"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,12 @@
|
|||||||
|
|
||||||
### `ResourceId` — 모든 식별자가 구현하는 마커 인터페이스
|
### `ResourceId` — 모든 식별자가 구현하는 마커 인터페이스
|
||||||
|
|
||||||
시스템의 모든 리소스 식별자 값 객체(예: `WorkLogId`)가 구현하는 공통 인터페이스다.
|
시스템의 모든 리소스 식별자 값 객체(예: `ArticleId`)가 구현하는 공통 인터페이스다.
|
||||||
식별자의 실제 문자열 값은 `value()` 하나로 노출한다.
|
식별자의 실제 문자열 값은 `value()` 하나로 노출한다.
|
||||||
|
|
||||||
- **왜 `sealed` 로 닫지 않았나.** 원래 레퍼런스 설계는 `permits WorkLogId` 처럼 허용 구현을
|
- **왜 `sealed` 로 닫지 않았나.** 실제 식별자 구현은 각 제품 도메인이 소유하고, 이 마커는
|
||||||
못 박는 닫힌 집합(`sealed`)으로 만들려 했다. 그런데 `WorkLogId` 같은 실제 식별자는
|
재사용 가능한 `domain-core`에 있다. `permits` 절로 구체 구현을 못 박으면 `domain-core`가
|
||||||
`sample-portfolio`(샘플 코드)에 있고, 이 마커는 재사용 가능한 `domain-core`에 있다.
|
바깥 도메인 구현을 의존하게 되어 모듈 의존성 규칙을 거스른다. 그래서 `sealed`을 포기했다.
|
||||||
`permits` 절을 쓰는 순간 `domain-core`가 `sample-portfolio`를 의존하게 되는데, 이건
|
|
||||||
모듈 의존성 규칙과 `production_code_does_not_depend_on_sample_portfolio` ArchUnit 규칙이
|
|
||||||
둘 다 금지하는 방향이다. 그래서 `sealed`을 포기했다.
|
|
||||||
- **그럼 "닫힌 집합" 보장은 어디서 하나.** `sealed`이 줬을 보장(아무 타입이나 식별자로
|
- **그럼 "닫힌 집합" 보장은 어디서 하나.** `sealed`이 줬을 보장(아무 타입이나 식별자로
|
||||||
쓰지 못하게 하는 것)은 대신 빌드 타임의 `no_long_id_pk` ArchUnit 규칙(D17)이 강제한다 —
|
쓰지 못하게 하는 것)은 대신 빌드 타임의 `no_long_id_pk` ArchUnit 규칙(D17)이 강제한다 —
|
||||||
도메인의 모든 `id` 필드는 반드시 `ResourceId` 구현이어야 하고, 날것의 `Long` PK 는 금지된다.
|
도메인의 모든 `id` 필드는 반드시 `ResourceId` 구현이어야 하고, 날것의 `Long` PK 는 금지된다.
|
||||||
|
|||||||
Binary file not shown.
@@ -1,53 +0,0 @@
|
|||||||
# sample-portfolio — fixture / reference consumer
|
|
||||||
|
|
||||||
## Registered identity
|
|
||||||
|
|
||||||
- Module ID: `sample-portfolio`
|
|
||||||
- Gradle path: `:sample-portfolio`
|
|
||||||
- Focused test (derived from Gradle path): `./gradlew :sample-portfolio:test --console=plain`
|
|
||||||
- Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0.
|
|
||||||
- Registry SSOT: `src/config/architecture/modules.json`.
|
|
||||||
|
|
||||||
Package root: `dev.caskeleton.sample.portfolio`.
|
|
||||||
|
|
||||||
코드 주석에서 덜어낸 **설계 결정의 근거**는 [README.md](README.md) 의 "설계 결정 참조" 절이 모아둔다 (이 문서는 모듈 규칙 SSOT).
|
|
||||||
|
|
||||||
## Responsibility
|
|
||||||
|
|
||||||
- Reference implementation for template adopters: the WorkLog domain slice, layered
|
|
||||||
exactly like a consuming project — `domain/worklog` (`WorkLog`, `WorkLogId`,
|
|
||||||
`Period`, `WorkLogRepository`), `application/` (commands, queries, use cases,
|
|
||||||
ports such as `RepoStatsPort`), and reference adapters under `adapter/web`,
|
|
||||||
`adapter/persistence`, `adapter/outbound/repostats` (`RepoStatsPortClient` +
|
|
||||||
`RepoStatsAclMapper`), `adapter/identifier` (`UuidWorkLogIdFactory`).
|
|
||||||
- Contract-test fixtures the app-bootstrap verification suite analyses
|
|
||||||
(`DomainExceptionHandler`, `PortfolioErrorCode`, wire/contract tests).
|
|
||||||
- Disposable messaging contract fixture: the exact `WorkLogReservedPayload` record, its static
|
|
||||||
application-core contract contribution, sample-owned schema/digest and golden vectors. It is not
|
|
||||||
runtime-discovered, and validator compatibility remains unproven until messaging Task 6.
|
|
||||||
- Sample application collaborators consume invocation context through application-core ports and
|
|
||||||
must not import SLF4J/MDC.
|
|
||||||
|
|
||||||
## Allowed
|
|
||||||
|
|
||||||
- Runtime leaves explicitly allowed for this fixture consumer by the `sample-portfolio` entry in
|
|
||||||
`src/config/architecture/modules.json`; do not duplicate the 19-leaf list here.
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Production code depending on this module — ArchUnit
|
|
||||||
`production_code_does_not_depend_on_sample_portfolio`. `app-bootstrap` references
|
|
||||||
it as `testImplementation` ONLY so the ArchUnit suite can analyse the reference
|
|
||||||
implementation ([app-bootstrap/build.gradle](../app-bootstrap/build.gradle) L21–24).
|
|
||||||
- Production-required behaviour living here: deleting this module must not break the
|
|
||||||
production build or runtime (the base `GlobalExceptionHandler`, envelope, and
|
|
||||||
error-code contract live in `adapter:inbound:web` / `shared-contract`).
|
|
||||||
- Treating a sample schema/contribution as an automatically discovered production registry entry,
|
|
||||||
or placing JSON mapper, physical topic, Kafka or bootstrap-server concerns in the contribution.
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd src
|
|
||||||
./gradlew :sample-portfolio:test
|
|
||||||
```
|
|
||||||
@@ -1,477 +0,0 @@
|
|||||||
# sample-portfolio — WorkLog 포트폴리오 게시판 (예시 도메인)
|
|
||||||
|
|
||||||
이 모듈은 ca-skeleton 템플릿이 유지하는 **fixture/reference 예시 도메인**입니다. production 모듈
|
|
||||||
(`domain-core`/`application-core`/`adapter-*`/`shared-contract`/`app-bootstrap`)의
|
|
||||||
경계와 운영 계약을 "어떻게 쓰는지" 보여줍니다. 새 프로젝트는 이 모듈을 import하지 않고 자신의
|
|
||||||
도메인을 production 모듈에 추가합니다. 템플릿에서는 이 모듈을 유지하며, 다운스트림 fork는
|
|
||||||
`./gradlew :app-bootstrap:sampleOffTest` 통과 후에만 선택적으로 정리할 수 있습니다.
|
|
||||||
|
|
||||||
## 도메인: WorkLog (엔지니어링 작업 기록 게시판)
|
|
||||||
|
|
||||||
메인 화면 = `GET /work-logs` 목록. 각 항목은 직접 수행한 작업입니다.
|
|
||||||
|
|
||||||
| 필드 | 설명 |
|
|
||||||
| --- | --- |
|
|
||||||
| title | 작업 제목 |
|
|
||||||
| category | INFRASTRUCTURE / DATABASE / BACKEND / PLATFORM |
|
|
||||||
| summary / content | 요약 / 본문 |
|
|
||||||
| techStack[] | 사용 기술 |
|
|
||||||
| links[] | 관련 링크 (repo/blog) |
|
|
||||||
| period | 기간 (end=null → 진행 중) |
|
|
||||||
|
|
||||||
## 엔드포인트
|
|
||||||
|
|
||||||
| Method | Path | 설명 | 시연 계약 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| GET | /work-logs | 목록 (메인) | Envelope wrap |
|
|
||||||
| GET | /work-logs/{id} | 상세 | 도메인 예외→404 envelope |
|
|
||||||
| POST | /work-logs | 생성 | Bean Validation @GroupSequence (B4), unknown-field 거부 (B1), mapper 실패→MAPPING_FAILED (B3) |
|
|
||||||
| PATCH | /work-logs/{id} | 부분수정 | JsonNullable 3-state (B2) |
|
|
||||||
| DELETE | /work-logs/{id} | 삭제 | 204 |
|
|
||||||
| POST | /work-logs/import | 일괄 등록 | BulkEnvelope 부분실패 (B8) |
|
|
||||||
|
|
||||||
## 샘플 데이터 (curl 예시)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1) 인프라 인증·인가 위임 (Keycloak + k3s + Vault)
|
|
||||||
curl -X POST localhost:8080/work-logs -H 'Content-Type: application/json' -d '{
|
|
||||||
"title": "Keycloak + k3s + Vault 기반 인증·인가 위임",
|
|
||||||
"category": "INFRASTRUCTURE",
|
|
||||||
"summary": "인증/인가를 Keycloak으로 위임, k3s + Vault로 시크릿·구성 관리",
|
|
||||||
"content": "...",
|
|
||||||
"techStack": ["keycloak", "k3s", "vault"],
|
|
||||||
"links": ["https://example.com/infra"],
|
|
||||||
"periodStart": "2025-01-01"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# 2) DB 쿼리 튜닝
|
|
||||||
curl -X POST localhost:8080/work-logs -H 'Content-Type: application/json' -d '{
|
|
||||||
"title": "DB 쿼리 튜닝",
|
|
||||||
"category": "DATABASE",
|
|
||||||
"summary": "slow query 분석 및 인덱스/실행계획 개선",
|
|
||||||
"content": "...",
|
|
||||||
"techStack": ["postgresql"],
|
|
||||||
"links": [],
|
|
||||||
"periodStart": "2025-02-01"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 계층 (adapter-mirrored)
|
|
||||||
- `domain/worklog` — WorkLog/WorkCategory/Period/RepoStats/WorkLogRepository (POJO)
|
|
||||||
- `application/worklog` — *UseCase (CommandUseCase/QueryUseCase + @UseCaseCapability)
|
|
||||||
- `adapter/web` — controller/dto/mapper/error
|
|
||||||
- `adapter/persistence` — entity/repository/mapper
|
|
||||||
- `adapter/outbound/repostats` — B7 ACL 예시
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 설계 결정 참조 (코드 주석에서 이전)
|
|
||||||
|
|
||||||
여기 아래는 예전에 각 클래스의 주석/JavaDoc 에 흩어져 있던 **"왜 이렇게 짰는가"** 설명을
|
|
||||||
한곳에 모은 것입니다. 코드에는 "무엇을 하는지"만 짧게 남기고, 그 배경·결정·트레이드오프는
|
|
||||||
이 문서로 옮겼습니다. 코드를 읽다 "왜 이렇게 했지?"가 궁금할 때 보세요.
|
|
||||||
|
|
||||||
읽는 법:
|
|
||||||
|
|
||||||
- 모듈 규칙(허용/금지 의존, 테스트 명령)의 기준은 [CLAUDE.md](CLAUDE.md) 입니다. 이 문서는
|
|
||||||
규칙이 아니라 **결정의 근거**를 모은 참조 기록입니다.
|
|
||||||
- 본문은 코드가 어떤 약속을 지키려고 존재하는지 설명합니다. 추적용 꼬리표는 코드 주석에
|
|
||||||
남기지 않고, 필요한 배경은 여기서 문장으로 풀어 설명합니다.
|
|
||||||
|
|
||||||
## domain — 도메인 (순수 POJO, 프레임워크 의존 없음)
|
|
||||||
|
|
||||||
### WorkLog (애그리거트 루트)
|
|
||||||
- WorkLog 애그리거트의 루트입니다. **상태를 바꾸는 길은 의도가 드러나는 메서드뿐**입니다
|
|
||||||
(`rename`, `recategorize`, `startProgress`, `close` …). 공개 `setXxx` 세터를 두지 않은 이유는,
|
|
||||||
세터를 열어두면 불변식(invariant)을 건너뛴 채로 객체를 망가뜨릴 수 있기 때문입니다. 모든
|
|
||||||
변경 경로가 불변식 검사를 거치도록 강제합니다.
|
|
||||||
- 불변식을 어기면 `WorkLogInvariantException` 을 던집니다. 이 예외는 안전한 사유(reason)만
|
|
||||||
담고, 로그 문자열이나 운영 에러 코드/HTTP 상태는 전혀 모릅니다 — 그 변환은 상위
|
|
||||||
(application/web)의 몫입니다.
|
|
||||||
- **id 는 도메인이 직접 만들지 않습니다.** `WorkLog.create(...)` 는 이미 만들어진
|
|
||||||
`WorkLogId` 를 받기만 합니다. UUIDv7 생성은 유스케이스가 `WorkLogIdFactory` 포트를 통해
|
|
||||||
하고, 도메인 안에는 `UUID.randomUUID()` 나 id 생성 라이브러리가 들어오지 않습니다(서버가 id
|
|
||||||
를 정해주는 계약).
|
|
||||||
- `version` 필드(낙관적 락 버전)의 역할: 새로 만든(아직 저장 안 된) 객체는 `null` 이고,
|
|
||||||
저장소가 값을 채우고 증가시킵니다. 영속 계층은 이 `null` 여부로 "INSERT 인지 UPDATE 인지"를
|
|
||||||
구분하고, 웹 경계에서는 이 값이 HTTP `ETag` / `If-Match` 의 출처가 됩니다. 즉 DB 가
|
|
||||||
낙관적 락 충돌로 잡아내는 그 충돌을, 웹에서는 HTTP 412 로 표현합니다.
|
|
||||||
- `title` 규칙: null 이면 안 되고(`NullPointerException`), 공백만 있어도 안 됩니다
|
|
||||||
(`TITLE_BLANK`). 공백 금지는 **도메인 불변식**이라, 웹 경계의 `@NotBlank`(형식 검사)와는
|
|
||||||
별개로 한 번 더 지킵니다 — 웹을 거치지 않는 호출자가 있어도 규칙이 새지 않도록.
|
|
||||||
|
|
||||||
### 값 객체 — WorkLogId / Period / WorkLogOwner
|
|
||||||
- **WorkLogId**: 36자 canonical UUID(RFC 9562 UUIDv7). 순수 값 객체라 형식(정규형)만 검증하고,
|
|
||||||
id 생성 라이브러리에는 의존하지 않습니다. 대소문자 정규화는 이 타입을 만들기 전에 웹 경계에서
|
|
||||||
끝냅니다(canonical 형태는 소문자 hex). 형식은 `8-4-4-4-12` hex 그룹입니다.
|
|
||||||
- **Period**: 작업 기간. `end == null` 이면 "진행 중"이라는 뜻입니다. 생성자에서 `end < start`
|
|
||||||
를 막습니다(기간 역전 불가).
|
|
||||||
- **WorkLogOwner**: 소유자 식별자. 공백/빈 값을 막고 trim 합니다.
|
|
||||||
|
|
||||||
### 열거형 — WorkLogStatus / WorkCategory / WorkLogSortField
|
|
||||||
- **WorkLogStatus**: OPEN → IN_PROGRESS → CLOSED. 전이 규칙은 WorkLog 가 강제합니다.
|
|
||||||
- **WorkCategory**: 포트폴리오 보드에 노출하는 작업 분류(INFRASTRUCTURE/DATABASE/BACKEND/PLATFORM).
|
|
||||||
- **WorkLogSortField**: 목록 정렬에 **허용된 필드만** 담은 닫힌 열거형입니다. 정렬 키를 ORM 에
|
|
||||||
넘기는 열린 문자열이 아니라 열거형으로 막아둔 이유는, 알 수 없는 필드를 쿼리까지 보내지 않고
|
|
||||||
웹 경계에서 400 으로 거절하기 위해서입니다(`?sort=field,direction` 계약을 안전하게 만드는 핵심).
|
|
||||||
|
|
||||||
### WorkLogInvariantException
|
|
||||||
- WorkLog 불변식 위반을 알리는 예외입니다. `Reason`(명사형 안전 enum)만 담고, **운영 에러
|
|
||||||
코드·HTTP 상태·로깅은 모릅니다.** 도메인은 로거를 갖지 않고 운영 코드로 매핑하지도 않는다는
|
|
||||||
규칙 때문입니다. `reason()` 을 로그 라인과 `error.category` 로 번역하는 일은 application/web 의
|
|
||||||
책임입니다. 사유가 거친 명사라서 민감 정보가 클라이언트로 새지 않습니다.
|
|
||||||
|
|
||||||
### WorkLogReserved (도메인 이벤트)
|
|
||||||
- "WorkLog 기간이 예약됨"이라는 도메인 사실을 표현하는 이벤트입니다. **전송(transport)과
|
|
||||||
무관**합니다 — 도메인 데이터(`WorkLogId`/`WorkCategory`/`LocalDate`)만 담고 broker/wire/HTTP
|
|
||||||
타입을 일절 참조하지 않습니다. 이것을 발행 가능한 integration 이벤트로 바꾸는 일은 application
|
|
||||||
경계의 몫입니다(`application.event.WorkLogReservedIntegrationEvent` 와 그 매퍼 참고).
|
|
||||||
|
|
||||||
### 포트 — WorkLogRepository / WorkLogIdFactory / OutboxEventIdFactory
|
|
||||||
- **WorkLogRepository**: WorkLog 영속 아웃바운드 포트(구현은 adapter-persistence). `findPage` 는
|
|
||||||
한 페이지 + 전체 개수를 함께 돌려주며, `sortField`/`category` 가 `null` 이면 정렬/필터 없음을
|
|
||||||
뜻합니다. 전체 개수는 같은 필터 기준으로 세어 `meta.page.total` 이 필터된 결과를 반영하게 합니다.
|
|
||||||
- **WorkLogIdFactory / OutboxEventIdFactory**: id 를 만들어 주는 도메인 포트. 둘을 **굳이 따로 둔
|
|
||||||
이유**는, 애그리거트 식별자 타입(`WorkLogId`)이 이벤트 id 모양을 묶어버리지 않게 하기 위해서입니다.
|
|
||||||
실제로는 둘 다 UUIDv7 기반이지만(identifier 어댑터가 구현), 도메인은 생성 방식을 모릅니다.
|
|
||||||
outbox 이벤트 id 는 outbox 봉투의 `eventId` 이자 `idempotencyKey` 로 쓰입니다(같은 값 → 이벤트
|
|
||||||
단위 중복 제거).
|
|
||||||
|
|
||||||
### RepoStats
|
|
||||||
- 외부 저장소 통계를 정규화한 결과 값입니다. 아웃바운드 ACL(B7)을 통과한 뒤에만 만들어집니다.
|
|
||||||
|
|
||||||
## application — 응용 (유스케이스)
|
|
||||||
|
|
||||||
### Command / Query — 스스로 검증하는 입력 모델
|
|
||||||
- `CreateWorkLogCommand`, `UpdateWorkLogCommand`, `DeleteWorkLogCommand`, `GetWorkLogQuery`,
|
|
||||||
`GetRepoStatsQuery`, `ListWorkLogsQuery`, `BatchCreateWorkLogsCommand` 는 **생성자에서 스스로
|
|
||||||
필수값을 검증**합니다("self-validating input model"). 이렇게 하면 어떤 인바운드 어댑터가
|
|
||||||
부르든(오늘은 웹, 내일은 메시징 컨슈머) 웹 경계의 jakarta validation 에 기대지 않고도 같은
|
|
||||||
계약을 보장받습니다. id 나 patch 가 null 인 것은 프로그래밍 오류이므로 생성 시점에 즉시
|
|
||||||
실패시킵니다("없음"은 `Patch.absent()` 로 표현하고 null 을 쓰지 않습니다).
|
|
||||||
- `UpdateWorkLogCommand` 의 필드는 `Patch<T>` 입니다. PATCH 의 3가지 상태 — 값 없음(변경 안 함) /
|
|
||||||
명시적 null(필드 비움) / 값(교체) — 을 구분하기 위해서입니다.
|
|
||||||
|
|
||||||
### CreateWorkLogUseCase — 생성 + outbox 동시 기록 (dual-write 금지)
|
|
||||||
- WorkLog 를 저장하면서 `WorkLogReserved` outbox 이벤트를 **같은 쓰기 트랜잭션 안에서** 함께
|
|
||||||
적재하는 예시입니다. 핵심은 `repository.save(...)` 와 `outboxAppendPort.append(...)` 가 같은
|
|
||||||
`tx.inWrite { ... }` 블록 안에 있다는 점입니다. 저장이나 적재 중 하나라도 실패하면 트랜잭션
|
|
||||||
전체가 롤백됩니다 — "DB 한 번, 브로커 한 번" 식의 분리된 이중 쓰기(dual-write)를 막는 보장입니다.
|
|
||||||
- 순서: ① 저장된 애그리거트로 도메인 이벤트 생성 → ② 매퍼로 integration 이벤트로 변환(경계는
|
|
||||||
application) → ③ 직접 만든 JSON 페이로드로 직렬화 → ④ 같은 트랜잭션에서 outbox 에 append.
|
|
||||||
- `eventId` 는 `OutboxEventIdFactory`(UUIDv7) 로 만들고, `idempotencyKey = eventId` 로 둡니다
|
|
||||||
(이벤트 단위 중복 제거).
|
|
||||||
- `correlationId` 는 application-core의 `CorrelationIdPort`로 읽습니다. inbound web adapter가
|
|
||||||
sanitized MDC 값을 포트 뒤에서 제공하므로 sample application 코드는 SLF4J/MDC를 알지 않습니다.
|
|
||||||
요청 밖에서 실행되거나 값이 blank면 `eventId`를 그대로 `correlationId`로 사용해 이벤트가
|
|
||||||
최소한 자신을 가리키게(self-correlation) 폴백합니다.
|
|
||||||
- 쓰기 작업이라 `@RequiresPermission("worklog:write")` 로 권한을 요구합니다(이 권한은 `user`/`admin`
|
|
||||||
역할 묶음에 부여).
|
|
||||||
|
|
||||||
### BatchCreateWorkLogsUseCase — 원자적 일괄 생성
|
|
||||||
- 동기 일괄 생성은 **전부 성공 아니면 전부 롤백**입니다. 모든 항목을 하나의 `tx.inWrite` 단위로
|
|
||||||
처리하므로 한 항목이라도(매핑/검증/영속) 실패하면 배치 전체가 되돌려지고, 엔드포인트는 부분
|
|
||||||
성공 대신 단일 4xx 를 냅니다. (`BATCH_PARTIAL_FAILURE` 같은 부분 실패 형태는 비동기 폴링
|
|
||||||
응답용으로 예약돼 있고, 동기 배치에는 쓰지 않습니다.)
|
|
||||||
|
|
||||||
### UpdateWorkLogUseCase — Patch 3-state 적용
|
|
||||||
- 각 필드의 `Patch` 상태에 따라 다르게 동작합니다: `hasValue()` 면 교체, `isExplicitNull()` 이면
|
|
||||||
비움, 둘 다 아니면(없음) 변경하지 않습니다. 상태 전이는 도메인 메서드(`startProgress`/`close`)에
|
|
||||||
위임하고, OPEN 으로 되돌리는 시도는 잘못된 전이로 막습니다.
|
|
||||||
|
|
||||||
### DeleteWorkLogUseCase — 권한 3-tier 가시화
|
|
||||||
- 삭제는 파괴적인 admin 등급 작업이라 `@RequiresPermission("worklog:close")` 로 막습니다.
|
|
||||||
`worklog:close` 는 **`admin` 역할 묶음에만** 부여되고 `user` 에는 주지 않습니다(샘플 모델에는 별도
|
|
||||||
"close" 유스케이스가 없어 delete 가 그 민감 작업 역할을 합니다). 이 한 군데가 3-tier 권한 모델을
|
|
||||||
눈에 보이게 만듭니다 — 인증된 `user` 는 여기서 거절되고 `admin` 만 통과합니다. (참고로 생성·수정·
|
|
||||||
일괄 생성은 `worklog:write` 로 막고, 이 권한은 `user`/`admin` 양쪽에 부여됩니다.)
|
|
||||||
|
|
||||||
### 읽기 모델(projection) — ListRecentWorkLogSummariesUseCase / WorkLogSummaryQueryPort / WorkLogSummary
|
|
||||||
- 이 세 타입은 **CQRS-lite "쿼리 우회(query bypass)"** 예시입니다. 보드/목록 화면처럼 가벼운 읽기는
|
|
||||||
WorkLog 애그리거트를 통째로 복원하지 않고, 필요한 컬럼만 뽑은 projection 으로 읽습니다
|
|
||||||
(`content`/`summary`/`techStack`/`links` 같은 무거운 상태를 빼서, lazy 컬렉션 조인 없는 컬럼-부분
|
|
||||||
SELECT 가 됩니다).
|
|
||||||
- **이것은 강제 기본값이 아니라 선택적 최적화**입니다. 애그리거트를 통해 읽는 길
|
|
||||||
(`WorkLogRepository`)도 단순한 읽기에서는 똑같이 유효한 기본 경로입니다.
|
|
||||||
- projection DTO 의 순수성: projection 은 **application 타입**이어야 합니다 — 도메인 애그리거트도,
|
|
||||||
JPA 엔티티도, 웹 DTO 도 아니어야 합니다. 도메인 *값* 타입(예: `WorkCategory` enum)을 참조하는 것은
|
|
||||||
허용됩니다(application 은 domain 에 의존 가능). 다만 쿼리 포트의 반환 시그니처에 도메인 애그리거트/
|
|
||||||
JPA 엔티티/웹 타입이 새면 안 됩니다(`List<WorkLogSummary>` 같은 제네릭 인자까지 포함, ArchUnit 규칙
|
|
||||||
`query_ports_do_not_leak_domain_jpa_or_web_types` 가 강제).
|
|
||||||
- **명명 규칙**: projection 을 반환하는 읽기 포트 이름은 `…QueryPort` 로 끝냅니다.
|
|
||||||
- **id 표현**: projection 의 `id` 는 애플리케이션의 표준 id 어휘인 36자 UUID 문자열입니다. 저장소
|
|
||||||
고유의 `UUID` 는 어댑터 안에서 변환되어, 읽기 모델은 저장 형식이 아니라 애플리케이션과 같은
|
|
||||||
식별자 형식을 말합니다.
|
|
||||||
- 격식(ceremony)은 그대로 지킵니다: projection 읽기여도 `QueryUseCase` 빈을 거치고
|
|
||||||
(`@UseCaseCapability` 계약을 모든 읽기에 기계적으로 적용), 기본은 `tx.inRead` 안에서 읽습니다.
|
|
||||||
projection 도 저장소 기반이면 여전히 `READ_REPOSITORY` 입니다 — "projection 이냐 애그리거트냐"는
|
|
||||||
반환 *모양*의 축이고, 저장소 접근 *수준*과는 별개라서 새 capability enum 이 필요 없습니다.
|
|
||||||
|
|
||||||
### WorkLogReservedIntegrationEvent / …Mapper — integration(wire) 이벤트
|
|
||||||
- **IntegrationEvent**: 도메인 이벤트와 달리 아웃바운드 어댑터가 실제로 발행할(예: Kafka)
|
|
||||||
직렬화 가능한 경계 소유 형태입니다. 도메인 사실을 wire 계약으로 옮기는 일 — 값 객체를 원시
|
|
||||||
타입으로 평탄화, wire 표현 선택 — 은 application 의 관심사라서 application 계층에 둡니다. 도메인은
|
|
||||||
wire 를 모릅니다.
|
|
||||||
- **Mapper**: 도메인 이벤트 → integration 이벤트 변환을 **application 경계에서** 수행하고, 트랜잭션
|
|
||||||
outbox 용 JSON 페이로드 문자열로 직렬화합니다.
|
|
||||||
- JSON 직렬화(`toJson`)는 의존성을 늘리지 않으려고 RFC 8259 §7 이스케이프까지 직접 구현했습니다.
|
|
||||||
실제 프로젝트라면 스키마 직렬화 계약(Avro/Protobuf, 스키마 레지스트리 붙은 Jackson)을 써야
|
|
||||||
합니다 — 샘플은 새 의존성 0을 유지합니다. 세 필드는 도메인 식별자와 enum 이름뿐이라 PII/토큰/
|
|
||||||
원문 본문이 없습니다.
|
|
||||||
- 이벤트 타입 이름 `"worklog.reserved"`: `도메인.동사` 패턴의 안정적인 점-구분 논리명입니다.
|
|
||||||
outbox relay 규약(`topic = eventType`)에 따라 그대로 브로커 토픽 이름이 됩니다. integration
|
|
||||||
이벤트의 클래스 단순명을 쓰는 대안도 있지만, 클래스 이름을 바꾸면 토픽이 조용히 바뀌어 컨슈머가
|
|
||||||
깨질 수 있어, 안정적인 문자열 리터럴을 택했습니다(토픽 마이그레이션 계획 없이는 바꾸지 말 것).
|
|
||||||
|
|
||||||
### WorkLogReservedPayload / …ContractContribution — canonical contract fixture
|
|
||||||
|
|
||||||
- `WorkLogReservedPayload`는 새 closed contract SPI에 제공하는 exact final record다. v1은
|
|
||||||
`workLogId` 하나만 소유하며, schema와 같은 canonical bounded identifier 규칙을 생성자에서 지킨다.
|
|
||||||
- `contracts/messaging/portfolio.worklog.reserved/v1.schema.json`과 golden vector는
|
|
||||||
**sample fixture contract**다. 공통 envelope는 WorkLog 필드를 알지 못하며 이 모듈을 삭제해도
|
|
||||||
production module build/runtime은 깨지지 않아야 한다.
|
|
||||||
- contribution은 contract ID, exact payload type/component order, classpath resource ID, checked-in
|
|
||||||
digest와 provider-neutral descriptor만 제공한다. JSON mapper, physical topic, Kafka 설정을 소유하지
|
|
||||||
않으며 매 호출 filesystem I/O도 하지 않는다.
|
|
||||||
- 기존 3-field `WorkLogReservedIntegrationEvent`/hand-rolled mapper는 현재 legacy outbox
|
|
||||||
characterization 경로에 남아 있다. 새 1-field canonical payload는 그 타입을 조용히 대체하지 않으며,
|
|
||||||
closed catalog와 encoder가 조립되기 전까지 양쪽은 서로 호출하거나 직렬화하지 않는다.
|
|
||||||
- 현재는 classpath scanning이나 runtime discovery가 없다. Task 5의 closed catalog 조립 전에는
|
|
||||||
자동 등록되지 않고, Task 6 validator qualification 전에는 Draft 2020-12 validator compatibility를
|
|
||||||
주장하지 않는다.
|
|
||||||
|
|
||||||
### RepoStatsPort
|
|
||||||
- 외부 저장소 통계를 가져오는 아웃바운드 포트(구현은 adapter-outbound).
|
|
||||||
|
|
||||||
## adapter/web — 인바운드 HTTP / 보안 / DTO
|
|
||||||
|
|
||||||
### WorkLogController
|
|
||||||
- 포트폴리오 보드 엔드포인트. 리소스 이름은 AIP-122 를 따릅니다: `/worklogs`(복수·소문자),
|
|
||||||
`/worklogs/repoStats`(lowerCamelCase 하위 세그먼트), 콜론-동사 커스텀 메서드
|
|
||||||
`/worklogs:batchCreate`. `/v1` 버전 접두사는 `PresentationWebConfig` 가 중앙에서 붙입니다.
|
|
||||||
- **GET /worklogs**: 페이지네이션 + (선택) 정렬 + (선택) 평면 동등 필터. 정렬 문자열은
|
|
||||||
`SortParam.parse` 가 파싱해 native 가 아닌 구문은 400 으로 거절하고, 허용 목록에 없는 필드도
|
|
||||||
거절합니다. offset 이 너무 깊으면 커서 사용을 권하는 `Deprecation` 헤더를 답니다.
|
|
||||||
- **GET /worklogs/{id}**: `ETag` 를 내보내고, `If-None-Match` 가 맞으면 본문 없이 304 를 답니다.
|
|
||||||
- **PATCH /worklogs/{id}**: `If-Match` 가 오면 현재 ETag 와 같아야 하고, 다르면 412(낙관적 동시성).
|
|
||||||
`If-Match` 가 없으면 검사를 건너뜁니다 — 스켈레톤은 권장 패턴을 보여주되 강제하지는 않습니다.
|
|
||||||
`ETag`/`If-Match` 는 HTTP 전송 관심사(RFC 7232)라, 읽고-비교하는 로직을 일부러 웹 어댑터에만
|
|
||||||
두고 application/domain 은 ETag 를 전혀 보지 않게 합니다.
|
|
||||||
- **POST /worklogs**: `Idempotency-Key` 헤더는 POST 요청 표면의 일부로 받아만 둡니다. 키의
|
|
||||||
모양·범위·재생(replay) 의미는 별도 계약(rate-limit-idempotency)의 몫이라, 그 브랜치가 replay 를
|
|
||||||
배선하기 전까지는 서버가 헤더를 그냥 허용(무시)합니다.
|
|
||||||
- **POST /worklogs:batchCreate**: AIP-136 콜론-동사 일괄 생성. **동기 = 원자적**이라 한 항목이라도
|
|
||||||
실패하면 배치 전체가 롤백되고 단일 4xx 가 납니다. 한 번에 보낼 수 있는 항목 수는
|
|
||||||
`MAX_BATCH_SIZE`(1000)로 제한하고, 넘으면 400 입니다. 요청 봉투는 `{ "requests": [ ... ] }` 형태입니다.
|
|
||||||
- **소유자(owner) 스탬프**: 생성되는 WorkLog 에는 인증된 호출자의 IdP subject 를 소유자로 찍습니다.
|
|
||||||
보안 컨텍스트에서 null 안전하게 읽으며, 이 값은 **가공 전 원시 principal id** 입니다 —
|
|
||||||
가명화(pseudonymization)는 별도 프라이버시 계약의 몫이라 여기서 적용하지 않고, 소유자 범위
|
|
||||||
인가(ABAC)는 의도적으로 뒤로 미룬 확장입니다. 유스케이스에 건 `worklog:write` 권한이 생성이 실제
|
|
||||||
실행될 시점엔 인증된 principal 이 있음을 보장합니다.
|
|
||||||
- **id 정규화(`toId`)**: 대소문자 무관 canonical UUID 경로 입력을 받아, 도메인 id 를 만들기 전에
|
|
||||||
표준 36자 소문자 UUID 로 정규화합니다. 여기서 쓰는 `UUID.fromString(...)` 은 (생성기가 아니라) 파서라,
|
|
||||||
잘못된 id 면 `IllegalArgumentException` 을 던지고 전역 핸들러가 이를 HTTP 400 으로 매핑합니다.
|
|
||||||
|
|
||||||
### OperationsController / SampleOperationStore / WorkLogExportResult — 장기 실행 작업(LRO) 예시
|
|
||||||
- `POST /worklogs:export` 는 `202 Accepted` + 폴링 경로를 가리키는 `Location` 헤더 + `operationId`/
|
|
||||||
`statusUrl` 을 담은 봉투를 돌려주고, `GET /operations/{id}` 로 상태를 폴링합니다. 상태는 5값
|
|
||||||
`OperationStatus` enum 에서 옵니다.
|
|
||||||
- **operation id 를 컨트롤러가 아니라 `SampleOperationStore` 에서 만드는 이유**: 컨트롤러/유스케이스는
|
|
||||||
id 를 직접 생성하면 안 된다는 규칙(`no_uuid_random_in_controller`) 때문입니다. `SampleOperationStore`
|
|
||||||
는 컨트롤러도 application 서비스도 아닌 웹 인프라 컴포넌트라 여기서 id 를 발급해도 됩니다.
|
|
||||||
- **단순화**: 이 "export" 는 동기로 끝나므로, 클라이언트가 폴링할 때 저장된 operation 은 이미
|
|
||||||
`SUCCEEDED` 입니다. 진짜 비동기 잡이라면 PENDING → RUNNING → 종료 로 전이하겠지만, wire 모양
|
|
||||||
(202 + Location + 폴링 enum)은 동일합니다.
|
|
||||||
|
|
||||||
### SamplePolymorphicRequest — 안전한 다형 역직렬화(B5)
|
|
||||||
- 다형 JSON 을 안전하게 받는 표준 예시입니다. `@JsonTypeInfo(use = NAME, property = "kind")` +
|
|
||||||
명시적 `@JsonSubTypes` 허용 목록은, RCE 취약점(CVE-2019-14379)의 입구인
|
|
||||||
`ObjectMapper.enableDefaultTyping()` 대신 Jackson 이 권장하는 방식입니다. 목록에 없는 하위 타입은
|
|
||||||
`InvalidTypeIdException` 으로 거절되고, 계약 핸들러가 이를 `MAPPING_FAILED` 로 보냅니다.
|
|
||||||
- `sealed` 키워드를 쓴 이유: 허용 목록과 타입 계층을 기계적으로 일치시키기 위해서입니다. `permits`
|
|
||||||
만 추가하고 `@JsonSubTypes.Type` 을 안 넣거나(혹은 반대로) 하면 조용히 통과되는 게 아니라
|
|
||||||
컴파일/테스트에서 잡힙니다.
|
|
||||||
|
|
||||||
### WorkLogWebMapper
|
|
||||||
- 요청 DTO ↔ application command 변환을 **웹 어댑터가 전부 책임**지게 모은 매퍼입니다. PATCH 도
|
|
||||||
컨트롤러에서 인라인으로 만들지 않고 매퍼를 거치게 해서 일관성을 지킵니다. `WorkLogId` 는 웹
|
|
||||||
edge 에서 파싱(UUID 경로 정규화)해 넘겨받아, id 구문 관심사를 컨트롤러에 둡니다.
|
|
||||||
- 응답에는 **소유자(owner)를 일부러 넣지 않습니다** — 원시 principal id 노출은 프라이버시 문제라,
|
|
||||||
가명화는 별도 프라이버시 계약의 몫입니다.
|
|
||||||
- 링크 URI 가 잘못되면 `MappingException` 을 던져 전역 핸들러가 `MAPPING_FAILED`(400)로 보냅니다.
|
|
||||||
|
|
||||||
### WorkLogIdSerializer
|
|
||||||
- `WorkLogId` 를 record 기본 모양(`{"value":"..."}`)이 아니라 **맨 36자 소문자 UUID 문자열**로
|
|
||||||
직렬화합니다. `@JsonComponent` 로 Spring Boot 가 자동 등록합니다.
|
|
||||||
|
|
||||||
### DomainExceptionHandler / PortfolioErrorCode
|
|
||||||
- **DomainExceptionHandler**: 이 샘플의 도메인 예외를 `Envelope` 로 매핑합니다. 운영/전송/보안
|
|
||||||
예외는 스켈레톤의 기본 `GlobalExceptionHandler` 가 처리하고, Spring 이 두 advice 를 함께 적용합니다.
|
|
||||||
이 핸들러를 `@Order(HIGHEST_PRECEDENCE)` 로 **기본 핸들러보다 앞**에 두는 이유는, 그렇지 않으면
|
|
||||||
기본 advice 의 포괄 핸들러(`@ExceptionHandler(Exception.class)`)가 도메인 예외를 먼저 잡아
|
|
||||||
INTERNAL_ERROR 로 만들어 버리기 때문입니다.
|
|
||||||
- **PortfolioErrorCode**: 이 샘플 전용 도메인 에러 코드. 운영/전송/보안 코드는 공용
|
|
||||||
`OperationalError` 에 있습니다.
|
|
||||||
|
|
||||||
### WorkLogValidationGroups
|
|
||||||
- `@GroupSequence` 용 검증 그룹 순서(형식 → 불변식)를 정의합니다 — 형식 검사가 실패하면 불변식
|
|
||||||
평가를 건너뛰게 합니다.
|
|
||||||
|
|
||||||
## adapter/persistence — RDBMS / JPA 영속
|
|
||||||
|
|
||||||
### WorkLogEntity
|
|
||||||
- WorkLog 애그리거트의 JPA 엔티티. `AuditableEntity` 를 상속해 `created_at/updated_at/created_by/
|
|
||||||
updated_by` 감사 컬럼을 물려받습니다. **감사 필드는 엔티티(영속 계층)에만 있고 도메인 WorkLog 에는
|
|
||||||
없습니다** — 값은 `WorkLogRepositoryAdapter` 가 Clock + 감사 컨텍스트로 찍어줍니다.
|
|
||||||
- **id 저장 형식**: UUIDv7 을 varchar(36)가 아니라 PostgreSQL 16 의 native `uuid` 타입(16바이트
|
|
||||||
바이너리)으로 저장합니다. UUID 문자열↔native uuid 변환은 `WorkLogPersistenceMapper` 가 합니다.
|
|
||||||
(tenant 범위 컬럼/복합 인덱스는 tenant 정책 계약으로 미뤄둠.)
|
|
||||||
- `@Version version`: 낙관적 락 버전이자 웹 경계 ETag/If-Match 의 출처. Hibernate 가 증가를
|
|
||||||
관리하고, `null` 이면 새 행이라 `save()` 가 INSERT 합니다.
|
|
||||||
|
|
||||||
### WorkLogPersistenceMapper — UUID 문자열 ↔ native uuid 변환
|
|
||||||
- 36자 canonical UUID 와 PostgreSQL native `uuid`(128비트)를 서로 변환합니다. `UuidCodec` 같은 공용
|
|
||||||
코덱이 아니라 JDK `java.util.UUID` 를 **직접** 쓰는 이유: 영속 어댑터는 경계 규칙상
|
|
||||||
`adapter-outbound`(코덱이 있는 곳)에 의존하면 안 되기 때문입니다(stdlib 이라 의존 문제 자체가 없음).
|
|
||||||
- 엔티티로 변환할 때 도메인 `version` 을 그대로 실어, JPA 가 새 행(`null` → INSERT)과 추적 중인 행
|
|
||||||
(non-null → 낙관적 merge)을 구분할 수 있게 합니다.
|
|
||||||
|
|
||||||
### WorkLogRepositoryAdapter — 저장 + 감사 스탬프
|
|
||||||
- `WorkLogRepository` 포트 구현체. 감사 메타데이터(생성/수정 시각·주체)를 **여기 영속 어댑터에서**
|
|
||||||
찍습니다(생성자 주입 Clock + 감사 컨텍스트). 도메인 WorkLog 는 감사 필드를 들고 있지 않습니다.
|
|
||||||
- INSERT(버전 null): 새 애그리거트라 created/updated 둘 다 지금 시각·주체로 초기화합니다.
|
|
||||||
- UPDATE(버전 non-null): 기존 행의 created 값을 읽어 그대로 이어가고(같은 트랜잭션에서 유스케이스가
|
|
||||||
이미 로드해둔 JPA 1차 캐시에서 served), updated 만 갱신합니다.
|
|
||||||
|
|
||||||
### WorkLogJpaRepository
|
|
||||||
- `findByCategory`: 계약이 허용하는 유일한 필터 구문인 **평면 동등 필터**를 데이터 계층 안에 둔
|
|
||||||
파생 쿼리입니다.
|
|
||||||
- `findRecentSummaryRows`: CQRS-lite projection 읽기. JPQL `SELECT new` 생성자 표현식으로 요약
|
|
||||||
컬럼만 골라(`content/summary/techStack/links` 제외) `WorkLogSummaryRow` 로 담는 컬럼-부분 읽기라,
|
|
||||||
애그리거트 복원과 lazy `@ElementCollection` 조인을 건너뜁니다. 닫힌 인터페이스 projection 대신
|
|
||||||
`SELECT new`/`JdbcTemplate` 을 택한 이유: 닫힌-projection 의 컬럼 가지치기는 구현체 의존적이고
|
|
||||||
Hibernate 6 에서 검증되지 않았기 때문입니다.
|
|
||||||
|
|
||||||
### WorkLogSummaryQueryAdapter / WorkLogSummaryRow
|
|
||||||
- **WorkLogSummaryQueryAdapter**: application `WorkLogSummaryQueryPort` 구현체. JPQL projection 쿼리에
|
|
||||||
위임하고, 저장소 native `WorkLogSummaryRow`(UUID id)를 application `WorkLogSummary`(UUID 문자열 id)로
|
|
||||||
매핑합니다. **저장소 native `UUID` 는 이 어댑터를 벗어나지 않습니다** — application 에 노출되는 읽기
|
|
||||||
모델은 canonical UUID 문자열 형식으로 말합니다.
|
|
||||||
- **WorkLogSummaryRow**: projection 의 영속-쪽 읽기 행. JPQL `SELECT new` 의 대상이며 저장 모양
|
|
||||||
(id 가 native `UUID`)을 그대로 반영합니다. 이 행을 persistence 패키지 안에 두는 것 자체가 native
|
|
||||||
`UUID` 가 어댑터를 벗어나지 못하게 막는 장치입니다.
|
|
||||||
|
|
||||||
### JpaConfig
|
|
||||||
- Spring Boot 메인 클래스가 `dev.caskeleton.bootstrap` 에 있어, 패키지 기준 기본 스캔이
|
|
||||||
`…sample.portfolio.adapter.persistence.*` 를 놓칩니다. 그래서 엔티티/리포지토리 스캔 위치를 여기서
|
|
||||||
명시해, 그 배선을 실제로 소유하는 모듈 안에 둡니다.
|
|
||||||
|
|
||||||
## adapter/outbound — 아웃바운드 통합 (저장소 통계 ACL)
|
|
||||||
|
|
||||||
### RepoStatsPortClient / RepoStatsAclMapper / RawRepoStatsResponse
|
|
||||||
- **ACL(Anti-Corruption Layer) 패턴(B7) 예시**입니다. 외부 응답이 도메인으로 들어오기 전에 반드시
|
|
||||||
ACL 매퍼를 거치게 해서, 원시 외부 타입이 도메인을 오염시키지 못하게 합니다.
|
|
||||||
- **RepoStatsPortClient**: 실제 HTTP 호출(`fetchRaw`)을 추상화해 템플릿을 가볍게 유지합니다 — 실제
|
|
||||||
프로젝트라면 여기에 WebClient/RestClient 를 끼웁니다. 원시 응답은 ACL 을 통과한 뒤에야 도메인 타입이
|
|
||||||
됩니다.
|
|
||||||
- **RepoStatsAclMapper**: ACL 의 세 가지 일 — 정규화(full name 소문자화), 마스킹(echoedToken 은 버려서
|
|
||||||
도메인에 닿지 않음), 공개 필드 선택(`fullName/stargazers/pushedAt` 만 `RepoStats` 로). 필수 필드가
|
|
||||||
없으면 `MappingException`.
|
|
||||||
- **RawRepoStatsResponse**: 외부 제공자 원시 모양. 도메인으로 넘어가지 않으며 package-private 입니다.
|
|
||||||
|
|
||||||
## adapter/identifier — ID 생성 (UUIDv7)
|
|
||||||
|
|
||||||
### UuidWorkLogIdFactory / UuidOutboxEventIdFactory
|
|
||||||
- 도메인 id 포트(`WorkLogIdFactory`, `OutboxEventIdFactory`)의 인프라 구현체입니다.
|
|
||||||
- 둘 다 `UuidCreator.getTimeOrderedEpochPlus1()` 을 씁니다 — RFC 9562 UUIDv7(time-ordered)이며 같은
|
|
||||||
밀리초 안에서도 단조 증가(monotonic)하고 secure random 으로 뒷받침됩니다.
|
|
||||||
- **여기가 샘플에서 `UuidCreator` 호출이 허용되는 유일한 곳**입니다. `no_uuid_random_in_controller`
|
|
||||||
ArchUnit 규칙이 웹/application 계층의 직접 id 생성을 금지하기 때문에, id 생성은 이 인프라 어댑터에만
|
|
||||||
둡니다.
|
|
||||||
|
|
||||||
## bootstrap — 샘플 전용 구성 루트
|
|
||||||
|
|
||||||
> **이 패키지가 왜 존재하는가 (공통 배경):** 프로덕션 조립은 `app-bootstrap` 모듈이 합니다. 그런데
|
|
||||||
> `sample-portfolio` 는 일부러 `app-bootstrap` 에 의존하지 않습니다(샘플이 부트스트랩을 끌어오면
|
|
||||||
> 안 됨). 그래서 스캔으로 자동 발견되지 않고 오직 `app-bootstrap` 에만 있는 구성 루트 빈들
|
|
||||||
> (tracing/metrics/idempotency/privacy/management/domain-context 등)을, 여기 `bootstrap.*` 패키지가
|
|
||||||
> **샘플 전용으로 동등하게 복제**해 제공합니다. 모두 일회용(disposable)입니다 — `sample-portfolio`
|
|
||||||
> 모듈을 지우면 함께 사라지고 프로덕션에는 영향이 없습니다. 프로덕션(`CaSkeletonApplication`)은 원래
|
|
||||||
> `app-bootstrap` 빈들을 그대로 씁니다.
|
|
||||||
|
|
||||||
### SamplePortfolioApplication — 독립 실행 구성 루트
|
|
||||||
- `dev.caskeleton` 하위 전부를 스캔해 프로덕션 모듈의 Spring 컴포넌트(adapter-web `SecurityConfig`,
|
|
||||||
adapter-persistence `PersistenceJpaConfig` 등)를 자동으로 발견·배선합니다. `app-bootstrap` 에만 있어
|
|
||||||
자동 발견되지 않는 구성 루트만 위 `bootstrap.*` 가 채웁니다.
|
|
||||||
- **`@SpringBootApplication` 대신 `@Configuration` + `@EnableAutoConfiguration` + `@ComponentScan` 을
|
|
||||||
쓴 이유**: `@SpringBootApplication` 은 `@SpringBootConfiguration` 을 포함하는데, 그러면 이 클래스가
|
|
||||||
Spring Boot 의 `AnnotatedClassFinder` 에 보입니다. 그 finder 가 이 클래스와 (같은 패키지·테스트
|
|
||||||
classpath 의) `SamplePortfolioTestApplication` **둘 다** 찾으면 "multiple @SpringBootConfiguration"
|
|
||||||
오류를 냅니다. `@Configuration`(= `@SpringBootConfiguration` 아님)을 쓰면 finder 에 숨으면서도,
|
|
||||||
`@SpringBootTest(classes = SamplePortfolioApplication.class)` 로 부팅하는 데는 문제가 없습니다.
|
|
||||||
- **`@ComponentScan` 에 exclude 두 개를 둔 이유**:
|
|
||||||
- `PostgreSqlPersistenceConfig` 제외 — 이 클래스는 `@PersistenceContext EntityManager` 필드와
|
|
||||||
`FlywayConfigurationCustomizer @Bean` 을 함께 가져, 깨지지 않는 초기화 순환을 만듭니다
|
|
||||||
(flyway → customizer 수집 → PostgreSqlPersistenceConfig 인스턴스화 → `@PersistenceContext` 가
|
|
||||||
entityManagerFactory 를 당김 → 그건 flywayInitializer 에 의존 → 순환). 대신
|
|
||||||
`SamplePostgreSqlPersistenceConfig` 가 static `@Bean` 으로 동일 빈을 제공해 순환을 끊습니다(아래 참고).
|
|
||||||
- `TestEnclosedConfigurationFilter` 제외 — 테스트 클래스 안에 중첩된 `@Configuration` 들이 넓은
|
|
||||||
컴포넌트 스캔에 잡혀, 두 테스트 픽스처가 같은 이름의 빈(예: `clock`)을 정의하면
|
|
||||||
`BeanDefinitionOverrideException` 이 나기 때문입니다.
|
|
||||||
|
|
||||||
### SamplePostgreSqlPersistenceConfig — Flyway ↔ EntityManager 초기화 순환 끊기
|
|
||||||
- `adapter-persistence-postgresql` 의 `PostgreSqlPersistenceConfig` 를 샘플 전용으로 대체한 것입니다.
|
|
||||||
- **왜 필요한가**: 원본은 `@PersistenceContext EntityManager` 필드와 `FlywayConfigurationCustomizer`
|
|
||||||
빈을 함께 등록합니다. Spring Boot 의 Flyway 자동 구성은 `entityManagerFactory` 가 준비되기 전에 모든
|
|
||||||
customizer 빈을 모읍니다(Flyway 가 먼저 마이그레이션을 돌려야 JPA 가 스키마를 검증할 수 있으니까).
|
|
||||||
그 customizer 를 모으려고 `PostgreSqlPersistenceConfig` 를 인스턴스화하면 `@PersistenceContext` 가
|
|
||||||
`entityManagerFactory` 를 당기는데, 그 빈은 `flywayInitializer` 완료에 의존합니다 →
|
|
||||||
`flyway → PostgreSqlPersistenceConfig → entityManagerFactory → flywayInitializer → flyway` 의 끊을 수
|
|
||||||
없는 순환이 생깁니다.
|
|
||||||
- **어떻게 끊는가**: customizer 를 **static `@Bean`** 으로 등록합니다. Spring 은 static `@Bean` 을
|
|
||||||
소유 클래스 인스턴스화 없이 호출하므로, Flyway 초기화 동안 `@PersistenceContext` 가 처리되지
|
|
||||||
않습니다. `EntityManager` 는 (런타임에 실제로 필요해질 때까지 미루도록) 빈 경로에서 늦게 해소됩니다.
|
|
||||||
- **마이그레이션 위치 주의**: 이 customizer 가 두 위치(프로덕션 `db/migration/postgresql` + 샘플 전용
|
|
||||||
`db/sample-migration`)를 모두 설정합니다. `configuration.locations(...)` 는 기존 설정을 **덮어쓰므로**
|
|
||||||
(append 아님), `application.yml` 의 `spring.flyway.locations` 는 런타임 효과 없는 문서용일 뿐이고
|
|
||||||
실제 기준은 이 customizer 입니다.
|
|
||||||
|
|
||||||
### TestEnclosedConfigurationFilter — 테스트 중첩 @Configuration 제외
|
|
||||||
- 테스트 클래스 안에 중첩된 `@Configuration` 을 컴포넌트 스캔에서 빼는 필터입니다.
|
|
||||||
- **문제**: `:sample-portfolio:test` 를 돌리면 테스트 클래스가 classpath 에 올라옵니다. 테스트 안
|
|
||||||
static 중첩 `@Configuration`(예: `WorkLogAuthorizationContractTest$AuthzTestConfig`)이 평범한
|
|
||||||
`@Configuration` 이라, 전체 컨텍스트로 부팅할 때 스캔에 발견·등록됩니다. 두 테스트 설정이 같은 이름의
|
|
||||||
빈(예: `clock`)을 정의하면 `BeanDefinitionOverrideException` 이 납니다(override 기본 false).
|
|
||||||
- **휴리스틱**: 바이너리 이름에 `$` 가 있고 그 바깥(enclosing) 클래스 이름이 `Test` 로 끝나면(자바
|
|
||||||
테스트 클래스 관례) 제외합니다. 테스트 프레임워크 의존성을 프로덕션 소스에 들이지 않고도 중첩
|
|
||||||
테스트 픽스처를 전부 거릅니다.
|
|
||||||
|
|
||||||
### 분산 추적 복제본 — SampleTracingConfig / …Settings / …SampleRateResolver / …EnvironmentPostProcessor / SampleMicrometerSpanErrorRecorder
|
|
||||||
- `app-bootstrap` 의 tracing 구성 루트를 샘플 전용으로 옮긴 것들입니다(위 "공통 배경" 참고). tracing
|
|
||||||
런타임(OTel/Micrometer 브리지)이 샘플 build.gradle 에 켜져 있지만, 그 빈들을 등록하는 조립 코드는
|
|
||||||
`app-bootstrap` 에만 있어서 복제했습니다.
|
|
||||||
- **per-profile 기본 샘플 레이트(SampleRateResolver)**: `prod=0.01`, `staging=0.10`, `dev/local=1.0`
|
|
||||||
(그 외 프로파일도 1.0). 설정값(`APP_TRACING_SAMPLE_RATE`)이 `[0.0, 1.0]` 범위로 들어오면 그 값이
|
|
||||||
per-profile 기본을 이깁니다.
|
|
||||||
- **EnvironmentPostProcessor**: 해소된 effective 레이트를 native 키
|
|
||||||
`management.tracing.sampling.probability` 로 이어줘, 실제 OTel sampler 가 gauge 값과 일치하게 합니다.
|
|
||||||
사용자가 native 키를 직접 지정했으면 그 값을 우선합니다. (이 PostProcessor 는 샘플 자신의
|
|
||||||
`META-INF/spring.factories` 로 등록됩니다.)
|
|
||||||
- **SampleMicrometerSpanErrorRecorder**: 현재 span 에 예외를 `error.code` 태그와 함께 기록(D12).
|
|
||||||
Tracer 가 있으면 실제 recorder, 없으면 NOOP 로 동작합니다(adapter-web `GlobalExceptionHandler` 가
|
|
||||||
`ObjectProvider` 로 집어 씀).
|
|
||||||
|
|
||||||
### 그 밖의 부트스트랩 복제본
|
|
||||||
- **SampleMetricsContractConfig**: Micrometer `MeterFilter` 두 개를 meter 등록 전에 설치합니다 —
|
|
||||||
고-cardinality 태그 키를 막는 필터(D8)와 SLO 기반 히스토그램 분포 필터(D9, 소유한 timer 이름에만 적용).
|
|
||||||
- **SampleIdempotencyConfig / …Settings**: 스캔된 `IdempotencyStoreAdapter`/`IdempotencyReaper` 와
|
|
||||||
`IdempotencyExecutor` 가 모두 `Clock` 을 필요로 하고, `@EnableScheduling` 이 reaper 의 `@Scheduled`
|
|
||||||
정리를 켭니다. TTL 은 양수이며 ≤ 72h 여야 합니다(아니면 시작 실패), reaper 주기 기본 10분.
|
|
||||||
- **SamplePseudonymizationConfig / SamplePrivacySettings**: adapter-web `RequestLoggingFilter` 가
|
|
||||||
`UserPrincipalPseudonymizerPort` 를 주입받으므로 그 포트를 채웁니다. salt 는
|
|
||||||
`APP_PRIVACY_PSEUDONYMIZATION_SALT` 에서 오며, 비어 있으면 로컬/테스트가 시작은 되도록 dev 센티넬
|
|
||||||
salt 로 폴백합니다(프로덕션 전엔 실제 시크릿 값을 넣을 것).
|
|
||||||
- **SampleDomainContextConfig**: `adapter-persistence-rdbms` 의 `DomainContextAuditContextPort` 가
|
|
||||||
`DomainContextPropagator` 빈을 무조건 주입받으므로, 기본 `THREAD_LOCAL` 전략 구현
|
|
||||||
(`ThreadLocalDomainContextPropagator`)을 제공합니다 — async 데코레이터를 안 켜는 WorkLog 데모에
|
|
||||||
적합합니다.
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
// Fixture/sample module. Production modules must not depend on this module.
|
|
||||||
// Lean standalone boot: apply the Spring Boot plugin so bootJar / bootRun are available.
|
|
||||||
apply plugin: 'org.springframework.boot'
|
|
||||||
|
|
||||||
apply from: "${rootProject.projectDir}/gradle/strict-qualification-test.gradle"
|
|
||||||
|
|
||||||
def repositoryRootForContractTests = rootProject.projectDir.parentFile
|
|
||||||
def errorCodeRegistryForContractTests = new File(
|
|
||||||
repositoryRootForContractTests,
|
|
||||||
'docs/registries/error-codes.yaml')
|
|
||||||
|
|
||||||
tasks.withType(Test).configureEach {
|
|
||||||
systemProperty 'ca.repository.root', repositoryRootForContractTests.absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('test') {
|
|
||||||
inputs.file(errorCodeRegistryForContractTests)
|
|
||||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
posterImageMigrationTest {
|
|
||||||
java.srcDir 'src/posterImageMigrationTest/java'
|
|
||||||
resources.srcDir 'src/posterImageMigrationTest/resources'
|
|
||||||
compileClasspath += sourceSets.main.output + sourceSets.test.output
|
|
||||||
runtimeClasspath += output + compileClasspath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
posterImageMigrationTestImplementation.extendsFrom testImplementation
|
|
||||||
posterImageMigrationTestRuntimeOnly.extendsFrom testRuntimeOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// The objectstorage adapter (runtimeOnly) pulls in software.amazon.awssdk:s3, whose version is
|
|
||||||
// managed by the AWS SDK v2 BOM (NOT the Spring Boot BOM). Import that BOM at this consumer's scope
|
|
||||||
// — mirroring the objectstorage module — so the transitive s3 dependency resolves on this module's
|
|
||||||
// runtime/test classpath. The AWS-SDK version SSOT stays ext.awsSdkVersion.
|
|
||||||
dependencyManagement {
|
|
||||||
imports {
|
|
||||||
mavenBom "software.amazon.awssdk:bom:${awsSdkVersion}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation project(':domain-core')
|
|
||||||
implementation project(':application-core')
|
|
||||||
implementation project(':adapter:outbound:persistence-jpa')
|
|
||||||
implementation project(':adapter:outbound:identifier')
|
|
||||||
// Object storage adapter (filesystem default / S3-MinIO opt-in) provides the ObjectStoragePort
|
|
||||||
// bean at runtime for the Poster image-upload use case; the port itself lives in application-core.
|
|
||||||
runtimeOnly project(':adapter:outbound:objectstorage')
|
|
||||||
implementation project(':adapter:inbound:web')
|
|
||||||
implementation project(':shared-contract')
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
||||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
|
||||||
// UUIDv7 generation (id factory) + UUID/String conversion (persistence mapper, web path).
|
|
||||||
implementation 'com.github.f4b6a3:uuid-creator:6.1.1'
|
|
||||||
// PATCH 3-state (absent / explicit-null / value) via JsonNullable. See README.
|
|
||||||
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
|
||||||
// Lean standalone boot: actuator + prometheus + tracing runtime + dotenv (mirrors app-bootstrap).
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
||||||
implementation 'io.micrometer:micrometer-registry-prometheus'
|
|
||||||
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
|
|
||||||
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
|
|
||||||
// Spring dotenv: picks up src/.env when bootRun workingDir = src/ (same as app-bootstrap).
|
|
||||||
implementation 'me.paulschwarz:spring-dotenv:4.0.0'
|
|
||||||
// Boot 4 Flyway API/autoconfiguration needed by sample-local persistence configuration.
|
|
||||||
implementation 'org.springframework.boot:spring-boot-flyway'
|
|
||||||
// Flyway core API needed by MigrationStartupConfig (scanned from adapter-persistence).
|
|
||||||
implementation 'org.flywaydb:flyway-core'
|
|
||||||
// Micrometer core for OutboxMetrics / BackgroundJobMetrics.
|
|
||||||
implementation 'io.micrometer:micrometer-core'
|
|
||||||
// (spring-boot-starter-security is already declared above; it also backs ManagementSecurityConfig.)
|
|
||||||
// Property-based testing for @ValueObject invariants (registers its own JUnit Platform engine).
|
|
||||||
testImplementation 'net.jqwik:jqwik:1.9.1'
|
|
||||||
// Real-DB integration test for the persistence adapter (disposable Postgres, real Flyway + JPA).
|
|
||||||
testImplementation 'org.testcontainers:testcontainers-postgresql'
|
|
||||||
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
|
|
||||||
// @ServiceConnection supplies datasource connection details to Spring context/slice tests.
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
|
|
||||||
// Boot 4 split JPA/JDBC slice annotations and TestEntityManager into dedicated test modules.
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-data-jpa-test'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-jpa-test'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-jdbc-test'
|
|
||||||
testRuntimeOnly 'org.postgresql:postgresql'
|
|
||||||
// Spring Security test support (MockMvc + @WithMockUser) for any slice tests.
|
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
|
||||||
}
|
|
||||||
|
|
||||||
// feature-contract-verification-test-suite §5 — OpenAPI drift gate.
|
|
||||||
// OpenApiDriftContractTest runs as a normal test (so `check` is release-blocking on drift) AND
|
|
||||||
// can be invoked through this task to (re)generate the committed snapshot after an intentional
|
|
||||||
// API change: ./gradlew :sample-portfolio:openapiCheckSnapshot -PapproveOpenApiChange
|
|
||||||
tasks.register('openapiCheckSnapshot', Test) {
|
|
||||||
group = 'verification'
|
|
||||||
description = 'OpenAPI drift gate: runtime springdoc /v3/api-docs vs the committed snapshot. ' +
|
|
||||||
'-PapproveOpenApiChange regenerates the committed baseline.'
|
|
||||||
testClassesDirs = sourceSets.test.output.classesDirs
|
|
||||||
classpath = sourceSets.test.runtimeClasspath
|
|
||||||
useJUnitPlatform()
|
|
||||||
filter {
|
|
||||||
includeTestsMatching 'dev.caskeleton.sample.portfolio.adapter.inbound.web.contract.OpenApiDriftContractTest'
|
|
||||||
}
|
|
||||||
systemProperty 'openapi.snapshot.write', project.hasProperty('approveOpenApiChange') ? 'true' : 'false'
|
|
||||||
// Always re-check drift; never serve a stale UP-TO-DATE result.
|
|
||||||
outputs.upToDateWhen { false }
|
|
||||||
// Pin UTC like the main test task for host-locale independence.
|
|
||||||
jvmArgs '-Duser.timezone=UTC'
|
|
||||||
}
|
|
||||||
|
|
||||||
def posterImageMigrationQualification = registerStrictQualificationTest(
|
|
||||||
name: 'posterImageMigrationTest',
|
|
||||||
sourceSet: sourceSets.posterImageMigrationTest,
|
|
||||||
requiredClasses: [
|
|
||||||
'dev.caskeleton.sample.portfolio.qualification.PosterImageIdempotencyRotationQualificationTest',
|
|
||||||
'dev.caskeleton.sample.portfolio.qualification.PosterImageRetirementQualificationTest',
|
|
||||||
'dev.caskeleton.sample.portfolio.qualification.PosterImageV8MigrationQualificationTest'
|
|
||||||
],
|
|
||||||
description: 'Runs the non-skipping PostgreSQL Poster image migration/rotation lane.')
|
|
||||||
posterImageMigrationQualification.configure {
|
|
||||||
shouldRunAfter tasks.named('test')
|
|
||||||
}
|
|
||||||
|
|
||||||
def messagingSampleContractQualification = registerStrictQualificationTest(
|
|
||||||
name: 'messagingSampleContractQualificationTest',
|
|
||||||
sourceSet: sourceSets.test,
|
|
||||||
requiredClasses: [
|
|
||||||
'dev.caskeleton.sample.portfolio.application.event.WorkLogReservedContractContributionTest'
|
|
||||||
],
|
|
||||||
junitXmlOutput: rootProject.layout.buildDirectory.dir(
|
|
||||||
'test-results/messaging-evidence/sample'),
|
|
||||||
binaryResultsOutput: rootProject.layout.buildDirectory.dir(
|
|
||||||
'test-results/messaging-evidence-binary/sample'),
|
|
||||||
description: 'Runs exact Messaging sample contract qualification tests.')
|
|
||||||
messagingSampleContractQualification.configure {
|
|
||||||
dependsOn ':prepareMessagingContractEvidence'
|
|
||||||
}
|
|
||||||
|
|
||||||
bootJar {
|
|
||||||
mainClass = 'dev.caskeleton.sample.portfolio.SamplePortfolioApplication'
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register('stageDockerJar', Sync) {
|
|
||||||
dependsOn tasks.named('bootJar')
|
|
||||||
from(tasks.named('bootJar').flatMap { it.archiveFile })
|
|
||||||
into(layout.buildDirectory.dir('docker'))
|
|
||||||
rename { 'application.jar' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run from the repo's src/ root so src/.env is picked up (same as app-bootstrap).
|
|
||||||
bootRun {
|
|
||||||
workingDir = rootProject.projectDir
|
|
||||||
}
|
|
||||||
@@ -1,338 +0,0 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
|
||||||
# Manual edits can break the build and are not advised.
|
|
||||||
# This file is expected to be part of source control.
|
|
||||||
aopalliance:aopalliance:1.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
ch.qos.logback:logback-classic:1.5.21=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
ch.qos.logback:logback-core:1.5.21=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.fasterxml:classmate:1.7.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.github.docker-java:docker-java-api:3.7.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.github.docker-java:docker-java-transport-zerodep:3.7.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.github.docker-java:docker-java-transport:3.7.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.github.f4b6a3:uuid-creator:6.1.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
|
|
||||||
com.github.spotbugs:spotbugs-annotations:4.8.6=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
com.github.spotbugs:spotbugs:4.10.2=spotbugs
|
|
||||||
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
|
|
||||||
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.auto:auto-common:1.2.2=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.code.findbugs:jsr305:3.0.2=checkstyle,compileClasspath,posterImageMigrationTestCompileClasspath,spotbugs,testCompileClasspath
|
|
||||||
com.google.code.gson:gson:2.13.2=spotbugs
|
|
||||||
com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
com.google.errorprone:error_prone_annotations:2.41.0=spotbugs
|
|
||||||
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
|
|
||||||
com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.guava:guava:33.5.0-jre=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.guava:guava:33.6.0-jre=checkstyle
|
|
||||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
com.h2database:h2:2.4.240=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
|
|
||||||
com.jayway.jsonpath:json-path:2.9.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
|
|
||||||
com.squareup.okhttp3:okhttp-jvm:5.2.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.squareup.okhttp3:okhttp:5.2.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.squareup.okio:okio-jvm:3.16.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.squareup.okio:okio:3.16.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.sun.istack:istack-commons-runtime:4.1.2=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
com.zaxxer:HikariCP:7.0.2=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
commons-beanutils:commons-beanutils:1.11.0=checkstyle
|
|
||||||
commons-codec:commons-codec:1.19.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
commons-collections:commons-collections:3.2.2=checkstyle
|
|
||||||
commons-io:commons-io:2.20.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
commons-io:commons-io:2.21.0=spotbugs
|
|
||||||
commons-logging:commons-logging:1.3.5=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
info.picocli:picocli:4.7.7=checkstyle
|
|
||||||
io.github.cdimascio:dotenv-java:3.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
io.micrometer:context-propagation:1.2.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-commons:1.16.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-core:1.16.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-jakarta9:1.16.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-observation:1.16.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-registry-prometheus:1.16.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-tracing-bridge-otel:1.6.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.micrometer:micrometer-tracing:1.6.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-buffer:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-base:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-compression:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-http2:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-http:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-marshalling:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec-protobuf:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-codec:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-common:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-handler:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-resolver:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-transport-classes-epoll:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-transport-native-unix-common:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.netty:netty-transport:4.2.17.Final=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-exporter-common:1.55.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.projectreactor:reactor-core:3.8.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.prometheus:prometheus-metrics-exposition-formats:1.4.3=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.swagger.core.v3:swagger-core-jakarta:2.2.38=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
io.swagger.core.v3:swagger-models-jakarta:2.2.38=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
jakarta.activation:jakarta.activation-api:2.1.4=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
jakarta.inject:jakarta.inject-api:2.0.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
jakarta.persistence:jakarta.persistence-api:3.2.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
javax.inject:javax.inject:1=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
jaxen:jaxen:2.0.0=spotbugs
|
|
||||||
me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.bytebuddy:byte-buddy-agent:1.17.8=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.bytebuddy:byte-buddy:1.17.8=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.java.dev.jna:jna:5.18.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.jqwik:jqwik-api:1.9.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.jqwik:jqwik-engine:1.9.1=posterImageMigrationTestRuntimeClasspath,testRuntimeClasspath
|
|
||||||
net.jqwik:jqwik-time:1.9.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.jqwik:jqwik-web:1.9.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.jqwik:jqwik:1.9.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.minidev:accessors-smart:2.6.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.minidev:json-smart:2.6.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
|
|
||||||
org.antlr:antlr4-runtime:4.13.2=checkstyle,compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.bcel:bcel:6.12.0=spotbugs
|
|
||||||
org.apache.commons:commons-compress:1.28.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.commons:commons-lang3:3.20.0=checkstyle,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.commons:commons-text:1.15.0=spotbugs
|
|
||||||
org.apache.commons:commons-text:1.3=checkstyle
|
|
||||||
org.apache.httpcomponents:httpclient:4.5.13=checkstyle,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.apache.httpcomponents:httpcore:4.4.16=checkstyle,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.apache.logging.log4j:log4j-api:2.25.2=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.logging.log4j:log4j-core:2.25.2=spotbugs
|
|
||||||
org.apache.logging.log4j:log4j-to-slf4j:2.25.2=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
|
|
||||||
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
|
|
||||||
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
|
|
||||||
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
|
|
||||||
org.apache.tomcat.embed:tomcat-embed-core:11.0.14=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.tomcat.embed:tomcat-embed-el:11.0.14=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.14=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.apache.xbean:xbean-reflect:3.7=checkstyle
|
|
||||||
org.apiguardian:apiguardian-api:1.1.2=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.aspectj:aspectjweaver:1.9.25=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.assertj:assertj-core:3.27.6=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.awaitility:awaitility:4.3.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.checkerframework:checker-qual:3.49.5=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
|
|
||||||
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
|
|
||||||
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
|
|
||||||
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
|
|
||||||
org.dom4j:dom4j:2.2.0=spotbugs
|
|
||||||
org.eclipse.angus:angus-activation:2.0.3=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.flywaydb:flyway-core:11.14.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.flywaydb:flyway-database-postgresql:11.14.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.glassfish.jaxb:jaxb-core:4.0.6=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.glassfish.jaxb:jaxb-runtime:4.0.6=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.glassfish.jaxb:txw2:4.0.6=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.hamcrest:hamcrest:3.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.hdrhistogram:HdrHistogram:2.2.2=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.hibernate.models:hibernate-models:1.0.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.hibernate.orm:hibernate-core:7.1.8.Final=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
|
||||||
org.jboss.logging:jboss-logging:3.6.1.Final=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.jetbrains:annotations:13.0=productionRuntimeClasspath,runtimeClasspath
|
|
||||||
org.jetbrains:annotations:17.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,posterImageMigrationTestAnnotationProcessor,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.junit.jupiter:junit-jupiter-api:6.0.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.junit.jupiter:junit-jupiter-engine:6.0.1=posterImageMigrationTestRuntimeClasspath,testRuntimeClasspath
|
|
||||||
org.junit.jupiter:junit-jupiter-params:6.0.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.junit.jupiter:junit-jupiter:6.0.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.junit.platform:junit-platform-commons:6.0.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.junit.platform:junit-platform-engine:6.0.1=posterImageMigrationTestRuntimeClasspath,testRuntimeClasspath
|
|
||||||
org.junit.platform:junit-platform-launcher:6.0.1=posterImageMigrationTestRuntimeClasspath,testRuntimeClasspath
|
|
||||||
org.junit:junit-bom:6.0.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.junit:junit-bom:6.1.0=spotbugs
|
|
||||||
org.latencyutils:LatencyUtils:2.0.3=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.mockito:mockito-core:5.20.0=mockitoAgent,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.mockito:mockito-junit-jupiter:5.20.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.objenesis:objenesis:3.3=posterImageMigrationTestRuntimeClasspath,testRuntimeClasspath
|
|
||||||
org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.opentest4j:opentest4j:1.3.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
org.osgi:org.osgi.resource:1.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
org.osgi:org.osgi.service.serviceloader:1.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,testCompileClasspath
|
|
||||||
org.ow2.asm:asm-analysis:9.10.1=spotbugs
|
|
||||||
org.ow2.asm:asm-commons:9.10.1=spotbugs
|
|
||||||
org.ow2.asm:asm-tree:9.10.1=spotbugs
|
|
||||||
org.ow2.asm:asm-util:9.10.1=spotbugs
|
|
||||||
org.ow2.asm:asm:9.10.1=spotbugs
|
|
||||||
org.ow2.asm:asm:9.7.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.pcollections:pcollections:4.0.1=annotationProcessor,posterImageMigrationTestAnnotationProcessor,testAnnotationProcessor
|
|
||||||
org.postgresql:postgresql:42.7.8=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.reactivestreams:reactive-streams:1.0.4=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.reflections:reflections:0.10.2=checkstyle
|
|
||||||
org.rnorth.duct-tape:duct-tape:1.0.8=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.skyscreamer:jsonassert:1.5.3=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.slf4j:slf4j-api:2.0.17=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.slf4j:slf4j-simple:2.0.17=checkstyle,spotbugsSlf4j
|
|
||||||
org.springdoc:springdoc-openapi-starter-common:3.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-actuator:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-configuration-processor:4.0.0=annotationProcessor
|
|
||||||
org.springframework.boot:spring-boot-data-commons:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-data-jpa-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-data-jpa:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-flyway:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-health:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-hibernate:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-http-client:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-http-converter:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-jackson:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-jdbc-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-jdbc:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-jpa-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-jpa:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-micrometer-metrics:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-micrometer-observation:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-persistence:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-restclient:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-resttestclient:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-security:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-servlet:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-sql:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-actuator:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-data-jpa:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-flyway:4.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-jackson-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-jackson:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-jdbc:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-logging:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-security:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-tomcat:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-validation:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-web:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter-webmvc:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-starter:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-test-autoconfigure:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-testcontainers:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-tomcat:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-transaction:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-validation:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-web-server:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-webmvc-test:4.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot-webmvc:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.boot:spring-boot:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.data:spring-data-commons:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.data:spring-data-jpa:4.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.integration:spring-integration-core:7.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.integration:spring-integration-jdbc:7.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-config:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-core:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-crypto:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-oauth2-core:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-oauth2-jose:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-oauth2-resource-server:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-test:7.0.0=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.security:spring-security-web:7.0.0=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework.session:spring-session-core:4.0.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-aop:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-aspects:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-beans:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-context:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-core:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-expression:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-jdbc:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-messaging:7.0.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-orm:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-test:7.0.1=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-tx:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-web:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-webflux:7.0.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
org.springframework:spring-webmvc:7.0.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.testcontainers:testcontainers-database-commons:2.0.2=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.testcontainers:testcontainers-jdbc:2.0.2=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.testcontainers:testcontainers-junit-jupiter:2.0.2=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.testcontainers:testcontainers-postgresql:2.0.2=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.testcontainers:testcontainers:2.0.2=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
|
|
||||||
org.xmlunit:xmlunit-core:2.10.4=posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
org.yaml:snakeyaml:2.5=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:annotations:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:apache-client:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:arns:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:auth:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:aws-core:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:aws-query-protocol:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:aws-xml-protocol:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:checksums-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:checksums:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:crt-core:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:endpoints-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth-aws-eventstream:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth-aws:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-auth:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:http-client-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:identity-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:json-utils:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:metrics-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:netty-nio-client:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:profiles:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:protocol-core:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:regions:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:retries-spi:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:retries:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:s3:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:sdk-core:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:third-party-jackson-core:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.awssdk:utils:2.30.0=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
software.amazon.eventstream:eventstream:1.0.1=posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
|
||||||
tools.jackson.core:jackson-core:3.0.2=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
tools.jackson.core:jackson-databind:3.0.2=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
tools.jackson:jackson-bom:3.0.2=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
|
||||||
empty=developmentOnly,testAndDevelopmentOnly
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.inbound.web.auth.SecurityConfig;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.authz.MethodSecurityConfig;
|
|
||||||
import dev.caskeleton.adapter.outbound.persistence.postgresql.PostgreSqlPersistenceConfig;
|
|
||||||
import dev.caskeleton.sample.portfolio.bootstrap.scan.TestEnclosedConfigurationFilter;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.FilterType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standalone composition root for the WorkLog sample/reference application. Scans all {@code
|
|
||||||
* dev.caskeleton} sub-packages so production-module components are auto-discovered;
|
|
||||||
* composition-root beans that live only in {@code app-bootstrap} are provided by the sample-local
|
|
||||||
* {@code bootstrap.*} package. Disposable: deleting the {@code sample-portfolio} module removes it
|
|
||||||
* without affecting production.
|
|
||||||
*
|
|
||||||
* <p>See README for the wiring rationale: why {@code @Configuration} is used instead of
|
|
||||||
* {@code @SpringBootApplication}, why the two component-scan exclusions exist, and the
|
|
||||||
* lean-bootstrap omissions a full clone would add.
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EnableAutoConfiguration
|
|
||||||
@ComponentScan(
|
|
||||||
basePackages = "dev.caskeleton",
|
|
||||||
excludeFilters = {
|
|
||||||
// Replaced by SamplePostgreSqlPersistenceConfig to break a Flyway/EntityManager
|
|
||||||
// init cycle (see README).
|
|
||||||
@ComponentScan.Filter(
|
|
||||||
type = FilterType.ASSIGNABLE_TYPE,
|
|
||||||
classes = {
|
|
||||||
PostgreSqlPersistenceConfig.class,
|
|
||||||
// The standalone sample is a public demo app: URL access should not require a
|
|
||||||
// configured IdP or method-security principal. Production modules keep these guards.
|
|
||||||
SecurityConfig.class,
|
|
||||||
MethodSecurityConfig.class
|
|
||||||
}),
|
|
||||||
// Excludes @Configuration nested in test classes (see README).
|
|
||||||
@ComponentScan.Filter(
|
|
||||||
type = FilterType.CUSTOM,
|
|
||||||
classes = TestEnclosedConfigurationFilter.class)
|
|
||||||
})
|
|
||||||
@ConfigurationPropertiesScan(basePackages = "dev.caskeleton")
|
|
||||||
public class SamplePortfolioApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(SamplePortfolioApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.controller;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.storage.StoredObject;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.StoredObjectResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper.PosterWebMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.UploadPosterImageCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.UploadPosterImageUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.shared.error.MappingException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
/** Explicit compatibility-profile whole-byte endpoint; absent from canonical publication mode. */
|
|
||||||
@RestController
|
|
||||||
@ConditionalOnProperty(prefix = "app.poster-image.api", name = "mode", havingValue = "legacy")
|
|
||||||
// This exact endpoint remains active until data/API migration is complete.
|
|
||||||
@SuppressWarnings("removal")
|
|
||||||
public final class LegacyPosterImageController {
|
|
||||||
|
|
||||||
private final UploadPosterImageUseCase upload;
|
|
||||||
|
|
||||||
public LegacyPosterImageController(UploadPosterImageUseCase upload) {
|
|
||||||
this.upload = upload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(value = "/posters/{id}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
||||||
public StoredObjectResponse uploadImage(
|
|
||||||
@PathVariable String id, @RequestParam("file") MultipartFile file) {
|
|
||||||
byte[] bytes;
|
|
||||||
try {
|
|
||||||
bytes = file.getBytes();
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new MappingException("failed to read uploaded file", exception);
|
|
||||||
}
|
|
||||||
String contentType =
|
|
||||||
file.getContentType() == null ? "application/octet-stream" : file.getContentType();
|
|
||||||
StoredObject stored = upload.handle(new UploadPosterImageCommand(toId(id), bytes, contentType));
|
|
||||||
return PosterWebMapper.toStoredObjectResponse(stored);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PosterId toId(String raw) {
|
|
||||||
return PosterId.of(UUID.fromString(raw.toLowerCase(Locale.ROOT)).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.controller;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.inbound.web.settings.PresentationSettings;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.operation.SampleOperationStore;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.operation.WorkLogExportResult;
|
|
||||||
import dev.caskeleton.shared.operation.Operation;
|
|
||||||
import java.net.URI;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Long-running-operation fixture. {@code POST /worklogs:export} returns {@code 202 Accepted} + a
|
|
||||||
* {@code Location} header + a polling envelope; {@code GET /operations/{id}} returns the {@link
|
|
||||||
* Operation}. The id is minted by {@link SampleOperationStore}, never by this controller. See
|
|
||||||
* README.
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
public class OperationsController {
|
|
||||||
|
|
||||||
private final SampleOperationStore operations;
|
|
||||||
private final PresentationSettings presentationSettings;
|
|
||||||
|
|
||||||
public OperationsController(
|
|
||||||
SampleOperationStore operations, PresentationSettings presentationSettings) {
|
|
||||||
this.operations = operations;
|
|
||||||
this.presentationSettings = presentationSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/worklogs:export")
|
|
||||||
public ResponseEntity<Operation<WorkLogExportResult>> export() {
|
|
||||||
Operation<WorkLogExportResult> accepted =
|
|
||||||
operations.startExport(presentationSettings.apiBasePath(), 0L);
|
|
||||||
return ResponseEntity.accepted().location(URI.create(accepted.statusUrl())).body(accepted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/operations/{id}")
|
|
||||||
public Operation<WorkLogExportResult> poll(@PathVariable String id) {
|
|
||||||
return operations
|
|
||||||
.find(id)
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("unknown operation id: " + id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-164
@@ -1,164 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.controller;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.inbound.web.observability.ResponseMetaFactory;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.pagination.PageParams;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.pagination.PageValidationException;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.pagination.SortParam;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.CreatePosterRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.UpdatePosterRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.PosterResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper.PosterWebMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.ArchivePosterCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.DeletePosterCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.PublishPosterCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.ArchivePosterUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.CreatePosterUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.DeletePosterUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.GetPosterUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.ListPostersUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.PublishPosterUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.UpdatePosterUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.query.GetPosterQuery;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.query.ListPostersQuery;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterPage;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterSortField;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterStatus;
|
|
||||||
import dev.caskeleton.shared.response.Envelope;
|
|
||||||
import dev.caskeleton.shared.response.PageMeta;
|
|
||||||
import dev.caskeleton.shared.response.ResponseMeta;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PatchMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Promotional-poster endpoints. Resource naming follows AIP-122 ({@code /posters}); lifecycle
|
|
||||||
* actions are modelled as sub-resource paths ({@code /posters/{id}/image}, {@code /publish}, {@code
|
|
||||||
* /archive}) — a resource-level colon-verb ({@code {id}:publish}) is rejected by the AIP-122
|
|
||||||
* guardrail, which only permits collection-level colon verbs. The {@code /v1} prefix is applied
|
|
||||||
* centrally. The upload → publish flow is what ties object storage to a domain invariant (a poster
|
|
||||||
* cannot be published without an image). Mirrors {@code WorkLogController} (conditional-request /
|
|
||||||
* owner / batch machinery deliberately omitted — WorkLog demonstrates those).
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
public class PosterController {
|
|
||||||
|
|
||||||
private final CreatePosterUseCase create;
|
|
||||||
private final UpdatePosterUseCase update;
|
|
||||||
private final PublishPosterUseCase publish;
|
|
||||||
private final ArchivePosterUseCase archive;
|
|
||||||
private final DeletePosterUseCase delete;
|
|
||||||
private final GetPosterUseCase get;
|
|
||||||
private final ListPostersUseCase list;
|
|
||||||
|
|
||||||
public PosterController(
|
|
||||||
CreatePosterUseCase create,
|
|
||||||
UpdatePosterUseCase update,
|
|
||||||
PublishPosterUseCase publish,
|
|
||||||
ArchivePosterUseCase archive,
|
|
||||||
DeletePosterUseCase delete,
|
|
||||||
GetPosterUseCase get,
|
|
||||||
ListPostersUseCase list) {
|
|
||||||
this.create = create;
|
|
||||||
this.update = update;
|
|
||||||
this.publish = publish;
|
|
||||||
this.archive = archive;
|
|
||||||
this.delete = delete;
|
|
||||||
this.get = get;
|
|
||||||
this.list = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Paginated, optionally sorted + status-filtered listing with {@code meta.page}. */
|
|
||||||
@GetMapping("/posters")
|
|
||||||
public Envelope<List<PosterResponse>> list(
|
|
||||||
@RequestParam(required = false) Integer page,
|
|
||||||
@RequestParam(required = false) Integer size,
|
|
||||||
@RequestParam(required = false) String sort,
|
|
||||||
@RequestParam(required = false) PosterStatus status) {
|
|
||||||
|
|
||||||
PageParams pageParams = PageParams.of(page, size);
|
|
||||||
PosterSortField sortField = null;
|
|
||||||
boolean ascending = true;
|
|
||||||
String appliedSort = null;
|
|
||||||
if (sort != null && !sort.isBlank()) {
|
|
||||||
SortParam parsed = SortParam.parse(sort); // rejects non-native syntax → 400
|
|
||||||
sortField = PosterSortField.fromRequest(parsed.field());
|
|
||||||
if (sortField == null) {
|
|
||||||
throw new PageValidationException(
|
|
||||||
"sort",
|
|
||||||
"SORT_FIELD_NOT_ALLOWED",
|
|
||||||
"sort field '" + parsed.field() + "' is not sortable");
|
|
||||||
}
|
|
||||||
ascending = parsed.ascending();
|
|
||||||
appliedSort = parsed.canonical();
|
|
||||||
}
|
|
||||||
|
|
||||||
PosterPage result =
|
|
||||||
list.handle(
|
|
||||||
new ListPostersQuery(
|
|
||||||
pageParams.page(), pageParams.size(), sortField, ascending, status));
|
|
||||||
List<PosterResponse> body = result.items().stream().map(PosterWebMapper::toResponse).toList();
|
|
||||||
|
|
||||||
PageMeta pageMeta = pageParams.toPageMeta(result.total(), appliedSort);
|
|
||||||
ResponseMeta meta = ResponseMetaFactory.fromMdc().withPage(pageMeta);
|
|
||||||
return Envelope.ok(body, meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/posters/{id}")
|
|
||||||
public PosterResponse getOne(@PathVariable String id) {
|
|
||||||
return PosterWebMapper.toResponse(get.handle(new GetPosterQuery(toId(id))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/posters")
|
|
||||||
public ResponseEntity<PosterResponse> create(@Valid @RequestBody CreatePosterRequest body) {
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED)
|
|
||||||
.body(PosterWebMapper.toResponse(create.handle(PosterWebMapper.toCommand(body))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PatchMapping("/posters/{id}")
|
|
||||||
public PosterResponse patch(
|
|
||||||
@PathVariable String id, @Valid @RequestBody UpdatePosterRequest body) {
|
|
||||||
return PosterWebMapper.toResponse(
|
|
||||||
update.handle(PosterWebMapper.toUpdateCommand(toId(id), body)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code DRAFT → PUBLISHED}. Fails with 400 {@code POSTER_IMAGE_REQUIRED} if no image is
|
|
||||||
* attached.
|
|
||||||
*/
|
|
||||||
@PostMapping("/posters/{id}/publish")
|
|
||||||
public PosterResponse publish(@PathVariable String id) {
|
|
||||||
return PosterWebMapper.toResponse(publish.handle(new PublishPosterCommand(toId(id))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/posters/{id}/archive")
|
|
||||||
public PosterResponse archive(@PathVariable String id) {
|
|
||||||
return PosterWebMapper.toResponse(archive.handle(new ArchivePosterCommand(toId(id))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/posters/{id}")
|
|
||||||
public ResponseEntity<Void> delete(@PathVariable String id) {
|
|
||||||
delete.handle(new DeletePosterCommand(toId(id)));
|
|
||||||
return ResponseEntity.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accepts case-insensitive canonical UUID path input and normalises it to the canonical 36-char
|
|
||||||
* lowercase UUID. {@code UUID.fromString(...)} is a parser (throws {@link
|
|
||||||
* IllegalArgumentException} on a malformed id → HTTP 400).
|
|
||||||
*/
|
|
||||||
private static PosterId toId(String raw) {
|
|
||||||
return PosterId.of(UUID.fromString(raw.toLowerCase(Locale.ROOT)).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-113
@@ -1,113 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.controller;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.inbound.web.auth.AuthenticatedPrincipal;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.http.ApiHeaders;
|
|
||||||
import dev.caskeleton.application.objectstorage.content.CancellationView;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationId;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectContentIdentity;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectDigest;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectDigestAlgorithm;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectMediaType;
|
|
||||||
import dev.caskeleton.application.outbound.CallBudget;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.PosterImagePublicationResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.PosterImagePublicationStatusResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper.PosterImagePublicationWebMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.objectstorage.MultipartObjectContentProducer;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.PublishPosterImageCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.GetPosterImagePublicationStatusUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.poster.PublishPosterImageUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.query.GetPosterImagePublicationStatusQuery;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
/** Additive streaming publication/status API; no raw locator or signing surface. */
|
|
||||||
@RestController
|
|
||||||
@ConditionalOnProperty(prefix = "app.poster-image.api", name = "mode", havingValue = "publication")
|
|
||||||
public final class PosterImagePublicationController {
|
|
||||||
|
|
||||||
private static final Duration CALL_TIMEOUT = Duration.ofMinutes(2);
|
|
||||||
|
|
||||||
private final PublishPosterImageUseCase publish;
|
|
||||||
private final GetPosterImagePublicationStatusUseCase status;
|
|
||||||
|
|
||||||
public PosterImagePublicationController(
|
|
||||||
PublishPosterImageUseCase publish, GetPosterImagePublicationStatusUseCase status) {
|
|
||||||
this.publish = publish;
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(
|
|
||||||
value = "/posters/{id}/imagePublications",
|
|
||||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
||||||
public ResponseEntity<PosterImagePublicationResponse> publish(
|
|
||||||
@PathVariable String id,
|
|
||||||
@RequestHeader(ApiHeaders.IDEMPOTENCY_KEY) String idempotencyKey,
|
|
||||||
@RequestParam("expectedVersion") long expectedVersion,
|
|
||||||
@RequestParam("sha256") String sha256,
|
|
||||||
@RequestParam("file") MultipartFile file,
|
|
||||||
@AuthenticationPrincipal AuthenticatedPrincipal principal) {
|
|
||||||
if (idempotencyKey == null
|
|
||||||
|| idempotencyKey.isBlank()
|
|
||||||
|| idempotencyKey.length() > 256
|
|
||||||
|| principal == null
|
|
||||||
|| principal.idpUserId() == null
|
|
||||||
|| principal.idpUserId().isBlank()) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"authenticated principal and Idempotency-Key are required");
|
|
||||||
}
|
|
||||||
ObjectContentIdentity identity =
|
|
||||||
new ObjectContentIdentity(
|
|
||||||
file.getSize(), ObjectDigest.of(ObjectDigestAlgorithm.SHA_256, sha256));
|
|
||||||
String contentType =
|
|
||||||
file.getContentType() == null ? "application/octet-stream" : file.getContentType();
|
|
||||||
var reservation =
|
|
||||||
publish.handle(
|
|
||||||
new PublishPosterImageCommand(
|
|
||||||
toId(id),
|
|
||||||
expectedVersion,
|
|
||||||
null,
|
|
||||||
principal.idpUserId(),
|
|
||||||
idempotencyKey.trim(),
|
|
||||||
identity,
|
|
||||||
ObjectMediaType.of(contentType),
|
|
||||||
new MultipartObjectContentProducer(file, identity),
|
|
||||||
CallBudget.fromNow(CALL_TIMEOUT),
|
|
||||||
CancellationView.never()));
|
|
||||||
PosterImagePublicationResponse response =
|
|
||||||
PosterImagePublicationWebMapper.toResponse(reservation);
|
|
||||||
return ResponseEntity.status(HttpStatus.ACCEPTED)
|
|
||||||
.header(ApiHeaders.LOCATION, response.statusPath())
|
|
||||||
.body(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/posters/{id}/imagePublications/{operationId}")
|
|
||||||
public PosterImagePublicationStatusResponse status(
|
|
||||||
@PathVariable String id,
|
|
||||||
@PathVariable String operationId,
|
|
||||||
@AuthenticationPrincipal AuthenticatedPrincipal principal) {
|
|
||||||
if (principal == null) {
|
|
||||||
throw new IllegalArgumentException("authenticated principal is required");
|
|
||||||
}
|
|
||||||
return PosterImagePublicationWebMapper.toResponse(
|
|
||||||
status.handle(
|
|
||||||
new GetPosterImagePublicationStatusQuery(toId(id), ObjectOperationId.of(operationId))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PosterId toId(String raw) {
|
|
||||||
return PosterId.of(UUID.fromString(raw.toLowerCase(Locale.ROOT)).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-254
@@ -1,254 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.controller;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.inbound.web.auth.AuthenticatedPrincipal;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.conditional.ETags;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.conditional.PreconditionFailedException;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.http.ApiHeaders;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.observability.ResponseMetaFactory;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.pagination.PageParams;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.pagination.PageValidationException;
|
|
||||||
import dev.caskeleton.adapter.inbound.web.pagination.SortParam;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.CreateWorkLogRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.UpdateWorkLogRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.RepoStatsResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.WorkLogResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.WorkLogSummaryResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper.WorkLogWebMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.BatchCreateWorkLogsCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.DeleteWorkLogCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.query.GetRepoStatsQuery;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.query.GetWorkLogQuery;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.query.ListWorkLogsQuery;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.BatchCreateWorkLogsUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.CreateWorkLogUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.DeleteWorkLogUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.GetRepoStatsUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.GetWorkLogUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.ListWorkLogsUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.worklog.UpdateWorkLogUseCase;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkCategory;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLog;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogPage;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogSortField;
|
|
||||||
import dev.caskeleton.shared.response.Envelope;
|
|
||||||
import dev.caskeleton.shared.response.PageMeta;
|
|
||||||
import dev.caskeleton.shared.response.ResponseMeta;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PatchMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Portfolio board endpoints. Resource naming follows AIP-122 ({@code /worklogs}, {@code
|
|
||||||
* /worklogs/repoStats}, colon-verb {@code /worklogs:batchCreate}); the {@code /v1} prefix is
|
|
||||||
* applied centrally. Pagination/sort/filter drive the list, and conditional requests ({@code
|
|
||||||
* ETag}/{@code If-Match}/{@code If-None-Match}) drive GET/PATCH. See README.
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
public class WorkLogController {
|
|
||||||
|
|
||||||
private final CreateWorkLogUseCase create;
|
|
||||||
private final BatchCreateWorkLogsUseCase batchCreate;
|
|
||||||
private final UpdateWorkLogUseCase update;
|
|
||||||
private final DeleteWorkLogUseCase delete;
|
|
||||||
private final GetWorkLogUseCase get;
|
|
||||||
private final ListWorkLogsUseCase list;
|
|
||||||
private final GetRepoStatsUseCase repoStats;
|
|
||||||
|
|
||||||
public WorkLogController(
|
|
||||||
CreateWorkLogUseCase create,
|
|
||||||
BatchCreateWorkLogsUseCase batchCreate,
|
|
||||||
UpdateWorkLogUseCase update,
|
|
||||||
DeleteWorkLogUseCase delete,
|
|
||||||
GetWorkLogUseCase get,
|
|
||||||
ListWorkLogsUseCase list,
|
|
||||||
GetRepoStatsUseCase repoStats) {
|
|
||||||
this.create = create;
|
|
||||||
this.batchCreate = batchCreate;
|
|
||||||
this.update = update;
|
|
||||||
this.delete = delete;
|
|
||||||
this.get = get;
|
|
||||||
this.list = list;
|
|
||||||
this.repoStats = repoStats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Paginated, optionally sorted + flat-filtered listing with {@code meta.page}. */
|
|
||||||
@GetMapping("/worklogs")
|
|
||||||
public ResponseEntity<Envelope<List<WorkLogSummaryResponse>>> list(
|
|
||||||
@RequestParam(required = false) Integer page,
|
|
||||||
@RequestParam(required = false) Integer size,
|
|
||||||
@RequestParam(required = false) String sort,
|
|
||||||
@RequestParam(required = false) WorkCategory category) {
|
|
||||||
|
|
||||||
PageParams pageParams = PageParams.of(page, size);
|
|
||||||
WorkLogSortField sortField = null;
|
|
||||||
boolean ascending = true;
|
|
||||||
String appliedSort = null;
|
|
||||||
if (sort != null && !sort.isBlank()) {
|
|
||||||
SortParam parsed = SortParam.parse(sort); // rejects non-native syntax → 400
|
|
||||||
sortField = WorkLogSortField.fromRequest(parsed.field());
|
|
||||||
if (sortField == null) {
|
|
||||||
throw new PageValidationException(
|
|
||||||
"sort",
|
|
||||||
"SORT_FIELD_NOT_ALLOWED",
|
|
||||||
"sort field '" + parsed.field() + "' is not sortable");
|
|
||||||
}
|
|
||||||
ascending = parsed.ascending();
|
|
||||||
appliedSort = parsed.canonical();
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkLogPage result =
|
|
||||||
list.handle(
|
|
||||||
new ListWorkLogsQuery(
|
|
||||||
pageParams.page(), pageParams.size(), sortField, ascending, category));
|
|
||||||
List<WorkLogSummaryResponse> body =
|
|
||||||
result.items().stream().map(WorkLogWebMapper::toSummary).toList();
|
|
||||||
|
|
||||||
PageMeta pageMeta = pageParams.toPageMeta(result.total(), appliedSort);
|
|
||||||
ResponseMeta meta = ResponseMetaFactory.fromMdc().withPage(pageMeta);
|
|
||||||
|
|
||||||
ResponseEntity.BodyBuilder builder = ResponseEntity.ok();
|
|
||||||
if (pageParams.isDeepOffset()) { // deep offset → recommend cursor
|
|
||||||
builder.header(ApiHeaders.DEPRECATION, "deep-offset-pagination");
|
|
||||||
}
|
|
||||||
return builder.body(Envelope.ok(body, meta));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/worklogs/repoStats")
|
|
||||||
public RepoStatsResponse repoStats(@RequestParam String repoUrl) {
|
|
||||||
return WorkLogWebMapper.toRepoStatsResponse(repoStats.handle(new GetRepoStatsQuery(repoUrl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Emits an {@code ETag}; honours {@code If-None-Match} with a 304 (no body). */
|
|
||||||
@GetMapping("/worklogs/{id}")
|
|
||||||
public ResponseEntity<WorkLogResponse> getOne(
|
|
||||||
@PathVariable String id,
|
|
||||||
@RequestHeader(value = ApiHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) {
|
|
||||||
WorkLog workLog = get.handle(new GetWorkLogQuery(toId(id)));
|
|
||||||
String etag = etagOf(workLog);
|
|
||||||
if (ETags.matches(ifNoneMatch, etag)) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(etag).build();
|
|
||||||
}
|
|
||||||
return ResponseEntity.ok().eTag(etag).body(WorkLogWebMapper.toResponse(workLog));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code Idempotency-Key} is accepted on the POST surface but only tolerated (ignored) until the
|
|
||||||
* rate-limit/idempotency contract wires replay. See README.
|
|
||||||
*/
|
|
||||||
@PostMapping("/worklogs")
|
|
||||||
public ResponseEntity<WorkLogResponse> create(
|
|
||||||
@Valid @RequestBody CreateWorkLogRequest body,
|
|
||||||
@RequestHeader(value = ApiHeaders.IDEMPOTENCY_KEY, required = false) String idempotencyKey) {
|
|
||||||
WorkLog created = create.handle(WorkLogWebMapper.toCommand(body, currentOwnerSubject()));
|
|
||||||
return ResponseEntity.ok().eTag(etagOf(created)).body(WorkLogWebMapper.toResponse(created));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When {@code If-Match} is supplied it must match the current ETag, else 412 (optimistic
|
|
||||||
* concurrency); an absent {@code If-Match} is allowed. See README.
|
|
||||||
*/
|
|
||||||
@PatchMapping("/worklogs/{id}")
|
|
||||||
public ResponseEntity<WorkLogResponse> patch(
|
|
||||||
@PathVariable String id,
|
|
||||||
@RequestHeader(value = ApiHeaders.IF_MATCH, required = false) String ifMatch,
|
|
||||||
@Valid @RequestBody UpdateWorkLogRequest body) {
|
|
||||||
WorkLogId workLogId = toId(id);
|
|
||||||
requireIfMatch(ifMatch, workLogId);
|
|
||||||
WorkLog updated = update.handle(WorkLogWebMapper.toUpdateCommand(workLogId, body));
|
|
||||||
return ResponseEntity.ok().eTag(etagOf(updated)).body(WorkLogWebMapper.toResponse(updated));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/worklogs/{id}")
|
|
||||||
public ResponseEntity<Void> delete(@PathVariable String id) {
|
|
||||||
delete.handle(new DeleteWorkLogCommand(toId(id)));
|
|
||||||
return ResponseEntity.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AIP-136 colon-verb batch create. Sync = atomic: {@link BatchCreateWorkLogsUseCase} runs every
|
|
||||||
* item in one transaction, so any failure rolls back the whole batch (single 4xx, never partial
|
|
||||||
* success). See README.
|
|
||||||
*/
|
|
||||||
@PostMapping("/worklogs:batchCreate")
|
|
||||||
public List<WorkLogResponse> batchCreate(
|
|
||||||
@Valid @RequestBody BatchCreateRequest request,
|
|
||||||
@RequestHeader(value = ApiHeaders.IDEMPOTENCY_KEY, required = false) String idempotencyKey) {
|
|
||||||
String owner = currentOwnerSubject();
|
|
||||||
List<WorkLog> created =
|
|
||||||
batchCreate.handle(
|
|
||||||
new BatchCreateWorkLogsCommand(
|
|
||||||
request.requests().stream()
|
|
||||||
.map(r -> WorkLogWebMapper.toCommand(r, owner))
|
|
||||||
.toList()));
|
|
||||||
return created.stream().map(WorkLogWebMapper::toResponse).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Batch size cap: a single sync batch may not exceed this many items (else 400). */
|
|
||||||
public static final int MAX_BATCH_SIZE = 1000;
|
|
||||||
|
|
||||||
/** AIP-233 batch request envelope: {@code { "requests": [ ... ] }}. */
|
|
||||||
public record BatchCreateRequest(
|
|
||||||
@Valid
|
|
||||||
@Size(max = MAX_BATCH_SIZE, message = "batch may not exceed " + MAX_BATCH_SIZE + " items")
|
|
||||||
List<CreateWorkLogRequest> requests) {}
|
|
||||||
|
|
||||||
private static String etagOf(WorkLog workLog) {
|
|
||||||
long version = workLog.version() == null ? 0L : workLog.version();
|
|
||||||
return ETags.weakFromVersion(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimistic-concurrency precondition. {@code ETag}/{@code If-Match} is an HTTP transport
|
|
||||||
* concern, so the read-then-compare stays in the web adapter; an absent/blank {@code If-Match}
|
|
||||||
* skips the check. See README.
|
|
||||||
*/
|
|
||||||
private void requireIfMatch(String ifMatch, WorkLogId id) {
|
|
||||||
if (ifMatch == null || ifMatch.isBlank()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WorkLog current = get.handle(new GetWorkLogQuery(id));
|
|
||||||
if (!ETags.matches(ifMatch, etagOf(current))) {
|
|
||||||
throw new PreconditionFailedException("If-Match does not match the current resource version");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The owner stamped on a created work log is the authenticated caller's IdP subject, read
|
|
||||||
* null-safely from the security context. This is the raw principal id (no pseudonymization here).
|
|
||||||
* See README.
|
|
||||||
*/
|
|
||||||
private static String currentOwnerSubject() {
|
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
if (auth != null
|
|
||||||
&& auth.isAuthenticated()
|
|
||||||
&& auth.getPrincipal() instanceof AuthenticatedPrincipal user) {
|
|
||||||
return user.idpUserId();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accepts case-insensitive canonical UUID path input and normalises it to the canonical 36-char
|
|
||||||
* lowercase UUID. {@code UUID.fromString(...)} is a parser (not a generator) and throws {@link
|
|
||||||
* IllegalArgumentException} on a malformed id → HTTP 400. See README.
|
|
||||||
*/
|
|
||||||
private static WorkLogId toId(String raw) {
|
|
||||||
return WorkLogId.of(UUID.fromString(raw.toLowerCase(Locale.ROOT)).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
|
|
||||||
/** Create a new poster (starts in DRAFT with no image). Bean Validation at the edge. */
|
|
||||||
public record CreatePosterRequest(
|
|
||||||
@NotBlank(message = "title is required")
|
|
||||||
@Size(max = 200, message = "title must be at most 200 characters")
|
|
||||||
String title,
|
|
||||||
@Size(max = 2000, message = "caption must be at most 2000 characters") String caption) {}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.WorkLogValidationGroups.Invariant;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.WorkLogValidationGroups.Syntax;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkCategory;
|
|
||||||
import jakarta.validation.GroupSequence;
|
|
||||||
import jakarta.validation.constraints.AssertTrue;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@GroupSequence({Syntax.class, Invariant.class, CreateWorkLogRequest.class})
|
|
||||||
public record CreateWorkLogRequest(
|
|
||||||
@NotBlank(groups = Syntax.class) @Size(max = 200, groups = Syntax.class) String title,
|
|
||||||
@NotNull(groups = Syntax.class) WorkCategory category,
|
|
||||||
String summary,
|
|
||||||
String content,
|
|
||||||
List<String> techStack,
|
|
||||||
List<String> links,
|
|
||||||
@NotNull(groups = Syntax.class) LocalDate periodStart,
|
|
||||||
LocalDate periodEnd) {
|
|
||||||
|
|
||||||
@AssertTrue(message = "periodEnd must not be before periodStart", groups = Invariant.class)
|
|
||||||
public boolean isPeriodOrdered() {
|
|
||||||
if (periodStart == null || periodEnd == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !periodEnd.isBefore(periodStart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference for safe polymorphic deserialization. {@code @JsonTypeInfo(use = NAME)} plus an
|
|
||||||
* explicit {@code @JsonSubTypes} allowlist replaces Jackson's unsafe default-typing entry point
|
|
||||||
* (CVE-2019-14379); the {@code sealed} hierarchy keeps the allowlist mechanically aligned. See
|
|
||||||
* README.
|
|
||||||
*/
|
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind")
|
|
||||||
@JsonSubTypes({
|
|
||||||
@JsonSubTypes.Type(value = SamplePolymorphicRequest.Text.class, name = "text"),
|
|
||||||
@JsonSubTypes.Type(value = SamplePolymorphicRequest.Image.class, name = "image")
|
|
||||||
})
|
|
||||||
public sealed interface SamplePolymorphicRequest
|
|
||||||
permits SamplePolymorphicRequest.Text, SamplePolymorphicRequest.Image {
|
|
||||||
|
|
||||||
record Text(String title, String body) implements SamplePolymorphicRequest {}
|
|
||||||
|
|
||||||
record Image(String title, String imageUrl) implements SamplePolymorphicRequest {}
|
|
||||||
}
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request;
|
|
||||||
|
|
||||||
import dev.caskeleton.shared.request.Patch;
|
|
||||||
import org.openapitools.jackson.nullable.JsonNullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Partial update of a poster. {@code JsonNullable} captures the JSON merge-patch 3-state (field
|
|
||||||
* absent / explicit null / value) at the web edge, converted to {@link Patch} for the command.
|
|
||||||
* Mirrors {@code UpdateWorkLogRequest}.
|
|
||||||
*/
|
|
||||||
public record UpdatePosterRequest(JsonNullable<String> title, JsonNullable<String> caption) {
|
|
||||||
|
|
||||||
public Patch<String> titlePatch() {
|
|
||||||
return toPatch(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Patch<String> captionPatch() {
|
|
||||||
return toPatch(caption);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Patch<T> toPatch(JsonNullable<T> field) {
|
|
||||||
if (field == null || !field.isPresent()) {
|
|
||||||
return Patch.absent();
|
|
||||||
}
|
|
||||||
return field.get() == null ? Patch.ofNull() : Patch.of(field.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkCategory;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogStatus;
|
|
||||||
import dev.caskeleton.shared.request.Patch;
|
|
||||||
import org.openapitools.jackson.nullable.JsonNullable;
|
|
||||||
|
|
||||||
public record UpdateWorkLogRequest(
|
|
||||||
JsonNullable<String> title,
|
|
||||||
JsonNullable<String> summary,
|
|
||||||
JsonNullable<String> content,
|
|
||||||
JsonNullable<WorkCategory> category,
|
|
||||||
JsonNullable<WorkLogStatus> status) {
|
|
||||||
public Patch<String> titlePatch() {
|
|
||||||
return toPatch(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Patch<String> summaryPatch() {
|
|
||||||
return toPatch(summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Patch<String> contentPatch() {
|
|
||||||
return toPatch(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Patch<WorkCategory> categoryPatch() {
|
|
||||||
return toPatch(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Patch<WorkLogStatus> statusPatch() {
|
|
||||||
return toPatch(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Patch<T> toPatch(JsonNullable<T> field) {
|
|
||||||
if (field == null || !field.isPresent()) {
|
|
||||||
return Patch.absent();
|
|
||||||
}
|
|
||||||
return field.get() == null ? Patch.ofNull() : Patch.of(field.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request;
|
|
||||||
|
|
||||||
/** Validation group ordering for @GroupSequence (syntax → invariant). */
|
|
||||||
public final class WorkLogValidationGroups {
|
|
||||||
private WorkLogValidationGroups() {}
|
|
||||||
|
|
||||||
public interface Syntax {}
|
|
||||||
|
|
||||||
public interface Invariant {}
|
|
||||||
}
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
/** Stable accepted response containing only an opaque operation and relative status path. */
|
|
||||||
public record PosterImagePublicationResponse(String operationId, String statusPath) {}
|
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
/** Locator-free publication progress/result. Reference appears only for READY. */
|
|
||||||
public record PosterImagePublicationStatusResponse(
|
|
||||||
String operationId,
|
|
||||||
String state,
|
|
||||||
String reference,
|
|
||||||
long exactLength,
|
|
||||||
String mediaType,
|
|
||||||
String sha256) {}
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterStatus;
|
|
||||||
|
|
||||||
/** A poster with its current state. {@code imageKey} is null until an image is uploaded. */
|
|
||||||
public record PosterResponse(
|
|
||||||
PosterId id, String title, String caption, String imageKey, PosterStatus status) {}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
public record RepoStatsResponse(String fullName, int stars, String lastPushedIso) {}
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
/** Receipt for an uploaded poster image stored in object storage. */
|
|
||||||
public record StoredObjectResponse(String key, long size, String contentType, String location) {}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkCategory;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogStatus;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record WorkLogResponse(
|
|
||||||
WorkLogId id,
|
|
||||||
String title,
|
|
||||||
WorkCategory category,
|
|
||||||
WorkLogStatus status,
|
|
||||||
String summary,
|
|
||||||
String content,
|
|
||||||
List<String> techStack,
|
|
||||||
List<String> links,
|
|
||||||
LocalDate periodStart,
|
|
||||||
LocalDate periodEnd) {}
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkCategory;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogStatus;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
|
|
||||||
public record WorkLogSummaryResponse(
|
|
||||||
WorkLogId id,
|
|
||||||
String title,
|
|
||||||
WorkCategory category,
|
|
||||||
WorkLogStatus status,
|
|
||||||
LocalDate periodStart,
|
|
||||||
LocalDate periodEnd) {}
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.error;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.inbound.web.error.ErrorResponseFactory;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.exception.PosterNotFoundException;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.exception.PosterTitleAlreadyExistsException;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.exception.WorkLogNotFoundException;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterInvariantException;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogInvariantException;
|
|
||||||
import dev.caskeleton.shared.response.Envelope;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain-specific exception → {@link Envelope} mapping for the portfolio sample. Operational/
|
|
||||||
* transport/security exceptions are handled by the base {@code GlobalExceptionHandler}. This advice
|
|
||||||
* is ordered ahead of the base so domain exceptions resolve to their specific code instead of the
|
|
||||||
* base catch-all's INTERNAL_ERROR. See README.
|
|
||||||
*/
|
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
||||||
@RestControllerAdvice
|
|
||||||
public class DomainExceptionHandler {
|
|
||||||
|
|
||||||
@ExceptionHandler(WorkLogNotFoundException.class)
|
|
||||||
public ResponseEntity<Envelope<Void>> handleWorkLogNotFound(WorkLogNotFoundException ex) {
|
|
||||||
return ErrorResponseFactory.envelope(
|
|
||||||
PortfolioErrorCode.WORKLOG_NOT_FOUND,
|
|
||||||
PortfolioClientSafeErrorMessages.forCode(PortfolioErrorCode.WORKLOG_NOT_FOUND),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(WorkLogInvariantException.class)
|
|
||||||
public ResponseEntity<Envelope<Void>> handleWorkLogInvariant(WorkLogInvariantException ex) {
|
|
||||||
return ErrorResponseFactory.envelope(
|
|
||||||
PortfolioErrorCode.WORKLOG_CONFLICT,
|
|
||||||
PortfolioClientSafeErrorMessages.forCode(PortfolioErrorCode.WORKLOG_CONFLICT),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(PosterNotFoundException.class)
|
|
||||||
public ResponseEntity<Envelope<Void>> handlePosterNotFound(PosterNotFoundException ex) {
|
|
||||||
return ErrorResponseFactory.envelope(
|
|
||||||
PortfolioErrorCode.POSTER_NOT_FOUND,
|
|
||||||
PortfolioClientSafeErrorMessages.forCode(PortfolioErrorCode.POSTER_NOT_FOUND),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(PosterTitleAlreadyExistsException.class)
|
|
||||||
public ResponseEntity<Envelope<Void>> handlePosterTitleConflict(
|
|
||||||
PosterTitleAlreadyExistsException ex) {
|
|
||||||
return ErrorResponseFactory.envelope(
|
|
||||||
PortfolioErrorCode.POSTER_ALREADY_EXISTS,
|
|
||||||
PortfolioClientSafeErrorMessages.forCode(PortfolioErrorCode.POSTER_ALREADY_EXISTS),
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Maps the invariant's {@code reason()} to a specific code (finer-grained than WorkLog's). */
|
|
||||||
@ExceptionHandler(PosterInvariantException.class)
|
|
||||||
public ResponseEntity<Envelope<Void>> handlePosterInvariant(PosterInvariantException ex) {
|
|
||||||
PortfolioErrorCode code =
|
|
||||||
switch (ex.reason()) {
|
|
||||||
case IMAGE_REQUIRED -> PortfolioErrorCode.POSTER_IMAGE_REQUIRED;
|
|
||||||
case INVALID_STATUS_TRANSITION, TITLE_BLANK -> PortfolioErrorCode.POSTER_INVALID_STATE;
|
|
||||||
};
|
|
||||||
return ErrorResponseFactory.envelope(
|
|
||||||
code, PortfolioClientSafeErrorMessages.forCode(code), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.error;
|
|
||||||
|
|
||||||
final class PortfolioClientSafeErrorMessages {
|
|
||||||
|
|
||||||
private PortfolioClientSafeErrorMessages() {}
|
|
||||||
|
|
||||||
static String forCode(PortfolioErrorCode code) {
|
|
||||||
return switch (code) {
|
|
||||||
case WORKLOG_NOT_FOUND -> "Work log was not found";
|
|
||||||
case WORKLOG_CONFLICT -> "Work log conflicts with the current state";
|
|
||||||
case POSTER_NOT_FOUND -> "Poster was not found";
|
|
||||||
case POSTER_ALREADY_EXISTS -> "Poster title is already in use";
|
|
||||||
case POSTER_INVALID_STATE -> "Poster is not valid in the current state";
|
|
||||||
case POSTER_IMAGE_REQUIRED -> "Poster image is required";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.error;
|
|
||||||
|
|
||||||
import dev.caskeleton.shared.error.ApiErrorCode;
|
|
||||||
import dev.caskeleton.shared.error.Category;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain-specific error codes for the portfolio sample. Operational/transport/ security codes live
|
|
||||||
* in {@code dev.caskeleton.shared.error.OperationalError}.
|
|
||||||
*/
|
|
||||||
public enum PortfolioErrorCode implements ApiErrorCode {
|
|
||||||
WORKLOG_NOT_FOUND(Category.NOT_FOUND, 404, false),
|
|
||||||
WORKLOG_CONFLICT(Category.CONFLICT, 409, false),
|
|
||||||
POSTER_NOT_FOUND(Category.NOT_FOUND, 404, false),
|
|
||||||
POSTER_ALREADY_EXISTS(Category.CONFLICT, 409, false),
|
|
||||||
POSTER_INVALID_STATE(Category.VALIDATION, 400, false),
|
|
||||||
POSTER_IMAGE_REQUIRED(Category.VALIDATION, 400, false);
|
|
||||||
|
|
||||||
private final Category category;
|
|
||||||
private final int httpStatus;
|
|
||||||
private final boolean retryable;
|
|
||||||
|
|
||||||
PortfolioErrorCode(Category category, int httpStatus, boolean retryable) {
|
|
||||||
this.category = category;
|
|
||||||
this.httpStatus = httpStatus;
|
|
||||||
this.retryable = retryable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String code() {
|
|
||||||
return name();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Category category() {
|
|
||||||
return category;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int httpStatus() {
|
|
||||||
return httpStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean retryable() {
|
|
||||||
return retryable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.json;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import org.springframework.boot.jackson.JacksonComponent;
|
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import tools.jackson.core.JsonGenerator;
|
|
||||||
import tools.jackson.databind.SerializationContext;
|
|
||||||
import tools.jackson.databind.ValueSerializer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialises a {@link PosterId} as its bare 36-character lowercase UUID string instead of the
|
|
||||||
* record's default {@code {"value":"..."}} shape. Registered via {@link JacksonComponent}. Mirrors
|
|
||||||
* {@code WorkLogIdSerializer}.
|
|
||||||
*/
|
|
||||||
@JacksonComponent
|
|
||||||
public class PosterIdSerializer extends ValueSerializer<PosterId> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(PosterId id, JsonGenerator gen, SerializationContext serializers)
|
|
||||||
throws JacksonException {
|
|
||||||
gen.writeString(id.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.json;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import org.springframework.boot.jackson.JacksonComponent;
|
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import tools.jackson.core.JsonGenerator;
|
|
||||||
import tools.jackson.databind.SerializationContext;
|
|
||||||
import tools.jackson.databind.ValueSerializer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialises a {@link WorkLogId} as its bare 36-character lowercase UUID string instead of the
|
|
||||||
* record's default {@code {"value":"..."}} shape. Registered via {@link JacksonComponent}. See
|
|
||||||
* README.
|
|
||||||
*/
|
|
||||||
@JacksonComponent
|
|
||||||
public class WorkLogIdSerializer extends ValueSerializer<WorkLogId> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(WorkLogId id, JsonGenerator gen, SerializationContext serializers)
|
|
||||||
throws JacksonException {
|
|
||||||
gen.writeString(id.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.PosterImagePublicationResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.PosterImagePublicationStatusResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImagePublicationReservation;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImagePublicationStatus;
|
|
||||||
|
|
||||||
public final class PosterImagePublicationWebMapper {
|
|
||||||
|
|
||||||
private PosterImagePublicationWebMapper() {}
|
|
||||||
|
|
||||||
public static PosterImagePublicationResponse toResponse(
|
|
||||||
PosterImagePublicationReservation reservation) {
|
|
||||||
return new PosterImagePublicationResponse(
|
|
||||||
reservation.operationId().value(),
|
|
||||||
statusPath(reservation.posterId().value(), reservation.operationId().value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PosterImagePublicationStatusResponse toResponse(
|
|
||||||
PosterImagePublicationStatus status) {
|
|
||||||
return new PosterImagePublicationStatusResponse(
|
|
||||||
status.operationId().value(),
|
|
||||||
status.state().name(),
|
|
||||||
status.reference() == null ? null : status.reference().value(),
|
|
||||||
status.contentIdentity().exactLength(),
|
|
||||||
status.mediaType().canonicalText(),
|
|
||||||
status.contentIdentity().fullDigest().base64Value());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String statusPath(String posterId, String operationId) {
|
|
||||||
return "/posters/" + posterId + "/imagePublications/" + operationId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.storage.StoredObject;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.CreatePosterRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.UpdatePosterRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.PosterResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.StoredObjectResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.CreatePosterCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.UpdatePosterCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.Poster;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
|
|
||||||
/** Translates between poster REST DTOs and application/domain types. */
|
|
||||||
public final class PosterWebMapper {
|
|
||||||
|
|
||||||
private PosterWebMapper() {}
|
|
||||||
|
|
||||||
public static CreatePosterCommand toCommand(CreatePosterRequest r) {
|
|
||||||
return new CreatePosterCommand(r.title(), r.caption());
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH goes through the mapper too (not built inline in the controller). The PosterId is parsed
|
|
||||||
// at the web edge and passed in. Mirrors WorkLogWebMapper.
|
|
||||||
public static UpdatePosterCommand toUpdateCommand(PosterId id, UpdatePosterRequest body) {
|
|
||||||
return new UpdatePosterCommand(id, body.titlePatch(), body.captionPatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PosterResponse toResponse(Poster p) {
|
|
||||||
return new PosterResponse(p.id(), p.title(), p.caption(), p.imageKey(), p.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
// This exact response mapper remains active until data/API migration is complete.
|
|
||||||
@SuppressWarnings("removal")
|
|
||||||
public static StoredObjectResponse toStoredObjectResponse(StoredObject s) {
|
|
||||||
return new StoredObjectResponse(s.key(), s.size(), s.contentType(), s.location().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.CreateWorkLogRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.request.UpdateWorkLogRequest;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.RepoStatsResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.WorkLogResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.inbound.web.dto.response.WorkLogSummaryResponse;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.CreateWorkLogCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.command.UpdateWorkLogCommand;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.RepoStats;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLog;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import dev.caskeleton.shared.error.MappingException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class WorkLogWebMapper {
|
|
||||||
|
|
||||||
private WorkLogWebMapper() {}
|
|
||||||
|
|
||||||
public static CreateWorkLogCommand toCommand(CreateWorkLogRequest r, String owner) {
|
|
||||||
List<String> links = r.links() == null ? List.of() : r.links();
|
|
||||||
for (String link : links) {
|
|
||||||
try {
|
|
||||||
new URI(link);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new MappingException("invalid link URI: " + link, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new CreateWorkLogCommand(
|
|
||||||
owner,
|
|
||||||
r.title(),
|
|
||||||
r.category(),
|
|
||||||
r.summary(),
|
|
||||||
r.content(),
|
|
||||||
r.techStack() == null ? List.of() : r.techStack(),
|
|
||||||
links,
|
|
||||||
r.periodStart(),
|
|
||||||
r.periodEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH goes through the mapper too (not built inline in the controller) so the web
|
|
||||||
// adapter owns all request DTO -> command translation. The WorkLogId is parsed at the
|
|
||||||
// web edge and passed in. See README.
|
|
||||||
public static UpdateWorkLogCommand toUpdateCommand(WorkLogId id, UpdateWorkLogRequest body) {
|
|
||||||
return new UpdateWorkLogCommand(
|
|
||||||
id,
|
|
||||||
body.titlePatch(),
|
|
||||||
body.summaryPatch(),
|
|
||||||
body.contentPatch(),
|
|
||||||
body.categoryPatch(),
|
|
||||||
body.statusPatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WorkLogResponse toResponse(WorkLog w) {
|
|
||||||
// owner (raw principal id) is intentionally NOT exposed in the response (privacy). See README.
|
|
||||||
return new WorkLogResponse(
|
|
||||||
w.id(),
|
|
||||||
w.title(),
|
|
||||||
w.category(),
|
|
||||||
w.status(),
|
|
||||||
w.summary(),
|
|
||||||
w.content(),
|
|
||||||
w.techStack(),
|
|
||||||
w.links(),
|
|
||||||
w.period().start(),
|
|
||||||
w.period().end());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WorkLogSummaryResponse toSummary(WorkLog w) {
|
|
||||||
return new WorkLogSummaryResponse(
|
|
||||||
w.id(), w.title(), w.category(), w.status(), w.period().start(), w.period().end());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RepoStatsResponse toRepoStatsResponse(RepoStats s) {
|
|
||||||
return new RepoStatsResponse(s.fullName(), s.stars(), s.lastPushedIso());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-65
@@ -1,65 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.objectstorage;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.objectstorage.content.ObjectChunkSink;
|
|
||||||
import dev.caskeleton.application.objectstorage.content.ObjectContentProducer;
|
|
||||||
import dev.caskeleton.application.objectstorage.content.ObjectContentProductionContext;
|
|
||||||
import dev.caskeleton.application.objectstorage.content.ObjectContentProductionException;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectContentIdentity;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
/** Single-use request-bound stream bridge; it never calls {@code MultipartFile.getBytes()}. */
|
|
||||||
public final class MultipartObjectContentProducer implements ObjectContentProducer {
|
|
||||||
|
|
||||||
private final MultipartFile file;
|
|
||||||
private final ObjectContentIdentity expected;
|
|
||||||
private final AtomicBoolean consumed = new AtomicBoolean();
|
|
||||||
|
|
||||||
public MultipartObjectContentProducer(MultipartFile file, ObjectContentIdentity expected) {
|
|
||||||
if (file == null || expected == null) {
|
|
||||||
throw new IllegalArgumentException("multipart producer inputs are required");
|
|
||||||
}
|
|
||||||
this.file = file;
|
|
||||||
this.expected = expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void produce(ObjectContentProductionContext context, ObjectChunkSink sink)
|
|
||||||
throws ObjectContentProductionException {
|
|
||||||
if (!consumed.compareAndSet(false, true)) {
|
|
||||||
throw new ObjectContentProductionException("multipart request content is already consumed");
|
|
||||||
}
|
|
||||||
try (InputStream input = file.getInputStream()) {
|
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
||||||
byte[] buffer = new byte[Math.min(context.maximumChunkBytes(), 64 * 1024)];
|
|
||||||
long delivered = 0;
|
|
||||||
int read;
|
|
||||||
while ((read = input.read(buffer)) >= 0) {
|
|
||||||
if (read == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
delivered += read;
|
|
||||||
if (delivered > expected.exactLength()) {
|
|
||||||
throw new ObjectContentProductionException(
|
|
||||||
"multipart content exceeds its declared length");
|
|
||||||
}
|
|
||||||
digest.update(buffer, 0, read);
|
|
||||||
sink.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
if (delivered != expected.exactLength()
|
|
||||||
|| !MessageDigest.isEqual(digest.digest(), expected.fullDigest().decodedValue())) {
|
|
||||||
throw new ObjectContentProductionException(
|
|
||||||
"multipart content identity does not match its declaration");
|
|
||||||
}
|
|
||||||
} catch (IOException exception) {
|
|
||||||
throw new ObjectContentProductionException(
|
|
||||||
"multipart request stream could not be read", exception);
|
|
||||||
} catch (NoSuchAlgorithmException exception) {
|
|
||||||
throw new IllegalStateException("SHA-256 must be available", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.operation;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.uuid.UuidCreator;
|
|
||||||
import dev.caskeleton.shared.operation.Operation;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In-memory long-running-operation store backing the sample {@code :export} fixture. It mints the
|
|
||||||
* operation id here (web infrastructure), not in the controller, to respect the {@code
|
|
||||||
* no_uuid_random_in_controller} rule. The export completes synchronously, so the stored operation
|
|
||||||
* is already {@code SUCCEEDED} when polled. See README.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class SampleOperationStore {
|
|
||||||
|
|
||||||
private final Map<String, Operation<WorkLogExportResult>> store = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accepts an export, persisting a terminal operation, and returns the accept-time (PENDING) view
|
|
||||||
* used for the 202 body. {@code apiBasePath} is the configured version prefix so {@code
|
|
||||||
* statusUrl} matches the live {@code /operations/{id}} route.
|
|
||||||
*/
|
|
||||||
public Operation<WorkLogExportResult> startExport(String apiBasePath, long exportedCount) {
|
|
||||||
String id = UuidCreator.getTimeOrderedEpochPlus1().toString();
|
|
||||||
String statusUrl = apiBasePath + "/operations/" + id;
|
|
||||||
store.put(id, Operation.succeeded(id, statusUrl, new WorkLogExportResult(exportedCount)));
|
|
||||||
return Operation.pending(id, statusUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Operation<WorkLogExportResult>> find(String operationId) {
|
|
||||||
return Optional.ofNullable(store.get(operationId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.inbound.web.operation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Result payload of the sample long-running {@code :export} operation, surfaced as {@code
|
|
||||||
* data.result} on the polling response once the operation reaches {@code SUCCEEDED}.
|
|
||||||
*/
|
|
||||||
public record WorkLogExportResult(long exportedCount) {}
|
|
||||||
-128
@@ -1,128 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.identifier;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageIdempotencyScopeDigest;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageIdempotencyScopeDigesterPort;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
/** HMAC-SHA-256 length-prefixed scope digester with a versioned retained key ring. */
|
|
||||||
public final class HmacPosterImageIdempotencyScopeDigester
|
|
||||||
implements PosterImageIdempotencyScopeDigesterPort {
|
|
||||||
|
|
||||||
private static final byte[] DOMAIN =
|
|
||||||
"poster-image-idempotency-scope-v1".getBytes(StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
private final Supplier<String> activeVersion;
|
|
||||||
private final Map<String, byte[]> keys;
|
|
||||||
|
|
||||||
public HmacPosterImageIdempotencyScopeDigester(String activeVersion, Map<String, byte[]> keys) {
|
|
||||||
this(() -> activeVersion, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HmacPosterImageIdempotencyScopeDigester(
|
|
||||||
Supplier<String> activeVersion, Map<String, byte[]> keys) {
|
|
||||||
this.activeVersion = Objects.requireNonNull(activeVersion, "activeVersion must be non-null");
|
|
||||||
Objects.requireNonNull(keys, "keys must be non-null");
|
|
||||||
Map<String, byte[]> copied = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, byte[]> entry : keys.entrySet()) {
|
|
||||||
String version = requireVersion(entry.getKey());
|
|
||||||
byte[] key =
|
|
||||||
Objects.requireNonNull(entry.getValue(), "key material must be non-null").clone();
|
|
||||||
if (key.length < 32 || key.length > 128) {
|
|
||||||
throw new IllegalArgumentException("idempotency HMAC key length is invalid");
|
|
||||||
}
|
|
||||||
copied.put(version, key);
|
|
||||||
}
|
|
||||||
if (!copied.containsKey(requireVersion(this.activeVersion.get()))) {
|
|
||||||
throw new IllegalArgumentException("active idempotency HMAC key is absent");
|
|
||||||
}
|
|
||||||
this.keys = Map.copyOf(copied);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterImageIdempotencyScopeDigest digest(
|
|
||||||
String tenant, String principal, String useCase, String idempotencyKey) {
|
|
||||||
byte[] canonical =
|
|
||||||
canonical(
|
|
||||||
tenant,
|
|
||||||
bounded(principal, "principal", 256),
|
|
||||||
bounded(useCase, "useCase", 128),
|
|
||||||
bounded(idempotencyKey, "idempotencyKey", 256));
|
|
||||||
String selected = requireVersion(activeVersion.get());
|
|
||||||
return digest(selected, canonical);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<PosterImageIdempotencyScopeDigest> retainedDigests(
|
|
||||||
String tenant, String principal, String useCase, String idempotencyKey) {
|
|
||||||
byte[] canonical =
|
|
||||||
canonical(
|
|
||||||
tenant,
|
|
||||||
bounded(principal, "principal", 256),
|
|
||||||
bounded(useCase, "useCase", 128),
|
|
||||||
bounded(idempotencyKey, "idempotencyKey", 256));
|
|
||||||
return keys.keySet().stream().sorted().map(version -> digest(version, canonical)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] canonical(
|
|
||||||
String tenant, String principal, String useCase, String idempotencyKey) {
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
field(output, DOMAIN);
|
|
||||||
field(output, new byte[] {(byte) (tenant == null ? 0 : 1)});
|
|
||||||
field(
|
|
||||||
output,
|
|
||||||
tenant == null
|
|
||||||
? new byte[0]
|
|
||||||
: bounded(tenant, "tenant", 256).getBytes(StandardCharsets.UTF_8));
|
|
||||||
field(output, principal.getBytes(StandardCharsets.UTF_8));
|
|
||||||
field(output, useCase.getBytes(StandardCharsets.UTF_8));
|
|
||||||
field(output, idempotencyKey.getBytes(StandardCharsets.UTF_8));
|
|
||||||
return output.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void field(ByteArrayOutputStream output, byte[] value) {
|
|
||||||
output.writeBytes(ByteBuffer.allocate(Integer.BYTES).putInt(value.length).array());
|
|
||||||
output.writeBytes(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bounded(String value, String label, int maximum) {
|
|
||||||
if (value == null || value.isBlank() || value.length() > maximum) {
|
|
||||||
throw new IllegalArgumentException(label + " is invalid");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String requireVersion(String value) {
|
|
||||||
if (value == null || !value.matches("[a-z0-9][a-z0-9._-]{0,63}")) {
|
|
||||||
throw new IllegalArgumentException("idempotency key version is invalid");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PosterImageIdempotencyScopeDigest digest(String version, byte[] canonical) {
|
|
||||||
byte[] key = keys.get(version);
|
|
||||||
if (key == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"DB-active idempotency HMAC key is not retained by this process");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
|
||||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
|
||||||
String encoded =
|
|
||||||
Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(canonical));
|
|
||||||
return new PosterImageIdempotencyScopeDigest(version, encoded);
|
|
||||||
} catch (GeneralSecurityException exception) {
|
|
||||||
throw new IllegalStateException("HmacSHA256 must be available", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.identifier;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.uuid.UuidCreator;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.OutboxEventIdFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infrastructure adapter for the {@link OutboxEventIdFactory} domain port. Uses the same {@code
|
|
||||||
* UuidCreator.getTimeOrderedEpochPlus1()} mechanism as {@link UuidWorkLogIdFactory}; id generation
|
|
||||||
* lives here, never in the application layer. See README.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class UuidOutboxEventIdFactory implements OutboxEventIdFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String newEventId() {
|
|
||||||
return UuidCreator.getTimeOrderedEpochPlus1().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.identifier;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.uuid.UuidCreator;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterIdFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infrastructure adapter for the {@link PosterIdFactory} domain port. {@code
|
|
||||||
* UuidCreator.getTimeOrderedEpochPlus1()} mints a monotonic UUIDv7 (RFC 9562, time-ordered): it
|
|
||||||
* increments strictly within a millisecond and is backed by a secure random source. Id generation
|
|
||||||
* is only allowed in this identifier adapter. Mirrors {@code UuidWorkLogIdFactory}.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class UuidPosterIdFactory implements PosterIdFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterId newId() {
|
|
||||||
return PosterId.of(UuidCreator.getTimeOrderedEpochPlus1().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.identifier;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.uuid.UuidCreator;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationId;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageOperationIdFactory;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/** UUIDv7 allocator for the DB-committed Poster image operation identity. */
|
|
||||||
@Component
|
|
||||||
public final class UuidPosterImageOperationIdFactory implements PosterImageOperationIdFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AllocatedOperation allocate() {
|
|
||||||
UUID value = UuidCreator.getTimeOrderedEpochPlus1();
|
|
||||||
return new AllocatedOperation(value, ObjectOperationId.of(value.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.identifier;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.uuid.UuidCreator;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogIdFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infrastructure adapter for the {@link WorkLogIdFactory} domain port. {@code
|
|
||||||
* UuidCreator.getTimeOrderedEpochPlus1()} mints a monotonic UUIDv7 (RFC 9562, time-ordered): it
|
|
||||||
* increments strictly within a millisecond and is backed by a secure random source. This is the
|
|
||||||
* only place in the sample allowed to generate ids. See README.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class UuidWorkLogIdFactory implements WorkLogIdFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WorkLogId newId() {
|
|
||||||
return WorkLogId.of(UuidCreator.getTimeOrderedEpochPlus1().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.config;
|
|
||||||
|
|
||||||
import org.springframework.boot.persistence.autoconfigure.EntityScan;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Spring Boot main class lives in {@code dev.caskeleton.bootstrap}, so the default
|
|
||||||
* package-relative scan misses {@code
|
|
||||||
* dev.caskeleton.sample.portfolio.adapter.outbound.persistence.*}. Pointing entity and repository
|
|
||||||
* scans here keeps that wiring in the module that actually owns them.
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EntityScan(basePackages = "dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity")
|
|
||||||
@EnableJpaRepositories(
|
|
||||||
basePackages = "dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository")
|
|
||||||
public class JpaConfig {}
|
|
||||||
-103
@@ -1,103 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.outbound.persistence.audit.AuditableEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterStatus;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.Version;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
|
||||||
import org.hibernate.type.SqlTypes;
|
|
||||||
|
|
||||||
// Extends AuditableEntity to inherit the created/updated audit columns; the domain Poster carries
|
|
||||||
// none of them — they are stamped by PosterRepositoryAdapter. Mirrors WorkLogEntity.
|
|
||||||
@Entity
|
|
||||||
@Table(name = "poster")
|
|
||||||
public class PosterEntity extends AuditableEntity {
|
|
||||||
|
|
||||||
// UUIDv7 stored as the PostgreSQL native `uuid` type (16-byte binary), not varchar.
|
|
||||||
// UUID string<->uuid conversion happens in PosterPersistenceMapper.
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
|
|
||||||
@JdbcTypeCode(SqlTypes.UUID)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Column(length = 2000)
|
|
||||||
private String caption;
|
|
||||||
|
|
||||||
// Object-storage key of the uploaded image; null until an image is attached.
|
|
||||||
@Column(name = "image_key", length = 512)
|
|
||||||
private String imageKey;
|
|
||||||
|
|
||||||
@Column(name = "image_reference", length = 96)
|
|
||||||
private String imageReference;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private PosterStatus status;
|
|
||||||
|
|
||||||
// Optimistic-lock version (ETag source). Hibernate manages increments; `null` marks a new row so
|
|
||||||
// save() inserts.
|
|
||||||
@Version private Long version;
|
|
||||||
|
|
||||||
protected PosterEntity() {}
|
|
||||||
|
|
||||||
public PosterEntity(
|
|
||||||
UUID id, String title, String caption, String imageKey, PosterStatus status, Long version) {
|
|
||||||
this(id, title, caption, imageKey, null, status, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PosterEntity(
|
|
||||||
UUID id,
|
|
||||||
String title,
|
|
||||||
String caption,
|
|
||||||
String imageKey,
|
|
||||||
String imageReference,
|
|
||||||
PosterStatus status,
|
|
||||||
Long version) {
|
|
||||||
if (imageKey != null && imageReference != null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"poster entity cannot contain both image key and image reference");
|
|
||||||
}
|
|
||||||
this.id = id;
|
|
||||||
this.title = title;
|
|
||||||
this.caption = caption;
|
|
||||||
this.imageKey = imageKey;
|
|
||||||
this.imageReference = imageReference;
|
|
||||||
this.status = status;
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCaption() {
|
|
||||||
return caption;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getImageKey() {
|
|
||||||
return imageKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getImageReference() {
|
|
||||||
return imageReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PosterStatus getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.Version;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
/** Single-row DB coordination fence for publication idempotency HMAC rotation. */
|
|
||||||
@Entity
|
|
||||||
@Table(name = "poster_image_idempotency_key_epoch")
|
|
||||||
public class PosterImageIdempotencyKeyEpochEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "epoch_id", nullable = false, length = 64, updatable = false)
|
|
||||||
private String epochId;
|
|
||||||
|
|
||||||
@Column(name = "active_key_version", nullable = false, length = 64)
|
|
||||||
private String activeKeyVersion;
|
|
||||||
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private Instant updatedAt;
|
|
||||||
|
|
||||||
@Version
|
|
||||||
@Column(nullable = false)
|
|
||||||
private long revision;
|
|
||||||
|
|
||||||
protected PosterImageIdempotencyKeyEpochEntity() {}
|
|
||||||
|
|
||||||
public PosterImageIdempotencyKeyEpochEntity(
|
|
||||||
String epochId, String activeKeyVersion, Instant updatedAt) {
|
|
||||||
this.epochId = epochId;
|
|
||||||
this.activeKeyVersion = activeKeyVersion;
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void activate(String activeKeyVersion, Instant now) {
|
|
||||||
this.activeKeyVersion = activeKeyVersion;
|
|
||||||
this.updatedAt = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEpochId() {
|
|
||||||
return epochId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getActiveKeyVersion() {
|
|
||||||
return activeKeyVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRevision() {
|
|
||||||
return revision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-198
@@ -1,198 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageRetirementIntentState;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.UniqueConstraint;
|
|
||||||
import jakarta.persistence.Version;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
|
||||||
import org.hibernate.type.SqlTypes;
|
|
||||||
|
|
||||||
/** JPA row for exact-reference logical retirement work. */
|
|
||||||
@Entity
|
|
||||||
@Table(
|
|
||||||
name = "poster_image_retirement_intent",
|
|
||||||
uniqueConstraints = {
|
|
||||||
@UniqueConstraint(
|
|
||||||
name = "uq_poster_image_retirement_operation",
|
|
||||||
columnNames = "operation_id"),
|
|
||||||
@UniqueConstraint(
|
|
||||||
name = "uq_poster_image_retirement_exact",
|
|
||||||
columnNames = {"image_reference", "exact_version"})
|
|
||||||
})
|
|
||||||
public class PosterImageRetirementIntentEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@JdbcTypeCode(SqlTypes.UUID)
|
|
||||||
@Column(nullable = false, updatable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@JdbcTypeCode(SqlTypes.UUID)
|
|
||||||
@Column(name = "poster_id", updatable = false)
|
|
||||||
private UUID posterId;
|
|
||||||
|
|
||||||
@Column(name = "operation_id", nullable = false, length = 64, updatable = false)
|
|
||||||
private String operationId;
|
|
||||||
|
|
||||||
@Column(name = "image_reference", nullable = false, length = 96, updatable = false)
|
|
||||||
private String imageReference;
|
|
||||||
|
|
||||||
@Column(name = "exact_version", nullable = false, length = 96, updatable = false)
|
|
||||||
private String exactVersion;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Column(nullable = false, length = 32)
|
|
||||||
private PosterImageRetirementIntentState state;
|
|
||||||
|
|
||||||
@Column(name = "claimant_digest", length = 64)
|
|
||||||
private String claimantDigest;
|
|
||||||
|
|
||||||
@Column(name = "claim_fence")
|
|
||||||
private Long claimFence;
|
|
||||||
|
|
||||||
@Column(name = "claim_expires_at")
|
|
||||||
private Instant claimExpiresAt;
|
|
||||||
|
|
||||||
@Column(name = "retain_until", nullable = false, updatable = false)
|
|
||||||
private Instant retainUntil;
|
|
||||||
|
|
||||||
@Column(name = "retry_at")
|
|
||||||
private Instant retryAt;
|
|
||||||
|
|
||||||
@Column(name = "attempt_count", nullable = false)
|
|
||||||
private int attemptCount;
|
|
||||||
|
|
||||||
@Column(name = "last_error_code", length = 64)
|
|
||||||
private String lastErrorCode;
|
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false, updatable = false)
|
|
||||||
private Instant createdAt;
|
|
||||||
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private Instant updatedAt;
|
|
||||||
|
|
||||||
@Version
|
|
||||||
@Column(nullable = false)
|
|
||||||
private long revision;
|
|
||||||
|
|
||||||
protected PosterImageRetirementIntentEntity() {}
|
|
||||||
|
|
||||||
public PosterImageRetirementIntentEntity(
|
|
||||||
UUID id,
|
|
||||||
UUID posterId,
|
|
||||||
String operationId,
|
|
||||||
String imageReference,
|
|
||||||
String exactVersion,
|
|
||||||
PosterImageRetirementIntentState state,
|
|
||||||
String claimantDigest,
|
|
||||||
Long claimFence,
|
|
||||||
Instant claimExpiresAt,
|
|
||||||
Instant retainUntil,
|
|
||||||
Instant retryAt,
|
|
||||||
int attemptCount,
|
|
||||||
String lastErrorCode,
|
|
||||||
Instant createdAt,
|
|
||||||
Instant updatedAt,
|
|
||||||
long revision) {
|
|
||||||
this.id = id;
|
|
||||||
this.posterId = posterId;
|
|
||||||
this.operationId = operationId;
|
|
||||||
this.imageReference = imageReference;
|
|
||||||
this.exactVersion = exactVersion;
|
|
||||||
this.state = state;
|
|
||||||
this.claimantDigest = claimantDigest;
|
|
||||||
this.claimFence = claimFence;
|
|
||||||
this.claimExpiresAt = claimExpiresAt;
|
|
||||||
this.retainUntil = retainUntil;
|
|
||||||
this.retryAt = retryAt;
|
|
||||||
this.attemptCount = attemptCount;
|
|
||||||
this.lastErrorCode = lastErrorCode;
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
this.revision = revision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void applyMutableState(PosterImageRetirementIntentEntity source) {
|
|
||||||
if (!id.equals(source.id) || revision + 1 != source.revision) {
|
|
||||||
throw new IllegalArgumentException("retirement intent replacement revision conflicts");
|
|
||||||
}
|
|
||||||
state = source.state;
|
|
||||||
claimantDigest = source.claimantDigest;
|
|
||||||
claimFence = source.claimFence;
|
|
||||||
claimExpiresAt = source.claimExpiresAt;
|
|
||||||
retryAt = source.retryAt;
|
|
||||||
attemptCount = source.attemptCount;
|
|
||||||
lastErrorCode = source.lastErrorCode;
|
|
||||||
updatedAt = source.updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getPosterId() {
|
|
||||||
return posterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperationId() {
|
|
||||||
return operationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getImageReference() {
|
|
||||||
return imageReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExactVersion() {
|
|
||||||
return exactVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PosterImageRetirementIntentState getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClaimantDigest() {
|
|
||||||
return claimantDigest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getClaimFence() {
|
|
||||||
return claimFence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getClaimExpiresAt() {
|
|
||||||
return claimExpiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getRetainUntil() {
|
|
||||||
return retainUntil;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getRetryAt() {
|
|
||||||
return retryAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAttemptCount() {
|
|
||||||
return attemptCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastErrorCode() {
|
|
||||||
return lastErrorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRevision() {
|
|
||||||
return revision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-311
@@ -1,311 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentState;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.UniqueConstraint;
|
|
||||||
import jakarta.persistence.Version;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
|
||||||
import org.hibernate.type.SqlTypes;
|
|
||||||
|
|
||||||
/** JPA representation of the V8 durable publication queue; no payload or provider locator. */
|
|
||||||
@Entity
|
|
||||||
@Table(
|
|
||||||
name = "poster_image_upload_intent",
|
|
||||||
uniqueConstraints = {
|
|
||||||
@UniqueConstraint(
|
|
||||||
name = "uq_poster_image_upload_operation",
|
|
||||||
columnNames = {"operation_destination", "operation_epoch", "operation_id"}),
|
|
||||||
@UniqueConstraint(
|
|
||||||
name = "uq_poster_image_upload_scope",
|
|
||||||
columnNames = {"hmac_key_version", "idempotency_scope_digest"})
|
|
||||||
})
|
|
||||||
public class PosterImageUploadIntentEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@JdbcTypeCode(SqlTypes.UUID)
|
|
||||||
@Column(name = "id", nullable = false, updatable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@JdbcTypeCode(SqlTypes.UUID)
|
|
||||||
@Column(name = "poster_id", nullable = false, updatable = false)
|
|
||||||
private UUID posterId;
|
|
||||||
|
|
||||||
@Column(name = "operation_destination", nullable = false, length = 64, updatable = false)
|
|
||||||
private String operationDestination;
|
|
||||||
|
|
||||||
@Column(name = "operation_epoch", nullable = false, length = 64, updatable = false)
|
|
||||||
private String operationEpoch;
|
|
||||||
|
|
||||||
@Column(name = "operation_id", nullable = false, length = 64, updatable = false)
|
|
||||||
private String operationId;
|
|
||||||
|
|
||||||
@Column(name = "hmac_key_version", nullable = false, length = 64, updatable = false)
|
|
||||||
private String hmacKeyVersion;
|
|
||||||
|
|
||||||
@Column(name = "idempotency_scope_digest", nullable = false, length = 43, updatable = false)
|
|
||||||
private String idempotencyScopeDigest;
|
|
||||||
|
|
||||||
@Column(name = "request_fingerprint", nullable = false, length = 64, updatable = false)
|
|
||||||
private String requestFingerprint;
|
|
||||||
|
|
||||||
@Column(name = "expected_poster_version", nullable = false, updatable = false)
|
|
||||||
private long expectedPosterVersion;
|
|
||||||
|
|
||||||
@Column(name = "content_length", nullable = false, updatable = false)
|
|
||||||
private long contentLength;
|
|
||||||
|
|
||||||
@Column(name = "content_sha256", nullable = false, length = 44, updatable = false)
|
|
||||||
private String contentSha256;
|
|
||||||
|
|
||||||
@Column(name = "media_type", nullable = false, length = 127, updatable = false)
|
|
||||||
private String mediaType;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Column(name = "state", nullable = false, length = 32)
|
|
||||||
private PosterImageUploadIntentState state;
|
|
||||||
|
|
||||||
@Column(name = "stage_handle", length = 96)
|
|
||||||
private String stageHandle;
|
|
||||||
|
|
||||||
@Column(name = "exact_version", length = 96)
|
|
||||||
private String exactVersion;
|
|
||||||
|
|
||||||
@Column(name = "handoff_claimant_digest", length = 64)
|
|
||||||
private String handoffClaimantDigest;
|
|
||||||
|
|
||||||
@Column(name = "handoff_fence")
|
|
||||||
private Long handoffFence;
|
|
||||||
|
|
||||||
@Column(name = "handoff_expires_at")
|
|
||||||
private Instant handoffExpiresAt;
|
|
||||||
|
|
||||||
@Column(name = "published_reference", length = 96)
|
|
||||||
private String publishedReference;
|
|
||||||
|
|
||||||
@Column(name = "published_version", length = 96)
|
|
||||||
private String publishedVersion;
|
|
||||||
|
|
||||||
@Column(name = "replaced_reference", length = 96)
|
|
||||||
private String replacedReference;
|
|
||||||
|
|
||||||
@Column(name = "retry_at")
|
|
||||||
private Instant retryAt;
|
|
||||||
|
|
||||||
@Column(name = "expires_at", nullable = false, updatable = false)
|
|
||||||
private Instant expiresAt;
|
|
||||||
|
|
||||||
@Column(name = "attempt_count", nullable = false)
|
|
||||||
private int attemptCount;
|
|
||||||
|
|
||||||
@Column(name = "last_error_code", length = 64)
|
|
||||||
private String lastErrorCode;
|
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false, updatable = false)
|
|
||||||
private Instant createdAt;
|
|
||||||
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private Instant updatedAt;
|
|
||||||
|
|
||||||
@Version
|
|
||||||
@Column(name = "revision", nullable = false)
|
|
||||||
private long revision;
|
|
||||||
|
|
||||||
protected PosterImageUploadIntentEntity() {}
|
|
||||||
|
|
||||||
public PosterImageUploadIntentEntity(
|
|
||||||
UUID id,
|
|
||||||
UUID posterId,
|
|
||||||
String operationDestination,
|
|
||||||
String operationEpoch,
|
|
||||||
String operationId,
|
|
||||||
String hmacKeyVersion,
|
|
||||||
String idempotencyScopeDigest,
|
|
||||||
String requestFingerprint,
|
|
||||||
long expectedPosterVersion,
|
|
||||||
long contentLength,
|
|
||||||
String contentSha256,
|
|
||||||
String mediaType,
|
|
||||||
PosterImageUploadIntentState state,
|
|
||||||
String stageHandle,
|
|
||||||
String exactVersion,
|
|
||||||
String handoffClaimantDigest,
|
|
||||||
Long handoffFence,
|
|
||||||
Instant handoffExpiresAt,
|
|
||||||
String publishedReference,
|
|
||||||
String publishedVersion,
|
|
||||||
String replacedReference,
|
|
||||||
Instant retryAt,
|
|
||||||
Instant expiresAt,
|
|
||||||
int attemptCount,
|
|
||||||
String lastErrorCode,
|
|
||||||
Instant createdAt,
|
|
||||||
Instant updatedAt,
|
|
||||||
long revision) {
|
|
||||||
this.id = id;
|
|
||||||
this.posterId = posterId;
|
|
||||||
this.operationDestination = operationDestination;
|
|
||||||
this.operationEpoch = operationEpoch;
|
|
||||||
this.operationId = operationId;
|
|
||||||
this.hmacKeyVersion = hmacKeyVersion;
|
|
||||||
this.idempotencyScopeDigest = idempotencyScopeDigest;
|
|
||||||
this.requestFingerprint = requestFingerprint;
|
|
||||||
this.expectedPosterVersion = expectedPosterVersion;
|
|
||||||
this.contentLength = contentLength;
|
|
||||||
this.contentSha256 = contentSha256;
|
|
||||||
this.mediaType = mediaType;
|
|
||||||
this.state = state;
|
|
||||||
this.stageHandle = stageHandle;
|
|
||||||
this.exactVersion = exactVersion;
|
|
||||||
this.handoffClaimantDigest = handoffClaimantDigest;
|
|
||||||
this.handoffFence = handoffFence;
|
|
||||||
this.handoffExpiresAt = handoffExpiresAt;
|
|
||||||
this.publishedReference = publishedReference;
|
|
||||||
this.publishedVersion = publishedVersion;
|
|
||||||
this.replacedReference = replacedReference;
|
|
||||||
this.retryAt = retryAt;
|
|
||||||
this.expiresAt = expiresAt;
|
|
||||||
this.attemptCount = attemptCount;
|
|
||||||
this.lastErrorCode = lastErrorCode;
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
this.revision = revision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void applyMutableState(PosterImageUploadIntentEntity source) {
|
|
||||||
if (!id.equals(source.id) || revision + 1 != source.revision) {
|
|
||||||
throw new IllegalArgumentException("upload intent replacement revision conflicts");
|
|
||||||
}
|
|
||||||
state = source.state;
|
|
||||||
stageHandle = source.stageHandle;
|
|
||||||
exactVersion = source.exactVersion;
|
|
||||||
handoffClaimantDigest = source.handoffClaimantDigest;
|
|
||||||
handoffFence = source.handoffFence;
|
|
||||||
handoffExpiresAt = source.handoffExpiresAt;
|
|
||||||
publishedReference = source.publishedReference;
|
|
||||||
publishedVersion = source.publishedVersion;
|
|
||||||
replacedReference = source.replacedReference;
|
|
||||||
retryAt = source.retryAt;
|
|
||||||
attemptCount = source.attemptCount;
|
|
||||||
lastErrorCode = source.lastErrorCode;
|
|
||||||
updatedAt = source.updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getPosterId() {
|
|
||||||
return posterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperationDestination() {
|
|
||||||
return operationDestination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperationEpoch() {
|
|
||||||
return operationEpoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperationId() {
|
|
||||||
return operationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHmacKeyVersion() {
|
|
||||||
return hmacKeyVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdempotencyScopeDigest() {
|
|
||||||
return idempotencyScopeDigest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestFingerprint() {
|
|
||||||
return requestFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExpectedPosterVersion() {
|
|
||||||
return expectedPosterVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getContentLength() {
|
|
||||||
return contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentSha256() {
|
|
||||||
return contentSha256;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaType() {
|
|
||||||
return mediaType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PosterImageUploadIntentState getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStageHandle() {
|
|
||||||
return stageHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExactVersion() {
|
|
||||||
return exactVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHandoffClaimantDigest() {
|
|
||||||
return handoffClaimantDigest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getHandoffFence() {
|
|
||||||
return handoffFence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getHandoffExpiresAt() {
|
|
||||||
return handoffExpiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublishedReference() {
|
|
||||||
return publishedReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublishedVersion() {
|
|
||||||
return publishedVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReplacedReference() {
|
|
||||||
return replacedReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getRetryAt() {
|
|
||||||
return retryAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getExpiresAt() {
|
|
||||||
return expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAttemptCount() {
|
|
||||||
return attemptCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastErrorCode() {
|
|
||||||
return lastErrorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRevision() {
|
|
||||||
return revision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-170
@@ -1,170 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.outbound.persistence.audit.AuditableEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkCategory;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogStatus;
|
|
||||||
import jakarta.persistence.CollectionTable;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.ElementCollection;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.Version;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
|
||||||
import org.hibernate.type.SqlTypes;
|
|
||||||
|
|
||||||
// Extends AuditableEntity to inherit the created/updated audit columns; the domain WorkLog
|
|
||||||
// carries none of them — they are stamped by WorkLogRepositoryAdapter. See README.
|
|
||||||
@Entity
|
|
||||||
@Table(name = "work_log")
|
|
||||||
public class WorkLogEntity extends AuditableEntity {
|
|
||||||
|
|
||||||
// UUIDv7 stored as the PostgreSQL native `uuid` type (16-byte binary), not varchar.
|
|
||||||
// UUID string<->uuid conversion happens in WorkLogPersistenceMapper. See README.
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
|
|
||||||
@JdbcTypeCode(SqlTypes.UUID)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
private String owner;
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private WorkCategory category;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private WorkLogStatus status;
|
|
||||||
|
|
||||||
@Column(length = 500)
|
|
||||||
private String summary;
|
|
||||||
|
|
||||||
@Column(length = 10000)
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.LAZY)
|
|
||||||
@CollectionTable(name = "work_log_tech_stack", joinColumns = @JoinColumn(name = "work_log_id"))
|
|
||||||
@Column(name = "tech")
|
|
||||||
private List<String> techStack = new ArrayList<>();
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.LAZY)
|
|
||||||
@CollectionTable(name = "work_log_link", joinColumns = @JoinColumn(name = "work_log_id"))
|
|
||||||
@Column(name = "url")
|
|
||||||
private List<String> links = new ArrayList<>();
|
|
||||||
|
|
||||||
private LocalDate periodStart;
|
|
||||||
private LocalDate periodEnd;
|
|
||||||
// Optimistic-lock version (ETag/If-Match source). Hibernate manages increments; `null`
|
|
||||||
// marks a new row so save() inserts. See README.
|
|
||||||
@Version private Long version;
|
|
||||||
|
|
||||||
protected WorkLogEntity() {}
|
|
||||||
|
|
||||||
public WorkLogEntity(
|
|
||||||
UUID id,
|
|
||||||
String title,
|
|
||||||
WorkCategory category,
|
|
||||||
String summary,
|
|
||||||
String content,
|
|
||||||
List<String> techStack,
|
|
||||||
List<String> links,
|
|
||||||
LocalDate periodStart,
|
|
||||||
LocalDate periodEnd,
|
|
||||||
Long version) {
|
|
||||||
this(
|
|
||||||
id,
|
|
||||||
"sample-owner",
|
|
||||||
title,
|
|
||||||
category,
|
|
||||||
WorkLogStatus.OPEN,
|
|
||||||
summary,
|
|
||||||
content,
|
|
||||||
techStack,
|
|
||||||
links,
|
|
||||||
periodStart,
|
|
||||||
periodEnd,
|
|
||||||
version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorkLogEntity(
|
|
||||||
UUID id,
|
|
||||||
String owner,
|
|
||||||
String title,
|
|
||||||
WorkCategory category,
|
|
||||||
WorkLogStatus status,
|
|
||||||
String summary,
|
|
||||||
String content,
|
|
||||||
List<String> techStack,
|
|
||||||
List<String> links,
|
|
||||||
LocalDate periodStart,
|
|
||||||
LocalDate periodEnd,
|
|
||||||
Long version) {
|
|
||||||
this.id = id;
|
|
||||||
this.owner = owner;
|
|
||||||
this.title = title;
|
|
||||||
this.category = category;
|
|
||||||
this.status = status;
|
|
||||||
this.summary = summary;
|
|
||||||
this.content = content;
|
|
||||||
this.techStack = new ArrayList<>(techStack);
|
|
||||||
this.links = new ArrayList<>(links);
|
|
||||||
this.periodStart = periodStart;
|
|
||||||
this.periodEnd = periodEnd;
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOwner() {
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorkCategory getCategory() {
|
|
||||||
return category;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorkLogStatus getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSummary() {
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContent() {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getTechStack() {
|
|
||||||
return techStack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getLinks() {
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDate getPeriodStart() {
|
|
||||||
return periodStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDate getPeriodEnd() {
|
|
||||||
return periodEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-54
@@ -1,54 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationId;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectVersionToken;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageRetirementIntentEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageRetirementIntent;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterImageReference;
|
|
||||||
|
|
||||||
/** Pure V8 retirement-intent domain/JPA translation. */
|
|
||||||
public final class PosterImageRetirementIntentPersistenceMapper {
|
|
||||||
|
|
||||||
private PosterImageRetirementIntentPersistenceMapper() {}
|
|
||||||
|
|
||||||
public static PosterImageRetirementIntentEntity toEntity(PosterImageRetirementIntent intent) {
|
|
||||||
return new PosterImageRetirementIntentEntity(
|
|
||||||
intent.id(),
|
|
||||||
intent.posterId() == null ? null : PosterPersistenceMapper.toUuid(intent.posterId()),
|
|
||||||
intent.operationId().value(),
|
|
||||||
intent.reference().value(),
|
|
||||||
intent.exactVersion().canonicalText(),
|
|
||||||
intent.state(),
|
|
||||||
intent.claimantDigest(),
|
|
||||||
intent.claimFence(),
|
|
||||||
intent.claimExpiresAt(),
|
|
||||||
intent.retainUntil(),
|
|
||||||
intent.retryAt(),
|
|
||||||
intent.attemptCount(),
|
|
||||||
intent.lastErrorCode(),
|
|
||||||
intent.createdAt(),
|
|
||||||
intent.updatedAt(),
|
|
||||||
intent.revision());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PosterImageRetirementIntent toDomain(PosterImageRetirementIntentEntity entity) {
|
|
||||||
return new PosterImageRetirementIntent(
|
|
||||||
entity.getId(),
|
|
||||||
entity.getPosterId() == null ? null : PosterId.of(entity.getPosterId().toString()),
|
|
||||||
ObjectOperationId.of(entity.getOperationId()),
|
|
||||||
PosterImageReference.of(entity.getImageReference()),
|
|
||||||
ObjectVersionToken.parse(entity.getExactVersion()),
|
|
||||||
entity.getState(),
|
|
||||||
entity.getClaimantDigest(),
|
|
||||||
entity.getClaimFence(),
|
|
||||||
entity.getClaimExpiresAt(),
|
|
||||||
entity.getRetainUntil(),
|
|
||||||
entity.getRetryAt(),
|
|
||||||
entity.getAttemptCount(),
|
|
||||||
entity.getLastErrorCode(),
|
|
||||||
entity.getCreatedAt(),
|
|
||||||
entity.getUpdatedAt(),
|
|
||||||
entity.getRevision());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-110
@@ -1,110 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.idempotency.RequestFingerprint;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectDestinationId;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationEpoch;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationId;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationKey;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectStageHandle;
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectVersionToken;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectContentIdentity;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectDigest;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectDigestAlgorithm;
|
|
||||||
import dev.caskeleton.application.objectstorage.model.ObjectMediaType;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageUploadIntentEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageIdempotencyScopeDigest;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntent;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterImageReference;
|
|
||||||
|
|
||||||
/** Pure V8 upload-intent domain/JPA translation. */
|
|
||||||
public final class PosterImageUploadIntentPersistenceMapper {
|
|
||||||
|
|
||||||
private PosterImageUploadIntentPersistenceMapper() {}
|
|
||||||
|
|
||||||
public static PosterImageUploadIntentEntity toEntity(PosterImageUploadIntent intent) {
|
|
||||||
return new PosterImageUploadIntentEntity(
|
|
||||||
intent.id(),
|
|
||||||
PosterPersistenceMapper.toUuid(intent.posterId()),
|
|
||||||
intent.operationKey().destination().value(),
|
|
||||||
intent.operationKey().epoch().value(),
|
|
||||||
intent.operationKey().operationId().value(),
|
|
||||||
intent.idempotencyScope().keyVersion(),
|
|
||||||
intent.idempotencyScope().base64UrlDigest(),
|
|
||||||
intent.requestFingerprint().hex(),
|
|
||||||
intent.expectedPosterVersion(),
|
|
||||||
intent.contentIdentity().exactLength(),
|
|
||||||
intent.contentIdentity().fullDigest().base64Value(),
|
|
||||||
intent.mediaType().canonicalText(),
|
|
||||||
intent.state(),
|
|
||||||
text(intent.stageHandle()),
|
|
||||||
text(intent.exactVersion()),
|
|
||||||
intent.handoffClaimantDigest(),
|
|
||||||
intent.handoffFence(),
|
|
||||||
intent.handoffExpiresAt(),
|
|
||||||
text(intent.publishedReference()),
|
|
||||||
text(intent.publishedVersion()),
|
|
||||||
text(intent.replacedReference()),
|
|
||||||
intent.retryAt(),
|
|
||||||
intent.expiresAt(),
|
|
||||||
intent.attemptCount(),
|
|
||||||
intent.lastErrorCode(),
|
|
||||||
intent.createdAt(),
|
|
||||||
intent.updatedAt(),
|
|
||||||
intent.revision());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PosterImageUploadIntent toDomain(PosterImageUploadIntentEntity entity) {
|
|
||||||
return new PosterImageUploadIntent(
|
|
||||||
entity.getId(),
|
|
||||||
PosterId.of(entity.getPosterId().toString()),
|
|
||||||
new ObjectOperationKey(
|
|
||||||
ObjectDestinationId.of(entity.getOperationDestination()),
|
|
||||||
ObjectOperationEpoch.of(entity.getOperationEpoch()),
|
|
||||||
ObjectOperationId.of(entity.getOperationId())),
|
|
||||||
new PosterImageIdempotencyScopeDigest(
|
|
||||||
entity.getHmacKeyVersion(), entity.getIdempotencyScopeDigest()),
|
|
||||||
new RequestFingerprint(entity.getRequestFingerprint()),
|
|
||||||
entity.getExpectedPosterVersion(),
|
|
||||||
new ObjectContentIdentity(
|
|
||||||
entity.getContentLength(),
|
|
||||||
ObjectDigest.of(ObjectDigestAlgorithm.SHA_256, entity.getContentSha256())),
|
|
||||||
ObjectMediaType.of(entity.getMediaType()),
|
|
||||||
entity.getState(),
|
|
||||||
entity.getStageHandle() == null ? null : ObjectStageHandle.parse(entity.getStageHandle()),
|
|
||||||
entity.getExactVersion() == null
|
|
||||||
? null
|
|
||||||
: ObjectVersionToken.parse(entity.getExactVersion()),
|
|
||||||
entity.getHandoffClaimantDigest(),
|
|
||||||
entity.getHandoffFence(),
|
|
||||||
entity.getHandoffExpiresAt(),
|
|
||||||
entity.getPublishedReference() == null
|
|
||||||
? null
|
|
||||||
: PosterImageReference.of(entity.getPublishedReference()),
|
|
||||||
entity.getPublishedVersion() == null
|
|
||||||
? null
|
|
||||||
: ObjectVersionToken.parse(entity.getPublishedVersion()),
|
|
||||||
entity.getReplacedReference() == null
|
|
||||||
? null
|
|
||||||
: PosterImageReference.of(entity.getReplacedReference()),
|
|
||||||
entity.getRetryAt(),
|
|
||||||
entity.getExpiresAt(),
|
|
||||||
entity.getAttemptCount(),
|
|
||||||
entity.getLastErrorCode(),
|
|
||||||
entity.getCreatedAt(),
|
|
||||||
entity.getUpdatedAt(),
|
|
||||||
entity.getRevision());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String text(ObjectStageHandle handle) {
|
|
||||||
return handle == null ? null : handle.canonicalText();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String text(ObjectVersionToken version) {
|
|
||||||
return version == null ? null : version.canonicalText();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String text(PosterImageReference reference) {
|
|
||||||
return reference == null ? null : reference.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.Poster;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pure translation between the {@link Poster} aggregate and its JPA entity. Mirrors {@code
|
|
||||||
* WorkLogPersistenceMapper}.
|
|
||||||
*/
|
|
||||||
public final class PosterPersistenceMapper {
|
|
||||||
|
|
||||||
private PosterPersistenceMapper() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Canonical UUID string -> PostgreSQL native {@code uuid} (128-bit). Uses {@code java.util.UUID}
|
|
||||||
* (JDK stdlib) directly, so the persistence adapter carries no id-library dependency.
|
|
||||||
*/
|
|
||||||
public static UUID toUuid(PosterId id) {
|
|
||||||
return UUID.fromString(id.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PosterEntity toEntity(Poster p) {
|
|
||||||
// Carry the domain version so JPA tells a new row (null → insert) from a tracked one.
|
|
||||||
return new PosterEntity(
|
|
||||||
toUuid(p.id()),
|
|
||||||
p.title(),
|
|
||||||
p.caption(),
|
|
||||||
p.imageAttachment()
|
|
||||||
instanceof dev.caskeleton.sample.portfolio.domain.poster.LegacyPosterImageAttachment
|
|
||||||
? p.imageKey()
|
|
||||||
: null,
|
|
||||||
p.imageReference(),
|
|
||||||
p.status(),
|
|
||||||
p.version());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Poster toDomain(PosterEntity e) {
|
|
||||||
PosterId id = PosterId.of(e.getId().toString());
|
|
||||||
return Poster.rehydrate(
|
|
||||||
id,
|
|
||||||
e.getTitle(),
|
|
||||||
e.getCaption(),
|
|
||||||
e.getImageKey(),
|
|
||||||
e.getImageReference(),
|
|
||||||
e.getStatus(),
|
|
||||||
e.getVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-54
@@ -1,54 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.WorkLogEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.Period;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLog;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.worklog.WorkLogOwner;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public final class WorkLogPersistenceMapper {
|
|
||||||
|
|
||||||
private WorkLogPersistenceMapper() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Canonical UUID string -> PostgreSQL native {@code uuid} (128-bit). Uses {@code java.util.UUID}
|
|
||||||
* (JDK stdlib) directly, so the persistence adapter carries no id-library dependency. See README.
|
|
||||||
*/
|
|
||||||
public static UUID toUuid(WorkLogId id) {
|
|
||||||
return UUID.fromString(id.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WorkLogEntity toEntity(WorkLog w) {
|
|
||||||
// Carry the domain version so JPA tells a new row (null → insert) from a tracked one.
|
|
||||||
return new WorkLogEntity(
|
|
||||||
toUuid(w.id()),
|
|
||||||
w.owner().value(),
|
|
||||||
w.title(),
|
|
||||||
w.category(),
|
|
||||||
w.status(),
|
|
||||||
w.summary(),
|
|
||||||
w.content(),
|
|
||||||
w.techStack(),
|
|
||||||
w.links(),
|
|
||||||
w.period().start(),
|
|
||||||
w.period().end(),
|
|
||||||
w.version());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WorkLog toDomain(WorkLogEntity e) {
|
|
||||||
WorkLogId id = WorkLogId.of(e.getId().toString());
|
|
||||||
return WorkLog.rehydrate(
|
|
||||||
id,
|
|
||||||
WorkLogOwner.of(e.getOwner()),
|
|
||||||
e.getTitle(),
|
|
||||||
e.getCategory(),
|
|
||||||
e.getStatus(),
|
|
||||||
e.getSummary(),
|
|
||||||
e.getContent(),
|
|
||||||
e.getTechStack(),
|
|
||||||
e.getLinks(),
|
|
||||||
new Period(e.getPeriodStart(), e.getPeriodEnd()),
|
|
||||||
e.getVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.outbound.persistence.audit.AuditContextPort;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageAttachmentCompareAndSetPort;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterImageReference;
|
|
||||||
import jakarta.persistence.EntityManager;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
/** PostgreSQL conditional V8 attachment update that returns the exact replaced reference. */
|
|
||||||
@Repository
|
|
||||||
public class PosterImageAttachmentCasRepository implements PosterImageAttachmentCompareAndSetPort {
|
|
||||||
|
|
||||||
private static final String ATTACH_SQL =
|
|
||||||
"""
|
|
||||||
WITH candidate AS (
|
|
||||||
SELECT id, image_reference AS replaced_reference
|
|
||||||
FROM poster
|
|
||||||
WHERE id = :posterId
|
|
||||||
AND version = :expectedVersion
|
|
||||||
AND image_key IS NULL
|
|
||||||
FOR UPDATE
|
|
||||||
),
|
|
||||||
updated AS (
|
|
||||||
UPDATE poster AS poster
|
|
||||||
SET image_reference = :reference,
|
|
||||||
updated_at = :updatedAt,
|
|
||||||
updated_by = :updatedBy,
|
|
||||||
version = poster.version + 1
|
|
||||||
FROM candidate
|
|
||||||
WHERE poster.id = candidate.id
|
|
||||||
RETURNING candidate.replaced_reference
|
|
||||||
)
|
|
||||||
SELECT TRUE AS attached, replaced_reference
|
|
||||||
FROM updated
|
|
||||||
""";
|
|
||||||
|
|
||||||
private final EntityManager entityManager;
|
|
||||||
private final AuditContextPort auditContext;
|
|
||||||
|
|
||||||
public PosterImageAttachmentCasRepository(
|
|
||||||
EntityManager entityManager, AuditContextPort auditContext) {
|
|
||||||
this.entityManager = entityManager;
|
|
||||||
this.auditContext = auditContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result attach(
|
|
||||||
PosterId posterId, long expectedPosterVersion, PosterImageReference reference, Instant now) {
|
|
||||||
java.util.Objects.requireNonNull(now, "now must be non-null");
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<Object[]> rows =
|
|
||||||
entityManager
|
|
||||||
.createNativeQuery(ATTACH_SQL)
|
|
||||||
.setParameter(
|
|
||||||
"posterId",
|
|
||||||
dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper
|
|
||||||
.PosterPersistenceMapper.toUuid(posterId))
|
|
||||||
.setParameter("expectedVersion", expectedPosterVersion)
|
|
||||||
.setParameter("reference", reference.value())
|
|
||||||
.setParameter("updatedAt", now)
|
|
||||||
.setParameter("updatedBy", auditContext.currentActor())
|
|
||||||
.getResultList();
|
|
||||||
if (rows.isEmpty()) {
|
|
||||||
return new Result(false, null);
|
|
||||||
}
|
|
||||||
String replaced = (String) rows.getFirst()[1];
|
|
||||||
return new Result(true, replaced == null ? null : PosterImageReference.of(replaced));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageIdempotencyKeyEpochEntity;
|
|
||||||
import jakarta.persistence.LockModeType;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Lock;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface PosterImageIdempotencyKeyEpochJpaRepository
|
|
||||||
extends JpaRepository<PosterImageIdempotencyKeyEpochEntity, String> {
|
|
||||||
|
|
||||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
|
||||||
@Query(
|
|
||||||
"select epoch from PosterImageIdempotencyKeyEpochEntity epoch"
|
|
||||||
+ " where epoch.epochId = :epochId")
|
|
||||||
Optional<PosterImageIdempotencyKeyEpochEntity> findByIdForUpdate(
|
|
||||||
@Param("epochId") String epochId);
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageIdempotencyKeyEpochEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageIdempotencyKeyEpochPort;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentConflictException;
|
|
||||||
import java.time.Clock;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public class PosterImageIdempotencyKeyEpochRepositoryAdapter
|
|
||||||
implements PosterImageIdempotencyKeyEpochPort {
|
|
||||||
|
|
||||||
static final String EPOCH_ID = "poster-image-publication";
|
|
||||||
|
|
||||||
private final PosterImageIdempotencyKeyEpochJpaRepository jpa;
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
public PosterImageIdempotencyKeyEpochRepositoryAdapter(
|
|
||||||
PosterImageIdempotencyKeyEpochJpaRepository jpa, Clock clock) {
|
|
||||||
this.jpa = jpa;
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String activeVersionForReservation() {
|
|
||||||
return jpa.findByIdForUpdate(EPOCH_ID)
|
|
||||||
.orElseThrow(
|
|
||||||
() ->
|
|
||||||
new IllegalStateException("poster image idempotency key epoch is not initialized"))
|
|
||||||
.getActiveKeyVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void activate(String expectedVersion, String newVersion) {
|
|
||||||
PosterImageIdempotencyKeyEpochEntity epoch =
|
|
||||||
jpa.findByIdForUpdate(EPOCH_ID)
|
|
||||||
.orElseGet(
|
|
||||||
() ->
|
|
||||||
new PosterImageIdempotencyKeyEpochEntity(
|
|
||||||
EPOCH_ID, expectedVersion, clock.instant()));
|
|
||||||
if (!epoch.getActiveKeyVersion().equals(expectedVersion)) {
|
|
||||||
throw new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image idempotency key epoch conflicts");
|
|
||||||
}
|
|
||||||
epoch.activate(newVersion, clock.instant());
|
|
||||||
jpa.saveAndFlush(epoch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageRetirementIntentEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageRetirementIntentState;
|
|
||||||
import jakarta.persistence.LockModeType;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Lock;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface PosterImageRetirementIntentJpaRepository
|
|
||||||
extends JpaRepository<PosterImageRetirementIntentEntity, UUID> {
|
|
||||||
|
|
||||||
Optional<PosterImageRetirementIntentEntity> findByOperationId(String operationId);
|
|
||||||
|
|
||||||
Optional<PosterImageRetirementIntentEntity> findByImageReferenceAndExactVersion(
|
|
||||||
String reference, String exactVersion);
|
|
||||||
|
|
||||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
|
||||||
@Query(
|
|
||||||
"""
|
|
||||||
select intent
|
|
||||||
from PosterImageRetirementIntentEntity intent
|
|
||||||
where intent.state in :states
|
|
||||||
and intent.retainUntil <= :now
|
|
||||||
and (intent.retryAt is null or intent.retryAt <= :now)
|
|
||||||
and (intent.claimExpiresAt is null or intent.claimExpiresAt <= :now)
|
|
||||||
order by intent.updatedAt, intent.id
|
|
||||||
""")
|
|
||||||
List<PosterImageRetirementIntentEntity> findClaimable(
|
|
||||||
@Param("states") Collection<PosterImageRetirementIntentState> states,
|
|
||||||
@Param("now") Instant now,
|
|
||||||
Pageable pageable);
|
|
||||||
}
|
|
||||||
-95
@@ -1,95 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationId;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageRetirementIntentEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper.PosterImageRetirementIntentPersistenceMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageRetirementIntent;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageRetirementIntentState;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageRetirementIntentStorePort;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentConflictException;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public class PosterImageRetirementIntentRepositoryAdapter
|
|
||||||
implements PosterImageRetirementIntentStorePort {
|
|
||||||
|
|
||||||
private final PosterImageRetirementIntentJpaRepository jpa;
|
|
||||||
|
|
||||||
public PosterImageRetirementIntentRepositoryAdapter(
|
|
||||||
PosterImageRetirementIntentJpaRepository jpa) {
|
|
||||||
this.jpa = jpa;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterImageRetirementIntent createOrRead(PosterImageRetirementIntent proposed) {
|
|
||||||
Optional<PosterImageRetirementIntentEntity> existing =
|
|
||||||
jpa.findByOperationId(proposed.operationId().value());
|
|
||||||
if (existing.isEmpty()) {
|
|
||||||
existing =
|
|
||||||
jpa.findByImageReferenceAndExactVersion(
|
|
||||||
proposed.reference().value(), proposed.exactVersion().canonicalText());
|
|
||||||
}
|
|
||||||
if (existing.isPresent()) {
|
|
||||||
PosterImageRetirementIntent found =
|
|
||||||
PosterImageRetirementIntentPersistenceMapper.toDomain(existing.orElseThrow());
|
|
||||||
if (!found.operationId().equals(proposed.operationId())
|
|
||||||
|| !found.reference().equals(proposed.reference())
|
|
||||||
|| !found.exactVersion().equals(proposed.exactVersion())) {
|
|
||||||
throw new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image retirement operation conflicts");
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
return PosterImageRetirementIntentPersistenceMapper.toDomain(
|
|
||||||
jpa.saveAndFlush(PosterImageRetirementIntentPersistenceMapper.toEntity(proposed)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<PosterImageRetirementIntent> findByOperation(ObjectOperationId operation) {
|
|
||||||
return jpa.findByOperationId(operation.value())
|
|
||||||
.map(PosterImageRetirementIntentPersistenceMapper::toDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterImageRetirementIntent compareAndSet(
|
|
||||||
long expectedRevision, PosterImageRetirementIntent replacement) {
|
|
||||||
PosterImageRetirementIntentEntity current =
|
|
||||||
jpa.findById(replacement.id())
|
|
||||||
.orElseThrow(
|
|
||||||
() ->
|
|
||||||
new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image retirement intent is absent"));
|
|
||||||
if (current.getRevision() != expectedRevision
|
|
||||||
|| replacement.revision() != expectedRevision + 1) {
|
|
||||||
throw new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image retirement revision conflicts");
|
|
||||||
}
|
|
||||||
current.applyMutableState(PosterImageRetirementIntentPersistenceMapper.toEntity(replacement));
|
|
||||||
return PosterImageRetirementIntentPersistenceMapper.toDomain(jpa.saveAndFlush(current));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<PosterImageRetirementIntent> claimReady(
|
|
||||||
String claimantDigest, Instant now, Duration leaseDuration) {
|
|
||||||
return jpa
|
|
||||||
.findClaimable(
|
|
||||||
List.of(
|
|
||||||
PosterImageRetirementIntentState.PENDING,
|
|
||||||
PosterImageRetirementIntentState.IN_FLIGHT),
|
|
||||||
now,
|
|
||||||
PageRequest.of(0, 1))
|
|
||||||
.stream()
|
|
||||||
.findFirst()
|
|
||||||
.map(PosterImageRetirementIntentPersistenceMapper::toDomain)
|
|
||||||
.map(
|
|
||||||
current ->
|
|
||||||
compareAndSet(
|
|
||||||
current.revision(),
|
|
||||||
current.claim(current.revision(), claimantDigest, now, leaseDuration)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageUploadIntentEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentState;
|
|
||||||
import jakarta.persistence.LockModeType;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Lock;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface PosterImageUploadIntentJpaRepository
|
|
||||||
extends JpaRepository<PosterImageUploadIntentEntity, UUID> {
|
|
||||||
|
|
||||||
Optional<PosterImageUploadIntentEntity> findByOperationDestinationAndOperationEpochAndOperationId(
|
|
||||||
String destination, String epoch, String operationId);
|
|
||||||
|
|
||||||
Optional<PosterImageUploadIntentEntity> findByHmacKeyVersionAndIdempotencyScopeDigest(
|
|
||||||
String version, String digest);
|
|
||||||
|
|
||||||
Optional<PosterImageUploadIntentEntity> findByPublishedReference(String reference);
|
|
||||||
|
|
||||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
|
||||||
@Query(
|
|
||||||
"""
|
|
||||||
select intent
|
|
||||||
from PosterImageUploadIntentEntity intent
|
|
||||||
where intent.state = :state
|
|
||||||
and (intent.retryAt is null or intent.retryAt <= :now)
|
|
||||||
and (intent.handoffExpiresAt is null or intent.handoffExpiresAt <= :now)
|
|
||||||
order by intent.updatedAt, intent.id
|
|
||||||
""")
|
|
||||||
List<PosterImageUploadIntentEntity> findClaimable(
|
|
||||||
@Param("state") PosterImageUploadIntentState state,
|
|
||||||
@Param("now") Instant now,
|
|
||||||
Pageable pageable);
|
|
||||||
}
|
|
||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.application.objectstorage.identity.ObjectOperationKey;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterImageUploadIntentEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper.PosterImageUploadIntentPersistenceMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageIdempotencyScopeDigest;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntent;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentClaim;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentConflictException;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentState;
|
|
||||||
import dev.caskeleton.sample.portfolio.application.posterimage.PosterImageUploadIntentStorePort;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterImageReference;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public class PosterImageUploadIntentRepositoryAdapter implements PosterImageUploadIntentStorePort {
|
|
||||||
|
|
||||||
private final PosterImageUploadIntentJpaRepository jpa;
|
|
||||||
|
|
||||||
public PosterImageUploadIntentRepositoryAdapter(PosterImageUploadIntentJpaRepository jpa) {
|
|
||||||
this.jpa = jpa;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterImageUploadIntent createOrRead(PosterImageUploadIntent proposed) {
|
|
||||||
Optional<PosterImageUploadIntent> existing =
|
|
||||||
findByAnyScopeDigest(java.util.List.of(proposed.idempotencyScope()));
|
|
||||||
if (existing.isEmpty()) {
|
|
||||||
existing = findByOperation(proposed.operationKey());
|
|
||||||
}
|
|
||||||
if (existing.isPresent()) {
|
|
||||||
PosterImageUploadIntent found = existing.orElseThrow();
|
|
||||||
if (!found.operationKey().equals(proposed.operationKey())
|
|
||||||
|| !found.requestFingerprint().equals(proposed.requestFingerprint())
|
|
||||||
|| !found.posterId().equals(proposed.posterId())) {
|
|
||||||
throw new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image idempotency scope is already bound to different work");
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
if (proposed.state() != PosterImageUploadIntentState.RESERVED || proposed.revision() != 0) {
|
|
||||||
throw new IllegalArgumentException("only a fresh RESERVED intent can be created");
|
|
||||||
}
|
|
||||||
return PosterImageUploadIntentPersistenceMapper.toDomain(
|
|
||||||
jpa.saveAndFlush(PosterImageUploadIntentPersistenceMapper.toEntity(proposed)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<PosterImageUploadIntent> findByOperation(ObjectOperationKey operation) {
|
|
||||||
return jpa.findByOperationDestinationAndOperationEpochAndOperationId(
|
|
||||||
operation.destination().value(),
|
|
||||||
operation.epoch().value(),
|
|
||||||
operation.operationId().value())
|
|
||||||
.map(PosterImageUploadIntentPersistenceMapper::toDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<PosterImageUploadIntent> findByAnyScopeDigest(
|
|
||||||
Collection<PosterImageIdempotencyScopeDigest> retainedDigests) {
|
|
||||||
for (PosterImageIdempotencyScopeDigest digest : retainedDigests) {
|
|
||||||
Optional<PosterImageUploadIntentEntity> found =
|
|
||||||
jpa.findByHmacKeyVersionAndIdempotencyScopeDigest(
|
|
||||||
digest.keyVersion(), digest.base64UrlDigest());
|
|
||||||
if (found.isPresent()) {
|
|
||||||
return found.map(PosterImageUploadIntentPersistenceMapper::toDomain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<PosterImageUploadIntent> findReadyByPublishedReference(
|
|
||||||
PosterImageReference reference) {
|
|
||||||
return jpa.findByPublishedReference(reference.value())
|
|
||||||
.map(PosterImageUploadIntentPersistenceMapper::toDomain)
|
|
||||||
.filter(intent -> intent.state() == PosterImageUploadIntentState.READY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterImageUploadIntent compareAndSet(
|
|
||||||
long expectedRevision, PosterImageUploadIntent replacement) {
|
|
||||||
PosterImageUploadIntentEntity current =
|
|
||||||
jpa.findById(replacement.id())
|
|
||||||
.orElseThrow(
|
|
||||||
() ->
|
|
||||||
new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image upload intent is absent"));
|
|
||||||
if (current.getRevision() != expectedRevision
|
|
||||||
|| replacement.revision() != expectedRevision + 1) {
|
|
||||||
throw new PosterImageUploadIntentConflictException(
|
|
||||||
"poster image upload intent revision conflicts");
|
|
||||||
}
|
|
||||||
current.applyMutableState(PosterImageUploadIntentPersistenceMapper.toEntity(replacement));
|
|
||||||
return PosterImageUploadIntentPersistenceMapper.toDomain(jpa.saveAndFlush(current));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<PosterImageUploadIntentClaim> claimPending(
|
|
||||||
String claimantDigest, Instant now, Duration leaseDuration) {
|
|
||||||
return jpa
|
|
||||||
.findClaimable(PosterImageUploadIntentState.PENDING, now, PageRequest.of(0, 1))
|
|
||||||
.stream()
|
|
||||||
.findFirst()
|
|
||||||
.map(PosterImageUploadIntentPersistenceMapper::toDomain)
|
|
||||||
.map(
|
|
||||||
current -> {
|
|
||||||
PosterImageUploadIntent replacement =
|
|
||||||
current.claim(current.revision(), claimantDigest, now, leaseDuration);
|
|
||||||
PosterImageUploadIntent saved = compareAndSet(current.revision(), replacement);
|
|
||||||
return new PosterImageUploadIntentClaim(
|
|
||||||
saved, saved.handoffFence(), saved.handoffExpiresAt());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterStatus;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface PosterJpaRepository extends JpaRepository<PosterEntity, UUID> {
|
|
||||||
|
|
||||||
// Flat equality filter (the only filter syntax the contract allows), kept in the data layer.
|
|
||||||
Page<PosterEntity> findByStatus(PosterStatus status, Pageable pageable);
|
|
||||||
|
|
||||||
boolean existsByTitleIgnoreCase(String title);
|
|
||||||
}
|
|
||||||
-95
@@ -1,95 +0,0 @@
|
|||||||
package dev.caskeleton.sample.portfolio.adapter.outbound.persistence.repository;
|
|
||||||
|
|
||||||
import dev.caskeleton.adapter.outbound.persistence.audit.AuditContextPort;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.entity.PosterEntity;
|
|
||||||
import dev.caskeleton.sample.portfolio.adapter.outbound.persistence.mapper.PosterPersistenceMapper;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.Poster;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterId;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterPage;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterRepository;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterSortField;
|
|
||||||
import dev.caskeleton.sample.portfolio.domain.poster.PosterStatus;
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public class PosterRepositoryAdapter implements PosterRepository {
|
|
||||||
|
|
||||||
private final PosterJpaRepository jpa;
|
|
||||||
// Audit metadata (time/actor) is stamped here in the persistence adapter; the domain Poster
|
|
||||||
// carries no audit fields. Mirrors WorkLogRepositoryAdapter.
|
|
||||||
private final Clock clock;
|
|
||||||
private final AuditContextPort auditContext;
|
|
||||||
|
|
||||||
public PosterRepositoryAdapter(
|
|
||||||
PosterJpaRepository jpa, Clock clock, AuditContextPort auditContext) {
|
|
||||||
this.jpa = jpa;
|
|
||||||
this.clock = clock;
|
|
||||||
this.auditContext = auditContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Poster save(Poster poster) {
|
|
||||||
PosterEntity entity = PosterPersistenceMapper.toEntity(poster);
|
|
||||||
Instant now = clock.instant();
|
|
||||||
String actor = auditContext.currentActor();
|
|
||||||
if (poster.version() == null) {
|
|
||||||
// INSERT: created_* and updated_* both take now/actor.
|
|
||||||
entity.initializeAudit(now, actor);
|
|
||||||
} else {
|
|
||||||
// UPDATE: carry the persisted created_* forward and bump only updated_*.
|
|
||||||
PosterEntity existing =
|
|
||||||
jpa.findById(entity.getId())
|
|
||||||
.orElseThrow(
|
|
||||||
() ->
|
|
||||||
new IllegalStateException(
|
|
||||||
"no persisted poster row to carry audit creation from: "
|
|
||||||
+ entity.getId()));
|
|
||||||
entity.carryCreation(existing.getCreatedAt(), existing.getCreatedBy());
|
|
||||||
entity.applyModification(now, actor);
|
|
||||||
}
|
|
||||||
return PosterPersistenceMapper.toDomain(jpa.save(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Poster> findById(PosterId id) {
|
|
||||||
return jpa.findById(PosterPersistenceMapper.toUuid(id)).map(PosterPersistenceMapper::toDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PosterPage findPage(
|
|
||||||
int page, int size, PosterSortField sortField, boolean ascending, PosterStatus status) {
|
|
||||||
int safeSize = size <= 0 ? 20 : size;
|
|
||||||
int safePage = Math.max(0, page);
|
|
||||||
Sort sort =
|
|
||||||
sortField == null
|
|
||||||
? Sort.unsorted()
|
|
||||||
: Sort.by(ascending ? Sort.Direction.ASC : Sort.Direction.DESC, sortField.property());
|
|
||||||
PageRequest pageRequest = PageRequest.of(safePage, safeSize, sort);
|
|
||||||
Page<PosterEntity> result =
|
|
||||||
status == null ? jpa.findAll(pageRequest) : jpa.findByStatus(status, pageRequest);
|
|
||||||
List<Poster> items = result.map(PosterPersistenceMapper::toDomain).getContent();
|
|
||||||
return new PosterPage(items, result.getTotalElements());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean existsById(PosterId id) {
|
|
||||||
return jpa.existsById(PosterPersistenceMapper.toUuid(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean existsByTitle(String title) {
|
|
||||||
return jpa.existsByTitleIgnoreCase(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteById(PosterId id) {
|
|
||||||
jpa.deleteById(PosterPersistenceMapper.toUuid(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user