Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
93decdb
feat: update null safety annotations and improve validation messages …
YeaChan05 Sep 4, 2025
ed482a3
feat: replace SpotBugs with Spring Boot DevTools in build configuration
YeaChan05 Sep 4, 2025
5a272a4
feat: update show registration to use ADMIN authority and enhance req…
YeaChan05 Sep 5, 2025
0b917f2
feat: add src/docs/ to .gitignore to exclude documentation files from…
YeaChan05 Sep 5, 2025
386285c
feat: integrate Spring REST Docs for API documentation and enhance te…
YeaChan05 Sep 5, 2025
2d79bbc
feat: add API documentation for movie schedule registration endpoint
YeaChan05 Sep 5, 2025
a5eca1a
feat: rename movie schedule registration endpoint to show schedule re…
YeaChan05 Sep 5, 2025
1500139
feat: implement show schedule registration functionality and related …
YeaChan05 Sep 5, 2025
ad69dd7
feat: add ShowCommandRepository to IntegrationTestUtils for testing s…
YeaChan05 Sep 5, 2025
637a95c
feat: update test checklist for show schedule registration to reflect…
YeaChan05 Sep 5, 2025
bc01f98
feat: add test for scheduleId inclusion in show schedule registration…
YeaChan05 Sep 5, 2025
f0db16d
feat: refactor authorization header handling in tests and add forbidd…
YeaChan05 Sep 5, 2025
1305884
feat: mark forbidden status check for unauthorized user token as comp…
YeaChan05 Sep 5, 2025
1725481
feat: add validation for runtimeMinutes in ShowScheduleRegisterReques…
YeaChan05 Sep 5, 2025
1355219
feat: update checklist for show schedule registration to mark runtime…
YeaChan05 Sep 5, 2025
d2832c9
feat: update checklist for show schedule registration to mark runtime…
YeaChan05 Sep 5, 2025
adffd84
feat: add NOT_FOUND status check for non-existent showId in show sche…
YeaChan05 Sep 6, 2025
a7c1304
feat: mark NOT_FOUND status check for non-existent showId as complete…
YeaChan05 Sep 6, 2025
3ed8db4
feat: add NOT_FOUND status checks for non-existent showId and hallId …
YeaChan05 Sep 6, 2025
5877930
feat: implement Hall entity and repository for hall verification
YeaChan05 Sep 6, 2025
d22b00f
feat: mark NOT_FOUND status check for non-existent hallId as complete…
YeaChan05 Sep 6, 2025
eeb681f
feat: enhance show schedule registration with hall verification and s…
YeaChan05 Sep 6, 2025
d2e820e
feat: add hall repository integration and dummy hall creation for sho…
YeaChan05 Sep 6, 2025
bb9c68b
feat: mark validation for show schedule date range as complete in reg…
YeaChan05 Sep 6, 2025
7e40118
feat: add logging for DomainException in GlobalExceptionHandler
YeaChan05 Sep 7, 2025
5ed4ba5
update hall repository integration to use HallCommandRepository and m…
YeaChan05 Sep 7, 2025
fccc02e
enhance show scheduling with hall association and conflict checks
YeaChan05 Sep 7, 2025
8a26366
integrate hall verification into show schedule registration
YeaChan05 Sep 7, 2025
f375c21
implement hall retrieval and scheduling validation in HallQueryReposi…
YeaChan05 Sep 7, 2025
5061c57
remove hall event handler
YeaChan05 Sep 7, 2025
e291acf
add HallCommandRepository for hall insertion functionality
YeaChan05 Sep 7, 2025
306d57d
add HallException for handling venue-related errors
YeaChan05 Sep 7, 2025
0c5ef6d
refactor IntegrationTest annotation for cleaner code
YeaChan05 Sep 7, 2025
ed46a80
update TestConfig to use HallCommandRepository for hall operations
YeaChan05 Sep 7, 2025
879a9bb
add test for overlapping show schedule registration to return INTERNA…
YeaChan05 Sep 7, 2025
85809e3
update show schedule registration tests for overlapping hallId scenarios
YeaChan05 Sep 7, 2025
9c2573e
refactor exception handling to use string status in DomainException a…
YeaChan05 Sep 7, 2025
68899d8
add proxy tests for AbstractEntity and implement ShowSchedule conflic…
YeaChan05 Sep 7, 2025
74a0234
refactor ShowScheduleTest to improve overlap test cases and streamlin…
YeaChan05 Sep 7, 2025
7e8cef1
add validation for runtimeMinutes to ensure it matches the difference…
YeaChan05 Sep 7, 2025
ae663eb
update show schedule registration request to clarify runtimeMinutes c…
YeaChan05 Sep 7, 2025
d3510ef
refactor hall query and show schedule registration for improved valid…
YeaChan05 Sep 8, 2025
f2faf96
implement hall existence check and refactor show schedule registratio…
YeaChan05 Sep 8, 2025
e69d4d0
remove unused event object
YeaChan05 Sep 8, 2025
2269883
add table annotation to Show entity for database mapping
YeaChan05 Sep 8, 2025
ce9dd59
remove logging configuration from application-local.yml
YeaChan05 Sep 8, 2025
66ad25d
add role hierarchy and method security expression handler to Security…
YeaChan05 Sep 8, 2025
befea66
update performance dates and hall ID in show registration and schedul…
YeaChan05 Sep 8, 2025
30b1590
add test for ADMIN user authorization in show schedule registration
YeaChan05 Sep 8, 2025
8a0f037
streamline ShowSchedule creation and introduce ShowScheduleCreateCommand
YeaChan05 Sep 8, 2025
e0124e4
update domain documentation for ShowSchedule registration and related…
YeaChan05 Sep 8, 2025
c7c5cba
update todo list with new tasks and completed items
YeaChan05 Sep 8, 2025
47f9696
document application event usage and encapsulation of entities in DDD…
YeaChan05 Sep 8, 2025
c7b151e
add venue and hall registration tasks to todo list
YeaChan05 Sep 8, 2025
446ca59
remove unused Rest Assured dependency from build.gradle
YeaChan05 Sep 8, 2025
318e61d
simplify JSON example in show schedule registration documentation
YeaChan05 Sep 8, 2025
6341a52
update Gradle CI configuration to use 'test' profile for build and te…
YeaChan05 Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ jobs:
distribution: "temurin"

- name: Build (no tests)
run: ./gradlew clean build -x test -x spotbugsMain -x spotbugsTest -Dspring.profiles.active=test
run: ./gradlew clean build -x test -Dspring.profiles.active=test

- name: Test
run: ./gradlew test -Dspring.profiles.active=local
run: ./gradlew test -Dspring.profiles.active=test

- name: Publish Test Report
uses: dorny/test-reporter@v1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/build
.idea
.aiassistant/rules/AGENTS.md
/src/docs/
53 changes: 48 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.5.4'
id 'io.spring.dependency-management' version '1.1.7'
id 'com.github.spotbugs' version '6.2.4'
id 'com.epages.restdocs-api-spec' version '0.18.2'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

group = 'org.mandarin'
version = '0.0.1-SNAPSHOT'

ext {
snippetsDir = file('build/generated-snippets')
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
Expand All @@ -20,6 +25,7 @@ repositories {

configurations {
byteBuddyAgent
asciidoctorExt
}

dependencies {
Expand All @@ -36,6 +42,13 @@ dependencies {
testRuntimeOnly 'com.h2database:h2'
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'

// ---- Querydsl ----
implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
// Ensure APT has Jakarta APIs (some environments require explicit presence)
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'

// ---- Security & Auth ----
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
Expand All @@ -44,14 +57,14 @@ dependencies {
testImplementation 'io.jsonwebtoken:jjwt-impl:0.12.6'

// ---- Lombok ----
implementation 'org.projectlombok:lombok:1.18.36'
compileOnly 'org.projectlombok:lombok:1.18.36'
annotationProcessor 'org.projectlombok:lombok:1.18.36'
testCompileOnly 'org.projectlombok:lombok:1.18.36'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.36'

// ---- Dev Only ----
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'

// ---- Code Quality / Tooling ----
spotbugs 'com.github.spotbugs:spotbugs:4.9.3'
developmentOnly 'org.springframework.boot:spring-boot-devtools'

// ---- Testing ----
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand All @@ -60,11 +73,41 @@ dependencies {
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
byteBuddyAgent 'net.bytebuddy:byte-buddy-agent:1.17.6'
testImplementation 'org.mockito:mockito-inline:5.2.0'

// ---- API Docs (REST Docs + Rest Assured) ----
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'

asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:3.1.0'

// null safety
implementation 'org.jspecify:jspecify:1.0.0'
}
ext {
set('snippetsDir', file('build/generated-snippets'))
}

tasks.named('test') {
useJUnitPlatform()
systemProperty 'spring.profiles.active', 'test'
// javaagent 선부착 (Mockito self-attach 방지)
jvmArgs "-javaagent:${configurations.byteBuddyAgent.singleFile}"
outputs.dir snippetsDir
}

asciidoctor {
group = 'documentation'
description = 'Converts AsciiDoc files to HTML, including REST Docs snippets.'
dependsOn test
baseDirFollowsSourceDir()
sourceDir = file('src/docs')
sources {
include '**/*.adoc'
}
inputs.dir snippetsDir
attributes 'snippets': snippetsDir
resources {
from(snippetsDir) { into 'snippets' }
}
outputDir = layout.buildDirectory.dir("docs/asciidoc").get().asFile
}

7 changes: 7 additions & 0 deletions docs/devlog/250908.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## 예찬

기존의 개발 방식을 고수하면서 이번에는 Aggregate root간의 의존을 최소화 하기 위해 Application Event를 적당히 활용해봤다. 실무에서도 이렇게 잘 쓰이는지는 모르겠다만, 결국 Spring Context에 의해 관리되는 영역인 만큼 그냥 써도 되지 않을까 하는 생각..

그리고 이번에 AR를 적극적으로 활용한 DDD를 하려 하다보니 Entity를 package private로 개발하는 방식을 처음으로 써봤다. 도메인 로직들은 통상 AR를 통해 호출되기 때문에, AR를 제외한 나머지 Entity들은 외부에서 직접 접근할 일이 거의 없다는 점에 착안한 것이다. 물론, 이 방식이 무조건 옳다고 생각하지는 않는다. 다만, 이번 프로젝트에서는 이 방식을 채택함으로써 도메인 모델의 캡슐화가 좀 더 강화된 느낌이다.

마침 AR를 사용한 설계의 장점을 극대화할 겸, 어차피 나중에 한번쯤은 손대려고 했던 모듈을 다음 기능 개발에 도입하려 한다. 이때 AR 기준 접근을 하려 했던 기존의 설계가 빛을 바랄거라고 생각되는데, 이 부분은 다음 개발기를 통해 좀 더 자세히 다뤄보도록 하겠다.
45 changes: 24 additions & 21 deletions docs/specs/api/show_register.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,33 @@
- 본문

```json

{
"title": "인셉션",
"type": "MUSICAL",
"rating": "AGE12",
"synopsis": "타인의 꿈속에 진입해 아이디어를 주입하는 특수 임무를 수행하는 이야기.",
"posterUrl": "https://example.com/posters/inception.jpg",
"performanceStartDate": "2025-10-01",
"performanceEndDate": "2025-10-31"
}
```


- curl 명령 예시

```bash
curl -i -X POST 'http://localhost:8080/api/show' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0MTIzNCIsInJvbGVzIjoiUk9MRV9ESVNUUklCVVRPUiIsInVzZXJJZCI6InRlc3QxMjM0Iiwibmlja05hbWUiOiJ0ZXN0IiwiaWF0IjoxNzU2NDM4MjIzLCJleHAiOjE3NTY0Mzg4MjN9.DN0wZb8BdKY-7Grd0KAALXf88KX3iF_tg6UmcfotkFOlbRoRnSuY1nNVUFfZk2TxP0hvju3A8AglK3mt_hnutQ' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0MTIzNCIsInJvbGVzIjoiUk9MRV9BRE1JTiIsInVzZXJJZCI6InRlc3QxMjM0Iiwibmlja05hbWUiOiJ0ZXN0IiwiaWF0IjoxNzU3MzExNDc5LCJleHAiOjE3NTczMTIwNzl9.xhEkuZEF0gZlvyX_F2kiAMEMGw_C2ZtGL8PmzLxhZQW32A9hmr6M0nauYEejXOFrZAb3nMdU3jFLxuhDWDbE2g' \
-H 'Content-Type: application/json' \
-d '{
"title": "인셉션",
"director": "크리스토퍼 놀란",
"runtimeMinutes": 148,
"genre": "SF",
"releaseDate": "2010-07-21",
"rating": "AGE12",
"synopsis": "타인의 꿈속에 진입해 아이디어를 주입하는 특수 임무를 수행하는 이야기.",
"posterUrl": "https://example.com/posters/inception.jpg",
"casts": [
"레오나르도 디카프리오",
"조셉 고든레빗",
"엘렌 페이지"
]
}'
"title": "인셉션",
"type": "MUSICAL",
"rating": "AGE12",
"synopsis": "타인의 꿈속에 진입해 아이디어를 주입하는 특수 임무를 수행하는 이야기.",
"posterUrl": "https://example.com/posters/inception.jpg",
"performanceStartDate": "2025-10-01",
"performanceEndDate": "2025-10-31"
}'
```

### 응답
Expand All @@ -50,15 +52,16 @@
"data": {
"showId": 1
},
"timestamp": "2024-06-10T12:34:56.789Z"
"timestamp": "2025-09-10T12:34:56.789Z"
}
```

### 테스트

- [x] 올바른 요청을 보내면 status가 SUCCESS이다
- [ ] 올바른 요청을 보내면 응답 본문에 showId가 존재한다
- [x] Authorization 헤더에 유효한 accessToken이 없으면 status가 UNAUTHORIZED이다
- [x] Authorization 헤더에 유효한 accessToken이 없으면 status가 UNAUTHORIZED이다
- [x] title, director, runtimeMinutes, genre, releaseDate, rating이 비어있으면 BAD_REQUEST이다
- [x] runtimeMinutes은 0 미만이면 BAD_REQUEST이다
- [x] releaseDate는 yyyy-MM-dd 형태를 준수하지 않으면 BAD_REQUEST이다
- [x] 허용되지 않은 타입이면 BAD_REQUEST이다
- [x] 올바른 요청을 보내면 응답 본문에 showId가 존재한다
- [x] 공연 시작일은 공연 종료일 이후면 INTERNAL_SERVER_ERROR이다
- [x] 중복된 제목의 공연을 등록하면 INTERNAL_SERVER_ERROR가 발생한다
65 changes: 65 additions & 0 deletions docs/specs/api/show_schedule_register.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
### 요청

- 메서드: `POST`
- 경로: `/api/show/schedule`
- 헤더

```
Content-Type: application/json
Authorization: Bearer <accessToken>
```

- 본문 예시

```json
{
"showId": 1,
"hallId": 10,
"startAt": "2025-10-10T19:00:00",
"endAt": "2025-10-10T21:30:00",
"runtimeMinutes": 150
}
```

- curl 명령 예시

```bash
curl -i -X POST 'http://localhost:8080/api/show/schedule' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0MTIzNCIsInJvbGVzIjoiUk9MRV9BRE1JTiIsInVzZXJJZCI6InRlc3QxMjM0Iiwibmlja05hbWUiOiJ0ZXN0IiwiaWF0IjoxNzU3MzExNDc5LCJleHAiOjE3NTczMTIwNzl9.xhEkuZEF0gZlvyX_F2kiAMEMGw_C2ZtGL8PmzLxhZQW32A9hmr6M0nauYEejXOFrZAb3nMdU3jFLxuhDWDbE2g' \
-d '{
"showId": 1,
"hallId": 1,
"startAt": "2025-10-10T19:00:00",
"endAt": "2025-10-10T21:30:00"
}'
```

---

### 응답

- 상태코드: `200 OK`
- 본문 예시

```json
{
"showId": 1
}
```

---

### 테스트

- [x] 올바른 접근 토큰과 유효한 요청을 보내면 SUCCESS 상태코드를 반환한다
- [x] ADMIN 권한을 가진 사용자가 올바른 요청을 하는 경우 SUCCESS 상태코드를 반환한다
- [x] 응답 본문에 scheduleId가 포함된다
- [x] 권한이 없는 사용자 토큰으로 요청하면 FORBIDDEN 상태코드를 반환한다
- [x] runtimeMinutes가 0 이하일 경우 BAD_REQUEST를 반환한다
- [x] runtimeMinutes은 startAt과 endAt의 차이만큼이 아니면 BAD_REQUEST를 반환한다
- [x] startAt이 endAt보다 늦은 경우 BAD_REQUEST를 반환한다
- [x] 존재하지 않는 showId를 보내면 NOT_FOUND 상태코드를 반환한다
- [x] 존재하지 않는 hallId를 보내면 NOT_FOUND 상태코드를 반환한다
- [x] 공연 기간 범위를 벗어나는 startAt 또는 endAt을 보낼 경우 BAD_REQUEST를 반환한다
- [x] 동일한 hallId와 시간이 겹치는 회차를 등록하려 하면 INTERNAL_SERVER_ERROR를 반환한다
14 changes: 4 additions & 10 deletions docs/specs/domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ _Aggregate Root_
- 포스터 URL(posterUrl)
- 공연 시작일(performanceStartDate, yyyy-MM-dd)
- 공연 종료일(performanceEndDate, yyyy-MM-dd)
- 공연 스케줄(schedules: List\<ShowSchedule>)

#### 행위
- `create(command: ShowCreateCommand)`
- `addSchedule(hallId, startAt, endAt, runtimeOverride)`
- `setCasting(scheduleId, roleName, personName)`
- `changePerformanceWindow(start, end)`
- `registerSchedule(hallId, startAt, endAt, runtimeOverride)`

#### 관련 타입
- `ShowCreateCommand`
Expand Down Expand Up @@ -73,15 +72,10 @@ _Aggregate Root_
- 주소(address)

#### 행위
- `addHall(name)`
- `addSeat(hallId, rowLabel, number, viewGrade, accessibility)`
- `defineGrade(hallId, name)`

- `create(show,hallId,command)`

#### 관련 타입
- `CreateVenueCommand`
- `AddHallCommand`
- `AddSeatCommand`
- `DefineGradeCommand`

---

Expand Down
2 changes: 1 addition & 1 deletion docs/specs/policy/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

// Act & Assert
var response = testUtils.get("/test/with-auth")
.withHeader("Authorization", invalidToken)
.withAuthorization(invalidToken)
.assertFailure();
assertThat(response.getStatus()).isEqualTo(UNAUTHORIZED);
assertThat(response.getData()).isEqualTo("유효한 토큰이 없습니다.");
Expand Down
15 changes: 14 additions & 1 deletion docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,17 @@
- [x] 리펙터링

2025.08.29
- [ ] `/api/members`와 같이 `/api`로 시작하지만 존재하지 않는 엔드포인트에 대한 처리가 `AuthenticationEntryPoint`에서 처리되고 있음

- [x] `/api/members`와 같이 `/api`로 시작하지만 존재하지 않는 엔드포인트에 대한 처리가 `AuthenticationEntryPoint`에서 처리되고 있음

2025.09.09

- [ ] 모듈화 설계
- [ ] public 떡칠하지 말고 기본 접근제어자 적극 활용
- [ ] Spring Modulith 사용 가능한지 점검

---

- [ ] venue register
- [ ] hall register
- [ ] 누가 AR인가? venue vs hall
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
Expand All @@ -29,17 +33,18 @@ public BCryptPasswordEncoder bCryptPasswordEncoder() {
@Bean
@Order(1)
public SecurityFilterChain apiChain(HttpSecurity http,
AuthenticationProvider authenticationProvider,
AuthenticationEntryPoint authenticationEntryPoint,
AccessDeniedHandler accessDeniedHandler)
AuthenticationProvider authenticationProvider,
AuthenticationEntryPoint authenticationEntryPoint,
AccessDeniedHandler accessDeniedHandler)
throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/api/member").permitAll()
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/auth/reissue").permitAll()
.requestMatchers(HttpMethod.POST, "/api/show").hasAuthority("ROLE_DISTRIBUTOR")
.requestMatchers(HttpMethod.POST, "/api/show/schedule").hasAuthority("ROLE_DISTRIBUTOR")
.requestMatchers(HttpMethod.POST, "/api/show").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
)
.formLogin(AbstractHttpConfigurer::disable)
Expand All @@ -53,6 +58,21 @@ public SecurityFilterChain apiChain(HttpSecurity http,
return http.build();
}

@Bean
static RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.withDefaultRolePrefix()
.role("ADMIN").implies("DISTRIBUTOR")
.role("DISTRIBUTOR").implies("USER")
.build();
}

@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}

@Bean
@Order(2)
SecurityFilterChain publicChain(HttpSecurity http) throws Exception {
Expand Down
Loading