diff --git a/CHANGELOG.ko.md b/CHANGELOG.ko.md index 909282a..ef9b4aa 100644 --- a/CHANGELOG.ko.md +++ b/CHANGELOG.ko.md @@ -11,6 +11,15 @@ English: [CHANGELOG.md](CHANGELOG.md) ## [Unreleased] +### 추가됨 (Added) +- **OpenAPI / Swagger UI 자동 구성** — springdoc-openapi가 classpath에 있으면(킷은 + `compileOnly`로 선언, 소비자가 `springdoc-openapi-starter-webmvc-ui`를 추가해 opt-in) + 설정 없이 `/swagger-ui.html`과 `/v3/api-docs`가 올라오고 `/admin/api/v1/**` + 엔드포인트가 하나의 그룹으로 묶입니다. 의존성을 제거하지 않고 + `devslab.kit.openapi.enabled=false`로 끌 수 있습니다(프로덕션에서 일반적). `OpenAPI` + 문서 빈과 관리자 `GroupedOpenApi` 빈은 `@ConditionalOnMissingBean`이라 소비자가 + 재정의 가능. springdoc `3.0.x`(Spring Boot 4 라인) 대상. + ## [0.1.0] — 2026-06-01 첫 공개 릴리스. diff --git a/CHANGELOG.md b/CHANGELOG.md index 453cf0a..1523744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ The library major aligns with the Spring Boot major: `4.x.y` targets Spring Boot ## [Unreleased] +### Added +- **OpenAPI / Swagger UI auto-configuration** — when springdoc-openapi is on the + classpath (declared `compileOnly` by the kit; a consumer opts in by adding + `springdoc-openapi-starter-webmvc-ui`), `/swagger-ui.html` and `/v3/api-docs` + come up with no wiring and the `/admin/api/v1/**` endpoints are collected into one + group. Disable without removing the dependency via `devslab.kit.openapi.enabled=false` + (typical in production). The `OpenAPI` document and admin `GroupedOpenApi` beans are + `@ConditionalOnMissingBean`, so a consumer can override either. Targets springdoc + `3.0.x` (the Spring Boot 4 line). + ## [0.1.0] — 2026-06-01 First public release. diff --git a/README.ko.md b/README.ko.md index de8903f..4bff09d 100644 --- a/README.ko.md +++ b/README.ko.md @@ -41,6 +41,7 @@ | **캐시** | 플러그형 캐시 — `in-memory` · `redis` · `none`. Redis 백엔드가 JSON 직렬화를 직접 책임지므로 `Serializable` 구현이나 직렬화기 배선이 필요 없습니다(ADR 0002). 사용자별 메뉴 캐시도 이 공유 매니저를 사용합니다. | | **최초 관리자 부트스트랩** | 첫 부팅 시 테넌트, `PLATFORM_ADMIN` 역할, `admin.*` 권한, 관리자 사용자를 멱등하게 생성 — opt-in, 프로퍼티 기반(ADR 0001). | | **관리자 REST API** | 위 모든 엔티티 + 진단 + 실시간 설정 뷰를 위한 `/admin/api/v1/**`. | +| **OpenAPI / Swagger UI** | springdoc만 추가하면 `/swagger-ui`가 관리자 API 그룹과 함께 자동으로 뜸 — 설정 불필요. `openapi.enabled=false`로 끔. | | **Override 친화적** | 모든 기본 빈이 `@ConditionalOnMissingBean` — 직접 선언하면 어느 조각이든 교체 가능. | | **GraalVM Native** | 리플렉션 중심 설계를 피하고, 샘플 앱이 `nativeCompile`을 검증. | @@ -145,6 +146,8 @@ Docker Compose(Postgres + Redis)와 Testcontainers 기반 테스트까지 갖춘 | `bootstrap.admin-login-id` | `admin` | 최초 관리자 로그인 id. | | `bootstrap.admin-password` | — | 비우면 강력한 랜덤 비밀번호를 한 번 로깅. | | `bootstrap.must-change-password` | `true` | 첫 로그인 시 변경 강제. | +| `openapi.enabled` | `true` | springdoc이 classpath에 있으면 Swagger UI / OpenAPI 노출. `false`로 비활성화(예: 프로덕션). | +| `openapi.title` | `devslab-kit Admin API` | OpenAPI 문서 / Swagger UI에 표시될 제목. | 런타임의 실제 적용값은 `GET /admin/api/v1/settings`에서도 볼 수 있습니다(시크릿 마스킹). diff --git a/README.md b/README.md index 1ec8276..c5a196b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ specific product's domain. | **Cache** | A pluggable cache — `in-memory`, `redis`, or `none`. The Redis backend owns JSON serialization, so you never implement `Serializable` or wire a serializer (ADR 0002). The per-user menu cache rides this shared manager. | | **First-admin bootstrap** | Idempotently provisions a tenant, a `PLATFORM_ADMIN` role, the `admin.*` permissions, and an admin user on first boot — opt-in and property-driven (ADR 0001). | | **Admin REST API** | `/admin/api/v1/**` for every entity above, plus diagnostics and a live settings view. | +| **OpenAPI / Swagger UI** | Add springdoc and `/swagger-ui` comes up with the admin API grouped — no wiring. Toggle off with `openapi.enabled=false`. | | **Override-friendly** | Every default bean is `@ConditionalOnMissingBean` — replace any piece by declaring your own. | | **GraalVM Native** | Reflection-heavy patterns are avoided; the sample app verifies `nativeCompile`. | @@ -148,6 +149,8 @@ All keys are under the `devslab.kit.*` prefix. Defaults shown. | `bootstrap.admin-login-id` | `admin` | First admin login id. | | `bootstrap.admin-password` | — | Blank → a strong random one is logged once. | | `bootstrap.must-change-password` | `true` | Force a rotation on first login. | +| `openapi.enabled` | `true` | Expose Swagger UI / OpenAPI when springdoc is on the classpath. Set `false` to disable (e.g. in production). | +| `openapi.title` | `devslab-kit Admin API` | Title shown in the OpenAPI doc / Swagger UI. | The live, effective values are also viewable at runtime via `GET /admin/api/v1/settings` (secrets masked). diff --git a/devslab-kit-autoconfigure/build.gradle.kts b/devslab-kit-autoconfigure/build.gradle.kts index 9f2a164..cb70758 100644 --- a/devslab-kit-autoconfigure/build.gradle.kts +++ b/devslab-kit-autoconfigure/build.gradle.kts @@ -21,6 +21,12 @@ dependencies { // Micrometer for custom metrics; consumer's spring-boot-actuator activates it at runtime. compileOnly("io.micrometer:micrometer-core") + + // springdoc-openapi for the optional Swagger UI auto-configuration. compileOnly: + // OpenApiAutoConfiguration is @ConditionalOnClass(GroupedOpenApi), so it stays + // dormant unless the consumer puts springdoc on their own classpath. Version + // pinned in gradle.properties (not managed by the Spring Boot BOM). + compileOnly("org.springdoc:springdoc-openapi-starter-webmvc-api:${property("SPRINGDOC_VERSION")}") } mavenPublishing { diff --git a/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitProperties.java b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitProperties.java index dad5988..ccf1d57 100644 --- a/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitProperties.java +++ b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/DevslabKitProperties.java @@ -12,6 +12,7 @@ public class DevslabKitProperties { private final Menu menu = new Menu(); private final Audit audit = new Audit(); private final Bootstrap bootstrap = new Bootstrap(); + private final OpenApi openApi = new OpenApi(); public Identity getIdentity() { return identity; @@ -37,6 +38,10 @@ public Audit getAudit() { return audit; } + public OpenApi getOpenApi() { + return openApi; + } + public static class Identity { private boolean enabled = true; @@ -355,4 +360,60 @@ public void setFailOnDefaultPasswordInProd(boolean failOnDefaultPasswordInProd) this.failOnDefaultPasswordInProd = failOnDefaultPasswordInProd; } } + + /** + * Swagger UI / OpenAPI exposure. Only takes effect when springdoc-openapi is on + * the consumer's classpath (the kit declares it {@code compileOnly}); a consumer + * opts in by adding {@code springdoc-openapi-starter-webmvc-ui}. When present and + * {@code enabled} (the default), the kit publishes a grouped OpenAPI spec and the + * Swagger UI. Set {@code devslab.kit.openapi.enabled=false} to turn it off without + * removing the dependency — e.g. in production, where the API docs are usually not + * exposed. + */ + public static class OpenApi { + + /** Master switch for the kit's OpenAPI/Swagger UI wiring. Default ON. */ + private boolean enabled = true; + + /** {@code GroupedOpenApi} group name for the admin API. */ + private String adminGroup = "admin"; + + /** Title shown in the generated OpenAPI document / Swagger UI header. */ + private String title = "devslab-kit Admin API"; + + /** Version string shown in the OpenAPI document. */ + private String version = "v1"; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getAdminGroup() { + return adminGroup; + } + + public void setAdminGroup(String adminGroup) { + this.adminGroup = adminGroup; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } } diff --git a/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/OpenApiAutoConfiguration.java b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/OpenApiAutoConfiguration.java new file mode 100644 index 0000000..abfc4f7 --- /dev/null +++ b/devslab-kit-autoconfigure/src/main/java/kr/devslab/kit/autoconfigure/OpenApiAutoConfiguration.java @@ -0,0 +1,69 @@ +package kr.devslab.kit.autoconfigure; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import kr.devslab.kit.admin.AdminApiPaths; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Exposes the admin REST API through OpenAPI / Swagger UI with no consumer wiring — + * add {@code springdoc-openapi-starter-webmvc-ui} to the classpath and + * {@code /swagger-ui.html} (plus {@code /v3/api-docs}) come up, with the kit's + * {@code /admin/api/v1/**} endpoints collected under one group. + * + *

Dormant unless springdoc is present: {@code @ConditionalOnClass(GroupedOpenApi.class)} + * means the kit ships this config but contributes nothing until the consumer opts in + * by adding the springdoc dependency (the kit declares it {@code compileOnly}). It can + * be turned off without removing the dependency via + * {@code devslab.kit.openapi.enabled=false} — typical in production, where the API docs + * are usually not exposed. + * + *

Both beans are {@code @ConditionalOnMissingBean}, so a consumer can define their own + * {@link OpenAPI} (e.g. to add security schemes or servers) or their own admin + * {@link GroupedOpenApi} and the kit backs off. + */ +@AutoConfiguration +@EnableConfigurationProperties(DevslabKitProperties.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnClass(GroupedOpenApi.class) +@ConditionalOnProperty( + prefix = "devslab.kit.openapi", + name = "enabled", + havingValue = "true", + matchIfMissing = true +) +public class OpenApiAutoConfiguration { + + /** + * Groups every {@code /admin/api/v1/**} endpoint into one Swagger UI group so the + * admin surface is browsable as a unit, separate from any of the consumer's own + * controllers. Backs off if the consumer already declares a {@code GroupedOpenApi}. + */ + @Bean + @ConditionalOnMissingBean(name = "devslabKitAdminApiGroup") + public GroupedOpenApi devslabKitAdminApiGroup(DevslabKitProperties props) { + return GroupedOpenApi.builder() + .group(props.getOpenApi().getAdminGroup()) + .pathsToMatch(AdminApiPaths.BASE + "/**") + .build(); + } + + /** + * Default OpenAPI document metadata (title + version). Only contributed if the + * consumer hasn't defined their own {@link OpenAPI} bean. + */ + @Bean + @ConditionalOnMissingBean + public OpenAPI devslabKitOpenApi(DevslabKitProperties props) { + return new OpenAPI().info(new Info() + .title(props.getOpenApi().getTitle()) + .version(props.getOpenApi().getVersion())); + } +} diff --git a/devslab-kit-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/devslab-kit-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 7eca95f..e736d08 100644 --- a/devslab-kit-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/devslab-kit-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -6,5 +6,6 @@ kr.devslab.kit.autoconfigure.MenuAutoConfiguration kr.devslab.kit.autoconfigure.AuditAutoConfiguration kr.devslab.kit.autoconfigure.AdminApiAutoConfiguration kr.devslab.kit.autoconfigure.AdminApiWebAutoConfiguration +kr.devslab.kit.autoconfigure.OpenApiAutoConfiguration kr.devslab.kit.autoconfigure.BootstrapAutoConfiguration kr.devslab.kit.autoconfigure.MetricsAutoConfiguration diff --git a/devslab-kit-sample-app/build.gradle.kts b/devslab-kit-sample-app/build.gradle.kts index fb73f6e..cbf6991 100644 --- a/devslab-kit-sample-app/build.gradle.kts +++ b/devslab-kit-sample-app/build.gradle.kts @@ -17,6 +17,12 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-webmvc") implementation("org.flywaydb:flyway-database-postgresql") + // Swagger UI / OpenAPI. The kit's OpenApiAutoConfiguration activates only when + // springdoc is on the classpath (a consumer opts in by adding this); it then + // exposes /swagger-ui and groups the admin API. Version pinned in gradle.properties + // (the Spring Boot BOM doesn't manage springdoc). + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:${property("SPRINGDOC_VERSION")}") + compileOnly("org.projectlombok:lombok") developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-docker-compose") diff --git a/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/OpenApiDisabledTests.java b/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/OpenApiDisabledTests.java new file mode 100644 index 0000000..b1fbd81 --- /dev/null +++ b/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/OpenApiDisabledTests.java @@ -0,0 +1,43 @@ +package kr.devslab.kit.sample; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; + +/** + * Proves the kill-switch: {@code devslab.kit.openapi.enabled=false} turns off the kit's + * OpenAPI wiring even with springdoc on the classpath. The kit's grouped admin doc must + * be gone; springdoc's own bare default may still answer {@code /v3/api-docs}, so this + * asserts the kit-specific group ({@code /v3/api-docs/admin}) is absent rather than that + * springdoc is fully silenced. + */ +@Import(TestcontainersConfiguration.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = "devslab.kit.openapi.enabled=false") +class OpenApiDisabledTests { + + @LocalServerPort + private int port; + + private final HttpClient http = HttpClient.newHttpClient(); + + @Test + void adminGroupIsAbsentWhenDisabled() throws Exception { + HttpResponse response = http.send( + HttpRequest.newBuilder() + .uri(URI.create("http://localhost:" + port + "/v3/api-docs/admin")) + .GET().build(), + HttpResponse.BodyHandlers.ofString()); + + // No kit-defined "admin" group -> springdoc has no such group -> not 200. + assertThat(response.statusCode()).isNotEqualTo(200); + } +} diff --git a/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/OpenApiEndpointTests.java b/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/OpenApiEndpointTests.java new file mode 100644 index 0000000..9e904d9 --- /dev/null +++ b/devslab-kit-sample-app/src/test/java/kr/devslab/kit/sample/OpenApiEndpointTests.java @@ -0,0 +1,70 @@ +package kr.devslab.kit.sample; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; + +/** + * Verifies the kit's OpenAPI / Swagger UI auto-configuration over the real HTTP stack: + * with springdoc on the classpath and no consumer wiring, {@code /v3/api-docs} serves a + * valid spec (this also catches the springdoc Jackson-2 vs Spring Boot 4 Jackson-3 + * serialization risk — a broken doc would 500 or return non-JSON), {@code /swagger-ui} + * is reachable, and both are public (not blocked by the admin security chain). + * + *

The {@code enabled=false} kill-switch is covered by {@link OpenApiDisabledTests}. + */ +@Import(TestcontainersConfiguration.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class OpenApiEndpointTests { + + @LocalServerPort + private int port; + + private final HttpClient http = HttpClient.newHttpClient(); + + private HttpResponse get(String path) throws Exception { + return http.send( + HttpRequest.newBuilder().uri(URI.create("http://localhost:" + port + path)).GET().build(), + HttpResponse.BodyHandlers.ofString()); + } + + @Test + void apiDocsServesValidJsonWithoutAuth() throws Exception { + HttpResponse response = get("/v3/api-docs"); + + assertThat(response.statusCode()).isEqualTo(200); + // Real serialization happened (guards against the springdoc/Jackson mismatch): + // a parseable OpenAPI document with our configured title. + assertThat(response.body()).contains("\"openapi\""); + assertThat(response.body()).contains("devslab-kit Admin API"); + } + + @Test + void adminApiGroupIsExposed() throws Exception { + HttpResponse response = get("/v3/api-docs/admin"); + + assertThat(response.statusCode()).isEqualTo(200); + // The grouped doc only matches /admin/api/v1/** — its paths should reflect that. + assertThat(response.body()).contains("/admin/api/v1"); + } + + @Test + void swaggerUiIsReachableWithoutAuth() throws Exception { + // springdoc redirects /swagger-ui.html -> /swagger-ui/index.html; either the + // redirect (3xx) or the page (200) proves it's public, not a 401/403. + HttpResponse response = http.send( + HttpRequest.newBuilder() + .uri(URI.create("http://localhost:" + port + "/swagger-ui/index.html")) + .GET().build(), + HttpResponse.BodyHandlers.ofString()); + + assertThat(response.statusCode()).isEqualTo(200); + } +} diff --git a/docs/reference/configuration.ko.md b/docs/reference/configuration.ko.md index d178c4a..21e6f4e 100644 --- a/docs/reference/configuration.ko.md +++ b/docs/reference/configuration.ko.md @@ -104,3 +104,31 @@ [최초 관리자 부트스트랩 가이드](../guides/bootstrap.md), [ADR 0001](../adr/0001-bootstrap-admin.md) 참고. + +## OpenAPI / Swagger UI — `devslab.kit.openapi.*` { #openapi } + +킷은 **springdoc이 classpath에 있을 때** OpenAPI와 Swagger UI를 자동 구성합니다 — +springdoc을 `compileOnly`로 가지고 있으므로, 의존성을 추가해 opt-in 합니다: + +```kotlin +implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3") +``` + +추가하면 추가 설정 없이 `/swagger-ui.html`과 `/v3/api-docs`가 올라오고, 킷의 +`/admin/api/v1/**` 엔드포인트가 하나의 그룹(`/v3/api-docs/admin`)으로 묶입니다. +springdoc `3.0.x`가 Spring Boot 4 라인입니다(`2.8.x`는 Spring Boot 3 대상). + +| 프로퍼티 | 타입 | 기본값 | 설명 | +| --- | --- | --- | --- | +| `enabled` | boolean | `true` | 마스터 스위치. `false`로 두면 의존성을 제거하지 않고도 킷의 OpenAPI 구성을 끕니다. | +| `admin-group` | string | `admin` | 관리자 API의 Swagger UI 그룹 이름. | +| `title` | string | `devslab-kit Admin API` | OpenAPI 문서 / Swagger UI에 표시될 제목. | +| `version` | string | `v1` | OpenAPI 문서의 버전 문자열. | + +`OpenAPI` 문서 빈과 관리자 `GroupedOpenApi` 모두 `@ConditionalOnMissingBean`이므로, +직접 선언하면(예: security scheme이나 server 추가) 킷이 물러납니다. + +!!! tip "프로덕션" + API 문서는 보통 프로덕션에 노출하지 않습니다. + `devslab.kit.openapi.enabled=false`로 끄거나(또는 프로덕션 빌드에서 springdoc + 의존성을 빼서) 해당 표면을 비활성화하세요. diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 83baeba..397c20e 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -110,3 +110,34 @@ opt in explicitly. See the [First-admin Bootstrap guide](../guides/bootstrap.md) and [ADR 0001](../adr/0001-bootstrap-admin.md). + +## OpenAPI / Swagger UI — `devslab.kit.openapi.*` { #openapi } + +The kit auto-configures OpenAPI and Swagger UI **when springdoc is on the +classpath** — it ships springdoc as `compileOnly`, so you opt in by adding the +dependency: + +```kotlin +implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3") +``` + +With that present, `/swagger-ui.html` and `/v3/api-docs` come up with no further +wiring, and the kit's `/admin/api/v1/**` endpoints are collected into one group +(`/v3/api-docs/admin`). springdoc `3.0.x` is the Spring Boot 4 line (`2.8.x` +targets Spring Boot 3). + +| Property | Type | Default | Description | +| --- | --- | --- | --- | +| `enabled` | boolean | `true` | Master switch. Set `false` to disable the kit's OpenAPI wiring without removing the dependency. | +| `admin-group` | string | `admin` | Swagger UI group name for the admin API. | +| `title` | string | `devslab-kit Admin API` | Title shown in the OpenAPI document / Swagger UI. | +| `version` | string | `v1` | Version string in the OpenAPI document. | + +Both the `OpenAPI` document bean and the admin `GroupedOpenApi` are +`@ConditionalOnMissingBean`, so you can declare your own (e.g. to add security +schemes or servers) and the kit backs off. + +!!! tip "Production" + API docs are usually not exposed in production. Set + `devslab.kit.openapi.enabled=false` (or omit the springdoc dependency from the + production build) to turn the surface off. diff --git a/gradle.properties b/gradle.properties index 5139f7a..faa4b4a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,11 @@ GROUP=kr.devslab VERSION=0.1.0-SNAPSHOT +# springdoc-openapi (Swagger UI). 3.0.x is the Spring Boot 4 / Spring Framework 7 +# line (2.8.x targets Spring Boot 3). Not managed by the Spring Boot BOM, so pinned +# here and consumed compileOnly by the autoconfigure module. +SPRINGDOC_VERSION=3.0.3 + # Project metadata for Maven Central POM (shared across all modules; each module # overrides name/description in its own mavenPublishing { pom { … } } block). POM_INCEPTION_YEAR=2026