Skip to content

Commit dcd242c

Browse files
committed
feat!: isSensitiveMasked를 공통으로 두는걸로 변경
1 parent 0004947 commit dcd242c

8 files changed

Lines changed: 250 additions & 34 deletions

File tree

src/main/kotlin/com/yourssu/scouter/hrms/application/domain/member/MemberController.kt

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ class MemberController(
3838
@AuthUser authUserInfo: AuthUserInfo,
3939
@RequestParam(required = false) search: String?,
4040
@RequestParam(required = false) partId: Long?,
41-
): ResponseEntity<List<ReadActiveMemberResponse>> {
41+
): ResponseEntity<MemberListResponse<ReadActiveMemberListItemResponse>> {
4242
val isPrivileged: Boolean = memberPrivacyService.isPrivilegedUser(authUserInfo.userId)
4343
val activeMemberDtos: List<ActiveMemberDto> = memberService.readAllActiveByFilters(
4444
search = search,
4545
partId = partId,
4646
)
47-
val responses: List<ReadActiveMemberResponse> = activeMemberDtos.map { ReadActiveMemberResponse.from(it) }
48-
val finalResponses: List<ReadActiveMemberResponse> =
49-
if (isPrivileged) responses else responses.map(ReadActiveMemberResponse::maskSensitive)
50-
51-
return ResponseEntity.ok(finalResponses)
47+
val items: List<ReadActiveMemberListItemResponse> =
48+
activeMemberDtos.map { ReadActiveMemberListItemResponse.from(it) }
49+
val finalItems: List<ReadActiveMemberListItemResponse> =
50+
if (isPrivileged) items else items.map(ReadActiveMemberListItemResponse::maskSensitive)
51+
return ResponseEntity.ok(MemberListResponse(members = finalItems, isSensitiveMasked = !isPrivileged))
5252
}
5353

5454
@Operation(summary = "비액티브 멤버 목록 조회/검색")
@@ -57,17 +57,17 @@ class MemberController(
5757
@AuthUser authUserInfo: AuthUserInfo,
5858
@RequestParam(required = false) search: String?,
5959
@RequestParam(required = false) partId: Long?,
60-
): ResponseEntity<List<ReadInactiveMemberResponse>> {
60+
): ResponseEntity<MemberListResponse<ReadInactiveMemberListItemResponse>> {
6161
val isPrivileged: Boolean = memberPrivacyService.isPrivilegedUser(authUserInfo.userId)
6262
val inactiveMemberDtos: List<InactiveMemberDto> = memberService.readAllInActiveByFilters(
6363
search = search,
6464
partId = partId,
6565
)
66-
val responses: List<ReadInactiveMemberResponse> = inactiveMemberDtos.map { ReadInactiveMemberResponse.from(it) }
67-
val finalResponses: List<ReadInactiveMemberResponse> =
68-
if (isPrivileged) responses else responses.map(ReadInactiveMemberResponse::maskSensitive)
69-
70-
return ResponseEntity.ok(finalResponses)
66+
val items: List<ReadInactiveMemberListItemResponse> =
67+
inactiveMemberDtos.map { ReadInactiveMemberListItemResponse.from(it) }
68+
val finalItems: List<ReadInactiveMemberListItemResponse> =
69+
if (isPrivileged) items else items.map(ReadInactiveMemberListItemResponse::maskSensitive)
70+
return ResponseEntity.ok(MemberListResponse(members = finalItems, isSensitiveMasked = !isPrivileged))
7171
}
7272

7373
@Operation(summary = "졸업 멤버 목록 조회/검색")
@@ -76,18 +76,17 @@ class MemberController(
7676
@AuthUser authUserInfo: AuthUserInfo,
7777
@RequestParam(required = false) search: String?,
7878
@RequestParam(required = false) partId: Long?,
79-
): ResponseEntity<List<ReadGraduatedMemberResponse>> {
79+
): ResponseEntity<MemberListResponse<ReadGraduatedMemberListItemResponse>> {
8080
val isPrivileged: Boolean = memberPrivacyService.isPrivilegedUser(authUserInfo.userId)
8181
val graduatedMemberDtos: List<GraduatedMemberDto> = memberService.readAllGraduatedByFilters(
8282
search = search,
8383
partId = partId,
8484
)
85-
val responses: List<ReadGraduatedMemberResponse> =
86-
graduatedMemberDtos.map { ReadGraduatedMemberResponse.from(it) }
87-
val finalResponses: List<ReadGraduatedMemberResponse> =
88-
if (isPrivileged) responses else responses.map(ReadGraduatedMemberResponse::maskSensitive)
89-
90-
return ResponseEntity.ok(finalResponses)
85+
val items: List<ReadGraduatedMemberListItemResponse> =
86+
graduatedMemberDtos.map { ReadGraduatedMemberListItemResponse.from(it) }
87+
val finalItems: List<ReadGraduatedMemberListItemResponse> =
88+
if (isPrivileged) items else items.map(ReadGraduatedMemberListItemResponse::maskSensitive)
89+
return ResponseEntity.ok(MemberListResponse(members = finalItems, isSensitiveMasked = !isPrivileged))
9190
}
9291

9392
@Operation(summary = "탈퇴 멤버 목록 조회/검색")
@@ -96,18 +95,17 @@ class MemberController(
9695
@AuthUser authUserInfo: AuthUserInfo,
9796
@RequestParam(required = false) search: String?,
9897
@RequestParam(required = false) partId: Long?,
99-
): ResponseEntity<List<ReadWithdrawnMemberResponse>> {
98+
): ResponseEntity<MemberListResponse<ReadWithdrawnMemberListItemResponse>> {
10099
val isPrivileged: Boolean = memberPrivacyService.isPrivilegedUser(authUserInfo.userId)
101100
val withdrawnMemberDtos: List<WithdrawnMemberDto> = memberService.readAllWithdrawnByFilters(
102101
search = search,
103102
partId = partId,
104103
)
105-
val responses: List<ReadWithdrawnMemberResponse> =
106-
withdrawnMemberDtos.map { ReadWithdrawnMemberResponse.from(it) }
107-
val finalResponses: List<ReadWithdrawnMemberResponse> =
108-
if (isPrivileged) responses else responses.map(ReadWithdrawnMemberResponse::maskSensitive)
109-
110-
return ResponseEntity.ok(finalResponses)
104+
val items: List<ReadWithdrawnMemberListItemResponse> =
105+
withdrawnMemberDtos.map { ReadWithdrawnMemberListItemResponse.from(it) }
106+
val finalItems: List<ReadWithdrawnMemberListItemResponse> =
107+
if (isPrivileged) items else items.map(ReadWithdrawnMemberListItemResponse::maskSensitive)
108+
return ResponseEntity.ok(MemberListResponse(members = finalItems, isSensitiveMasked = !isPrivileged))
111109
}
112110

113111
@Operation(summary = "액티브 멤버 정보 수정", description = "변경되지 않은 정보는 보내면 안됩니다.")
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.yourssu.scouter.hrms.application.domain.member
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
/**
6+
* 멤버 목록 API 공통 응답 래퍼.
7+
* [isSensitiveMasked]가 true이면 목록 내 모든 멤버의 민감 필드(전화번호, 생년월일, 학번 등)가 null로 내려갑니다.
8+
*/
9+
@Schema(
10+
description = "멤버 목록과 민감정보 마스킹 여부. isSensitiveMasked가 true면 members 내 민감 필드는 null.",
11+
example = """{"members":[],"isSensitiveMasked":false}""",
12+
)
13+
data class MemberListResponse<T>(
14+
val members: List<T>,
15+
val isSensitiveMasked: Boolean,
16+
)

src/main/kotlin/com/yourssu/scouter/hrms/application/domain/member/MemberResponseMasking.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
package com.yourssu.scouter.hrms.application.domain.member
22

3+
fun ReadActiveMemberListItemResponse.maskSensitive(): ReadActiveMemberListItemResponse =
4+
this.copy(
5+
phoneNumber = null,
6+
studentId = null,
7+
birthDate = null,
8+
membershipFee = null,
9+
note = null,
10+
)
11+
12+
fun ReadInactiveMemberListItemResponse.maskSensitive(): ReadInactiveMemberListItemResponse =
13+
this.copy(
14+
phoneNumber = null,
15+
studentId = null,
16+
birthDate = null,
17+
expectedReturnSemester = null,
18+
note = null,
19+
)
20+
21+
fun ReadGraduatedMemberListItemResponse.maskSensitive(): ReadGraduatedMemberListItemResponse =
22+
this.copy(
23+
phoneNumber = null,
24+
studentId = null,
25+
birthDate = null,
26+
note = null,
27+
)
28+
29+
fun ReadWithdrawnMemberListItemResponse.maskSensitive(): ReadWithdrawnMemberListItemResponse =
30+
this.copy(
31+
phoneNumber = null,
32+
studentId = null,
33+
birthDate = null,
34+
note = null,
35+
)
36+
337
fun ReadActiveMemberResponse.maskSensitive(): ReadActiveMemberResponse =
438
this.copy(
539
phoneNumber = null,

src/main/kotlin/com/yourssu/scouter/hrms/application/domain/member/ReadActiveMemberResponse.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,47 @@ import com.yourssu.scouter.hrms.business.support.utils.NicknameConverter
77
import io.swagger.v3.oas.annotations.media.Schema
88
import java.time.LocalDate
99

10+
/** 목록 API용 아이템 (isSensitiveMasked 없음, 상단 래퍼에서 한 번만 내려감) */
11+
data class ReadActiveMemberListItemResponse(
12+
val memberId: Long,
13+
val parts: List<ReadDivisionAndPartInMemberResponse>,
14+
val role: String,
15+
val name: String,
16+
val nickname: String,
17+
val state: String,
18+
val email: String,
19+
val phoneNumber: String?,
20+
val department: String,
21+
val studentId: String?,
22+
val birthDate: LocalDate?,
23+
val joinDate: LocalDate,
24+
val membershipFee: Boolean?,
25+
val note: String?,
26+
) {
27+
companion object {
28+
fun from(activeMemberDto: ActiveMemberDto): ReadActiveMemberListItemResponse =
29+
ReadActiveMemberListItemResponse(
30+
memberId = activeMemberDto.member.id,
31+
parts = activeMemberDto.member.parts.map { ReadDivisionAndPartInMemberResponse.from(it) },
32+
role = MemberRoleConverter.convertToString(activeMemberDto.member.role),
33+
name = activeMemberDto.member.name,
34+
nickname = NicknameConverter.combine(
35+
nicknameEnglish = activeMemberDto.member.nicknameEnglish,
36+
nicknameKorean = activeMemberDto.member.nicknameKorean
37+
),
38+
state = MemberStateConverter.convertToString(activeMemberDto.member.state),
39+
email = activeMemberDto.member.email,
40+
phoneNumber = activeMemberDto.member.phoneNumber,
41+
department = activeMemberDto.member.department.name,
42+
studentId = activeMemberDto.member.studentId,
43+
birthDate = activeMemberDto.member.birthDate,
44+
joinDate = activeMemberDto.member.joinDate,
45+
membershipFee = activeMemberDto.isMembershipFeePaid,
46+
note = activeMemberDto.member.note,
47+
)
48+
}
49+
}
50+
1051
data class ReadActiveMemberResponse(
1152

1253
val memberId: Long,

src/main/kotlin/com/yourssu/scouter/hrms/application/domain/member/ReadGraduatedMemberResponse.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,49 @@ import com.yourssu.scouter.hrms.business.support.utils.NicknameConverter
77
import io.swagger.v3.oas.annotations.media.Schema
88
import java.time.LocalDate
99

10+
/** 목록 API용 아이템 (isSensitiveMasked 없음) */
11+
data class ReadGraduatedMemberListItemResponse(
12+
val memberId: Long,
13+
val parts: List<ReadDivisionAndPartInMemberResponse>,
14+
val role: String,
15+
val name: String,
16+
val nickname: String,
17+
val state: String,
18+
val email: String,
19+
val phoneNumber: String?,
20+
val department: String,
21+
val studentId: String?,
22+
val birthDate: LocalDate?,
23+
val joinDate: LocalDate,
24+
val activePeriod: ReadSemesterPeriodInMemberResponse,
25+
val isAdvisorDesired: Boolean,
26+
val note: String?,
27+
) {
28+
companion object {
29+
fun from(graduatedMemberDto: GraduatedMemberDto): ReadGraduatedMemberListItemResponse =
30+
ReadGraduatedMemberListItemResponse(
31+
memberId = graduatedMemberDto.member.id,
32+
parts = graduatedMemberDto.member.parts.map { ReadDivisionAndPartInMemberResponse.from(it) },
33+
role = MemberRoleConverter.convertToString(graduatedMemberDto.member.role),
34+
name = graduatedMemberDto.member.name,
35+
nickname = NicknameConverter.combine(
36+
nicknameEnglish = graduatedMemberDto.member.nicknameEnglish,
37+
nicknameKorean = graduatedMemberDto.member.nicknameKorean
38+
),
39+
state = MemberStateConverter.convertToString(graduatedMemberDto.member.state),
40+
email = graduatedMemberDto.member.email,
41+
phoneNumber = graduatedMemberDto.member.phoneNumber,
42+
department = graduatedMemberDto.member.department.name,
43+
studentId = graduatedMemberDto.member.studentId,
44+
birthDate = graduatedMemberDto.member.birthDate,
45+
joinDate = graduatedMemberDto.member.joinDate,
46+
activePeriod = ReadSemesterPeriodInMemberResponse.from(graduatedMemberDto.activePeriod),
47+
isAdvisorDesired = graduatedMemberDto.isAdvisorDesired,
48+
note = graduatedMemberDto.member.note,
49+
)
50+
}
51+
}
52+
1053
data class ReadGraduatedMemberResponse(
1154

1255
val memberId: Long,

src/main/kotlin/com/yourssu/scouter/hrms/application/domain/member/ReadInactiveMemberResponse.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,51 @@ import com.yourssu.scouter.hrms.business.support.utils.NicknameConverter
88
import io.swagger.v3.oas.annotations.media.Schema
99
import java.time.LocalDate
1010

11+
/** 목록 API용 아이템 (isSensitiveMasked 없음) */
12+
data class ReadInactiveMemberListItemResponse(
13+
val memberId: Long,
14+
val parts: List<ReadDivisionAndPartInMemberResponse>,
15+
val role: String,
16+
val name: String,
17+
val nickname: String,
18+
val state: String,
19+
val email: String,
20+
val phoneNumber: String?,
21+
val department: String,
22+
val studentId: String?,
23+
val birthDate: LocalDate?,
24+
val joinDate: LocalDate,
25+
val activePeriod: ReadSemesterPeriodInMemberResponse,
26+
val expectedReturnSemester: String?,
27+
val inactivePeriod: ReadSemesterPeriodInMemberResponse,
28+
val note: String?,
29+
) {
30+
companion object {
31+
fun from(inactiveMemberDto: InactiveMemberDto): ReadInactiveMemberListItemResponse =
32+
ReadInactiveMemberListItemResponse(
33+
memberId = inactiveMemberDto.member.id,
34+
parts = inactiveMemberDto.member.parts.map { ReadDivisionAndPartInMemberResponse.from(it) },
35+
role = MemberRoleConverter.convertToString(inactiveMemberDto.member.role),
36+
name = inactiveMemberDto.member.name,
37+
nickname = NicknameConverter.combine(
38+
nicknameEnglish = inactiveMemberDto.member.nicknameEnglish,
39+
nicknameKorean = inactiveMemberDto.member.nicknameKorean
40+
),
41+
state = MemberStateConverter.convertToString(inactiveMemberDto.member.state),
42+
email = inactiveMemberDto.member.email,
43+
phoneNumber = inactiveMemberDto.member.phoneNumber,
44+
department = inactiveMemberDto.member.department.name,
45+
studentId = inactiveMemberDto.member.studentId,
46+
birthDate = inactiveMemberDto.member.birthDate,
47+
joinDate = inactiveMemberDto.member.joinDate,
48+
activePeriod = ReadSemesterPeriodInMemberResponse.from(inactiveMemberDto.activePeriod),
49+
expectedReturnSemester = SemesterConverter.convertToIntString(inactiveMemberDto.expectedReturnSemester),
50+
inactivePeriod = ReadSemesterPeriodInMemberResponse.from(inactiveMemberDto.inactivePeriod),
51+
note = inactiveMemberDto.member.note,
52+
)
53+
}
54+
}
55+
1156
data class ReadInactiveMemberResponse(
1257

1358
val memberId: Long,

src/main/kotlin/com/yourssu/scouter/hrms/application/domain/member/ReadWithdrawnMemberResponse.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,45 @@ import com.yourssu.scouter.hrms.business.support.utils.NicknameConverter
77
import io.swagger.v3.oas.annotations.media.Schema
88
import java.time.LocalDate
99

10+
/** 목록 API용 아이템 (isSensitiveMasked 없음) */
11+
data class ReadWithdrawnMemberListItemResponse(
12+
val memberId: Long,
13+
val parts: List<ReadDivisionAndPartInMemberResponse>,
14+
val role: String,
15+
val name: String,
16+
val nickname: String,
17+
val state: String,
18+
val email: String,
19+
val phoneNumber: String?,
20+
val department: String,
21+
val studentId: String?,
22+
val birthDate: LocalDate?,
23+
val joinDate: LocalDate,
24+
val note: String?,
25+
) {
26+
companion object {
27+
fun from(withdrawnMemberDto: WithdrawnMemberDto): ReadWithdrawnMemberListItemResponse =
28+
ReadWithdrawnMemberListItemResponse(
29+
memberId = withdrawnMemberDto.member.id,
30+
parts = withdrawnMemberDto.member.parts.map { ReadDivisionAndPartInMemberResponse.from(it) },
31+
role = MemberRoleConverter.convertToString(withdrawnMemberDto.member.role),
32+
name = withdrawnMemberDto.member.name,
33+
nickname = NicknameConverter.combine(
34+
nicknameEnglish = withdrawnMemberDto.member.nicknameEnglish,
35+
nicknameKorean = withdrawnMemberDto.member.nicknameKorean
36+
),
37+
state = MemberStateConverter.convertToString(withdrawnMemberDto.member.state),
38+
email = withdrawnMemberDto.member.email,
39+
phoneNumber = withdrawnMemberDto.member.phoneNumber,
40+
department = withdrawnMemberDto.member.department.name,
41+
studentId = withdrawnMemberDto.member.studentId,
42+
birthDate = withdrawnMemberDto.member.birthDate,
43+
joinDate = withdrawnMemberDto.member.joinDate,
44+
note = withdrawnMemberDto.member.note,
45+
)
46+
}
47+
}
48+
1049
data class ReadWithdrawnMemberResponse(
1150

1251
val memberId: Long,

src/test/kotlin/com/yourssu/scouter/hrms/application/domain/member/MemberControllerPrivacyTest.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class MemberControllerPrivacyTest {
3333
)
3434

3535
@Test
36-
fun `readAllActive는 privileged가 아니면 민감 필드를 마스킹하고 isSensitiveMasked를 true로 내려준다`() {
36+
fun `readAllActive는 privileged가 아니면 민감 필드를 마스킹하고 상단 isSensitiveMasked를 true로 내려준다`() {
3737
// given
3838
val authUserInfo = AuthUserInfo(userId = 1L)
3939
whenever(memberPrivacyService.isPrivilegedUser(authUserInfo.userId)).thenReturn(false)
@@ -51,9 +51,9 @@ class MemberControllerPrivacyTest {
5151
val body = responseEntity.body!!
5252

5353
// then
54-
assertThat(body).hasSize(1)
55-
val first = body.first()
56-
assertThat(first.isSensitiveMasked).isTrue()
54+
assertThat(body.isSensitiveMasked).isTrue()
55+
assertThat(body.members).hasSize(1)
56+
val first = body.members.first()
5757
assertThat(first.phoneNumber).isNull()
5858
assertThat(first.studentId).isNull()
5959
assertThat(first.birthDate).isNull()
@@ -66,7 +66,7 @@ class MemberControllerPrivacyTest {
6666
}
6767

6868
@Test
69-
fun `readAllActive는 privileged이면 민감 필드를 마스킹하지 않는다`() {
69+
fun `readAllActive는 privileged이면 민감 필드를 마스킹하지 않고 isSensitiveMasked를 false로 내려준다`() {
7070
// given
7171
val authUserInfo = AuthUserInfo(userId = 2L)
7272
whenever(memberPrivacyService.isPrivilegedUser(authUserInfo.userId)).thenReturn(true)
@@ -84,9 +84,9 @@ class MemberControllerPrivacyTest {
8484
val body = responseEntity.body!!
8585

8686
// then
87-
assertThat(body).hasSize(1)
88-
val first = body.first()
89-
assertThat(first.isSensitiveMasked).isFalse()
87+
assertThat(body.isSensitiveMasked).isFalse()
88+
assertThat(body.members).hasSize(1)
89+
val first = body.members.first()
9090
assertThat(first.phoneNumber).isEqualTo(dto.member.phoneNumber)
9191
assertThat(first.studentId).isEqualTo(dto.member.studentId)
9292
assertThat(first.birthDate).isEqualTo(dto.member.birthDate)

0 commit comments

Comments
 (0)