From d47909e3f32e10748735c5dec243c99e643f8fef Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 17:13:20 +0900 Subject: [PATCH 001/115] =?UTF-8?q?refactor:=20AccountRepository=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/AccountRepository.java | 4 +- .../persistence/AccountGatewayRepository.java | 44 +++++++++---------- .../application/internal/LoginService.java | 4 +- .../config/stub/StubAccountRepository.java | 4 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java index 5cb41ec8..5a30edbd 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java @@ -1,7 +1,7 @@ package com.dnd.spaced.core.account.domain.repository; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.enums.RegistrationId; +import com.dnd.spaced.core.account.domain.embed.SocialInfo; import java.util.Optional; public interface AccountRepository { @@ -12,7 +12,7 @@ public interface AccountRepository { Optional findBy(Long accountId); - Optional findBy(RegistrationId registrationId, String socialIdentifier); + Optional findBy(SocialInfo socialInfo); Optional findPreInitializationAccountBy(Long accountId); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java index 915e293e..25412d92 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java @@ -3,7 +3,7 @@ import static com.dnd.spaced.core.account.domain.QAccount.account; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.enums.RegistrationId; +import com.dnd.spaced.core.account.domain.embed.SocialInfo; import com.dnd.spaced.core.account.domain.repository.AccountRepository; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -21,9 +21,9 @@ public class AccountGatewayRepository implements AccountRepository { @Override public boolean existsBy(Long accountId) { Integer result = queryFactory.selectOne() - .from(account) - .where(account.id.eq(accountId)) - .fetchFirst(); + .from(account) + .where(eqAccountId(accountId)) + .fetchFirst(); return result != null; } @@ -36,20 +36,16 @@ public Account save(Account account) { @Override public Optional findBy(Long accountId) { Account result = queryFactory.selectFrom(account) - .where(eqAccountId(accountId), account.deleted.isFalse()) - .fetchOne(); + .where(eqAccountId(accountId)) + .fetchOne(); return Optional.ofNullable(result); } @Override - public Optional findBy(RegistrationId registrationId, String socialIdentifier) { + public Optional findBy(SocialInfo socialInfo) { Account result = queryFactory.selectFrom(account) - .where( - account.socialInfo.socialIdentifier.eq(socialIdentifier), - account.deleted.isFalse(), - account.socialInfo.registrationId.eq(registrationId) - ) + .where(eqSocialInfo(socialInfo)) .fetchOne(); return Optional.ofNullable(result); @@ -58,22 +54,16 @@ public Optional findBy(RegistrationId registrationId, String socialIden @Override public Optional findPreInitializationAccountBy(Long accountId) { Account result = queryFactory.selectFrom(account) - .where( - account.id.eq(accountId), - account.deleted.isFalse(), - isNullCareerInfo() - ) + .where(eqAccountId(accountId), isNullCareerInfo()) .fetchOne(); return Optional.ofNullable(result); } - private BooleanExpression eqAccountId(Long accountId) { - if (accountId == null) { - return null; - } - - return account.id.eq(accountId); + private BooleanExpression eqSocialInfo(SocialInfo socialInfo) { + return account.socialInfo.socialIdentifier.eq(socialInfo.getSocialIdentifier()) + .and(account.deleted.isFalse()) + .and(account.socialInfo.registrationId.eq(socialInfo.getRegistrationId())); } private BooleanExpression isNullCareerInfo() { @@ -81,4 +71,12 @@ private BooleanExpression isNullCareerInfo() { .and(account.careerInfo.experience.isNull()) .and(account.careerInfo.jobGroup.isNull()); } + + private BooleanExpression eqAccountId(Long accountId) { + if (accountId == null) { + return null; + } + + return account.id.eq(accountId).and(account.deleted.isFalse()); + } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java index 2e0152b2..e2a8b092 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java @@ -1,6 +1,7 @@ package com.dnd.spaced.core.auth.application.internal; import com.dnd.spaced.core.account.domain.Account; +import com.dnd.spaced.core.account.domain.embed.SocialInfo; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.repository.AccountRepository; import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountInfoDto; @@ -16,8 +17,9 @@ public class LoginService { public LoggedInAccountInfoDto login(String registrationIdName, String socialIdentifier) { RegistrationId registrationId = RegistrationId.findBy(registrationIdName); + SocialInfo socialInfo = new SocialInfo(registrationId, socialIdentifier); - return accountRepository.findBy(registrationId, socialIdentifier) + return accountRepository.findBy(socialInfo) .map(account -> new LoggedInAccountInfoDto( account.getId(), diff --git a/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java b/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java index f18197cb..640cd58d 100644 --- a/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java +++ b/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java @@ -1,7 +1,7 @@ package com.dnd.spaced.config.stub; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.enums.RegistrationId; +import com.dnd.spaced.core.account.domain.embed.SocialInfo; import com.dnd.spaced.core.account.domain.repository.AccountRepository; import java.util.Optional; @@ -23,7 +23,7 @@ public Optional findBy(Long accountId) { } @Override - public Optional findBy(RegistrationId registrationId, String socialIdentifier) { + public Optional findBy(SocialInfo socialInfo) { throw new UnsupportedOperationException("지원하지 않는 기능입니다."); } From cb6ea489b1b976b9131a8a096f5df10b36c8bf2e Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 17:16:10 +0900 Subject: [PATCH 002/115] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/application/AccountService.java | 10 ++--- .../dto/mapper/AccountResponseMapper.java | 18 ++++---- ...oRequest.java => ChangeCareerRequest.java} | 2 +- ...Request.java => ChangeProfileRequest.java} | 2 +- .../spaced/core/account/domain/Account.java | 30 ++++++------- .../embed/{CareerInfo.java => Career.java} | 4 +- .../embed/{ProfileInfo.java => Profile.java} | 8 ++-- .../embed/{SocialInfo.java => Social.java} | 4 +- .../domain/repository/AccountRepository.java | 4 +- .../persistence/AccountGatewayRepository.java | 22 +++++----- .../presentation/AccountController.java | 8 ++-- ...ice.java => InitAccountCareerService.java} | 6 +-- .../application/internal/LoginService.java | 6 +-- .../auth/presentation/AuthController.java | 6 +-- .../persistence/CommentGatewayRepository.java | 8 ++-- .../config/stub/StubAccountRepository.java | 4 +- .../application/AccountServiceTest.java | 20 ++++----- .../core/account/domain/AccountTest.java | 30 ++++++------- .../{CareerInfoTest.java => CareerTest.java} | 42 +++++++++---------- ...{ProfileInfoTest.java => ProfileTest.java} | 24 +++++------ .../{SocialInfoTest.java => SocialTest.java} | 10 ++--- .../presentation/AccountControllerTest.java | 13 +++--- ...java => InitAccountCareerServiceTest.java} | 14 +++---- .../internal/SignUpServiceTest.java | 4 +- .../auth/presentation/AuthControllerTest.java | 10 ++--- 25 files changed, 154 insertions(+), 155 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/{ChangeCareerInfoRequest.java => ChangeCareerRequest.java} (87%) rename space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/{ChangeProfileInfoRequest.java => ChangeProfileRequest.java} (85%) rename space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/{CareerInfo.java => Career.java} (88%) rename space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/{ProfileInfo.java => Profile.java} (90%) rename space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/{SocialInfo.java => Social.java} (85%) rename space-d/src/main/java/com/dnd/spaced/core/auth/application/{InitAccountCareerInfoService.java => InitAccountCareerService.java} (82%) rename space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/{CareerInfoTest.java => CareerTest.java} (65%) rename space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/{ProfileInfoTest.java => ProfileTest.java} (79%) rename space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/{SocialInfoTest.java => SocialTest.java} (71%) rename space-d/src/test/java/com/dnd/spaced/core/auth/application/{InitAccountCareerInfoServiceTest.java => InitAccountCareerServiceTest.java} (87%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java index 0a25d8d5..84e78a02 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java @@ -1,8 +1,8 @@ package com.dnd.spaced.core.account.application; import com.dnd.spaced.core.account.application.dto.mapper.AccountResponseMapper; -import com.dnd.spaced.core.account.application.dto.request.ChangeCareerInfoRequest; -import com.dnd.spaced.core.account.application.dto.request.ChangeProfileInfoRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeCareerRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeProfileRequest; import com.dnd.spaced.core.account.application.dto.response.AccountResponse; import com.dnd.spaced.core.account.application.exception.ForbiddenAccountException; import com.dnd.spaced.core.account.domain.Account; @@ -26,10 +26,10 @@ public void withdrawal(Long accountId) { } @Transactional - public void changeCareerInfo(Long accountId, ChangeCareerInfoRequest request) { + public void changeCareerInfo(Long accountId, ChangeCareerRequest request) { Account authorizedAccount = findAuthorizedAccount(accountId); - authorizedAccount.changeCareerInfo( + authorizedAccount.changeCareer( request.changedJobGroupName(), request.changedCompanyName(), request.changedExperienceName() @@ -37,7 +37,7 @@ public void changeCareerInfo(Long accountId, ChangeCareerInfoRequest request) { } @Transactional - public void changeProfileInfo(Long accountId, ChangeProfileInfoRequest request) { + public void changeProfileInfo(Long accountId, ChangeProfileRequest request) { Account authorizedAccount = findAuthorizedAccount(accountId); ProfileImageName changedProfileImageName = ProfileImageName.findBy(request.changedProfileImageKoreanName()); diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java index 21a06a10..3c47bbeb 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java @@ -2,8 +2,8 @@ import com.dnd.spaced.core.account.application.dto.response.AccountResponse; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.embed.CareerInfo; -import com.dnd.spaced.core.account.domain.embed.ProfileInfo; +import com.dnd.spaced.core.account.domain.embed.Career; +import com.dnd.spaced.core.account.domain.embed.Profile; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -11,15 +11,15 @@ public final class AccountResponseMapper { public static AccountResponse toDto(Account account) { - ProfileInfo profileInfo = account.getProfileInfo(); - CareerInfo careerInfo = account.getCareerInfo(); + Profile profile = account.getProfile(); + Career career = account.getCareer(); return new AccountResponse( - profileInfo.getNickname(), - profileInfo.getProfileImage(), - careerInfo.getJobGroup().getName(), - careerInfo.getCompany().getName(), - careerInfo.getExperience().getName() + profile.getNickname(), + profile.getProfileImage(), + career.getJobGroup().getName(), + career.getCompany().getName(), + career.getExperience().getName() ); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeCareerInfoRequest.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeCareerRequest.java similarity index 87% rename from space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeCareerInfoRequest.java rename to space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeCareerRequest.java index 2131d760..fd127de3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeCareerInfoRequest.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeCareerRequest.java @@ -2,7 +2,7 @@ import jakarta.validation.constraints.NotBlank; -public record ChangeCareerInfoRequest( +public record ChangeCareerRequest( @NotBlank String changedJobGroupName, diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeProfileInfoRequest.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeProfileRequest.java similarity index 85% rename from space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeProfileInfoRequest.java rename to space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeProfileRequest.java index b2698be2..85a188a8 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeProfileInfoRequest.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/request/ChangeProfileRequest.java @@ -2,7 +2,7 @@ import jakarta.validation.constraints.NotBlank; -public record ChangeProfileInfoRequest( +public record ChangeProfileRequest( @NotBlank String changedNickname, diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java index 58544375..32e33268 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java @@ -1,8 +1,8 @@ package com.dnd.spaced.core.account.domain; -import com.dnd.spaced.core.account.domain.embed.CareerInfo; -import com.dnd.spaced.core.account.domain.embed.ProfileInfo; -import com.dnd.spaced.core.account.domain.embed.SocialInfo; +import com.dnd.spaced.core.account.domain.embed.Career; +import com.dnd.spaced.core.account.domain.embed.Profile; +import com.dnd.spaced.core.account.domain.embed.Social; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.enums.Role; import com.dnd.spaced.global.audit.BaseTimeEntity; @@ -37,13 +37,13 @@ public class Account extends BaseTimeEntity { private boolean deleted = false; @Embedded - private SocialInfo socialInfo; + private Social social; @Embedded - private ProfileInfo profileInfo; + private Profile profile; @Embedded - private CareerInfo careerInfo; + private Career career; @Builder private Account( @@ -53,28 +53,28 @@ private Account( RegistrationId registrationId, String socialIdentifier ) { - this.profileInfo = ProfileInfo.of(nickname, profileImage); + this.profile = Profile.of(nickname, profileImage); this.role = role; - this.socialInfo = new SocialInfo(registrationId, socialIdentifier); + this.social = new Social(registrationId, socialIdentifier); } public void withdrawal() { this.deleted = true; } - public void changeCareerInfo( + public void changeCareer( String changedJobGroupName, String changedCompanyName, String changedExperienceName) { - this.careerInfo = CareerInfo.builder() - .jobGroupName(changedJobGroupName) - .companyName(changedCompanyName) - .experienceName(changedExperienceName) - .build(); + this.career = Career.builder() + .jobGroupName(changedJobGroupName) + .companyName(changedCompanyName) + .experienceName(changedExperienceName) + .build(); } public void changeProfileInfo(String changedNickname, String changedProfileImage) { - this.profileInfo = ProfileInfo.of(changedNickname, changedProfileImage); + this.profile = Profile.of(changedNickname, changedProfileImage); } public boolean isEqualTo(Long id) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/CareerInfo.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Career.java similarity index 88% rename from space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/CareerInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Career.java index b87637a5..be2352d5 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/CareerInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Career.java @@ -14,7 +14,7 @@ @Getter @Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class CareerInfo { +public class Career { @Enumerated(EnumType.STRING) private JobGroup jobGroup; @@ -26,7 +26,7 @@ public class CareerInfo { private Experience experience; @Builder - private CareerInfo(String jobGroupName, String companyName, String experienceName) { + private Career(String jobGroupName, String companyName, String experienceName) { this.jobGroup = JobGroup.findBy(jobGroupName); this.company = Company.findBy(companyName); this.experience = Experience.findBy(experienceName); diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/ProfileInfo.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java similarity index 90% rename from space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/ProfileInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java index b4b7b870..5ee415d6 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/ProfileInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java @@ -10,7 +10,7 @@ @Getter @Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ProfileInfo { +public class Profile { private static final int NICKNAME_MIN_LENGTH = 5; private static final int NICKNAME_MAX_LENGTH = 10; @@ -23,10 +23,10 @@ public class ProfileInfo { private String nickname; private String profileImage; - public static ProfileInfo of(String nickname, String profileImage) { + public static Profile of(String nickname, String profileImage) { validateContent(nickname, profileImage); - return new ProfileInfo(nickname, profileImage); + return new Profile(nickname, profileImage); } private static void validateContent(String nickname, String profileImage) { @@ -47,7 +47,7 @@ private static boolean isInvalidProfileImage(String profileImage) { return profileImage == null || profileImage.isBlank(); } - private ProfileInfo(String nickname, String profileImage) { + private Profile(String nickname, String profileImage) { this.nickname = nickname; this.profileImage = profileImage; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/SocialInfo.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java similarity index 85% rename from space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/SocialInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java index e0ea4053..305110eb 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/SocialInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java @@ -11,14 +11,14 @@ @Getter @Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class SocialInfo { +public class Social { @Enumerated(EnumType.STRING) private RegistrationId registrationId; private String socialIdentifier; - public SocialInfo(RegistrationId registrationId, String socialIdentifier) { + public Social(RegistrationId registrationId, String socialIdentifier) { this.registrationId = registrationId; this.socialIdentifier = socialIdentifier; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java index 5a30edbd..035f372f 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/repository/AccountRepository.java @@ -1,7 +1,7 @@ package com.dnd.spaced.core.account.domain.repository; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.embed.SocialInfo; +import com.dnd.spaced.core.account.domain.embed.Social; import java.util.Optional; public interface AccountRepository { @@ -12,7 +12,7 @@ public interface AccountRepository { Optional findBy(Long accountId); - Optional findBy(SocialInfo socialInfo); + Optional findBy(Social social); Optional findPreInitializationAccountBy(Long accountId); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java index 25412d92..19c6b9be 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java @@ -3,7 +3,7 @@ import static com.dnd.spaced.core.account.domain.QAccount.account; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.embed.SocialInfo; +import com.dnd.spaced.core.account.domain.embed.Social; import com.dnd.spaced.core.account.domain.repository.AccountRepository; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -43,9 +43,9 @@ public Optional findBy(Long accountId) { } @Override - public Optional findBy(SocialInfo socialInfo) { + public Optional findBy(Social social) { Account result = queryFactory.selectFrom(account) - .where(eqSocialInfo(socialInfo)) + .where(eqSocial(social)) .fetchOne(); return Optional.ofNullable(result); @@ -54,22 +54,22 @@ public Optional findBy(SocialInfo socialInfo) { @Override public Optional findPreInitializationAccountBy(Long accountId) { Account result = queryFactory.selectFrom(account) - .where(eqAccountId(accountId), isNullCareerInfo()) + .where(eqAccountId(accountId), isNullCareer()) .fetchOne(); return Optional.ofNullable(result); } - private BooleanExpression eqSocialInfo(SocialInfo socialInfo) { - return account.socialInfo.socialIdentifier.eq(socialInfo.getSocialIdentifier()) + private BooleanExpression eqSocial(Social social) { + return account.social.socialIdentifier.eq(social.getSocialIdentifier()) .and(account.deleted.isFalse()) - .and(account.socialInfo.registrationId.eq(socialInfo.getRegistrationId())); + .and(account.social.registrationId.eq(social.getRegistrationId())); } - private BooleanExpression isNullCareerInfo() { - return account.careerInfo.company.isNull() - .and(account.careerInfo.experience.isNull()) - .and(account.careerInfo.jobGroup.isNull()); + private BooleanExpression isNullCareer() { + return account.career.company.isNull() + .and(account.career.experience.isNull()) + .and(account.career.jobGroup.isNull()); } private BooleanExpression eqAccountId(Long accountId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java index efc32461..df340a32 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java @@ -1,8 +1,8 @@ package com.dnd.spaced.core.account.presentation; import com.dnd.spaced.core.account.application.AccountService; -import com.dnd.spaced.core.account.application.dto.request.ChangeCareerInfoRequest; -import com.dnd.spaced.core.account.application.dto.request.ChangeProfileInfoRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeCareerRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeProfileRequest; import com.dnd.spaced.core.account.application.dto.response.AccountResponse; import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; @@ -34,7 +34,7 @@ public ResponseEntity withdrawal(@CurrentAccountInfo AuthAccountInfo accou @PutMapping("/career-info") public ResponseEntity changeCareerInfo( @CurrentAccountInfo AuthAccountInfo accountInfo, - @Valid @RequestBody ChangeCareerInfoRequest request + @Valid @RequestBody ChangeCareerRequest request ) { accountService.changeCareerInfo(accountInfo.accountId(), request); @@ -44,7 +44,7 @@ public ResponseEntity changeCareerInfo( @PutMapping("/profile-info") public ResponseEntity changeProfileInfo( @CurrentAccountInfo AuthAccountInfo accountInfo, - @Valid @RequestBody ChangeProfileInfoRequest request + @Valid @RequestBody ChangeProfileRequest request ) { accountService.changeProfileInfo(accountInfo.accountId(), request); diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerInfoService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java similarity index 82% rename from space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerInfoService.java rename to space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java index e88ef11c..e46fea01 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerInfoService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java @@ -10,15 +10,15 @@ @Service @RequiredArgsConstructor -public class InitAccountCareerInfoService { +public class InitAccountCareerService { private final AccountRepository accountRepository; @Transactional - public void initCareerInfo(Long accountId, InitAccountCareerInfoRequest request) { + public void initCareer(Long accountId, InitAccountCareerInfoRequest request) { Account account = findPreInitializationAccount(accountId); - account.changeCareerInfo(request.jobGroupName(), request.companyName(), request.experienceName()); + account.changeCareer(request.jobGroupName(), request.companyName(), request.experienceName()); } private Account findPreInitializationAccount(Long accountId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java index e2a8b092..14b01069 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java @@ -1,7 +1,7 @@ package com.dnd.spaced.core.auth.application.internal; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.embed.SocialInfo; +import com.dnd.spaced.core.account.domain.embed.Social; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.repository.AccountRepository; import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountInfoDto; @@ -17,9 +17,9 @@ public class LoginService { public LoggedInAccountInfoDto login(String registrationIdName, String socialIdentifier) { RegistrationId registrationId = RegistrationId.findBy(registrationIdName); - SocialInfo socialInfo = new SocialInfo(registrationId, socialIdentifier); + Social social = new Social(registrationId, socialIdentifier); - return accountRepository.findBy(socialInfo) + return accountRepository.findBy(social) .map(account -> new LoggedInAccountInfoDto( account.getId(), diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java index 4be383cd..cae54097 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.auth.presentation; -import com.dnd.spaced.core.auth.application.InitAccountCareerInfoService; +import com.dnd.spaced.core.auth.application.InitAccountCareerService; import com.dnd.spaced.core.auth.application.RefreshTokenService; import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerInfoRequest; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; @@ -35,14 +35,14 @@ public class AuthController { private final TokenProperties tokenProperties; private final RefreshTokenService refreshTokenService; - private final InitAccountCareerInfoService initAccountCareerInfoService; + private final InitAccountCareerService initAccountCareerService; @PostMapping("/profile") public ResponseEntity initAccountCareerInfo( @CurrentAccountInfo AuthAccountInfo accountInfo, @Valid @RequestBody InitAccountCareerInfoRequest request ) { - initAccountCareerInfoService.initCareerInfo(accountInfo.accountId(), request); + initAccountCareerService.initCareer(accountInfo.accountId(), request); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java index ef0623f5..194c7681 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java @@ -120,8 +120,8 @@ private ConstructorExpression getLikedCommentInfoWithLiked() { LikedCommentInfo.class, comment, like.id.isNotNull(), - account.profileInfo.nickname, - account.profileInfo.profileImage + account.profile.nickname, + account.profile.profileImage ); } @@ -129,8 +129,8 @@ private ConstructorExpression getLikedCommentInfoWithoutLiked( return Projections.constructor( LikedCommentInfo.class, comment, - account.profileInfo.nickname, - account.profileInfo.profileImage + account.profile.nickname, + account.profile.profileImage ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java b/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java index 640cd58d..ef7d67bb 100644 --- a/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java +++ b/space-d/src/test/java/com/dnd/spaced/config/stub/StubAccountRepository.java @@ -1,7 +1,7 @@ package com.dnd.spaced.config.stub; import com.dnd.spaced.core.account.domain.Account; -import com.dnd.spaced.core.account.domain.embed.SocialInfo; +import com.dnd.spaced.core.account.domain.embed.Social; import com.dnd.spaced.core.account.domain.repository.AccountRepository; import java.util.Optional; @@ -23,7 +23,7 @@ public Optional findBy(Long accountId) { } @Override - public Optional findBy(SocialInfo socialInfo) { + public Optional findBy(Social social) { throw new UnsupportedOperationException("지원하지 않는 기능입니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java index ebf298a0..ef388bd5 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java @@ -5,8 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import com.dnd.spaced.core.account.application.dto.request.ChangeCareerInfoRequest; -import com.dnd.spaced.core.account.application.dto.request.ChangeProfileInfoRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeCareerRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeProfileRequest; import com.dnd.spaced.core.account.application.dto.response.AccountResponse; import com.dnd.spaced.core.account.application.exception.ForbiddenAccountException; import com.dnd.spaced.core.account.domain.enums.ProfileImageName; @@ -55,7 +55,7 @@ class AccountServiceTest { @Sql("classpath:sql/account/account.sql") void 회원_경력_정보를_변경한다() { // given - ChangeCareerInfoRequest request = new ChangeCareerInfoRequest( + ChangeCareerRequest request = new ChangeCareerRequest( "개발자", "비공개", "1~2년 차" @@ -79,7 +79,7 @@ class AccountServiceTest { @Sql("classpath:sql/account/account.sql") void 유효한_직군_이름이_아니라면_경력_정보를_변경할_수_없다(String invalidJobGroupName) { // given - ChangeCareerInfoRequest request = new ChangeCareerInfoRequest( + ChangeCareerRequest request = new ChangeCareerRequest( invalidJobGroupName, "비공개", "1~2년 차" @@ -96,7 +96,7 @@ class AccountServiceTest { @Sql("classpath:sql/account/account.sql") void 유효한_회사명이_아니라면_경력_정보를_변경할_수_없다(String invalidCompanyName) { // given - ChangeCareerInfoRequest request = new ChangeCareerInfoRequest( + ChangeCareerRequest request = new ChangeCareerRequest( "개발자", invalidCompanyName, "1~2년 차" @@ -113,7 +113,7 @@ class AccountServiceTest { @Sql("classpath:sql/account/account.sql") void 유효한_경력이_아니라면_경력_정보를_변경할_수_없다(String invalidExperienceName) { // given - ChangeCareerInfoRequest request = new ChangeCareerInfoRequest( + ChangeCareerRequest request = new ChangeCareerRequest( "개발자", "비공개", invalidExperienceName @@ -128,7 +128,7 @@ class AccountServiceTest { @Test void 없거나_탈퇴한_회원의_ID라면_경력_정보를_변경할_수_없다() { // given - ChangeCareerInfoRequest request = new ChangeCareerInfoRequest( + ChangeCareerRequest request = new ChangeCareerRequest( "개발자", "비공개", "1~2년 차" @@ -150,7 +150,7 @@ private static Stream changeProfileInfoTestWithProfileImageKoreanName @Sql("classpath:sql/account/account.sql") void 회원_프로필_정보를_변경한다(ProfileImageName profileImageName) { // given - ChangeProfileInfoRequest request = new ChangeProfileInfoRequest( + ChangeProfileRequest request = new ChangeProfileRequest( "행복한지구001", profileImageName.getKorean() ); @@ -172,7 +172,7 @@ private static Stream changeProfileInfoTestWithProfileImageKoreanName @Sql("classpath:sql/account/account.sql") void 프로필_이미지_경로가_비어_있으면_프로필_정보를_변경할_수_없다(String invalidProfileImageKoreanName) { // given - ChangeProfileInfoRequest request = new ChangeProfileInfoRequest( + ChangeProfileRequest request = new ChangeProfileRequest( "재빠른지구001", invalidProfileImageKoreanName ); @@ -186,7 +186,7 @@ private static Stream changeProfileInfoTestWithProfileImageKoreanName @Test void 없거나_탈퇴한_회원의_ID라면_프로필_정보를_변경할_수_없다() { // given - ChangeProfileInfoRequest request = new ChangeProfileInfoRequest( + ChangeProfileRequest request = new ChangeProfileRequest( "재빠른지구001", "earth.png" ); diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java index 2818725d..89e17e58 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import com.dnd.spaced.core.account.domain.embed.CareerInfo; +import com.dnd.spaced.core.account.domain.embed.Career; import com.dnd.spaced.core.account.domain.embed.exception.InvalidNicknameException; import com.dnd.spaced.core.account.domain.embed.exception.InvalidProfileImageException; import com.dnd.spaced.core.account.domain.enums.Company; @@ -44,10 +44,10 @@ class AccountTest { ); assertAll( - () -> assertThat(actual.getSocialInfo().getRegistrationId()).isEqualTo(RegistrationId.KAKAO), - () -> assertThat(actual.getSocialInfo().getSocialIdentifier()).isEqualTo("12345"), - () -> assertThat(actual.getProfileInfo().getNickname()).isEqualTo("재빠른지구001"), - () -> assertThat(actual.getProfileInfo().getProfileImage()).isEqualTo("earth.png"), + () -> assertThat(actual.getSocial().getRegistrationId()).isEqualTo(RegistrationId.KAKAO), + () -> assertThat(actual.getSocial().getSocialIdentifier()).isEqualTo("12345"), + () -> assertThat(actual.getProfile().getNickname()).isEqualTo("재빠른지구001"), + () -> assertThat(actual.getProfile().getProfileImage()).isEqualTo("earth.png"), () -> assertThat(actual.getRole()).isEqualTo(Role.ROLE_USER) ); } @@ -106,15 +106,15 @@ private static Stream builderTestWithInvalidNickname() { .build(); // when - account.changeCareerInfo("개발자", "비공개", "1~2년 차"); + account.changeCareer("개발자", "비공개", "1~2년 차"); // then - CareerInfo careerInfo = account.getCareerInfo(); + Career career = account.getCareer(); assertAll( - () -> assertThat(careerInfo.getCompany()).isEqualTo(Company.BLIND), - () -> assertThat(careerInfo.getJobGroup()).isEqualTo(JobGroup.DEVELOP), - () -> assertThat(careerInfo.getExperience()).isEqualTo(Experience.BETWEEN_FIRST_SECOND) + () -> assertThat(career.getCompany()).isEqualTo(Company.BLIND), + () -> assertThat(career.getJobGroup()).isEqualTo(JobGroup.DEVELOP), + () -> assertThat(career.getExperience()).isEqualTo(Experience.BETWEEN_FIRST_SECOND) ); } @@ -132,7 +132,7 @@ private static Stream builderTestWithInvalidNickname() { // when & then assertThatThrownBy( - () -> account.changeCareerInfo( + () -> account.changeCareer( "개발자", invalidCompanyName, "1~2년 차" @@ -156,7 +156,7 @@ private static Stream builderTestWithInvalidNickname() { // when & then assertThatThrownBy( - () -> account.changeCareerInfo( + () -> account.changeCareer( invalidJobGroupName, "비공개", "1~2년 차" @@ -180,7 +180,7 @@ private static Stream builderTestWithInvalidNickname() { // when & then assertThatThrownBy( - () -> account.changeCareerInfo( + () -> account.changeCareer( "개발자", "비공개", invalidExperienceName @@ -209,8 +209,8 @@ private static Stream builderTestWithInvalidNickname() { // then assertAll( - () -> assertThat(account.getProfileInfo().getNickname()).isEqualTo(changedNickname), - () -> assertThat(account.getProfileInfo().getProfileImage()).isEqualTo(changedProfileImage) + () -> assertThat(account.getProfile().getNickname()).isEqualTo(changedNickname), + () -> assertThat(account.getProfile().getProfileImage()).isEqualTo(changedProfileImage) ); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/CareerInfoTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/CareerTest.java similarity index 65% rename from space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/CareerInfoTest.java rename to space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/CareerTest.java index 31054d41..503a2a56 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/CareerInfoTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/CareerTest.java @@ -14,17 +14,17 @@ @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class CareerInfoTest { +class CareerTest { @Test void 경력_정보를_초기화한다() { // when & then assertDoesNotThrow( - () -> CareerInfo.builder() - .jobGroupName("개발자") - .experienceName("1~2년 차") - .companyName("비공개") - .build() + () -> Career.builder() + .jobGroupName("개발자") + .experienceName("1~2년 차") + .companyName("비공개") + .build() ); } @@ -33,11 +33,11 @@ class CareerInfoTest { void 유효한_경력이_아니라면_경력_정보를_초기화할_수_없다(String invalidExperienceName) { // when & then assertThatThrownBy( - () -> CareerInfo.builder() - .jobGroupName("개발자") - .experienceName(invalidExperienceName) - .companyName("비공개") - .build() + () -> Career.builder() + .jobGroupName("개발자") + .experienceName(invalidExperienceName) + .companyName("비공개") + .build() ).isInstanceOf(InvalidExperienceException.class) .hasMessageContaining("잘못된 경력"); } @@ -47,11 +47,11 @@ class CareerInfoTest { void 유효한_회사명이_아니라면_경력_정보를_초기화할_수_없다(String invalidCompanyName) { // when & then assertThatThrownBy( - () -> CareerInfo.builder() - .jobGroupName("개발자") - .experienceName("1~2년 차") - .companyName(invalidCompanyName) - .build() + () -> Career.builder() + .jobGroupName("개발자") + .experienceName("1~2년 차") + .companyName(invalidCompanyName) + .build() ).isInstanceOf(InvalidCompanyException.class) .hasMessageContaining("잘못된 회사 이름"); } @@ -61,11 +61,11 @@ class CareerInfoTest { void 유효한_직군_이름이_아니라면_경력_정보를_초기화할_수_없다(String invalidJobGroupName) { // when & then assertThatThrownBy( - () -> CareerInfo.builder() - .jobGroupName(invalidJobGroupName) - .experienceName("1~2년 차") - .companyName("비공개") - .build() + () -> Career.builder() + .jobGroupName(invalidJobGroupName) + .experienceName("1~2년 차") + .companyName("비공개") + .build() ).isInstanceOf(InvalidJobGroupException.class) .hasMessageContaining("잘못된 직군 이름"); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileInfoTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java similarity index 79% rename from space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileInfoTest.java rename to space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java index e749402f..d8445dd9 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileInfoTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java @@ -18,12 +18,12 @@ @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class ProfileInfoTest { +class ProfileTest { @Test void 프로필_정보를_초기화한다() { // when & then - assertDoesNotThrow(() -> ProfileInfo.of("재빠른지구001", "earth.png")); + assertDoesNotThrow(() -> Profile.of("재빠른지구001", "earth.png")); } private static Stream constructorTestWithInvalidNickname() { @@ -40,7 +40,7 @@ private static Stream constructorTestWithInvalidNickname() { @MethodSource("constructorTestWithInvalidNickname") void 유효한_길이의_닉네임이_아니라면_프로필_정보를_초기화할_수_없다(String invalidNickname) { // when & then - assertThatThrownBy(() -> ProfileInfo.of(invalidNickname, "earth.png")) + assertThatThrownBy(() -> Profile.of(invalidNickname, "earth.png")) .isInstanceOf(InvalidNicknameException.class) .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); } @@ -49,7 +49,7 @@ private static Stream constructorTestWithInvalidNickname() { @NullAndEmptySource void 비어_있는_프로필_이미지_경로라면_프로필_정보를_초기화할_수_없다(String invalidProfileImage) { // when & then - assertThatThrownBy(() -> ProfileInfo.of("행복한지구001", invalidProfileImage)) + assertThatThrownBy(() -> Profile.of("행복한지구001", invalidProfileImage)) .isInstanceOf(InvalidProfileImageException.class) .hasMessage("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); } @@ -57,18 +57,18 @@ private static Stream constructorTestWithInvalidNickname() { @Test void 프로필_정보를_변경한다() { // given - ProfileInfo profileInfo = ProfileInfo.of("재빠른지구001", "earth.png"); + Profile profile = Profile.of("재빠른지구001", "earth.png"); // when String changedNickname = "행복한화성001"; String changedProfileImage = "mars.png"; - profileInfo.changeProfileInfo(changedNickname, changedProfileImage); + profile.changeProfileInfo(changedNickname, changedProfileImage); // then assertAll( - () -> assertThat(profileInfo.getNickname()).isEqualTo(changedNickname), - () -> assertThat(profileInfo.getProfileImage()).isEqualTo(changedProfileImage) + () -> assertThat(profile.getNickname()).isEqualTo(changedNickname), + () -> assertThat(profile.getProfileImage()).isEqualTo(changedProfileImage) ); } @@ -76,10 +76,10 @@ private static Stream constructorTestWithInvalidNickname() { @NullAndEmptySource void 비어_있는_프로필_이미지_경로라면_프로필_정보를_변경할_수_없다(String invalidProfileImage) { // given - ProfileInfo profileInfo = ProfileInfo.of("재빠른지구001", "earth.png"); + Profile profile = Profile.of("재빠른지구001", "earth.png"); // when & then - assertThatThrownBy(() -> profileInfo.changeProfileInfo("행복한화성001", invalidProfileImage)) + assertThatThrownBy(() -> profile.changeProfileInfo("행복한화성001", invalidProfileImage)) .isInstanceOf(InvalidProfileImageException.class) .hasMessage("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); } @@ -98,10 +98,10 @@ private static Stream changeProfileInfoTestWithInvalidNickname() { @MethodSource("changeProfileInfoTestWithInvalidNickname") void 유효한_닉네임_길이가_아니라면_프로필_정보를_변경할_수_없다(String invalidNickname) { // given - ProfileInfo profileInfo = ProfileInfo.of("nickname", "profileImage"); + Profile profile = Profile.of("nickname", "profileImage"); // when & then - assertThatThrownBy(() -> profileInfo.changeProfileInfo(invalidNickname, "profileImage")) + assertThatThrownBy(() -> profile.changeProfileInfo(invalidNickname, "profileImage")) .isInstanceOf(InvalidNicknameException.class) .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialInfoTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java similarity index 71% rename from space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialInfoTest.java rename to space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java index bd8ddd2e..daa3fb90 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialInfoTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java @@ -10,7 +10,7 @@ @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class SocialInfoTest { +class SocialTest { @Test void 회원의_소셜_정보를_초기화한다() { @@ -18,12 +18,12 @@ class SocialInfoTest { RegistrationId registrationId = RegistrationId.findBy("kakao"); // when - SocialInfo socialInfo = new SocialInfo(registrationId, "41258"); + Social social = new Social(registrationId, "41258"); // then assertAll( - () -> assertThat(socialInfo.getSocialIdentifier()).isEqualTo("41258"), - () -> assertThat(socialInfo.getRegistrationId()).isEqualTo(registrationId) + () -> assertThat(social.getSocialIdentifier()).isEqualTo("41258"), + () -> assertThat(social.getRegistrationId()).isEqualTo(registrationId) ); } -} \ No newline at end of file +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java index c75b2f7a..1d645c0c 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java @@ -20,12 +20,11 @@ import com.dnd.spaced.config.common.CommonControllerSliceTest; import com.dnd.spaced.config.docs.link.DocumentLinkGenerator.DocsUrl; import com.dnd.spaced.core.account.application.AccountService; -import com.dnd.spaced.core.account.application.dto.request.ChangeCareerInfoRequest; -import com.dnd.spaced.core.account.application.dto.request.ChangeProfileInfoRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeCareerRequest; +import com.dnd.spaced.core.account.application.dto.request.ChangeProfileRequest; import com.dnd.spaced.core.account.application.dto.response.AccountResponse; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; @@ -66,7 +65,7 @@ class AccountControllerTest extends CommonControllerSliceTest { @WithMockUser("1") void 회원_경력_정보_변경_요청_성공_테스트() throws Exception { // given - ChangeCareerInfoRequest request = new ChangeCareerInfoRequest("개발자", "중소기업", "비공개"); + ChangeCareerRequest request = new ChangeCareerRequest("개발자", "중소기업", "비공개"); // when & then @@ -78,7 +77,7 @@ class AccountControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(accountService).changeCareerInfo(anyLong(), any(ChangeCareerInfoRequest.class)); + verify(accountService).changeCareerInfo(anyLong(), any(ChangeCareerRequest.class)); 회원_경력_정보_변경_요청_문서화(resultActions); } @@ -102,7 +101,7 @@ class AccountControllerTest extends CommonControllerSliceTest { @WithMockUser("1") void 회원_프로필_정보_변경_요청_성공_테스트() throws Exception { // given - ChangeProfileInfoRequest request = new ChangeProfileInfoRequest("행복한금성001", "금성"); + ChangeProfileRequest request = new ChangeProfileRequest("행복한금성001", "금성"); // when & then @@ -114,7 +113,7 @@ class AccountControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(accountService).changeProfileInfo(anyLong(), any(ChangeProfileInfoRequest.class)); + verify(accountService).changeProfileInfo(anyLong(), any(ChangeProfileRequest.class)); 회원_프로필_정보_변경_요청_문서화(resultActions); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerInfoServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java similarity index 87% rename from space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerInfoServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java index c483f6c5..6ad8d8d9 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerInfoServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java @@ -21,10 +21,10 @@ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class InitAccountCareerInfoServiceTest { +class InitAccountCareerServiceTest { @Autowired - InitAccountCareerInfoService initAccountCareerInfoService; + InitAccountCareerService initAccountCareerService; @Test @Sql("classpath:sql/auth/account.sql") @@ -37,7 +37,7 @@ class InitAccountCareerInfoServiceTest { ); // when & then - assertDoesNotThrow(() -> initAccountCareerInfoService.initCareerInfo(1L, request)); + assertDoesNotThrow(() -> initAccountCareerService.initCareer(1L, request)); } @ParameterizedTest(name = "회사명이 {0}일 때 경력 정보를 초기화할 수 없다") @@ -52,7 +52,7 @@ class InitAccountCareerInfoServiceTest { ); // when & then - assertThatThrownBy(() -> initAccountCareerInfoService.initCareerInfo(1L, request)) + assertThatThrownBy(() -> initAccountCareerService.initCareer(1L, request)) .isInstanceOf(InvalidCompanyException.class) .hasMessageContaining("잘못된 회사 이름"); } @@ -69,7 +69,7 @@ class InitAccountCareerInfoServiceTest { ); // when & then - assertThatThrownBy(() -> initAccountCareerInfoService.initCareerInfo(1L, request)) + assertThatThrownBy(() -> initAccountCareerService.initCareer(1L, request)) .isInstanceOf(InvalidJobGroupException.class) .hasMessageContaining("잘못된 직군 이름"); } @@ -86,7 +86,7 @@ class InitAccountCareerInfoServiceTest { ); // when & then - assertThatThrownBy(() -> initAccountCareerInfoService.initCareerInfo(1L, request)) + assertThatThrownBy(() -> initAccountCareerService.initCareer(1L, request)) .isInstanceOf(InvalidExperienceException.class) .hasMessageContaining("잘못된 경력"); } @@ -101,7 +101,7 @@ class InitAccountCareerInfoServiceTest { ); // when & then - assertThatThrownBy(() -> initAccountCareerInfoService.initCareerInfo(-999L, request)) + assertThatThrownBy(() -> initAccountCareerService.initCareer(-999L, request)) .isInstanceOf(ForbiddenInitCareerInfoException.class) .hasMessage("최초로 가입한 회원이 아닙니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java index 4bfe1591..135e9ffc 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java @@ -42,8 +42,8 @@ class SignUpServiceTest { // then assertAll( () -> assertThat(actual.getId()).isEqualTo(1L), - () -> assertThat(actual.getSocialInfo().getSocialIdentifier()).isEqualTo("12345"), - () -> assertThat(actual.getSocialInfo().getRegistrationId()).isEqualTo(RegistrationId.KAKAO) + () -> assertThat(actual.getSocial().getSocialIdentifier()).isEqualTo("12345"), + () -> assertThat(actual.getSocial().getRegistrationId()).isEqualTo(RegistrationId.KAKAO) ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java index 706d0b32..0c5e0bee 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java @@ -24,7 +24,7 @@ import com.dnd.spaced.config.common.CommonControllerSliceTest; import com.dnd.spaced.config.docs.link.DocumentLinkGenerator.DocsUrl; -import com.dnd.spaced.core.auth.application.InitAccountCareerInfoService; +import com.dnd.spaced.core.auth.application.InitAccountCareerService; import com.dnd.spaced.core.auth.application.RefreshTokenService; import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerInfoRequest; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; @@ -40,7 +40,7 @@ class AuthControllerTest extends CommonControllerSliceTest { @Autowired - InitAccountCareerInfoService initAccountCareerInfoService; + InitAccountCareerService initAccountCareerService; @Autowired RefreshTokenService refreshTokenService; @@ -49,8 +49,8 @@ class AuthControllerTest extends CommonControllerSliceTest { @WithMockUser("1") void 회원_프로필_초기화_요청_성공_테스트() throws Exception { // given - willDoNothing().given(initAccountCareerInfoService) - .initCareerInfo(anyLong(), any(InitAccountCareerInfoRequest.class)); + willDoNothing().given(initAccountCareerService) + .initCareer(anyLong(), any(InitAccountCareerInfoRequest.class)); InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( "개발자", "중소기업", @@ -66,7 +66,7 @@ class AuthControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(initAccountCareerInfoService).initCareerInfo(anyLong(), any(InitAccountCareerInfoRequest.class)); + verify(initAccountCareerService).initCareer(anyLong(), any(InitAccountCareerInfoRequest.class)); 회원_프로필_초기화_요청_문서화(resultActions); } From afbd4aef38616c743aa223a017cd20d0d81bf888 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 18:01:25 +0900 Subject: [PATCH 003/115] =?UTF-8?q?test:=20AccountGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AccountGatewayRepositoryTest.java | 141 ++++++++++++++++++ .../resources/sql/account/deleted_account.sql | 2 + .../sql/account/pre_init_account.sql | 4 + 3 files changed, 147 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java create mode 100644 space-d/src/test/resources/sql/account/deleted_account.sql create mode 100644 space-d/src/test/resources/sql/account/pre_init_account.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java new file mode 100644 index 00000000..df0374ad --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java @@ -0,0 +1,141 @@ +package com.dnd.spaced.core.account.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.dnd.spaced.core.account.domain.Account; +import com.dnd.spaced.core.account.domain.embed.Social; +import com.dnd.spaced.core.account.domain.enums.RegistrationId; +import com.dnd.spaced.core.account.domain.enums.Role; +import com.dnd.spaced.core.account.domain.repository.AccountRepository; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class AccountGatewayRepositoryTest { + + @Autowired + AccountRepository accountRepository; + + @Test + @Sql("classpath:sql/account/account.sql") + void 탈퇴하지_않은_회원의_식별자로_영속화_여부를_확인한다() { + // when + boolean actual = accountRepository.existsBy(1L); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 탈퇴한_회원의_식별자로_영속화_여부를_확인한다() { + // when + boolean actual = accountRepository.existsBy(2L); + + // then + assertThat(actual).isFalse(); + } + + @Test + @Sql("classpath:sql/account/account.sql") + void 탈퇴하지_않은_회원을_조회한다() { + // when + Optional actual = accountRepository.findBy(1L); + + // then + assertThat(actual).isPresent(); + } + + @Test + @Sql("classpath:sql/account/deleted_account.sql") + void 탈퇴한_회원은_조회할_수_없다() { + // when + Optional actual = accountRepository.findBy(2L); + + // then + assertThat(actual).isEmpty(); + } + + @Test + void 회원을_영속화_한다() { + // given + Account account = Account.builder() + .registrationId(RegistrationId.KAKAO) + .socialIdentifier("12345") + .nickname("재빠른지구001") + .profileImage("earth.png") + .role(Role.ROLE_USER) + .build(); + + // when + Account actual = accountRepository.save(account); + + // then + assertThat(actual.getId()).isPositive(); + } + + @Test + @Sql("classpath:sql/account/account.sql") + void 회원의_소셜_정보로_탈퇴하지_않은_회원을_조회한다() { + // given + RegistrationId registrationId = RegistrationId.KAKAO; + Social social = new Social(registrationId, "12345"); + + // when + Optional actual = accountRepository.findBy(social); + + // then + assertThat(actual).isPresent(); + } + + @Test + @Sql("classpath:sql/account/deleted_account.sql") + void 회원의_소셜_정보로_탈퇴한_회원을_조회한다() { + // given + RegistrationId registrationId = RegistrationId.KAKAO; + Social social = new Social(registrationId, "54321"); + + // when + Optional actual = accountRepository.findBy(social); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql("classpath:sql/account/pre_init_account.sql") + void 초기_설정된_회원을_식별자로_조회한다() { + // when + Optional actual = accountRepository.findPreInitializationAccountBy(3L); + + // then + assertThat(actual).isPresent(); + } + + @Test + @Sql("classpath:sql/account/account.sql") + void 설정을_모두_완료한_회원을_식별자로_조회할_수_없다() { + // when + Optional actual = accountRepository.findPreInitializationAccountBy(1L); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql("classpath:sql/account/pre_init_account.sql") + void 탈퇴한_회원을_식별자로_조회할_수_없다() { + // when + Optional actual = accountRepository.findPreInitializationAccountBy(4L); + + // then + assertThat(actual).isEmpty(); + } +} diff --git a/space-d/src/test/resources/sql/account/deleted_account.sql b/space-d/src/test/resources/sql/account/deleted_account.sql new file mode 100644 index 00000000..021f7e02 --- /dev/null +++ b/space-d/src/test/resources/sql/account/deleted_account.sql @@ -0,0 +1,2 @@ +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +VALUES (2, now(), now(), 'KAKAO', '54321', 'STARTUP', 'UNDER_FIRST', 'ETC', true, '재빠른지구002', 'earth.png'); diff --git a/space-d/src/test/resources/sql/account/pre_init_account.sql b/space-d/src/test/resources/sql/account/pre_init_account.sql new file mode 100644 index 00000000..1d1886a2 --- /dev/null +++ b/space-d/src/test/resources/sql/account/pre_init_account.sql @@ -0,0 +1,4 @@ +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +VALUES (3, now(), now(), 'KAKAO', '54321', null, null, null, false, '재빠른지구002', 'earth.png'); +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +VALUES (4, now(), now(), 'KAKAO', '54321', null, null, null, true, '재빠른지구002', 'earth.png'); From 282876b4f0678372676453adc653939c1b92e0c8 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 18:55:23 +0900 Subject: [PATCH 004/115] =?UTF-8?q?refactor:=20Social=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/spaced/core/account/domain/embed/Social.java | 6 +++--- .../persistence/AccountGatewayRepository.java | 2 +- .../com/dnd/spaced/core/account/domain/AccountTest.java | 2 +- .../dnd/spaced/core/account/domain/embed/SocialTest.java | 2 +- .../core/auth/application/internal/SignUpServiceTest.java | 2 +- space-d/src/test/resources/sql/account/account.sql | 2 +- space-d/src/test/resources/sql/account/deleted_account.sql | 2 +- space-d/src/test/resources/sql/account/pre_init_account.sql | 4 ++-- space-d/src/test/resources/sql/auth/account.sql | 2 +- space-d/src/test/resources/sql/like/account.sql | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java index 305110eb..de556614 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Social.java @@ -16,10 +16,10 @@ public class Social { @Enumerated(EnumType.STRING) private RegistrationId registrationId; - private String socialIdentifier; + private String socialId; - public Social(RegistrationId registrationId, String socialIdentifier) { + public Social(RegistrationId registrationId, String socialId) { this.registrationId = registrationId; - this.socialIdentifier = socialIdentifier; + this.socialId = socialId; } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java index 19c6b9be..fe11dc68 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepository.java @@ -61,7 +61,7 @@ public Optional findPreInitializationAccountBy(Long accountId) { } private BooleanExpression eqSocial(Social social) { - return account.social.socialIdentifier.eq(social.getSocialIdentifier()) + return account.social.socialId.eq(social.getSocialId()) .and(account.deleted.isFalse()) .and(account.social.registrationId.eq(social.getRegistrationId())); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java index 89e17e58..d547ea2b 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java @@ -45,7 +45,7 @@ class AccountTest { assertAll( () -> assertThat(actual.getSocial().getRegistrationId()).isEqualTo(RegistrationId.KAKAO), - () -> assertThat(actual.getSocial().getSocialIdentifier()).isEqualTo("12345"), + () -> assertThat(actual.getSocial().getSocialId()).isEqualTo("12345"), () -> assertThat(actual.getProfile().getNickname()).isEqualTo("재빠른지구001"), () -> assertThat(actual.getProfile().getProfileImage()).isEqualTo("earth.png"), () -> assertThat(actual.getRole()).isEqualTo(Role.ROLE_USER) diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java index daa3fb90..b2f17baf 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/SocialTest.java @@ -22,7 +22,7 @@ class SocialTest { // then assertAll( - () -> assertThat(social.getSocialIdentifier()).isEqualTo("41258"), + () -> assertThat(social.getSocialId()).isEqualTo("41258"), () -> assertThat(social.getRegistrationId()).isEqualTo(registrationId) ); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java index 135e9ffc..779c0263 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java @@ -42,7 +42,7 @@ class SignUpServiceTest { // then assertAll( () -> assertThat(actual.getId()).isEqualTo(1L), - () -> assertThat(actual.getSocial().getSocialIdentifier()).isEqualTo("12345"), + () -> assertThat(actual.getSocial().getSocialId()).isEqualTo("12345"), () -> assertThat(actual.getSocial().getRegistrationId()).isEqualTo(RegistrationId.KAKAO) ); } diff --git a/space-d/src/test/resources/sql/account/account.sql b/space-d/src/test/resources/sql/account/account.sql index bcd093f5..457d5842 100644 --- a/space-d/src/test/resources/sql/account/account.sql +++ b/space-d/src/test/resources/sql/account/account.sql @@ -1,2 +1,2 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) VALUES (1, now(), now(), 'KAKAO', '12345', 'STARTUP', 'UNDER_FIRST', 'ETC', false, '재빠른지구001', 'earth.png'); diff --git a/space-d/src/test/resources/sql/account/deleted_account.sql b/space-d/src/test/resources/sql/account/deleted_account.sql index 021f7e02..4f6dd70b 100644 --- a/space-d/src/test/resources/sql/account/deleted_account.sql +++ b/space-d/src/test/resources/sql/account/deleted_account.sql @@ -1,2 +1,2 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) VALUES (2, now(), now(), 'KAKAO', '54321', 'STARTUP', 'UNDER_FIRST', 'ETC', true, '재빠른지구002', 'earth.png'); diff --git a/space-d/src/test/resources/sql/account/pre_init_account.sql b/space-d/src/test/resources/sql/account/pre_init_account.sql index 1d1886a2..793478ff 100644 --- a/space-d/src/test/resources/sql/account/pre_init_account.sql +++ b/space-d/src/test/resources/sql/account/pre_init_account.sql @@ -1,4 +1,4 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) VALUES (3, now(), now(), 'KAKAO', '54321', null, null, null, false, '재빠른지구002', 'earth.png'); -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) VALUES (4, now(), now(), 'KAKAO', '54321', null, null, null, true, '재빠른지구002', 'earth.png'); diff --git a/space-d/src/test/resources/sql/auth/account.sql b/space-d/src/test/resources/sql/auth/account.sql index 00f51fd6..1d632ab5 100644 --- a/space-d/src/test/resources/sql/auth/account.sql +++ b/space-d/src/test/resources/sql/auth/account.sql @@ -1,2 +1,2 @@ -INSERT INTO accounts(id, created_at, updated_at, deleted, role, registration_id, social_identifier) +INSERT INTO accounts(id, created_at, updated_at, deleted, role, registration_id, social_id) VALUES (1, now(), now(), false, 'ROLE_USER', 'KAKAO', '12345'); diff --git a/space-d/src/test/resources/sql/like/account.sql b/space-d/src/test/resources/sql/like/account.sql index bcd093f5..457d5842 100644 --- a/space-d/src/test/resources/sql/like/account.sql +++ b/space-d/src/test/resources/sql/like/account.sql @@ -1,2 +1,2 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_identifier, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) VALUES (1, now(), now(), 'KAKAO', '12345', 'STARTUP', 'UNDER_FIRST', 'ETC', false, '재빠른지구001', 'earth.png'); From e8c5e49dae9cba63f593c7ff5475b78d4682c68d Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 19:38:15 +0900 Subject: [PATCH 005/115] =?UTF-8?q?refactor:=20=EC=9D=B8=EA=B0=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AccountController.java | 20 +++++------ .../auth/presentation/AuthController.java | 8 ++--- .../presentation/BookmarkController.java | 16 ++++----- .../presentation/CommentController.java | 22 ++++++------ .../like/presentation/LikeController.java | 8 ++--- .../quiz/presentation/QuizController.java | 28 +++++++-------- .../presentation/TodayQuizController.java | 22 ++++++------ .../report/presentation/ReportController.java | 8 ++--- .../skill/presentation/SkillController.java | 8 ++--- .../com/dnd/spaced/global/auth/AccountId.java | 4 +++ .../dnd/spaced/global/auth/AccountInfo.java | 4 --- .../com/dnd/spaced/global/auth/AuthStore.java | 6 ++-- .../auth/interceptor/AuthInterceptor.java | 6 ++-- ....java => AuthAccountArgumentResolver.java} | 36 ++++++++++++------- ...uthAccountInfo.java => AuthAccountId.java} | 2 +- ...ntAccountInfo.java => CurrentAccount.java} | 2 +- ...java => GuestAccountArgumentResolver.java} | 32 +++++++++-------- ...stAccountInfo.java => GuestAccountId.java} | 4 +-- .../dnd/spaced/global/config/AppConfig.java | 12 +++---- .../common/CommonControllerSliceTest.java | 8 ++--- 20 files changed, 134 insertions(+), 122 deletions(-) create mode 100644 space-d/src/main/java/com/dnd/spaced/global/auth/AccountId.java delete mode 100644 space-d/src/main/java/com/dnd/spaced/global/auth/AccountInfo.java rename space-d/src/main/java/com/dnd/spaced/global/auth/resolver/{GuestAccountInfoArgumentResolver.java => AuthAccountArgumentResolver.java} (58%) rename space-d/src/main/java/com/dnd/spaced/global/auth/resolver/{AuthAccountInfo.java => AuthAccountId.java} (50%) rename space-d/src/main/java/com/dnd/spaced/global/auth/resolver/{CurrentAccountInfo.java => CurrentAccount.java} (87%) rename space-d/src/main/java/com/dnd/spaced/global/auth/resolver/{AuthAccountInfoArgumentResolver.java => GuestAccountArgumentResolver.java} (62%) rename space-d/src/main/java/com/dnd/spaced/global/auth/resolver/{GuestAccountInfo.java => GuestAccountId.java} (64%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java index df340a32..319c2966 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java @@ -4,8 +4,8 @@ import com.dnd.spaced.core.account.application.dto.request.ChangeCareerRequest; import com.dnd.spaced.core.account.application.dto.request.ChangeProfileRequest; import com.dnd.spaced.core.account.application.dto.response.AccountResponse; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -25,35 +25,35 @@ public class AccountController { private final AccountService accountService; @DeleteMapping("/withdrawal") - public ResponseEntity withdrawal(@CurrentAccountInfo AuthAccountInfo accountInfo) { - accountService.withdrawal(accountInfo.accountId()); + public ResponseEntity withdrawal(@CurrentAccount AuthAccountId accountId) { + accountService.withdrawal(accountId.id()); return ResponseEntityConst.NO_CONTENT; } @PutMapping("/career-info") public ResponseEntity changeCareerInfo( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ChangeCareerRequest request ) { - accountService.changeCareerInfo(accountInfo.accountId(), request); + accountService.changeCareerInfo(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } @PutMapping("/profile-info") public ResponseEntity changeProfileInfo( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ChangeProfileRequest request ) { - accountService.changeProfileInfo(accountInfo.accountId(), request); + accountService.changeProfileInfo(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } @GetMapping - public ResponseEntity readAccount(@CurrentAccountInfo AuthAccountInfo accountInfo) { - AccountResponse response = accountService.readAccount(accountInfo.accountId()); + public ResponseEntity readAccount(@CurrentAccount AuthAccountId accountId) { + AccountResponse response = accountService.readAccount(accountId.id()); return ResponseEntity.ok(response); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java index cae54097..6ed45bdb 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java @@ -6,8 +6,8 @@ import com.dnd.spaced.core.auth.application.dto.response.TokenDto; import com.dnd.spaced.core.auth.presentation.dto.response.AccessTokenResponse; import com.dnd.spaced.core.auth.presentation.exception.RefreshTokenNotFoundException; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; import com.dnd.spaced.global.config.properties.TokenProperties; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; import jakarta.servlet.http.Cookie; @@ -39,10 +39,10 @@ public class AuthController { @PostMapping("/profile") public ResponseEntity initAccountCareerInfo( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody InitAccountCareerInfoRequest request ) { - initAccountCareerService.initCareer(accountInfo.accountId(), request); + initAccountCareerService.initCareer(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java index 5623527b..7335a117 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java @@ -5,8 +5,8 @@ import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; import com.dnd.spaced.global.resolver.bookmark.BookmarkPageable; import jakarta.validation.Valid; @@ -29,31 +29,31 @@ public class BookmarkController { @GetMapping public ResponseEntity readBookmarks( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, ReadAllBookmarkRequest request, @BookmarkPageable Pageable pageable ) { - BookmarkCollectionResponse response = bookmarkService.readBookmarks(accountInfo.accountId(), request, pageable); + BookmarkCollectionResponse response = bookmarkService.readBookmarks(accountId.id(), request, pageable); return ResponseEntity.ok(response); } @PostMapping public ResponseEntity createBookmark( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody CreateBookmarkRequest request ) { - bookmarkService.createBookmark(accountInfo.accountId(), request); + bookmarkService.createBookmark(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } @DeleteMapping public ResponseEntity deleteBookmark( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody DeleteBookmarkRequest request ) { - bookmarkService.deleteBookmark(accountInfo.accountId(), request); + bookmarkService.deleteBookmark(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java b/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java index f46cf8fd..c29f17a3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java @@ -5,9 +5,9 @@ import com.dnd.spaced.core.comment.application.dto.request.ReadAllCommentRequest; import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.GuestAccountInfo; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.GuestAccountId; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; import com.dnd.spaced.global.resolver.comment.CommentPageable; import jakarta.validation.Valid; @@ -32,11 +32,11 @@ public class CommentController { @PostMapping("/words/{wordId}/comments") public ResponseEntity creteComment( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody CreateCommentRequest request, @PathVariable Long wordId ) { - commentService.createComment(accountInfo.accountId(), wordId, request); + commentService.createComment(accountId.id(), wordId, request); URI location = UriComponentsBuilder.fromPath("/words/{wordId}") .buildAndExpand(wordId) @@ -47,32 +47,32 @@ public ResponseEntity creteComment( } @DeleteMapping("/comments/{commentId}") - public ResponseEntity deleteComment(@CurrentAccountInfo AuthAccountInfo accountInfo, @PathVariable Long commentId) { - commentService.deleteComment(accountInfo.accountId(), commentId); + public ResponseEntity deleteComment(@CurrentAccount AuthAccountId accountId, @PathVariable Long commentId) { + commentService.deleteComment(accountId.id(), commentId); return ResponseEntityConst.NO_CONTENT; } @PutMapping("/comments/{commentId}") public ResponseEntity update( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody UpdateCommentRequest request, @PathVariable Long commentId ) { - commentService.updateComment(accountInfo.accountId(), commentId, request); + commentService.updateComment(accountId.id(), commentId, request); return ResponseEntityConst.NO_CONTENT; } @GetMapping("/words/{wordId}/comments") public ResponseEntity readComments( - @CurrentAccountInfo GuestAccountInfo accountInfo, + @CurrentAccount GuestAccountId accountId, @PathVariable Long wordId, ReadAllCommentRequest request, @CommentPageable Pageable pageable ) { CommentCollectionResponse response = commentService.readComments( - accountInfo.accountId(), + accountId.id(), wordId, request.lastCommentId(), pageable diff --git a/space-d/src/main/java/com/dnd/spaced/core/like/presentation/LikeController.java b/space-d/src/main/java/com/dnd/spaced/core/like/presentation/LikeController.java index 2a70718a..cb511d51 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/like/presentation/LikeController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/like/presentation/LikeController.java @@ -1,8 +1,8 @@ package com.dnd.spaced.core.like.presentation; import com.dnd.spaced.core.like.application.LikeService; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -20,10 +20,10 @@ public class LikeController { @PostMapping public ResponseEntity processLike( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @PathVariable Long commentId ) { - likeService.processLike(accountInfo.accountId(), commentId); + likeService.processLike(accountId.id(), commentId); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java index 801d2fb7..edd0f655 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java @@ -8,8 +8,8 @@ import com.dnd.spaced.core.quiz.application.dto.response.QuizGradedAnswerCollectionResponse; import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse; import com.dnd.spaced.core.quiz.application.dto.response.QuizResponse; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; import com.dnd.spaced.global.resolver.quiz.GradedAnswerPageable; import com.dnd.spaced.global.resolver.quiz.QuizPageable; import jakarta.validation.Valid; @@ -34,10 +34,10 @@ public class QuizController { @PostMapping public ResponseEntity createQuiz( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody CreateQuizRequest request ) { - Long savedQuizId = quizService.createQuiz(accountInfo.accountId(), request); + Long savedQuizId = quizService.createQuiz(accountId.id(), request); URI location = UriComponentsBuilder.fromPath("/quizzes/{quizId}") .buildAndExpand(savedQuizId) .toUri(); @@ -48,11 +48,11 @@ public ResponseEntity createQuiz( @PostMapping("/{quizId}/graded-answers") public ResponseEntity gradeQuiz( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @PathVariable Long quizId, @Valid @RequestBody GradeQuizRequest request ) { - quizService.grade(accountInfo.accountId(), quizId, request); + quizService.grade(accountId.id(), quizId, request); URI location = UriComponentsBuilder.fromPath("/quizzes/{id}/graded-answer") .buildAndExpand(quizId) .toUri(); @@ -63,12 +63,12 @@ public ResponseEntity gradeQuiz( @GetMapping("/graded-answers") public ResponseEntity readQuizGradedAnswers( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, ReadQuizGradedAnswerSearchRequest request, @GradedAnswerPageable Pageable pageable ) { QuizGradedAnswerCollectionResponse response = quizService.readGradedAnswers( - accountInfo.accountId(), + accountId.id(), request, pageable ); @@ -78,31 +78,31 @@ public ResponseEntity readQuizGradedAnswers( @GetMapping("/{quizId}/graded-answers") public ResponseEntity readTargetQuizGradedAnswers( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @PathVariable Long quizId ) { - QuizGradedAnswerCollectionResponse response = quizService.readGradedAnswers(accountInfo.accountId(), quizId); + QuizGradedAnswerCollectionResponse response = quizService.readGradedAnswers(accountId.id(), quizId); return ResponseEntity.ok(response); } @GetMapping("/{quizId}") public ResponseEntity readQuiz( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @PathVariable Long quizId ) { - QuizResponse response = quizService.readQuiz(accountInfo.accountId(), quizId); + QuizResponse response = quizService.readQuiz(accountId.id(), quizId); return ResponseEntity.ok(response); } @GetMapping public ResponseEntity readQuizzes( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, ReadAllQuizRequest request, @QuizPageable Pageable pageable ) { - QuizCollectionResponse response = quizService.readQuizzes(accountInfo.accountId(), request, pageable); + QuizCollectionResponse response = quizService.readQuizzes(accountId.id(), request, pageable); return ResponseEntity.ok(response); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java index 24707731..1b012b8a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java @@ -7,9 +7,9 @@ import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizGradedAnswerCollectionResponse; import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizGradedAnswerResponse; import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.GuestAccountInfo; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.GuestAccountId; import com.dnd.spaced.global.resolver.quiz.GradedAnswerPageable; import jakarta.validation.Valid; import java.net.URI; @@ -38,21 +38,21 @@ public ResponseEntity readLatestTodayQuiz() { @GetMapping("/{todayQuizId}") public ResponseEntity readTodayQuiz( - @CurrentAccountInfo GuestAccountInfo accountInfo, + @CurrentAccount GuestAccountId accountId, @PathVariable Long todayQuizId ) { return ResponseEntity.ok( - todayQuizService.readTodayQuiz(accountInfo.accountId(), todayQuizId) + todayQuizService.readTodayQuiz(accountId.id(), todayQuizId) ); } @PostMapping("/{todayQuizId}/graded-answers") public ResponseEntity gradeTodayQuiz( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @PathVariable Long todayQuizId, @Valid @RequestBody GradeTodayQuizRequest request ) { - todayQuizService.grade(accountInfo.accountId(), todayQuizId, request); + todayQuizService.grade(accountId.id(), todayQuizId, request); URI location = UriComponentsBuilder.fromPath("/today-quizzes/{id}/graded-answers") .buildAndExpand(todayQuizId) .toUri(); @@ -63,20 +63,20 @@ public ResponseEntity gradeTodayQuiz( @GetMapping("/{todayQuizId}/graded-answers") public ResponseEntity readTargetTodayQuizGradedAnswers( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @PathVariable Long todayQuizId ) { - return ResponseEntity.ok(todayQuizService.readTargetTodayQuizGradedAnswers(accountInfo.accountId(), todayQuizId)); + return ResponseEntity.ok(todayQuizService.readTargetTodayQuizGradedAnswers(accountId.id(), todayQuizId)); } @GetMapping("/graded-answers") public ResponseEntity readTodayQuizGradedAnswers( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, ReadTodayQuizGradedAnswerSearchRequest request, @GradedAnswerPageable Pageable pageable ) { return ResponseEntity.ok( - todayQuizService.readTodayQuizGradedAnswers(accountInfo.accountId(), request, pageable) + todayQuizService.readTodayQuizGradedAnswers(accountId.id(), request, pageable) ); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/report/presentation/ReportController.java b/space-d/src/main/java/com/dnd/spaced/core/report/presentation/ReportController.java index 1aeb6275..f28f7c11 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/report/presentation/ReportController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/report/presentation/ReportController.java @@ -2,8 +2,8 @@ import com.dnd.spaced.core.report.application.ReportService; import com.dnd.spaced.core.report.application.dto.request.ReportRequest; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -22,10 +22,10 @@ public class ReportController { @PostMapping public ResponseEntity report( - @CurrentAccountInfo AuthAccountInfo accountInfo, + @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ReportRequest request ) { - reportService.report(accountInfo.accountId(), request); + reportService.report(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/skill/presentation/SkillController.java b/space-d/src/main/java/com/dnd/spaced/core/skill/presentation/SkillController.java index 8c2440dc..4523a5c6 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/skill/presentation/SkillController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/skill/presentation/SkillController.java @@ -2,8 +2,8 @@ import com.dnd.spaced.core.skill.application.SkillService; import com.dnd.spaced.core.skill.application.dto.response.SkillResponse; -import com.dnd.spaced.global.auth.resolver.CurrentAccountInfo; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfo; +import com.dnd.spaced.global.auth.resolver.CurrentAccount; +import com.dnd.spaced.global.auth.resolver.AuthAccountId; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -18,8 +18,8 @@ public class SkillController { private final SkillService skillService; @GetMapping - public ResponseEntity readSkill(@CurrentAccountInfo AuthAccountInfo accountInfo) { - SkillResponse response = skillService.readSkill(accountInfo.accountId()); + public ResponseEntity readSkill(@CurrentAccount AuthAccountId accountId) { + SkillResponse response = skillService.readSkill(accountId.id()); return ResponseEntity.ok(response); } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/AccountId.java b/space-d/src/main/java/com/dnd/spaced/global/auth/AccountId.java new file mode 100644 index 00000000..a9065e19 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/AccountId.java @@ -0,0 +1,4 @@ +package com.dnd.spaced.global.auth; + +public record AccountId(Long id) { +} diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/AccountInfo.java b/space-d/src/main/java/com/dnd/spaced/global/auth/AccountInfo.java deleted file mode 100644 index 6b290a41..00000000 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/AccountInfo.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.dnd.spaced.global.auth; - -public record AccountInfo(Long accountId) { -} diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/AuthStore.java b/space-d/src/main/java/com/dnd/spaced/global/auth/AuthStore.java index 223bc21d..0596b389 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/AuthStore.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/AuthStore.java @@ -5,13 +5,13 @@ @Component public class AuthStore { - private final ThreadLocal threadLocalAuthenticationStore = new ThreadLocal<>(); + private final ThreadLocal threadLocalAuthenticationStore = new ThreadLocal<>(); - public void set(AccountInfo userInfo) { + public void set(AccountId userInfo) { threadLocalAuthenticationStore.set(userInfo); } - public AccountInfo get() { + public AccountId get() { return threadLocalAuthenticationStore.get(); } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/interceptor/AuthInterceptor.java b/space-d/src/main/java/com/dnd/spaced/global/auth/interceptor/AuthInterceptor.java index be7f2142..5fb575b9 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/interceptor/AuthInterceptor.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/interceptor/AuthInterceptor.java @@ -1,6 +1,6 @@ package com.dnd.spaced.global.auth.interceptor; -import com.dnd.spaced.global.auth.AccountInfo; +import com.dnd.spaced.global.auth.AccountId; import com.dnd.spaced.global.auth.AuthStore; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -24,13 +24,13 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons .getAuthentication(); if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { - store.set(new AccountInfo(null)); + store.set(new AccountId(null)); return true; } String id = ((UserDetails) authentication.getPrincipal()).getUsername(); - store.set(new AccountInfo(Long.parseLong(id))); + store.set(new AccountId(Long.parseLong(id))); return true; } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountInfoArgumentResolver.java b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountArgumentResolver.java similarity index 58% rename from space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountInfoArgumentResolver.java rename to space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountArgumentResolver.java index 7325bd54..acf5efb6 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountInfoArgumentResolver.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountArgumentResolver.java @@ -1,7 +1,7 @@ package com.dnd.spaced.global.auth.resolver; import com.dnd.spaced.core.account.domain.repository.AccountRepository; -import com.dnd.spaced.global.auth.AccountInfo; +import com.dnd.spaced.global.auth.AccountId; import com.dnd.spaced.global.auth.AuthStore; import com.dnd.spaced.global.auth.exception.UnauthorizedException; import lombok.RequiredArgsConstructor; @@ -14,15 +14,15 @@ @Component @RequiredArgsConstructor -public class GuestAccountInfoArgumentResolver implements HandlerMethodArgumentResolver { +public class AuthAccountArgumentResolver implements HandlerMethodArgumentResolver { private final AuthStore store; private final AccountRepository accountRepository; @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(CurrentAccountInfo.class) && parameter.getParameterType() - .equals(GuestAccountInfo.class); + return parameter.hasParameterAnnotation(CurrentAccount.class) && parameter.getParameterType() + .equals(AuthAccountId.class); } @Override @@ -32,24 +32,34 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory ) { - AccountInfo accountInfo = store.get(); + AccountId accountId = store.get(); - if (isInvalidAccountPrincipal(accountInfo)) { - return new GuestAccountInfo(); - } + validateAccountPrincipal(accountId); + + Long id = accountId.id(); - validateExistsAccountId(accountInfo.accountId()); + validateExistsAccountId(id); + + return new AuthAccountId(accountId.id()); + } - return new GuestAccountInfo(accountInfo.accountId()); + private void validateAccountPrincipal(AccountId accountId) { + if (isEmptyAccountId(accountId)) { + throw new UnauthorizedException(); + } } - private boolean isInvalidAccountPrincipal(AccountInfo accountInfo) { - return accountInfo == null || accountInfo.accountId() == null; + private boolean isEmptyAccountId(AccountId accountId) { + return accountId == null || accountId.id() == null; } private void validateExistsAccountId(Long accountId) { - if (!accountRepository.existsBy(accountId)) { + if (isBrokenAccount(accountId)) { throw new UnauthorizedException(); } } + + private boolean isBrokenAccount(Long accountId) { + return !accountRepository.existsBy(accountId); + } } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountInfo.java b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountId.java similarity index 50% rename from space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountInfo.java rename to space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountId.java index 807e55a8..e0b61a6e 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountId.java @@ -1,4 +1,4 @@ package com.dnd.spaced.global.auth.resolver; -public record AuthAccountInfo(Long accountId) { +public record AuthAccountId(Long id) { } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/CurrentAccountInfo.java b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/CurrentAccount.java similarity index 87% rename from space-d/src/main/java/com/dnd/spaced/global/auth/resolver/CurrentAccountInfo.java rename to space-d/src/main/java/com/dnd/spaced/global/auth/resolver/CurrentAccount.java index 2e8e48a6..8700ec3b 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/CurrentAccountInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/CurrentAccount.java @@ -7,5 +7,5 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface CurrentAccountInfo { +public @interface CurrentAccount { } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountInfoArgumentResolver.java b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountArgumentResolver.java similarity index 62% rename from space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountInfoArgumentResolver.java rename to space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountArgumentResolver.java index acf9abe0..8e85cc89 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/AuthAccountInfoArgumentResolver.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountArgumentResolver.java @@ -1,7 +1,7 @@ package com.dnd.spaced.global.auth.resolver; import com.dnd.spaced.core.account.domain.repository.AccountRepository; -import com.dnd.spaced.global.auth.AccountInfo; +import com.dnd.spaced.global.auth.AccountId; import com.dnd.spaced.global.auth.AuthStore; import com.dnd.spaced.global.auth.exception.UnauthorizedException; import lombok.RequiredArgsConstructor; @@ -14,15 +14,15 @@ @Component @RequiredArgsConstructor -public class AuthAccountInfoArgumentResolver implements HandlerMethodArgumentResolver { +public class GuestAccountArgumentResolver implements HandlerMethodArgumentResolver { private final AuthStore store; private final AccountRepository accountRepository; @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(CurrentAccountInfo.class) && parameter.getParameterType() - .equals(AuthAccountInfo.class); + return parameter.hasParameterAnnotation(CurrentAccount.class) && parameter.getParameterType() + .equals(GuestAccountId.class); } @Override @@ -32,26 +32,28 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory ) { - AccountInfo accountInfo = store.get(); + AccountId accountId = store.get(); - validateAccountPrincipal(accountInfo); - - Long accountId = accountInfo.accountId(); + if (isEmptyAccountPrincipal(accountId)) { + return new GuestAccountId(); + } - validateExistsAccountId(accountId); + validateExistsAccountId(accountId.id()); - return new AuthAccountInfo(accountInfo.accountId()); + return new GuestAccountId(accountId.id()); } - private void validateAccountPrincipal(AccountInfo accountInfo) { - if (accountInfo == null || accountInfo.accountId() == null) { - throw new UnauthorizedException(); - } + private boolean isEmptyAccountPrincipal(AccountId accountId) { + return accountId == null || accountId.id() == null; } private void validateExistsAccountId(Long accountId) { - if (!accountRepository.existsBy(accountId)) { + if (isBrokenAccount(accountId)) { throw new UnauthorizedException(); } } + + private boolean isBrokenAccount(Long accountId) { + return !accountRepository.existsBy(accountId); + } } diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountInfo.java b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountId.java similarity index 64% rename from space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountInfo.java rename to space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountId.java index 76700a26..eb2e736c 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/resolver/GuestAccountId.java @@ -2,9 +2,9 @@ import com.dnd.spaced.global.consts.AuthConst; -public record GuestAccountInfo(Long accountId) { +public record GuestAccountId(Long id) { - public GuestAccountInfo() { + public GuestAccountId() { this(AuthConst.GUEST_ACCOUNT_ID); } } diff --git a/space-d/src/main/java/com/dnd/spaced/global/config/AppConfig.java b/space-d/src/main/java/com/dnd/spaced/global/config/AppConfig.java index 0d449bce..88b603b5 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/config/AppConfig.java +++ b/space-d/src/main/java/com/dnd/spaced/global/config/AppConfig.java @@ -1,8 +1,8 @@ package com.dnd.spaced.global.config; import com.dnd.spaced.global.auth.interceptor.AuthInterceptor; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfoArgumentResolver; -import com.dnd.spaced.global.auth.resolver.GuestAccountInfoArgumentResolver; +import com.dnd.spaced.global.auth.resolver.AuthAccountArgumentResolver; +import com.dnd.spaced.global.auth.resolver.GuestAccountArgumentResolver; import com.dnd.spaced.global.log.QueryTraceInterceptor; import com.dnd.spaced.global.resolver.admin.report.ReportPageableArgumentResolver; import com.dnd.spaced.global.resolver.bookmark.BookmarkPageableArgumentResolver; @@ -38,8 +38,8 @@ public class AppConfig implements WebMvcConfigurer { private final AuthInterceptor authInterceptor; private final QueryTraceInterceptor queryTraceInterceptor; - private final AuthAccountInfoArgumentResolver authAccountInfoArgumentResolver; - private final GuestAccountInfoArgumentResolver guestAccountInfoArgumentResolver; + private final AuthAccountArgumentResolver authAccountArgumentResolver; + private final GuestAccountArgumentResolver guestAccountArgumentResolver; private final ReportPageableArgumentResolver reportPageableArgumentResolver; private final BookmarkPageableArgumentResolver bookmarkPageableArgumentResolver; private final CommentPageableArgumentResolver commentPageableArgumentResolver; @@ -73,8 +73,8 @@ public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomiz @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(authAccountInfoArgumentResolver); - resolvers.add(guestAccountInfoArgumentResolver); + resolvers.add(authAccountArgumentResolver); + resolvers.add(guestAccountArgumentResolver); resolvers.add(wordPageableArgumentResolver); resolvers.add(commentPageableArgumentResolver); resolvers.add(reportPageableArgumentResolver); diff --git a/space-d/src/test/java/com/dnd/spaced/config/common/CommonControllerSliceTest.java b/space-d/src/test/java/com/dnd/spaced/config/common/CommonControllerSliceTest.java index 3411f42d..851be9ae 100644 --- a/space-d/src/test/java/com/dnd/spaced/config/common/CommonControllerSliceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/config/common/CommonControllerSliceTest.java @@ -8,8 +8,8 @@ import com.dnd.spaced.config.stub.StubAccountRepository; import com.dnd.spaced.global.auth.AuthStore; import com.dnd.spaced.global.auth.interceptor.AuthInterceptor; -import com.dnd.spaced.global.auth.resolver.AuthAccountInfoArgumentResolver; -import com.dnd.spaced.global.auth.resolver.GuestAccountInfoArgumentResolver; +import com.dnd.spaced.global.auth.resolver.AuthAccountArgumentResolver; +import com.dnd.spaced.global.auth.resolver.GuestAccountArgumentResolver; import com.dnd.spaced.global.exception.GlobalControllerAdvice; import com.dnd.spaced.global.resolver.admin.report.ReportPageableArgumentResolver; import com.dnd.spaced.global.resolver.bookmark.BookmarkPageableArgumentResolver; @@ -113,8 +113,8 @@ FixedStandaloneMockMvcBuilder configureMessageConverters() { FixedStandaloneMockMvcBuilder configureArgumentResolvers() { builder.setCustomArgumentResolvers( - new AuthAccountInfoArgumentResolver(store, new StubAccountRepository()), - new GuestAccountInfoArgumentResolver(store, new StubAccountRepository()), + new AuthAccountArgumentResolver(store, new StubAccountRepository()), + new GuestAccountArgumentResolver(store, new StubAccountRepository()), new WordPageableArgumentResolver(), new CommentPageableArgumentResolver(), new GradedAnswerPageableArgumentResolver(), From ae3969991b968991b25d12244f7771e5ed3b6d59 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 20:13:05 +0900 Subject: [PATCH 006/115] =?UTF-8?q?refactor:=20BookmarkGatewayRepository?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/BookmarkGatewayRepository.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java index 2dc863a2..8a69c443 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java @@ -42,7 +42,7 @@ public Optional findBy(Long accountId, Long wordId) { ) .fetchOne(); - if (result == null || result.wordDeleted()) { + if (isDeletedWord(result)) { return Optional.empty(); } @@ -64,7 +64,7 @@ public boolean existsBy(Long accountId, Long wordId) { ) .fetchOne(); - return result != null && !result.wordDeleted; + return isExistBookmark(result); } @Override @@ -95,11 +95,19 @@ public List findAllBy(Long accountId, Long lastBookmarkId, Pageable pa .limit(pageable.getPageSize()) .fetch() .stream() - .filter(BookmarkWithWord::isNotWordDeleted) + .filter(BookmarkWithWord::isValidWord) .map(BookmarkWithWord::bookmark) .toList(); } + private boolean isDeletedWord(BookmarkWithWord result) { + return result == null || result.wordDeleted(); + } + + private boolean isExistBookmark(BookmarkWithWord result) { + return result != null && !result.wordDeleted; + } + private BooleanExpression ltLastBookmarkId(Long lastBookmarkId) { if (lastBookmarkId == null) { return null; @@ -110,7 +118,7 @@ private BooleanExpression ltLastBookmarkId(Long lastBookmarkId) { public record BookmarkWithWord(Bookmark bookmark, boolean wordDeleted) { - public boolean isNotWordDeleted() { + boolean isValidWord() { return !wordDeleted(); } } From 85e4e4382b85146fe3bb08febbc9693ec1864ca4 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 20:14:26 +0900 Subject: [PATCH 007/115] =?UTF-8?q?style:=20BookmarkGatewayRepository=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=8F=AC=EB=A7=B7=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BookmarkGatewayRepository.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java index 8a69c443..bc7ed213 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepository.java @@ -29,11 +29,13 @@ public void save(Bookmark bookmark) { @Override public Optional findBy(Long accountId, Long wordId) { - BookmarkWithWord result = queryFactory.select(Projections.constructor( - BookmarkWithWord.class, - bookmark, - word.deleted - )) + BookmarkWithWord result = queryFactory.select( + Projections.constructor( + BookmarkWithWord.class, + bookmark, + word.deleted + ) + ) .from(bookmark) .leftJoin(word).on(bookmark.wordId.eq(word.id)) .where( @@ -51,11 +53,13 @@ public Optional findBy(Long accountId, Long wordId) { @Override public boolean existsBy(Long accountId, Long wordId) { - BookmarkWithWord result = queryFactory.select(Projections.constructor( - BookmarkWithWord.class, - bookmark, - word.deleted - )) + BookmarkWithWord result = queryFactory.select( + Projections.constructor( + BookmarkWithWord.class, + bookmark, + word.deleted + ) + ) .from(bookmark) .leftJoin(word).on(bookmark.wordId.eq(word.id)) .where( @@ -83,11 +87,13 @@ public void deleteAllBy(Set wordId) { @Override public List findAllBy(Long accountId, Long lastBookmarkId, Pageable pageable) { - return queryFactory.select(Projections.constructor( - BookmarkWithWord.class, - bookmark, - word.deleted - )) + return queryFactory.select( + Projections.constructor( + BookmarkWithWord.class, + bookmark, + word.deleted + ) + ) .from(bookmark) .leftJoin(word).on(word.id.eq(bookmark.wordId)) .where(bookmark.accountId.eq(accountId), ltLastBookmarkId(lastBookmarkId)) From 493403d730641dacf71a9384d4bfd501c0a6527b Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 20:17:13 +0900 Subject: [PATCH 008/115] =?UTF-8?q?refactor:=20Comment=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/spaced/core/comment/application/CommentService.java | 4 ++-- .../java/com/dnd/spaced/core/comment/domain/Comment.java | 4 ++-- .../com/dnd/spaced/core/comment/domain/CommentTest.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java index 31517051..744dd28a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java @@ -67,13 +67,13 @@ private Comment findComment(Long commentId) { } private void validateDeleteAuthority(Comment comment, Long accountId) { - if (comment.isNotWriter(accountId)) { + if (comment.isReader(accountId)) { throw new ForbiddenCommentException("댓글을 삭제할 권한이 없습니다."); } } private void validateUpdateAuthority(Comment comment, Long accountId) { - if (comment.isNotWriter(accountId)) { + if (comment.isReader(accountId)) { throw new ForbiddenCommentException("댓글을 수정할 권한이 없습니다."); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/Comment.java b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/Comment.java index 08749e08..3b9891f9 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/Comment.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/Comment.java @@ -68,11 +68,11 @@ public boolean isWriter(Account account) { return account.isEqualTo(this.writerId); } - public boolean isNotWriter(Account account) { + public boolean isReader(Account account) { return !isWriter(account); } - public boolean isNotWriter(Long accountId) { + public boolean isReader(Long accountId) { return !isWriter(accountId); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java index 44bace1a..879c5639 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java @@ -57,7 +57,7 @@ class CommentTest { Comment comment = new Comment(2L, 1L, "이 용어 언제 쓰는건가요?"); // when - boolean actual = comment.isNotWriter(account); + boolean actual = comment.isReader(account); // then assertThat(actual).isTrue(); @@ -77,7 +77,7 @@ class CommentTest { Comment comment = new Comment(writer.getId(), 1L, "이 용어 언제 쓰는건가요?"); // when - boolean actual = comment.isNotWriter(writer); + boolean actual = comment.isReader(writer); // then assertThat(actual).isFalse(); @@ -101,7 +101,7 @@ class CommentTest { Comment comment = new Comment(1L, 1L, "이 용어 언제 쓰는건가요?"); // when - boolean actual = comment.isNotWriter(1L); + boolean actual = comment.isReader(1L); // then assertThat(actual).isFalse(); From f21295d8521aa24068621353132e2fd624294f8e Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 20:34:00 +0900 Subject: [PATCH 009/115] =?UTF-8?q?refactor:=20CommentRepository=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/CommentRepository.java | 2 -- .../persistence/CommentGatewayRepository.java | 27 +++++++------------ 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java index 47fbd29a..2cdc54b8 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java @@ -14,8 +14,6 @@ public interface CommentRepository { List findAllBy(Long accountId, Long wordId, Long lastCommentId, Pageable pageable); - void delete(Comment comment); - void increaseLikeCount(Long commentId); void decreaseLikeCount(Long commentId); diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java index 194c7681..46953f44 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java @@ -32,7 +32,11 @@ public Comment save(Comment comment) { @Override public Optional findBy(Long commentId) { - return commentCrudRepository.findById(commentId); + Comment result = queryFactory.selectFrom(comment) + .where(comment.id.eq(commentId), comment.deleted.isFalse()) + .fetchOne(); + + return Optional.ofNullable(result); } @Override @@ -44,16 +48,11 @@ public List findAllBy(Long accountId, Long wordId, Long lastCo return findAllWithIsLikedBy(accountId, wordId, lastCommentId, pageable); } - @Override - public void delete(Comment comment) { - commentCrudRepository.delete(comment); - } - @Override public void increaseLikeCount(Long commentId) { queryFactory.update(comment) .set(comment.likeCount, comment.likeCount.add(1)) - .where(comment.id.eq(commentId)) + .where(comment.id.eq(commentId), comment.deleted.isFalse()) .execute(); } @@ -61,7 +60,7 @@ public void increaseLikeCount(Long commentId) { public void decreaseLikeCount(Long commentId) { queryFactory.update(comment) .set(comment.likeCount, comment.likeCount.subtract(1)) - .where(comment.id.eq(commentId)) + .where(comment.id.eq(commentId), comment.deleted.isFalse()) .execute(); } @@ -78,7 +77,7 @@ private List findAllWithIsLikedBy( .where( comment.wordId.eq(wordId), comment.deleted.isFalse(), - calculateLastIdExpression(lastCommentId) + gtLastCommentId(lastCommentId) ) .orderBy(comment.id.asc()) .limit(pageable.getPageSize()) @@ -92,21 +91,13 @@ private List findAllWithoutIsLikedBy(Long wordId, Long lastCom .where( comment.wordId.eq(wordId), comment.deleted.isFalse(), - calculateLastIdExpression(lastCommentId) + gtLastCommentId(lastCommentId) ) .orderBy(comment.id.asc()) .limit(pageable.getPageSize()) .fetch(); } - private BooleanExpression calculateLastIdExpression(Long lastCommentId) { - if (lastCommentId == null) { - return null; - } - - return gtLastCommentId(lastCommentId); - } - private BooleanExpression gtLastCommentId(Long commentId) { if (commentId == null) { return null; From 55576d8f771a734a08df432c90ce9e514c689f25 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 22:56:33 +0900 Subject: [PATCH 010/115] =?UTF-8?q?test:=20CommentGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentGatewayRepositoryTest.java | 171 ++++++++++++++++++ .../resources/sql/comment/deleted_comment.sql | 2 + .../resources/sql/comment/deleted_word.sql | 8 + 3 files changed, 181 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java create mode 100644 space-d/src/test/resources/sql/comment/deleted_comment.sql create mode 100644 space-d/src/test/resources/sql/comment/deleted_word.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java new file mode 100644 index 00000000..9b88b6d3 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java @@ -0,0 +1,171 @@ +package com.dnd.spaced.core.comment.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.comment.domain.Comment; +import com.dnd.spaced.core.comment.domain.dto.LikedCommentInfo; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CommentGatewayRepositoryTest { + + @Autowired + CommentGatewayRepository commentRepository; + + @Autowired + CommentCrudRepository commentCrudRepository; + + @Test + void 댓글을_영속화_한다() { + // given + Comment comment = new Comment(1L, 1L, "이 용어 언제 쓰는건가요?"); + + // when + Comment actual = commentRepository.save(comment); + + // then + assertThat(actual.getId()).isPositive(); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + void 삭제하지_않은_댓글을_댓글_식별자로_조회한다() { + // when + Optional actual = commentRepository.findBy(1L); + + // then + assertThat(actual).isPresent(); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/deleted_comment.sql" + }) + void 삭제한_댓글은_댓글_식별자로_조회할_수_없다() { + // when + Optional actual = commentRepository.findBy(2L); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql", + "classpath:sql/comment/deleted_comment.sql", + "classpath:sql/comment/like.sql", + }) + void 로그인하지_않은_상태로_용어의_삭제하지_않은_모든_댓글을_조회한다() { + // when + List actual = commentRepository.findAllBy(-1L, 1L, null, PageRequest.of(0, 10)); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).comment().getId()).isEqualTo(1L), + () -> assertThat(actual.get(0).isLiked()).isFalse() + ); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql", + "classpath:sql/comment/deleted_comment.sql", + "classpath:sql/comment/like.sql", + }) + void 로그인한_상태로_용어의_삭제하지_않은_모든_댓글을_조회한다() { + // when + List actual = commentRepository.findAllBy(2L, 1L, null, PageRequest.of(0, 10)); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).comment().getId()).isEqualTo(1L), + () -> assertThat(actual.get(0).isLiked()).isTrue() + ); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + @Transactional + void 삭제하지_않은_댓글에_좋아요_카운트를_1_증가시킨다() { + // when + commentRepository.increaseLikeCount(1L); + + // then + Optional actual = commentRepository.findBy(1L); + + assertThat(actual.get().getLikeCount()).isEqualTo(1L); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/deleted_comment.sql" + }) + @Transactional + void 삭제한_댓글에_좋아요_카운트를_증가시킬_수_없다() { + // when + commentRepository.increaseLikeCount(2L); + + // then + Optional actual = commentCrudRepository.findById(2L); + + assertThat(actual.get().getLikeCount()).isZero(); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql", + "classpath:sql/comment/like.sql", + }) + @Transactional + void 삭제하지_않은_댓글에_좋아요_카운트를_1_감소시킨다() { + // when + commentRepository.decreaseLikeCount(1L); + + // then + Optional actual = commentRepository.findBy(1L); + + assertThat(actual.get().getLikeCount()).isZero(); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/deleted_comment.sql" + }) + @Transactional + void 삭제한_댓글에_좋아요_카운트를_감소시킬_수_없다() { + // when + commentRepository.decreaseLikeCount(2L); + + // then + Optional actual = commentCrudRepository.findById(2L); + + assertThat(actual.get().getLikeCount()).isZero(); + } +} diff --git a/space-d/src/test/resources/sql/comment/deleted_comment.sql b/space-d/src/test/resources/sql/comment/deleted_comment.sql new file mode 100644 index 00000000..2ced3f0f --- /dev/null +++ b/space-d/src/test/resources/sql/comment/deleted_comment.sql @@ -0,0 +1,2 @@ +INSERT INTO comments(id, created_at, updated_at, content, deleted, like_count, word_id, writer_id) +VALUES (2, now(), now(), '이 용어 쓰는걸 본 적이 없는거 같아요', true, 0, 1, 1); diff --git a/space-d/src/test/resources/sql/comment/deleted_word.sql b/space-d/src/test/resources/sql/comment/deleted_word.sql new file mode 100644 index 00000000..3b7aca38 --- /dev/null +++ b/space-d/src/test/resources/sql/comment/deleted_word.sql @@ -0,0 +1,8 @@ +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(2, now(), now(), 0, 'DEVELOP', 'HTTP', 0, 'HyperText Transfer Protocol의 약자.', true); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(2, now(), now(), '에이치티티피', 'KOREAN', 2, true); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(2, now(), now(), 'HTTP 통신이 정상적으로 수행되지 않는 것 같습니다.', 2, true); From defe1e576f386d75724a9ab23b0a39fb445171f4 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 6 Jul 2025 23:56:02 +0900 Subject: [PATCH 011/115] =?UTF-8?q?fix:=20Quiz=20=ED=92=80=EC=9D=B4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/spaced/core/quiz/domain/Quiz.java | 9 +++++---- .../com/dnd/spaced/core/quiz/domain/QuizTest.java | 13 +------------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/Quiz.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/Quiz.java index 6964a580..6b9d0fd3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/Quiz.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/Quiz.java @@ -41,12 +41,9 @@ public Quiz(Long accountId) { this.accountId = accountId; } - public void solve() { - this.solved = true; - } - public List grade(Long accountId, List submitAnswers) { validateAnswers(submitAnswers); + solve(); return gradeQuestions(accountId, submitAnswers); } @@ -57,6 +54,10 @@ private void validateAnswers(List submitAnswers) { } } + private void solve() { + this.solved = true; + } + private List gradeQuestions(Long accountId, List submitAnswers) { List quizGradedAnswers = new ArrayList<>(); diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/QuizTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/QuizTest.java index 781519e3..968f8842 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/QuizTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/QuizTest.java @@ -65,6 +65,7 @@ class QuizTest { // then assertAll( + () -> assertThat(quiz.isSolved()).isTrue(), () -> assertThat(actual).hasSize(5), () -> assertThat(actual.get(0).getAccountId()).isEqualTo(1L), () -> assertThat(actual.get(0).getQuizId()).isEqualTo(6L), @@ -109,16 +110,4 @@ class QuizTest { // when & then assertThatThrownBy(() -> quizQuestions.remove(0)).isInstanceOf(UnsupportedOperationException.class); } - - @Test - void 퀴즈를_푼_상태로_변경한다() { - // given - Quiz quiz = new Quiz(1L); - - // when - quiz.solve(); - - // then - assertThat(quiz.isSolved()).isEqualTo(true); - } } From 17be76ea300af4ee15b71ff07a0ff5c75f4e5bfd Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 01:03:09 +0900 Subject: [PATCH 012/115] =?UTF-8?q?refactor:=20QuizService=20CQS=20?= =?UTF-8?q?=EB=B0=8F=20Facade=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminTodayQuizService.java | 16 +- .../quiz/application/CreateQuizService.java | 166 ++++++++++++ .../quiz/application/GradeQuizService.java | 50 ++++ .../core/quiz/application/QuizService.java | 245 ------------------ .../quiz/application/QuizServiceFacade.java | 78 ++++++ .../quiz/application/ReadQuizService.java | 49 ++++ .../mapper/QuizCollectionResponseMapper.java | 10 +- .../dto/mapper/QuizResponseMapper.java | 12 +- .../enums/QuizWordCountValidator.java | 2 +- .../schedule/CreateTodayQuizScheduler.java | 16 +- .../dto/{QuizInfo.java => QuizDto.java} | 10 +- ...SimpleQuizInfo.java => SimpleQuizDto.java} | 6 +- .../domain/dto/mapper/QuizInfoMapper.java | 40 +-- .../domain/repository/QuizRepository.java | 8 +- .../persistence/QuizGatewayRepository.java | 18 +- .../quiz/presentation/QuizController.java | 16 +- .../core/word/domain/dto/SimpleWord.java | 4 + .../core/word/domain/dto/SimpleWordInfo.java | 4 - .../repository/WordRandomRepository.java | 4 +- .../WordRandomGatewayRepository.java | 12 +- .../application/CreateQuizServiceTest.java | 60 +++++ .../application/GradeQuizServiceTest.java | 87 +++++++ ...ceTest.java => QuizServiceFacadeTest.java} | 38 +-- .../quiz/application/ReadQuizServiceTest.java | 140 ++++++++++ .../enums/QuizWordCountValidatorTest.java | 8 +- .../quiz/presentation/QuizControllerTest.java | 26 +- .../listener/GradedQuizEventListenerTest.java | 10 +- .../WordRandomGatewayRepositoryTest.java | 8 +- 28 files changed, 767 insertions(+), 376 deletions(-) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java rename space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/{QuizInfo.java => QuizDto.java} (77%) rename space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/{SimpleQuizInfo.java => SimpleQuizDto.java} (62%) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWord.java delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWordInfo.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java rename space-d/src/test/java/com/dnd/spaced/core/quiz/application/{QuizServiceTest.java => QuizServiceFacadeTest.java} (88%) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java index 3b6c6d33..a4c763e2 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java @@ -13,7 +13,7 @@ import com.dnd.spaced.core.quiz.domain.repository.TodayQuizOptionRepository; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; import com.dnd.spaced.core.word.domain.WordMetadata; -import com.dnd.spaced.core.word.domain.dto.SimpleWordInfo; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; import com.dnd.spaced.global.config.properties.QuizQuestionProperties; @@ -71,13 +71,13 @@ private WordMetadata findWordMetadata() { } private void validateQuizWordCount(QuizCategory quizCategory, WordMetadata wordMetadata) { - if (QuizWordCountValidator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { + if (QuizWordCountValidator.isBlocked(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { throw new InvalidTodayQuizWordCountException("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } } private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { - List randomWords = findRandomWords(quizCategory); + List randomWords = findRandomWords(quizCategory); TodayQuiz todayQuiz = initTodayQuiz(quizCategory, randomWords); TodayQuiz savedTodayQuiz = todayQuizRepository.save(todayQuiz); @@ -86,12 +86,12 @@ private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { return savedTodayQuiz; } - private List findRandomWords(QuizCategory quizCategory) { + private List findRandomWords(QuizCategory quizCategory) { return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_QUIZ_WORD_COUNT); } - private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { - SimpleWordInfo answerWord = randomWords.get(ANSWER_OPTION_INDEX); + private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { + SimpleWord answerWord = randomWords.get(ANSWER_OPTION_INDEX); TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption( answerWord.id(), answerWord.name() @@ -106,12 +106,12 @@ private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List return new TodayQuiz(todayQuizQuestion); } - private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { + private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { Collections.shuffle(randomWords); List todayQuizOptions = new ArrayList<>(); for (int i = 0; i < randomWords.size(); i++) { - SimpleWordInfo word = randomWords.get(i); + SimpleWord word = randomWords.get(i); TodayQuizOption todayQuizOption = TodayQuizOption.of(word.id(), word.name(), i, todayQuiz); todayQuizOptions.add(todayQuizOption); diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java new file mode 100644 index 00000000..3b3d0a10 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java @@ -0,0 +1,166 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; +import com.dnd.spaced.core.quiz.application.enums.QuizWordCountValidator; +import com.dnd.spaced.core.quiz.application.exception.InvalidQuizWordCountException; +import com.dnd.spaced.core.quiz.application.exception.WordMetadataNotFoundException; +import com.dnd.spaced.core.quiz.domain.Quiz; +import com.dnd.spaced.core.quiz.domain.QuizOption; +import com.dnd.spaced.core.quiz.domain.QuizQuestion; +import com.dnd.spaced.core.quiz.domain.embed.QuizAnswerOption; +import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; +import com.dnd.spaced.core.quiz.domain.repository.QuizOptionRepository; +import com.dnd.spaced.core.quiz.domain.repository.QuizQuestionRepository; +import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; +import com.dnd.spaced.core.word.domain.WordMetadata; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; +import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; +import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; +import com.dnd.spaced.global.config.properties.QuizQuestionProperties; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CreateQuizService { + + private static final Long DEFAULT_WORD_METADATA_ID = 1L; + private static final int QUIZ_QUESTION_WORD_COUNT = 5; + private static final int REQUIRED_QUIZ_WORD_COUNT = 20; + private static final int REQUIRED_QUESTION_WORD_COUNT = 4; + private static final int ANSWER_OPTION_INDEX = 0; + + private final QuizRepository quizRepository; + private final QuizQuestionRepository quizQuestionRepository; + private final QuizOptionRepository quizOptionRepository; + private final WordRandomRepository wordRandomRepository; + private final WordMetadataRepository wordMetadataRepository; + private final QuizQuestionProperties quizQuestionProperties; + + Long createQuiz(Long accountId, CreateQuizRequest request) { + QuizCategory quizCategory = findQuizCategory(request); + + validateQuizCreation(quizCategory); + + Quiz quiz = createQuiz(accountId, quizCategory); + + return quiz.getId(); + } + + private QuizCategory findQuizCategory(CreateQuizRequest request) { + return QuizCategory.findBy(request.quizCategoryName()); + } + + private void validateQuizCreation(QuizCategory quizCategory) { + WordMetadata wordMetadata = findWordMetadata(); + + validateQuizMetadata(quizCategory, wordMetadata); + } + + private WordMetadata findWordMetadata() { + return wordMetadataRepository.findBy(DEFAULT_WORD_METADATA_ID) + .orElseThrow(() -> new WordMetadataNotFoundException( + "용어 메타데이터가 정상적으로 설정되지 않았습니다.") + ); + } + + private void validateQuizMetadata(QuizCategory quizCategory, WordMetadata wordMetadata) { + if (QuizWordCountValidator.isBlocked(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { + throw new InvalidQuizWordCountException("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); + } + } + + private Quiz createQuiz(Long accountId, QuizCategory quizCategory) { + Quiz quiz = persistQuiz(accountId); + List> randomSplitWords = findRandomSplitWords(quizCategory); + List quizQuestionIds = persistQuizQuestion(quizCategory, randomSplitWords, quiz); + + persistQuizOptions(randomSplitWords, quizQuestionIds); + return quiz; + } + + private Quiz persistQuiz(Long accountId) { + Quiz quiz = new Quiz(accountId); + + return quizRepository.save(quiz); + } + + private List> findRandomSplitWords(QuizCategory quizCategory) { + List randomWords = findRandomWords(quizCategory); + + return splitByQuestionWordCount(randomWords); + } + + private List findRandomWords(QuizCategory quizCategory) { + return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_QUIZ_WORD_COUNT); + } + + private List> splitByQuestionWordCount(List words) { + return IntStream.range(0, QUIZ_QUESTION_WORD_COUNT) + .mapToObj(index -> index * REQUIRED_QUESTION_WORD_COUNT) + .map(index -> splitByIndex(words, index)) + .toList(); + } + + private List splitByIndex(List words, Integer startIndex) { + int endIndex = Math.min(startIndex + REQUIRED_QUESTION_WORD_COUNT, REQUIRED_QUIZ_WORD_COUNT); + + return words.subList(startIndex, endIndex); + } + + private List persistQuizQuestion( + QuizCategory quizCategory, + List> splitWords, + Quiz savedQuiz + ) { + List quizQuestions = splitWords.stream() + .map(words -> convertQuizQuestion(quizCategory, savedQuiz, words)) + .toList(); + + return quizQuestionRepository.saveAll(quizQuestions); + } + + private QuizQuestion convertQuizQuestion(QuizCategory quizCategory, Quiz savedQuiz, List words) { + SimpleWord answerWord = words.get(ANSWER_OPTION_INDEX); + + return QuizQuestion.of( + quizCategory, + quizQuestionProperties.getQuestion(), + answerWord.meaning(), + new QuizAnswerOption( + answerWord.id(), + answerWord.name() + ), + savedQuiz + ); + } + + private void persistQuizOptions(List> splitWords, List quizQuestionIds) { + List quizOptions = IntStream.range(0, splitWords.size()) + .mapToObj(index -> + convertQuizOptions( + splitWords.get(index), + quizQuestionIds.get(index) + ) + ) + .flatMap(List::stream) + .toList(); + + quizOptionRepository.saveAll(quizOptions); + } + + private List convertQuizOptions(List targetWords, Long targetQuizQuestionId) { + Collections.shuffle(targetWords); + + return IntStream.range(0, targetWords.size()) + .mapToObj(index -> convertQuizOption(targetQuizQuestionId, index, targetWords.get(index))) + .toList(); + } + + private QuizOption convertQuizOption(Long targetQuizQuestionId, int index, SimpleWord word) { + return QuizOption.of(word.id(), word.name(), index, targetQuizQuestionId); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java new file mode 100644 index 00000000..2f2d9ee7 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java @@ -0,0 +1,50 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; +import com.dnd.spaced.core.quiz.application.exception.AlreadyGradeQuizException; +import com.dnd.spaced.core.quiz.application.exception.QuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.Quiz; +import com.dnd.spaced.core.quiz.domain.Quiz.SubmitAnswer; +import com.dnd.spaced.core.quiz.domain.QuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.repository.QuizGradedAnswerRepository; +import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GradeQuizService { + + private final QuizRepository quizRepository; + private final QuizGradedAnswerRepository quizGradedAnswerRepository; + + void gradeQuiz(Long accountId, Long quizId, GradeQuizRequest request) { + Quiz quiz = findQuiz(quizId); + + validateQuiz(quiz); + + convertQuizGradedAnswer(accountId, request, quiz); + } + + private Quiz findQuiz(Long quizId) { + return quizRepository.findBy(quizId) + .orElseThrow(() -> new QuizNotFoundException("지정한 id의 퀴즈를 찾지 못했습니다.")); + } + + private void validateQuiz(Quiz quiz) { + if (quiz.isSolved()) { + throw new AlreadyGradeQuizException("이미 풀었던 퀴즈입니다."); + } + } + + private void convertQuizGradedAnswer(Long accountId, GradeQuizRequest request, Quiz quiz) { + List submitAnswers = Arrays.stream(request.submitAnswers()) + .map(submitAnswer -> new SubmitAnswer(submitAnswer.wordId(), submitAnswer.content())) + .toList(); + List quizGradedAnswers = quiz.grade(accountId, submitAnswers); + + quizGradedAnswerRepository.saveAll(quizGradedAnswers); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizService.java deleted file mode 100644 index 4766bcb6..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizService.java +++ /dev/null @@ -1,245 +0,0 @@ -package com.dnd.spaced.core.quiz.application; - -import com.dnd.spaced.core.quiz.application.dto.mapper.QuizGradedAnswerCollectionResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.mapper.QuizResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.mapper.QuizCollectionResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; -import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; -import com.dnd.spaced.core.quiz.application.dto.request.ReadAllQuizRequest; -import com.dnd.spaced.core.quiz.application.dto.request.ReadQuizGradedAnswerSearchRequest; -import com.dnd.spaced.core.quiz.application.dto.response.QuizGradedAnswerCollectionResponse; -import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse; -import com.dnd.spaced.core.quiz.application.dto.response.QuizResponse; -import com.dnd.spaced.core.quiz.application.enums.QuizWordCountValidator; -import com.dnd.spaced.core.quiz.application.event.dto.AddedQuizQuestionEvent; -import com.dnd.spaced.core.quiz.application.exception.AlreadyGradeQuizException; -import com.dnd.spaced.core.quiz.application.exception.InvalidQuizWordCountException; -import com.dnd.spaced.core.quiz.application.exception.QuizNotFoundException; -import com.dnd.spaced.core.quiz.application.exception.WordMetadataNotFoundException; -import com.dnd.spaced.core.quiz.domain.Quiz; -import com.dnd.spaced.core.quiz.domain.Quiz.SubmitAnswer; -import com.dnd.spaced.core.quiz.domain.QuizGradedAnswer; -import com.dnd.spaced.core.quiz.domain.QuizOption; -import com.dnd.spaced.core.quiz.domain.QuizQuestion; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizInfo; -import com.dnd.spaced.core.quiz.domain.embed.QuizAnswerOption; -import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; -import com.dnd.spaced.core.quiz.domain.repository.QuizGradedAnswerRepository; -import com.dnd.spaced.core.quiz.domain.repository.QuizOptionRepository; -import com.dnd.spaced.core.quiz.domain.repository.QuizQuestionRepository; -import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; -import com.dnd.spaced.core.skill.application.event.dto.GradedQuizEvent; -import com.dnd.spaced.core.word.domain.WordMetadata; -import com.dnd.spaced.core.word.domain.dto.SimpleWordInfo; -import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; -import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; -import com.dnd.spaced.global.config.properties.QuizQuestionProperties; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.IntStream; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class QuizService { - - private static final Long DEFAULT_WORD_METADATA_ID = 1L; - private static final int REQUIRED_QUIZ_WORD_COUNT = 20; - private static final int REQUIRED_QUESTION_WORD_COUNT = 4; - private static final int ANSWER_OPTION_INDEX = 0; - - private final QuizRepository quizRepository; - private final QuizQuestionRepository quizQuestionRepository; - private final QuizOptionRepository quizOptionRepository; - private final WordRandomRepository wordRandomRepository; - private final WordMetadataRepository wordMetadataRepository; - private final QuizGradedAnswerRepository quizGradedAnswerRepository; - private final QuizQuestionProperties quizQuestionProperties; - private final ApplicationEventPublisher eventPublisher; - - @Transactional - public Long createQuiz(Long accountId, CreateQuizRequest request) { - QuizCategory quizCategory = QuizCategory.findBy(request.quizCategoryName()); - - validateQuizCreation(quizCategory); - - Quiz quiz = createQuiz(accountId, quizCategory); - Quiz savedQuiz = quizRepository.save(quiz); - - publishAddedQuizQuestionEvent(); - return savedQuiz.getId(); - } - - @Transactional - public void grade(Long accountId, Long quizId, GradeQuizRequest request) { - Quiz quiz = findQuiz(quizId); - - validateQuiz(quiz); - - List submitAnswers = convertSubmitAnswers(request); - List quizGradedAnswers = quiz.grade(accountId, submitAnswers); - - quizGradedAnswerRepository.saveAll(quizGradedAnswers); - quiz.solve(); - publishGradedQuizEvent(accountId, quizGradedAnswers); - } - - public QuizGradedAnswerCollectionResponse readGradedAnswers( - Long accountId, - ReadQuizGradedAnswerSearchRequest request, - Pageable pageable - ) { - List quizGradedAnswers = quizGradedAnswerRepository.findAllBy( - accountId, - request.lastQuizGradedAnswerId(), - pageable - ); - - return QuizGradedAnswerCollectionResponseMapper.toCollectionDto(quizGradedAnswers); - } - - public QuizGradedAnswerCollectionResponse readGradedAnswers(Long accountId, Long quizId) { - List quizGradedAnswers = quizGradedAnswerRepository.findAllBy(accountId, quizId); - - return QuizGradedAnswerCollectionResponseMapper.toCollectionDto(quizGradedAnswers); - } - - public QuizResponse readQuiz(Long accountId, Long quizId) { - QuizInfo quizInfo = findQuizInfo(quizId, accountId); - - return QuizResponseMapper.toDto(quizInfo); - } - - public QuizCollectionResponse readQuizzes(Long accountId, ReadAllQuizRequest request, Pageable pageable) { - List quizzes = quizRepository.findAllBy(accountId, request.lastQuizId(), pageable); - - return QuizCollectionResponseMapper.toCollectionResponse(quizzes); - } - - private void validateQuiz(Quiz quiz) { - if (quiz.isSolved()) { - throw new AlreadyGradeQuizException("이미 풀었던 퀴즈입니다."); - } - } - - private Quiz findQuiz(Long quizId) { - return quizRepository.findBy(quizId) - .orElseThrow(() -> new QuizNotFoundException("지정한 id의 퀴즈를 찾지 못했습니다.")); - } - - private QuizInfo findQuizInfo(Long quizId, Long accountId) { - return quizRepository.findBy(quizId, accountId) - .orElseThrow(() -> new QuizNotFoundException("지정한 id의 퀴즈를 찾지 못했습니다.")); - } - - private void publishAddedQuizQuestionEvent() { - eventPublisher.publishEvent(new AddedQuizQuestionEvent()); - } - - private void validateQuizCreation(QuizCategory quizCategory) { - WordMetadata wordMetadata = wordMetadataRepository.findBy(DEFAULT_WORD_METADATA_ID) - .orElseThrow(() -> new WordMetadataNotFoundException( - "용어 메타데이터가 정상적으로 설정되지 않았습니다.") - ); - - if (QuizWordCountValidator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { - throw new InvalidQuizWordCountException("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); - } - } - - private Quiz createQuiz(Long accountId, QuizCategory quizCategory) { - Quiz quiz = new Quiz(accountId); - Quiz savedQuiz = quizRepository.save(quiz); - List randomWords = findRandomWords(quizCategory); - List> splitWords = splitByQuestionWordCount(randomWords); - List quizQuestionIds = persistQuizQuestion(quizCategory, splitWords, savedQuiz); - - persistQuizOptions(splitWords, quizQuestionIds); - return quiz; - } - - private List findRandomWords(QuizCategory quizCategory) { - return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_QUIZ_WORD_COUNT); - } - - private List> splitByQuestionWordCount(List words) { - List> result = new ArrayList<>(); - - for (int startIndex = 0; startIndex < REQUIRED_QUIZ_WORD_COUNT; startIndex += REQUIRED_QUESTION_WORD_COUNT) { - int endIndex = Math.min(startIndex + REQUIRED_QUESTION_WORD_COUNT, REQUIRED_QUIZ_WORD_COUNT); - - result.add(words.subList(startIndex, endIndex)); - } - - return result; - } - - private List persistQuizQuestion( - QuizCategory quizCategory, - List> splitWords, - Quiz savedQuiz - ) { - List quizQuestions = splitWords.stream() - .map(words -> { - SimpleWordInfo answerWord = words.get(ANSWER_OPTION_INDEX); - - return QuizQuestion.of( - quizCategory, - quizQuestionProperties.getQuestion(), - answerWord.meaning(), - new QuizAnswerOption( - answerWord.id(), - answerWord.name() - ), - savedQuiz - ); - }) - .toList(); - - return quizQuestionRepository.saveAll(quizQuestions); - } - - private void persistQuizOptions(List> splitWords, List quizQuestionIds) { - List quizOptions = IntStream.range(0, splitWords.size()) - .mapToObj(index -> - convertQuizOptions( - splitWords.get(index), - quizQuestionIds.get(index) - ) - ) - .flatMap(List::stream) - .toList(); - - quizOptionRepository.saveAll(quizOptions); - } - - private List convertQuizOptions(List targetWords, Long targetQuizQuestionId) { - Collections.shuffle(targetWords); - - return IntStream.range(0, targetWords.size()) - .mapToObj(index -> convertQuizOption(targetQuizQuestionId, index, targetWords.get(index)) - ) - .toList(); - } - - private QuizOption convertQuizOption(Long targetQuizQuestionId, int index, SimpleWordInfo word) { - return QuizOption.of(word.id(), word.name(), index, targetQuizQuestionId); - } - - private void publishGradedQuizEvent(Long accountId, List quizGradedAnswers) { - eventPublisher.publishEvent(GradedQuizEvent.of(accountId, quizGradedAnswers)); - } - - private List convertSubmitAnswers(GradeQuizRequest request) { - return Arrays.stream(request.submitAnswers()) - .map(submitAnswer -> new SubmitAnswer(submitAnswer.wordId(), submitAnswer.content())) - .toList(); - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java new file mode 100644 index 00000000..bfce16f7 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java @@ -0,0 +1,78 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.mapper.QuizGradedAnswerCollectionResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.mapper.QuizResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.mapper.QuizCollectionResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.ReadAllQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.ReadQuizGradedAnswerSearchRequest; +import com.dnd.spaced.core.quiz.application.dto.response.QuizGradedAnswerCollectionResponse; +import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse; +import com.dnd.spaced.core.quiz.application.dto.response.QuizResponse; +import com.dnd.spaced.core.quiz.application.event.dto.AddedQuizQuestionEvent; +import com.dnd.spaced.core.quiz.domain.QuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; +import com.dnd.spaced.core.skill.application.event.dto.GradedQuizEvent; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class QuizServiceFacade { + + private final CreateQuizService createQuizService; + private final GradeQuizService gradeQuizService; + private final ReadQuizService readQuizService; + private final ApplicationEventPublisher eventPublisher; + + @Transactional + public Long createQuiz(Long accountId, CreateQuizRequest request) { + Long quizId = createQuizService.createQuiz(accountId, request); + + eventPublisher.publishEvent(new AddedQuizQuestionEvent()); + return quizId; + } + + @Transactional + public void grade(Long accountId, Long quizId, GradeQuizRequest request) { + gradeQuizService.gradeQuiz(accountId, quizId, request); + + List quizGradedAnswers = readQuizService.readGradedAnswers(accountId, quizId); + + eventPublisher.publishEvent(GradedQuizEvent.of(accountId, quizGradedAnswers)); + } + + public QuizGradedAnswerCollectionResponse readGradedAnswers( + Long accountId, + ReadQuizGradedAnswerSearchRequest request, + Pageable pageable + ) { + List quizGradedAnswers = readQuizService.readGradedAnswers(accountId, request, pageable); + + return QuizGradedAnswerCollectionResponseMapper.toCollectionDto(quizGradedAnswers); + } + + public QuizGradedAnswerCollectionResponse readGradedAnswers(Long accountId, Long quizId) { + List quizGradedAnswers = readQuizService.readGradedAnswers(accountId, quizId); + + return QuizGradedAnswerCollectionResponseMapper.toCollectionDto(quizGradedAnswers); + } + + public QuizResponse readQuiz(Long accountId, Long quizId) { + QuizDto quizDto = readQuizService.readQuiz(quizId, accountId); + + return QuizResponseMapper.toDto(quizDto); + } + + public QuizCollectionResponse readQuizzes(Long accountId, ReadAllQuizRequest request, Pageable pageable) { + List quizzes = readQuizService.readQuizzes(accountId, request, pageable); + + return QuizCollectionResponseMapper.toCollectionResponse(quizzes); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java new file mode 100644 index 00000000..418ec1a3 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java @@ -0,0 +1,49 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.request.ReadAllQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.ReadQuizGradedAnswerSearchRequest; +import com.dnd.spaced.core.quiz.application.exception.QuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.QuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; +import com.dnd.spaced.core.quiz.domain.repository.QuizGradedAnswerRepository; +import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReadQuizService { + + private final QuizRepository quizRepository; + private final QuizGradedAnswerRepository quizGradedAnswerRepository; + + List readGradedAnswers( + Long accountId, + ReadQuizGradedAnswerSearchRequest request, + Pageable pageable + ) { + return quizGradedAnswerRepository.findAllBy( + accountId, + request.lastQuizGradedAnswerId(), + pageable + ); + } + + ListreadGradedAnswers(Long accountId, Long quizId) { + return quizGradedAnswerRepository.findAllBy(accountId, quizId); + } + + QuizDto readQuiz(Long accountId, Long quizId) { + return quizRepository.findBy(quizId, accountId) + .orElseThrow( + () -> new QuizNotFoundException("지정한 id의 퀴즈를 찾지 못했습니다.") + ); + } + + List readQuizzes(Long accountId, ReadAllQuizRequest request, Pageable pageable) { + return quizRepository.findAllBy(accountId, request.lastQuizId(), pageable); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java index b80edea9..41d7843b 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java @@ -1,11 +1,11 @@ package com.dnd.spaced.core.quiz.application.dto.mapper; import static com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse.*; -import static com.dnd.spaced.core.quiz.domain.dto.SimpleQuizInfo.*; +import static com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto.*; import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse; import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse.QuizResponse.QuizQuestionResponse; -import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizInfo; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; import java.util.Collections; import java.util.List; import lombok.AccessLevel; @@ -14,7 +14,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class QuizCollectionResponseMapper { - public static QuizCollectionResponse toCollectionResponse(List quizzes) { + public static QuizCollectionResponse toCollectionResponse(List quizzes) { if (quizzes == null || quizzes.isEmpty()) { return new QuizCollectionResponse( Collections.emptyList(), @@ -29,7 +29,7 @@ public static QuizCollectionResponse toCollectionResponse(List q return new QuizCollectionResponse(quizResponses, quizResponses.get(quizResponses.size() - 1).id()); } - private static QuizResponse toQuizResponse(SimpleQuizInfo quiz) { + private static QuizResponse toQuizResponse(SimpleQuizDto quiz) { return new QuizResponse( quiz.id(), quiz.accountId(), @@ -39,7 +39,7 @@ private static QuizResponse toQuizResponse(SimpleQuizInfo quiz) { ); } - private static List toQuizQuestionResponse(List quizQuestions) { + private static List toQuizQuestionResponse(List quizQuestions) { return quizQuestions.stream() .map( quizQuestion -> diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java index 63bffb63..34ed3b7f 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java @@ -3,9 +3,9 @@ import com.dnd.spaced.core.quiz.application.dto.response.QuizResponse; import com.dnd.spaced.core.quiz.application.dto.response.QuizResponse.QuizQuestionResponse; import com.dnd.spaced.core.quiz.application.dto.response.QuizResponse.QuizQuestionResponse.QuizOptionResponse; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo.QuizQuestionInfo; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo.QuizQuestionInfo.QuizOptionInfo; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto.QuizQuestionDto; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto.QuizQuestionDto.QuizOptionDto; import java.util.List; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -13,7 +13,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class QuizResponseMapper { - public static QuizResponse toDto(QuizInfo quiz) { + public static QuizResponse toDto(QuizDto quiz) { List quizQuestionResponses = quiz.quizQuestions() .stream() .map(QuizResponseMapper::toQuizDto) @@ -26,7 +26,7 @@ public static QuizResponse toDto(QuizInfo quiz) { ); } - private static QuizQuestionResponse toQuizDto(QuizQuestionInfo quizQuestion) { + private static QuizQuestionResponse toQuizDto(QuizQuestionDto quizQuestion) { List quizOptionResponses = quizQuestion.quizOptions() .stream() .map(QuizResponseMapper::toQuizOptionDto) @@ -42,7 +42,7 @@ private static QuizQuestionResponse toQuizDto(QuizQuestionInfo quizQuestion) { ); } - private static QuizOptionResponse toQuizOptionDto(QuizOptionInfo quizOption) { + private static QuizOptionResponse toQuizOptionDto(QuizOptionDto quizOption) { return new QuizOptionResponse(quizOption.id(), quizOption.wordId(), quizOption.content()); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java index 9b8942f6..aed08302 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java @@ -30,7 +30,7 @@ public static boolean isValidate(QuizCategory category, WordMetadata wordMetadat .test(wordMetadata, requiredWordCount); } - public static boolean isInvalidate(QuizCategory category, WordMetadata wordMetadata, int requiredWordCount) { + public static boolean isBlocked(QuizCategory category, WordMetadata wordMetadata, int requiredWordCount) { return !isValidate(category, wordMetadata, requiredWordCount); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java index 9587af8d..4d79c751 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java @@ -13,7 +13,7 @@ import com.dnd.spaced.core.quiz.domain.repository.TodayQuizOptionRepository; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; import com.dnd.spaced.core.word.domain.WordMetadata; -import com.dnd.spaced.core.word.domain.dto.SimpleWordInfo; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; import com.dnd.spaced.global.config.properties.QuizQuestionProperties; @@ -58,7 +58,7 @@ public void schedule() { } private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { - List randomWords = findRandomWords(quizCategory); + List randomWords = findRandomWords(quizCategory); TodayQuiz todayQuiz = initTodayQuiz(quizCategory, randomWords); TodayQuiz savedTodayQuiz = todayQuizRepository.save(todayQuiz); @@ -67,12 +67,12 @@ private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { return savedTodayQuiz; } - private List findRandomWords(QuizCategory quizCategory) { + private List findRandomWords(QuizCategory quizCategory) { return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_QUIZ_WORD_COUNT); } - private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { - SimpleWordInfo answerWord = randomWords.get(ANSWER_OPTION_INDEX); + private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { + SimpleWord answerWord = randomWords.get(ANSWER_OPTION_INDEX); TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption( answerWord.id(), answerWord.name() @@ -87,12 +87,12 @@ private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List return new TodayQuiz(todayQuizQuestion); } - private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { + private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { Collections.shuffle(randomWords); List todayQuizOptions = new ArrayList<>(); for (int i = 0; i < randomWords.size(); i++) { - SimpleWordInfo word = randomWords.get(i); + SimpleWord word = randomWords.get(i); TodayQuizOption todayQuizOption = TodayQuizOption.of(word.id(), word.name(), i, todayQuiz); todayQuizOptions.add(todayQuizOption); @@ -111,7 +111,7 @@ private void validateQuizCreation(QuizCategory quizCategory) { "용어 메타데이터가 정상적으로 설정되지 않았습니다.") ); - if (QuizWordCountValidator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { + if (QuizWordCountValidator.isBlocked(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { throw new InvalidTodayQuizWordCountException("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/QuizInfo.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/QuizDto.java similarity index 77% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/QuizInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/QuizDto.java index 56779a59..b7061de3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/QuizInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/QuizDto.java @@ -5,24 +5,24 @@ import java.time.LocalDateTime; import java.util.List; -public record QuizInfo( +public record QuizDto( Long id, Long accountId, boolean solved, LocalDateTime createdAt, - List quizQuestions + List quizQuestions ) { - public record QuizQuestionInfo( + public record QuizQuestionDto( Long id, QuizCategory quizCategory, QuizAnswerOption quizAnswerOption, String questionContent, String questionExample, - List quizOptions + List quizOptions ) { - public record QuizOptionInfo( + public record QuizOptionDto( Long id, Long wordId, String content, diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleQuizInfo.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleQuizDto.java similarity index 62% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleQuizInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleQuizDto.java index 6afbf1c7..0d88063c 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleQuizInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleQuizDto.java @@ -4,14 +4,14 @@ import java.time.LocalDateTime; import java.util.List; -public record SimpleQuizInfo( +public record SimpleQuizDto( Long id, Long accountId, boolean solved, LocalDateTime createdAt, - List quizQuestions + List quizQuestions ) { - public record QuizQuestionInfo(QuizCategory quizCategory, String questionExample) { + public record QuizQuestionDto(QuizCategory quizCategory, String questionExample) { } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java index 787806c2..5c8d0f07 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java @@ -3,9 +3,9 @@ import com.dnd.spaced.core.quiz.domain.Quiz; import com.dnd.spaced.core.quiz.domain.QuizOption; import com.dnd.spaced.core.quiz.domain.QuizQuestion; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo.QuizQuestionInfo; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo.QuizQuestionInfo.QuizOptionInfo; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto.QuizQuestionDto; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto.QuizQuestionDto.QuizOptionDto; import java.util.Collections; import java.util.List; import java.util.Map; @@ -15,8 +15,8 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class QuizInfoMapper { - public static QuizInfo toDto(Quiz quiz) { - return new QuizInfo( + public static QuizDto toDto(Quiz quiz) { + return new QuizDto( quiz.getId(), quiz.getAccountId(), quiz.isSolved(), @@ -25,13 +25,13 @@ public static QuizInfo toDto(Quiz quiz) { ); } - public static QuizInfo toDto(Quiz quiz, Map> quizOptionMap) { - List quizQuestions = quiz.getQuizQuestions() - .stream() - .map(quizQuestion -> toQuizQuestionDto(quizQuestion, quizOptionMap.get(quizQuestion.getId()))) - .toList(); + public static QuizDto toDto(Quiz quiz, Map> quizOptionMap) { + List quizQuestions = quiz.getQuizQuestions() + .stream() + .map(quizQuestion -> toQuizQuestionDto(quizQuestion, quizOptionMap.get(quizQuestion.getId()))) + .toList(); - return new QuizInfo( + return new QuizDto( quiz.getId(), quiz.getAccountId(), quiz.isSolved(), @@ -40,9 +40,9 @@ public static QuizInfo toDto(Quiz quiz, Map> quizOptionMa ); } - private static QuizQuestionInfo toQuizQuestionDto(QuizQuestion quizQuestion, List quizOptions) { + private static QuizQuestionDto toQuizQuestionDto(QuizQuestion quizQuestion, List quizOptions) { if (quizOptions == null) { - return new QuizQuestionInfo( + return new QuizQuestionDto( quizQuestion.getId(), quizQuestion.getQuizCategory(), quizQuestion.getQuizAnswerOption(), @@ -51,22 +51,22 @@ private static QuizQuestionInfo toQuizQuestionDto(QuizQuestion quizQuestion, Lis Collections.emptyList() ); } - List quizOptionInfos = quizOptions.stream() - .map(QuizInfoMapper::toQuizOptionDto) - .toList(); + List quizOptionDtos = quizOptions.stream() + .map(QuizInfoMapper::toQuizOptionDto) + .toList(); - return new QuizQuestionInfo( + return new QuizQuestionDto( quizQuestion.getId(), quizQuestion.getQuizCategory(), quizQuestion.getQuizAnswerOption(), quizQuestion.getQuestion(), quizQuestion.getPassage(), - quizOptionInfos + quizOptionDtos ); } - private static QuizOptionInfo toQuizOptionDto(QuizOption quizOption) { - return new QuizOptionInfo( + private static QuizOptionDto toQuizOptionDto(QuizOption quizOption) { + return new QuizOptionDto( quizOption.getId(), quizOption.getWordId(), quizOption.getContent(), diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/QuizRepository.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/QuizRepository.java index f42cb57c..66f8b8b7 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/QuizRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/QuizRepository.java @@ -1,8 +1,8 @@ package com.dnd.spaced.core.quiz.domain.repository; import com.dnd.spaced.core.quiz.domain.Quiz; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizInfo; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Pageable; @@ -15,7 +15,7 @@ public interface QuizRepository { Optional findBy(Long quizId); - Optional findBy(Long quizId, Long accountId); + Optional findBy(Long quizId, Long accountId); - List findAllBy(Long accountId, Long lastQuizId, Pageable pageable); + List findAllBy(Long accountId, Long lastQuizId, Pageable pageable); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java index 6b135657..5586730f 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java @@ -6,9 +6,9 @@ import com.dnd.spaced.core.quiz.domain.Quiz; import com.dnd.spaced.core.quiz.domain.QuizOption; import com.dnd.spaced.core.quiz.domain.QuizQuestion; -import com.dnd.spaced.core.quiz.domain.dto.QuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizInfo.QuizQuestionInfo; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto.QuizQuestionDto; import com.dnd.spaced.core.quiz.domain.dto.mapper.QuizInfoMapper; import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; @@ -77,7 +77,7 @@ public Optional findBy(Long quizId) { } @Override - public Optional findBy(Long quizId, Long accountId) { + public Optional findBy(Long quizId, Long accountId) { Quiz result = findQuizFetchJoinWithQuizQuestions(quizId, accountId); if (result == null) { @@ -86,13 +86,13 @@ public Optional findBy(Long quizId, Long accountId) { List quizQuestionId = findQuizQuestionIds(result); Map> quizOptionMap = findQuizOptions(quizQuestionId); - QuizInfo quizInfo = QuizInfoMapper.toDto(result, quizOptionMap); + QuizDto quizDto = QuizInfoMapper.toDto(result, quizOptionMap); - return Optional.of(quizInfo); + return Optional.of(quizDto); } @Override - public List findAllBy(Long accountId, Long lastQuizId, Pageable pageable) { + public List findAllBy(Long accountId, Long lastQuizId, Pageable pageable) { String sql = calculateFindAllSql(lastQuizId); MapSqlParameterSource sqlParameters = calculateSqlParameters(accountId, lastQuizId, pageable); @@ -106,7 +106,7 @@ public List findAllBy(Long accountId, Long lastQuizId, Pageable simpleQuizValue.createdAt ), Collectors.mapping( - simpleQuizValue -> new QuizQuestionInfo( + simpleQuizValue -> new QuizQuestionDto( simpleQuizValue.quizCategory, simpleQuizValue.questionContent ), @@ -115,7 +115,7 @@ public List findAllBy(Long accountId, Long lastQuizId, Pageable )) .entrySet() .stream() - .map(entry -> new SimpleQuizInfo( + .map(entry -> new SimpleQuizDto( entry.getKey().id, entry.getKey().accountId, entry.getKey().solved, diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java index edd0f655..ae35cd08 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/QuizController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.quiz.presentation; -import com.dnd.spaced.core.quiz.application.QuizService; +import com.dnd.spaced.core.quiz.application.QuizServiceFacade; import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.ReadAllQuizRequest; @@ -30,14 +30,14 @@ @RequiredArgsConstructor public class QuizController { - private final QuizService quizService; + private final QuizServiceFacade quizServiceFacade; @PostMapping public ResponseEntity createQuiz( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody CreateQuizRequest request ) { - Long savedQuizId = quizService.createQuiz(accountId.id(), request); + Long savedQuizId = quizServiceFacade.createQuiz(accountId.id(), request); URI location = UriComponentsBuilder.fromPath("/quizzes/{quizId}") .buildAndExpand(savedQuizId) .toUri(); @@ -52,7 +52,7 @@ public ResponseEntity gradeQuiz( @PathVariable Long quizId, @Valid @RequestBody GradeQuizRequest request ) { - quizService.grade(accountId.id(), quizId, request); + quizServiceFacade.grade(accountId.id(), quizId, request); URI location = UriComponentsBuilder.fromPath("/quizzes/{id}/graded-answer") .buildAndExpand(quizId) .toUri(); @@ -67,7 +67,7 @@ public ResponseEntity readQuizGradedAnswers( ReadQuizGradedAnswerSearchRequest request, @GradedAnswerPageable Pageable pageable ) { - QuizGradedAnswerCollectionResponse response = quizService.readGradedAnswers( + QuizGradedAnswerCollectionResponse response = quizServiceFacade.readGradedAnswers( accountId.id(), request, pageable @@ -81,7 +81,7 @@ public ResponseEntity readTargetQuizGradedAn @CurrentAccount AuthAccountId accountId, @PathVariable Long quizId ) { - QuizGradedAnswerCollectionResponse response = quizService.readGradedAnswers(accountId.id(), quizId); + QuizGradedAnswerCollectionResponse response = quizServiceFacade.readGradedAnswers(accountId.id(), quizId); return ResponseEntity.ok(response); } @@ -91,7 +91,7 @@ public ResponseEntity readQuiz( @CurrentAccount AuthAccountId accountId, @PathVariable Long quizId ) { - QuizResponse response = quizService.readQuiz(accountId.id(), quizId); + QuizResponse response = quizServiceFacade.readQuiz(accountId.id(), quizId); return ResponseEntity.ok(response); } @@ -102,7 +102,7 @@ public ResponseEntity readQuizzes( ReadAllQuizRequest request, @QuizPageable Pageable pageable ) { - QuizCollectionResponse response = quizService.readQuizzes(accountId.id(), request, pageable); + QuizCollectionResponse response = quizServiceFacade.readQuizzes(accountId.id(), request, pageable); return ResponseEntity.ok(response); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWord.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWord.java new file mode 100644 index 00000000..da84ea73 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWord.java @@ -0,0 +1,4 @@ +package com.dnd.spaced.core.word.domain.dto; + +public record SimpleWord(Long id, String categoryName, String name, String meaning) { +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWordInfo.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWordInfo.java deleted file mode 100644 index 4848fe6d..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/SimpleWordInfo.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.dnd.spaced.core.word.domain.dto; - -public record SimpleWordInfo(Long id, String categoryName, String name, String meaning) { -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRandomRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRandomRepository.java index dd208b0b..6297e6fc 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRandomRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRandomRepository.java @@ -2,7 +2,7 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.word.domain.Word; -import com.dnd.spaced.core.word.domain.dto.SimpleWordInfo; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.enums.Category; import java.util.List; @@ -10,5 +10,5 @@ public interface WordRandomRepository { void saveWith(Word word, Category category); - List findRandomAllBy(QuizCategory quizCategory, long limit); + List findRandomAllBy(QuizCategory quizCategory, long limit); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java index 1c5ee46a..bfe979e0 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java @@ -3,7 +3,7 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.word.domain.Word; import com.dnd.spaced.core.word.domain.WordRandom; -import com.dnd.spaced.core.word.domain.dto.SimpleWordInfo; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.enums.Category; import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; import java.util.List; @@ -18,7 +18,7 @@ public class WordRandomGatewayRepository implements WordRandomRepository { private static final int RANDOM_BOUND = 1_000_000; - private static final RowMapper simpleWordInfoRowMapper = (rs, ignoreRowNum) -> new SimpleWordInfo( + private static final RowMapper simpleWordInfoRowMapper = (rs, ignoreRowNum) -> new SimpleWord( rs.getLong(1), rs.getString(2), rs.getString(3), @@ -45,10 +45,10 @@ public void saveWith(Word word, Category category) { } @Override - public List findRandomAllBy(QuizCategory quizCategory, long limit) { + public List findRandomAllBy(QuizCategory quizCategory, long limit) { int random = ThreadLocalRandom.current().nextInt(RANDOM_BOUND); - List result = findGoe(random, quizCategory, limit); + List result = findGoe(random, quizCategory, limit); if (result.size() < limit) { result.addAll(findLoe(random, quizCategory, limit)); @@ -57,7 +57,7 @@ public List findRandomAllBy(QuizCategory quizCategory, long limi return result.subList(0, (int) (limit)); } - private List findGoe(int random, QuizCategory quizCategory, long limit) { + private List findGoe(int random, QuizCategory quizCategory, long limit) { String sql = """ SELECT w.id, @@ -90,7 +90,7 @@ private List findGoe(int random, QuizCategory quizCategory, long return namedParameterJdbcTemplate.query(sql, sqlParameters, simpleWordInfoRowMapper); } - private List findLoe(int random, QuizCategory quizCategory, long limit) { + private List findLoe(int random, QuizCategory quizCategory, long limit) { String sql = """ SELECT w.id, diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java new file mode 100644 index 00000000..a5ad4a05 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java @@ -0,0 +1,60 @@ +package com.dnd.spaced.core.quiz.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; +import com.dnd.spaced.core.quiz.application.exception.InvalidQuizWordCountException; +import com.dnd.spaced.core.quiz.application.exception.WordMetadataNotFoundException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CreateQuizServiceTest { + + @Autowired + CreateQuizService createQuizService; + + @Test + void 용어_메타데이터가_정상적으로_설정되지_않다면_퀴즈를_생성할_수_없다() { + // given + CreateQuizRequest request = new CreateQuizRequest("전체 실무"); + + // when & then + assertThatThrownBy(() -> createQuizService.createQuiz(1L, request)) + .isInstanceOf(WordMetadataNotFoundException.class) + .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); + } + + @Test + @Sql("classpath:sql/quiz/word_metadata.sql") + void 등록된_용어_수가_퀴즈_생성_시_필요한_용어_수보다_적으면_퀴즈를_생성할_수_없다() { + // given + CreateQuizRequest request = new CreateQuizRequest("전체 실무"); + + // when & then + assertThatThrownBy(() -> createQuizService.createQuiz(1L, request)) + .isInstanceOf(InvalidQuizWordCountException.class) + .hasMessage("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); + } + + @Test + @Sql(scripts = {"classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/word.sql"}) + void 퀴즈를_생성한다() { + // given + CreateQuizRequest request = new CreateQuizRequest("전체 실무"); + + // when + Long actual = createQuizService.createQuiz(1L, request); + + // then + assertThat(actual).isPositive(); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java new file mode 100644 index 00000000..510fedcb --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java @@ -0,0 +1,87 @@ +package com.dnd.spaced.core.quiz.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest.SubmitAnswerRequest; +import com.dnd.spaced.core.quiz.application.exception.AlreadyGradeQuizException; +import com.dnd.spaced.core.quiz.application.exception.QuizNotFoundException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class GradeQuizServiceTest { + + @Autowired + GradeQuizService gradeQuizService; + + @Test + void 유효하지_않는_퀴즈_id로_퀴즈_답을_제출할_수_없다() { + // given + SubmitAnswerRequest[] submitAnswers = { + new SubmitAnswerRequest(1L, "Authorization"), + new SubmitAnswerRequest(2L, "Domain"), + new SubmitAnswerRequest(3L, "Controller"), + new SubmitAnswerRequest(2L, "Web"), + new SubmitAnswerRequest(1L, "HTTP") + }; + GradeQuizRequest request = new GradeQuizRequest(submitAnswers); + + // when & then + assertThatThrownBy(() -> gradeQuizService.gradeQuiz(1L, -999L, request)) + .isInstanceOf(QuizNotFoundException.class) + .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/quiz.sql" + }) + void 퀴즈_정답을_제출한다() { + // given + SubmitAnswerRequest[] submitAnswers = { + new SubmitAnswerRequest(1L, "Authorization"), + new SubmitAnswerRequest(2L, "Domain"), + new SubmitAnswerRequest(3L, "Controller"), + new SubmitAnswerRequest(2L, "Web"), + new SubmitAnswerRequest(1L, "HTTP") + }; + GradeQuizRequest request = new GradeQuizRequest(submitAnswers); + + // when & then + assertDoesNotThrow(() -> gradeQuizService.gradeQuiz(1L, 1L, request)); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/solved_quiz.sql" + }) + void 이미_푼_퀴즈인_경우_정답을_제출할_수_없다() { + // given + SubmitAnswerRequest[] submitAnswers = { + new SubmitAnswerRequest(1L, "Authorization"), + new SubmitAnswerRequest(2L, "Domain"), + new SubmitAnswerRequest(3L, "Controller"), + new SubmitAnswerRequest(2L, "Web"), + new SubmitAnswerRequest(1L, "HTTP") + }; + GradeQuizRequest request = new GradeQuizRequest(submitAnswers); + + // when & then + assertThatThrownBy(() -> gradeQuizService.gradeQuiz(1L, 1L, request)) + .isInstanceOf(AlreadyGradeQuizException.class) + .hasMessage("이미 풀었던 퀴즈입니다."); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java similarity index 88% rename from space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java index ba2f9274..6debb34f 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java @@ -34,13 +34,13 @@ @RecordApplicationEvents @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class QuizServiceTest { +class QuizServiceFacadeTest { @Autowired ApplicationEvents events; @Autowired - QuizService quizService; + QuizServiceFacade quizServiceFacade; @Test void 용어_메타데이터가_정상적으로_설정되지_않다면_퀴즈를_생성할_수_없다() { @@ -48,7 +48,7 @@ class QuizServiceTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when & then - assertThatThrownBy(() -> quizService.createQuiz(1L, request)) + assertThatThrownBy(() -> quizServiceFacade.createQuiz(1L, request)) .isInstanceOf(WordMetadataNotFoundException.class) .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); } @@ -60,7 +60,7 @@ class QuizServiceTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when & then - assertThatThrownBy(() -> quizService.createQuiz(1L, request)) + assertThatThrownBy(() -> quizServiceFacade.createQuiz(1L, request)) .isInstanceOf(InvalidQuizWordCountException.class) .hasMessage("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } @@ -73,7 +73,7 @@ class QuizServiceTest { }) void 퀴즈를_조회한다() { // when - QuizResponse actual = quizService.readQuiz(1L, 1L); + QuizResponse actual = quizServiceFacade.readQuiz(1L, 1L); // then assertAll( @@ -89,13 +89,16 @@ class QuizServiceTest { } @Test - @Sql(scripts = {"classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/word.sql"}) + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql" + }) void 퀴즈를_생성한다() { // given CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when - Long actual = quizService.createQuiz(1L, request); + Long actual = quizServiceFacade.createQuiz(1L, request); // then assertAll( @@ -107,16 +110,19 @@ class QuizServiceTest { @Test void 유효하지_않는_퀴즈_id로_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> quizService.readQuiz(1L, -999L)) + assertThatThrownBy(() -> quizServiceFacade.readQuiz(1L, -999L)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @Test - @Sql(scripts = {"classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/quiz.sql"}) + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/quiz.sql" + }) void 회원이_생성한_퀴즈가_아니라면_존재하는_퀴즈_id더라도_퀴즈_정보를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> quizService.readQuiz(5L, 1L)) + assertThatThrownBy(() -> quizServiceFacade.readQuiz(5L, 1L)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @@ -134,7 +140,7 @@ class QuizServiceTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertThatThrownBy(() -> quizService.grade(1L, -999L, request)) + assertThatThrownBy(() -> quizServiceFacade.grade(1L, -999L, request)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @@ -157,7 +163,7 @@ class QuizServiceTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertDoesNotThrow(() -> quizService.grade(1L, 1L, request)); + assertDoesNotThrow(() -> quizServiceFacade.grade(1L, 1L, request)); } @Test @@ -178,7 +184,7 @@ class QuizServiceTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertThatThrownBy(() -> quizService.grade(1L, 1L, request)) + assertThatThrownBy(() -> quizServiceFacade.grade(1L, 1L, request)) .isInstanceOf(AlreadyGradeQuizException.class) .hasMessage("이미 풀었던 퀴즈입니다."); } @@ -195,7 +201,7 @@ class QuizServiceTest { ReadQuizGradedAnswerSearchRequest request = new ReadQuizGradedAnswerSearchRequest(null); // when - QuizGradedAnswerCollectionResponse actual = quizService.readGradedAnswers( + QuizGradedAnswerCollectionResponse actual = quizServiceFacade.readGradedAnswers( 1L, request, PageRequest.of(0, 10) ); @@ -219,7 +225,7 @@ class QuizServiceTest { }) void 특정_퀴즈의_제출했던_답을_조회한다() { // when - QuizGradedAnswerCollectionResponse actual = quizService.readGradedAnswers(1L, 1L); + QuizGradedAnswerCollectionResponse actual = quizServiceFacade.readGradedAnswers(1L, 1L); // then assertAll( @@ -243,7 +249,7 @@ class QuizServiceTest { ReadAllQuizRequest request = new ReadAllQuizRequest(null); // when - QuizCollectionResponse actual = quizService.readQuizzes(1L, request, Pageable.ofSize(10)); + QuizCollectionResponse actual = quizServiceFacade.readQuizzes(1L, request, Pageable.ofSize(10)); // then assertAll( diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java new file mode 100644 index 00000000..8d496699 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java @@ -0,0 +1,140 @@ +package com.dnd.spaced.core.quiz.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.quiz.application.dto.request.ReadAllQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.ReadQuizGradedAnswerSearchRequest; +import com.dnd.spaced.core.quiz.application.exception.QuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.QuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.dto.QuizDto; +import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ReadQuizServiceTest { + + @Autowired + ReadQuizService readQuizService; + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/quiz.sql" + }) + void 퀴즈를_조회한다() { + // when + QuizDto actual = readQuizService.readQuiz(1L, 1L); + + // then + assertAll( + () -> assertThat(actual.id()).isEqualTo(1L), + () -> assertThat(actual.accountId()).isEqualTo(1L), + () -> assertThat(actual.quizQuestions()).hasSize(5), + () -> assertThat(actual.quizQuestions().get(0).quizOptions()).hasSize(4), + () -> assertThat(actual.quizQuestions().get(1).quizOptions()).hasSize(4), + () -> assertThat(actual.quizQuestions().get(2).quizOptions()).hasSize(4), + () -> assertThat(actual.quizQuestions().get(3).quizOptions()).hasSize(4), + () -> assertThat(actual.quizQuestions().get(4).quizOptions()).hasSize(4) + ); + } + + @Test + void 유효하지_않는_퀴즈_id로_퀴즈를_조회할_수_없다() { + // when & then + assertThatThrownBy(() -> readQuizService.readQuiz(1L, -999L)) + .isInstanceOf(QuizNotFoundException.class) + .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); + } + + @Test + @Sql(scripts = {"classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/quiz.sql"}) + void 회원이_생성한_퀴즈가_아니라면_존재하는_퀴즈_id더라도_퀴즈_정보를_조회할_수_없다() { + // when & then + assertThatThrownBy(() -> readQuizService.readQuiz(5L, 1L)) + .isInstanceOf(QuizNotFoundException.class) + .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/quiz.sql", + "classpath:sql/quiz/quiz_graded_answer.sql" + }) + void 모든_퀴즈의_제출했던_답을_조회한다() { + // given + ReadQuizGradedAnswerSearchRequest request = new ReadQuizGradedAnswerSearchRequest(null); + + // when + List actual = readQuizService.readGradedAnswers( + 1L, request, PageRequest.of(0, 10) + ); + + // then + assertAll( + () -> assertThat(actual).hasSize(5), + () -> assertThat(actual.get(0).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(1).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(2).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(3).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(4).getSelectedContent()).isNotBlank() + ); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/quiz.sql", + "classpath:sql/quiz/quiz_graded_answer.sql" + }) + void 특정_퀴즈의_제출했던_답을_조회한다() { + // when + List actual = readQuizService.readGradedAnswers(1L, 1L); + + // then + assertAll( + () -> assertThat(actual).hasSize(5), + () -> assertThat(actual.get(0).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(1).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(2).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(3).getSelectedContent()).isNotBlank(), + () -> assertThat(actual.get(4).getSelectedContent()).isNotBlank() + ); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/quiz.sql" + }) + void 회원이_생성한_퀴즈_목록을_조회한다() { + // given + ReadAllQuizRequest request = new ReadAllQuizRequest(null); + + // when + List actual = readQuizService.readQuizzes(1L, request, Pageable.ofSize(10)); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).id()).isEqualTo(1L) + ); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java index ac647d77..4ecb3fe8 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java @@ -34,7 +34,7 @@ class QuizWordCountValidatorTest { WordMetadata wordMetadata = new WordMetadata(); // when - boolean actual = QuizWordCountValidator.isInvalidate(QuizCategory.DESIGN, wordMetadata, 5); + boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.DESIGN, wordMetadata, 5); // then assertThat(actual).isTrue(); @@ -58,7 +58,7 @@ class QuizWordCountValidatorTest { WordMetadata wordMetadata = new WordMetadata(); // when - boolean actual = QuizWordCountValidator.isInvalidate(QuizCategory.BUSINESS, wordMetadata, 5); + boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.BUSINESS, wordMetadata, 5); // then assertThat(actual).isTrue(); @@ -82,7 +82,7 @@ class QuizWordCountValidatorTest { WordMetadata wordMetadata = new WordMetadata(); // when - boolean actual = QuizWordCountValidator.isInvalidate(QuizCategory.DEVELOP, wordMetadata, 5); + boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.DEVELOP, wordMetadata, 5); // then assertThat(actual).isTrue(); @@ -106,7 +106,7 @@ class QuizWordCountValidatorTest { WordMetadata wordMetadata = new WordMetadata(); // when - boolean actual = QuizWordCountValidator.isInvalidate(QuizCategory.TOTAL, wordMetadata, 5); + boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.TOTAL, wordMetadata, 5); // then assertThat(actual).isTrue(); diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/QuizControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/QuizControllerTest.java index 37320dbb..dbd4dc0b 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/QuizControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/QuizControllerTest.java @@ -21,7 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.dnd.spaced.config.common.CommonControllerSliceTest; -import com.dnd.spaced.core.quiz.application.QuizService; +import com.dnd.spaced.core.quiz.application.QuizServiceFacade; import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest.SubmitAnswerRequest; @@ -47,13 +47,13 @@ class QuizControllerTest extends CommonControllerSliceTest { @Autowired - QuizService quizService; + QuizServiceFacade quizServiceFacade; @Test @WithMockUser("1") void 퀴즈_생성_요청_성공_테스트() throws Exception { // given - given(quizService.createQuiz(anyLong(), any(CreateQuizRequest.class))).willReturn(1L); + given(quizServiceFacade.createQuiz(anyLong(), any(CreateQuizRequest.class))).willReturn(1L); CreateQuizRequest request = new CreateQuizRequest("전체 실무"); @@ -67,7 +67,7 @@ class QuizControllerTest extends CommonControllerSliceTest { header().string("Location", "/quizzes/1") ); - verify(quizService).createQuiz(anyLong(), any(CreateQuizRequest.class)); + verify(quizServiceFacade).createQuiz(anyLong(), any(CreateQuizRequest.class)); 퀴즈_생성_요청_문서화(resultActions); } @@ -86,7 +86,7 @@ class QuizControllerTest extends CommonControllerSliceTest { @WithMockUser("1") void 퀴즈_채점_요청_성공_테스트() throws Exception { // given - willDoNothing().given(quizService).grade(anyLong(), anyLong(), any(GradeQuizRequest.class)); + willDoNothing().given(quizServiceFacade).grade(anyLong(), anyLong(), any(GradeQuizRequest.class)); SubmitAnswerRequest[] submitAnswers = { new SubmitAnswerRequest(1L, "Authorization"), @@ -107,7 +107,7 @@ class QuizControllerTest extends CommonControllerSliceTest { header().string("Location", "/quizzes/1/graded-answer") ); - verify(quizService).grade(anyLong(), anyLong(), any(GradeQuizRequest.class)); + verify(quizServiceFacade).grade(anyLong(), anyLong(), any(GradeQuizRequest.class)); 퀴즈_채점_요청_문서화(resultActions); } @@ -224,7 +224,7 @@ class QuizControllerTest extends CommonControllerSliceTest { ), quizGradedAnswerResponse5.id() ); - given(quizService.readGradedAnswers(anyLong(), any(ReadQuizGradedAnswerSearchRequest.class), any(Pageable.class))).willReturn(response); + given(quizServiceFacade.readGradedAnswers(anyLong(), any(ReadQuizGradedAnswerSearchRequest.class), any(Pageable.class))).willReturn(response); // when & then ResultActions resultActions = mockMvc.perform( @@ -246,7 +246,7 @@ class QuizControllerTest extends CommonControllerSliceTest { jsonPath("answers[*].corrected").exists() ); - verify(quizService).readGradedAnswers( + verify(quizServiceFacade).readGradedAnswers( anyLong(), any(ReadQuizGradedAnswerSearchRequest.class), any(Pageable.class) @@ -379,7 +379,7 @@ class QuizControllerTest extends CommonControllerSliceTest { quizGradedAnswerResponse5.id() ); - given(quizService.readGradedAnswers(anyLong(), anyLong())).willReturn(response); + given(quizServiceFacade.readGradedAnswers(anyLong(), anyLong())).willReturn(response); // when & then ResultActions resultActions = mockMvc.perform( @@ -401,7 +401,7 @@ class QuizControllerTest extends CommonControllerSliceTest { jsonPath("answers[*].corrected").exists() ); - verify(quizService).readGradedAnswers(anyLong(), anyLong()); + verify(quizServiceFacade).readGradedAnswers(anyLong(), anyLong()); 특정_퀴즈에_대한_회원이_제출한_답_목록_조회_요청_문서화(resultActions); } @@ -438,7 +438,7 @@ class QuizControllerTest extends CommonControllerSliceTest { @WithMockUser("1") void 퀴즈_조회_요청_성공_테스트() throws Exception { // given - given(quizService.readQuiz(anyLong(), anyLong())).willReturn(createQuizResponse()); + given(quizServiceFacade.readQuiz(anyLong(), anyLong())).willReturn(createQuizResponse()); // when & then ResultActions resultActions = mockMvc.perform( @@ -464,7 +464,7 @@ class QuizControllerTest extends CommonControllerSliceTest { jsonPath("quizQuestions[0].answerOptionWordId", is(1L), Long.class) ); - verify(quizService).readQuiz(anyLong(), anyLong()); + verify(quizServiceFacade).readQuiz(anyLong(), anyLong()); 퀴즈_조회_요청_문서화(resultActions); } @@ -514,7 +514,7 @@ class QuizControllerTest extends CommonControllerSliceTest { ); QuizCollectionResponse response = new QuizCollectionResponse(List.of(quizResponse), 1L); - given(quizService.readQuizzes(anyLong(), any(ReadAllQuizRequest.class), any(Pageable.class))).willReturn(response); + given(quizServiceFacade.readQuizzes(anyLong(), any(ReadAllQuizRequest.class), any(Pageable.class))).willReturn(response); // when & then ResultActions resultActions = mockMvc.perform( diff --git a/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java b/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java index c5cea710..3dd215e3 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.dnd.spaced.core.quiz.application.QuizService; +import com.dnd.spaced.core.quiz.application.QuizServiceFacade; import com.dnd.spaced.core.quiz.application.TodayQuizService; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest.SubmitAnswerRequest; @@ -44,7 +44,7 @@ class GradedQuizEventListenerTest { ApplicationEvents events; @Autowired - QuizService quizService; + QuizServiceFacade quizServiceFacade; @Autowired TodayQuizService todayQuizService; @@ -76,7 +76,7 @@ class GradedQuizEventListenerTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when - quizService.grade(1L, 1L, request); + quizServiceFacade.grade(1L, 1L, request); // then assertAll( @@ -109,7 +109,7 @@ class GradedQuizEventListenerTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when - quizService.grade(1L, 1L, request); + quizServiceFacade.grade(1L, 1L, request); // then assertAll( @@ -140,7 +140,7 @@ class GradedQuizEventListenerTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when - quizService.grade(1L, 1L, request); + quizServiceFacade.grade(1L, 1L, request); // then assertAll( diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepositoryTest.java index c4f6d02a..4e2e424a 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepositoryTest.java @@ -5,7 +5,7 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.word.domain.Word; -import com.dnd.spaced.core.word.domain.dto.SimpleWordInfo; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.enums.Category; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -54,7 +54,7 @@ void beforeEach() { wordRandomGatewayRepository.saveWith(savedWord, Category.DEVELOP); // then - List actual = wordRandomGatewayRepository.findRandomAllBy(QuizCategory.DEVELOP, 1L); + List actual = wordRandomGatewayRepository.findRandomAllBy(QuizCategory.DEVELOP, 1L); assertAll( () -> assertThat(actual).hasSize(1), @@ -89,7 +89,7 @@ void beforeEach() { wordRandomGatewayRepository.saveWith(tomlWord, Category.DEVELOP); // when - List actual = wordRandomGatewayRepository.findRandomAllBy(QuizCategory.DEVELOP, 2L); + List actual = wordRandomGatewayRepository.findRandomAllBy(QuizCategory.DEVELOP, 2L); // then assertThat(actual).hasSize(2); @@ -120,7 +120,7 @@ void beforeEach() { wordRandomGatewayRepository.saveWith(kpiWord, Category.BUSINESS); // when - List actual = wordRandomGatewayRepository.findRandomAllBy(QuizCategory.TOTAL, 3L); + List actual = wordRandomGatewayRepository.findRandomAllBy(QuizCategory.TOTAL, 3L); // then assertThat(actual).hasSize(3); From 6c3e081f17dfc13a25741429d080d0e73c4d65b1 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 01:43:40 +0900 Subject: [PATCH 013/115] =?UTF-8?q?refactor:=20TodayQuizService=20CQS=20?= =?UTF-8?q?=EB=B0=8F=20Facade=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/GradeTodayQuizService.java | 49 ++++++ .../application/ReadTodayQuizService.java | 63 ++++++++ .../quiz/application/TodayQuizService.java | 126 --------------- .../application/TodayQuizServiceFacade.java | 77 ++++++++++ .../mapper/SimpleTodayQuizResponseMapper.java | 14 +- .../dto/mapper/TodayQuizResponseMapper.java | 23 ++- .../dto/response/ReadTodayQuizDto.java | 6 + ...yQuizInfo.java => SimpleTodayQuizDto.java} | 2 +- .../dto/mapper/TodayQuizInfoMapper.java | 10 +- .../repository/TodayQuizRepository.java | 4 +- .../TodayQuizGatewayRepository.java | 10 +- .../presentation/TodayQuizController.java | 14 +- .../GradeTodayQuizServiceTest.java | 79 ++++++++++ .../application/ReadTodayQuizServiceTest.java | 144 ++++++++++++++++++ .../application/TodayQuizServiceTest.java | 24 +-- .../presentation/TodayQuizControllerTest.java | 25 ++- .../listener/GradedQuizEventListenerTest.java | 10 +- 17 files changed, 489 insertions(+), 191 deletions(-) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/response/ReadTodayQuizDto.java rename space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/{SimpleTodayQuizInfo.java => SimpleTodayQuizDto.java} (92%) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java new file mode 100644 index 00000000..10ee6fee --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java @@ -0,0 +1,49 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; +import com.dnd.spaced.core.quiz.application.exception.AlreadyGradeTodayQuizException; +import com.dnd.spaced.core.quiz.application.exception.TodayQuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.TodayQuiz; +import com.dnd.spaced.core.quiz.domain.TodayQuiz.SubmitAnswer; +import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizGradedAnswerRepository; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GradeTodayQuizService { + + private final TodayQuizRepository todayQuizRepository; + private final TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; + + void gradeTodayQuiz(Long accountId, Long todayQuizId, GradeTodayQuizRequest request) { + TodayQuiz todayQuiz = findTodayQuiz(todayQuizId); + + validateTodayQuizGradedAnswer(accountId, todayQuizId); + gradeTodayQuiz(accountId, request, todayQuiz); + } + + private TodayQuiz findTodayQuiz(Long todayQuizId) { + return todayQuizRepository.findTodayQuizBy(todayQuizId) + .orElseThrow( + () -> new TodayQuizNotFoundException( + "지정한 id의 오늘의 퀴즈를 찾지 못했습니다." + ) + ); + } + + private void validateTodayQuizGradedAnswer(Long accountId, Long todayQuizId) { + if (todayQuizGradedAnswerRepository.existsBy(accountId, todayQuizId)) { + throw new AlreadyGradeTodayQuizException("이미 오늘의 퀴즈를 풀었습니다."); + } + } + + private void gradeTodayQuiz(Long accountId, GradeTodayQuizRequest request, TodayQuiz todayQuiz) { + SubmitAnswer submitAnswer = new SubmitAnswer(request.selectedWordId(), request.selectedContent()); + TodayQuizGradedAnswer gradedAnswer = todayQuiz.grade(accountId, submitAnswer); + + todayQuizGradedAnswerRepository.save(gradedAnswer); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java new file mode 100644 index 00000000..2b28fc5e --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java @@ -0,0 +1,63 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.request.ReadTodayQuizGradedAnswerSearchRequest; +import com.dnd.spaced.core.quiz.application.dto.response.ReadTodayQuizDto; +import com.dnd.spaced.core.quiz.application.exception.TodayQuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.TodayQuiz; +import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizGradedAnswerRepository; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReadTodayQuizService { + + private final TodayQuizRepository todayQuizRepository; + private final TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; + + SimpleTodayQuizDto readLatestTodayQuiz() { + return todayQuizRepository.findLatest() + .orElseThrow( + () -> new TodayQuizNotFoundException( + "오늘의 퀴즈가 생성되지 않았습니다.") + ); + } + + ReadTodayQuizDto readTodayQuiz(Long accountId, Long todayQuizId) { + TodayQuiz todayQuiz = todayQuizRepository.findWithTodayQuizOptionBy(todayQuizId) + .orElseThrow( + () -> new TodayQuizNotFoundException( + "지정한 id의 오늘의 퀴즈를 찾지 못했습니다." + ) + ); + boolean solved = todayQuizGradedAnswerRepository.existsBy(accountId, todayQuizId); + + return new ReadTodayQuizDto(todayQuiz, solved); + } + + List readTodayQuizGradedAnswers( + Long accountId, + ReadTodayQuizGradedAnswerSearchRequest request, + Pageable pageable + ) { + return todayQuizGradedAnswerRepository.findAllBy( + accountId, + request.lastTodayQuizGradedAnswerId(), + pageable + ); + } + + TodayQuizGradedAnswer readTargetTodayQuizGradedAnswers(Long accountId, Long todayQuizId) { + return todayQuizGradedAnswerRepository.findBy(accountId, todayQuizId) + .orElseThrow( + () -> new TodayQuizNotFoundException( + "지정한 오늘의 퀴즈 답안지를 찾지 못했습니다." + ) + ); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizService.java deleted file mode 100644 index c4643e3d..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizService.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.dnd.spaced.core.quiz.application; - -import com.dnd.spaced.core.quiz.application.dto.mapper.SimpleTodayQuizResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.mapper.TodayQuizGradedAnswerResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.mapper.TodayQuizGradedAnswerCollectionResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.mapper.TodayQuizResponseMapper; -import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; -import com.dnd.spaced.core.quiz.application.dto.request.ReadTodayQuizGradedAnswerSearchRequest; -import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse; -import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizGradedAnswerCollectionResponse; -import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizGradedAnswerResponse; -import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse; -import com.dnd.spaced.core.quiz.application.exception.AlreadyGradeTodayQuizException; -import com.dnd.spaced.core.quiz.application.exception.TodayQuizNotFoundException; -import com.dnd.spaced.core.quiz.domain.TodayQuiz; -import com.dnd.spaced.core.quiz.domain.TodayQuiz.SubmitAnswer; -import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; -import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizInfo; -import com.dnd.spaced.core.quiz.domain.repository.TodayQuizGradedAnswerRepository; -import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; -import com.dnd.spaced.core.skill.application.event.dto.GradedTodayQuizEvent; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class TodayQuizService { - - private final ApplicationEventPublisher eventPublisher; - private final TodayQuizRepository todayQuizRepository; - private final TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; - - public SimpleTodayQuizResponse readLatestTodayQuiz() { - SimpleTodayQuizInfo simpleTodayQuizInfo = findLatestQuiz(); - - return SimpleTodayQuizResponseMapper.toDto(simpleTodayQuizInfo); - } - - public TodayQuizResponse readTodayQuiz(Long accountId, Long todayQuizId) { - TodayQuiz todayQuiz = findTodayQuizInfo(todayQuizId); - boolean solved = todayQuizGradedAnswerRepository.existsBy(accountId, todayQuizId); - - return TodayQuizResponseMapper.toDto(todayQuiz, accountId, solved); - } - - @Transactional - public void grade(Long accountId, Long todayQuizId, GradeTodayQuizRequest request) { - TodayQuiz todayQuiz = findTodayQuiz(todayQuizId); - - validateTodayQuizGradedAnswer(accountId, todayQuizId); - - SubmitAnswer submitAnswer = new SubmitAnswer(request.selectedWordId(), request.selectedContent()); - TodayQuizGradedAnswer gradedAnswer = todayQuiz.grade(accountId, submitAnswer); - - todayQuizGradedAnswerRepository.save(gradedAnswer); - publishGradedTodayQuizEvent(accountId, gradedAnswer); - } - - public TodayQuizGradedAnswerCollectionResponse readTodayQuizGradedAnswers( - Long accountId, - ReadTodayQuizGradedAnswerSearchRequest request, - Pageable pageable - ) { - List todayQuizGradedAnswers = todayQuizGradedAnswerRepository.findAllBy( - accountId, - request.lastTodayQuizGradedAnswerId(), - pageable - ); - - return TodayQuizGradedAnswerCollectionResponseMapper.toDto(todayQuizGradedAnswers); - } - - public TodayQuizGradedAnswerResponse readTargetTodayQuizGradedAnswers(Long accountId, Long todayQuizId) { - TodayQuizGradedAnswer todayQuizGradedAnswer = findTodayQuizGradedAnswer(accountId, todayQuizId); - - return TodayQuizGradedAnswerResponseMapper.toDto(todayQuizGradedAnswer); - } - - private SimpleTodayQuizInfo findLatestQuiz() { - return todayQuizRepository.findLatest() - .orElseThrow( - () -> new TodayQuizNotFoundException("오늘의 퀴즈가 생성되지 않았습니다.") - ); - } - - private TodayQuiz findTodayQuizInfo(Long todayQuizId) { - return todayQuizRepository.findWithTodayQuizOptionBy(todayQuizId) - .orElseThrow( - () -> new TodayQuizNotFoundException( - "지정한 id의 오늘의 퀴즈를 찾지 못했습니다." - ) - ); - } - - private TodayQuiz findTodayQuiz(Long todayQuizId) { - return todayQuizRepository.findTodayQuizBy(todayQuizId) - .orElseThrow( - () -> new TodayQuizNotFoundException( - "지정한 id의 오늘의 퀴즈를 찾지 못했습니다." - ) - ); - } - - private void publishGradedTodayQuizEvent(Long accountId, TodayQuizGradedAnswer gradedAnswer) { - eventPublisher.publishEvent(GradedTodayQuizEvent.of(accountId, gradedAnswer.isCorrect())); - } - - private TodayQuizGradedAnswer findTodayQuizGradedAnswer(Long accountId, Long todayQuizId) { - return todayQuizGradedAnswerRepository.findBy(accountId, todayQuizId) - .orElseThrow( - () -> new TodayQuizNotFoundException( - "지정한 오늘의 퀴즈 답안지를 찾지 못했습니다." - ) - ); - } - - private void validateTodayQuizGradedAnswer(Long accountId, Long todayQuizId) { - if (todayQuizGradedAnswerRepository.existsBy(accountId, todayQuizId)) { - throw new AlreadyGradeTodayQuizException("이미 오늘의 퀴즈를 풀었습니다."); - } - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java new file mode 100644 index 00000000..3e4c78b3 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java @@ -0,0 +1,77 @@ +package com.dnd.spaced.core.quiz.application; + +import com.dnd.spaced.core.quiz.application.dto.mapper.SimpleTodayQuizResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.mapper.TodayQuizGradedAnswerResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.mapper.TodayQuizGradedAnswerCollectionResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.mapper.TodayQuizResponseMapper; +import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; +import com.dnd.spaced.core.quiz.application.dto.request.ReadTodayQuizGradedAnswerSearchRequest; +import com.dnd.spaced.core.quiz.application.dto.response.ReadTodayQuizDto; +import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse; +import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizGradedAnswerCollectionResponse; +import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizGradedAnswerResponse; +import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse; +import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; +import com.dnd.spaced.core.skill.application.event.dto.GradedTodayQuizEvent; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class TodayQuizServiceFacade { + + private final ReadTodayQuizService readTodayQuizService; + private final GradeTodayQuizService gradeTodayQuizService; + private final ApplicationEventPublisher eventPublisher; + + @Transactional + public void gradeTodayQuiz(Long accountId, Long todayQuizId, GradeTodayQuizRequest request) { + gradeTodayQuizService.gradeTodayQuiz(accountId, todayQuizId, request); + TodayQuizGradedAnswer todayQuizGradedAnswer = readTodayQuizService.readTargetTodayQuizGradedAnswers( + accountId, + todayQuizId + ); + + eventPublisher.publishEvent(GradedTodayQuizEvent.of(accountId, todayQuizGradedAnswer.isCorrect())); + } + + public SimpleTodayQuizResponse readLatestTodayQuiz() { + SimpleTodayQuizDto simpleTodayQuizDto = readTodayQuizService.readLatestTodayQuiz(); + + return SimpleTodayQuizResponseMapper.toDto(simpleTodayQuizDto); + } + + public TodayQuizResponse readTodayQuiz(Long accountId, Long todayQuizId) { + ReadTodayQuizDto readTodayQuizDto = readTodayQuizService.readTodayQuiz(accountId, todayQuizId); + + return TodayQuizResponseMapper.toDto(readTodayQuizDto, accountId); + } + + public TodayQuizGradedAnswerCollectionResponse readTodayQuizGradedAnswers( + Long accountId, + ReadTodayQuizGradedAnswerSearchRequest request, + Pageable pageable + ) { + List todayQuizGradedAnswers = readTodayQuizService.readTodayQuizGradedAnswers( + accountId, + request, + pageable + ); + + return TodayQuizGradedAnswerCollectionResponseMapper.toDto(todayQuizGradedAnswers); + } + + public TodayQuizGradedAnswerResponse readTargetTodayQuizGradedAnswers(Long accountId, Long todayQuizId) { + TodayQuizGradedAnswer todayQuizGradedAnswer = readTodayQuizService.readTargetTodayQuizGradedAnswers( + accountId, + todayQuizId + ); + + return TodayQuizGradedAnswerResponseMapper.toDto(todayQuizGradedAnswer); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java index 7b6bad45..58ad4693 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java @@ -2,24 +2,24 @@ import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse; import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse.TodayQuizQuestionResponse; -import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizInfo; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class SimpleTodayQuizResponseMapper { - public static SimpleTodayQuizResponse toDto(SimpleTodayQuizInfo simpleTodayQuizInfo) { + public static SimpleTodayQuizResponse toDto(SimpleTodayQuizDto simpleTodayQuizDto) { TodayQuizQuestionResponse todayQuizQuestionResponse = new TodayQuizQuestionResponse( - simpleTodayQuizInfo.quizCategory().getName(), - simpleTodayQuizInfo.question(), - simpleTodayQuizInfo.questionContent() + simpleTodayQuizDto.quizCategory().getName(), + simpleTodayQuizDto.question(), + simpleTodayQuizDto.questionContent() ); return new SimpleTodayQuizResponse( - simpleTodayQuizInfo.id(), + simpleTodayQuizDto.id(), todayQuizQuestionResponse, - simpleTodayQuizInfo.createdAt() + simpleTodayQuizDto.createdAt() ); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java index d6511caf..b60e348a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java @@ -1,11 +1,12 @@ package com.dnd.spaced.core.quiz.application.dto.mapper; +import com.dnd.spaced.core.quiz.application.dto.response.ReadTodayQuizDto; import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse; import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse.TodayQuizQuestionResponse; import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse.TodayQuizQuestionResponse.TodayQuizOptionResponse; import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse.TodayQuizStatus; -import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; +import com.dnd.spaced.global.consts.AuthConst; import java.util.List; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -13,16 +14,22 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class TodayQuizResponseMapper { - public static TodayQuizResponse toDto(TodayQuiz todayQuiz, Long accountId, boolean solved) { - TodayQuizQuestionResponse todayQuizQuestion = toTodayQuizQuestionDto(todayQuiz.getTodayQuizQuestion()); + public static TodayQuizResponse toDto(ReadTodayQuizDto todayQuizDto, Long accountId) { + TodayQuizQuestionResponse todayQuizQuestion = toTodayQuizQuestionDto( + todayQuizDto.todayQuiz().getTodayQuizQuestion() + ); - if (accountId == -1L) { - return new TodayQuizResponse(todayQuiz.getId(), todayQuizQuestion, TodayQuizStatus.NOT_LOGGED_IN); + if (AuthConst.GUEST_ACCOUNT_ID.equals(accountId)) { + return new TodayQuizResponse( + todayQuizDto.todayQuiz().getId(), + todayQuizQuestion, + TodayQuizStatus.NOT_LOGGED_IN + ); } - if (solved) { - return new TodayQuizResponse(todayQuiz.getId(), todayQuizQuestion, TodayQuizStatus.SOLVED); + if (todayQuizDto.solved()) { + return new TodayQuizResponse(todayQuizDto.todayQuiz().getId(), todayQuizQuestion, TodayQuizStatus.SOLVED); } - return new TodayQuizResponse(todayQuiz.getId(), todayQuizQuestion, TodayQuizStatus.NOT_SOLVED); + return new TodayQuizResponse(todayQuizDto.todayQuiz().getId(), todayQuizQuestion, TodayQuizStatus.NOT_SOLVED); } private static TodayQuizQuestionResponse toTodayQuizQuestionDto(TodayQuizQuestion todayQuizQuestion) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/response/ReadTodayQuizDto.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/response/ReadTodayQuizDto.java new file mode 100644 index 00000000..4705f944 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/response/ReadTodayQuizDto.java @@ -0,0 +1,6 @@ +package com.dnd.spaced.core.quiz.application.dto.response; + +import com.dnd.spaced.core.quiz.domain.TodayQuiz; + +public record ReadTodayQuizDto(TodayQuiz todayQuiz, boolean solved) { +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleTodayQuizInfo.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleTodayQuizDto.java similarity index 92% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleTodayQuizInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleTodayQuizDto.java index b130b8ca..24b62fe1 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleTodayQuizInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/SimpleTodayQuizDto.java @@ -4,7 +4,7 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import java.time.LocalDateTime; -public record SimpleTodayQuizInfo( +public record SimpleTodayQuizDto( Long id, QuizCategory quizCategory, String question, diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java index 85bcaf42..c4ab7fd0 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java @@ -2,7 +2,7 @@ import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.TodayQuizOption; -import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizInfo; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; import com.dnd.spaced.core.quiz.domain.dto.TodayQuizInfo; import com.dnd.spaced.core.quiz.domain.dto.TodayQuizInfo.TodayQuizOptionInfo; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizAnswerOption; @@ -16,10 +16,10 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class TodayQuizInfoMapper { - public static SimpleTodayQuizInfo toDto(TodayQuiz todayQuiz) { + public static SimpleTodayQuizDto toDto(TodayQuiz todayQuiz) { TodayQuizQuestion todayQuizQuestion = todayQuiz.getTodayQuizQuestion(); - return new SimpleTodayQuizInfo( + return new SimpleTodayQuizDto( todayQuiz.getId(), todayQuizQuestion.getQuizCategory(), todayQuizQuestion.getQuestion(), @@ -29,7 +29,7 @@ public static SimpleTodayQuizInfo toDto(TodayQuiz todayQuiz) { ); } - public static SimpleTodayQuizInfo toDto( + public static SimpleTodayQuizDto toDto( Long id, LocalDateTime createdAt, String question, @@ -40,7 +40,7 @@ public static SimpleTodayQuizInfo toDto( ) { TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption(answerWordId, answerContent); - return new SimpleTodayQuizInfo(id, quizCategory, question, questionContent, todayQuizAnswerOption, createdAt); + return new SimpleTodayQuizDto(id, quizCategory, question, questionContent, todayQuizAnswerOption, createdAt); } public static TodayQuizInfo toDto(TodayQuiz todayQuiz, List todayQuizOptions) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/TodayQuizRepository.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/TodayQuizRepository.java index e2ceb6ac..573b0410 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/TodayQuizRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/repository/TodayQuizRepository.java @@ -1,14 +1,14 @@ package com.dnd.spaced.core.quiz.domain.repository; import com.dnd.spaced.core.quiz.domain.TodayQuiz; -import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizInfo; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; import java.util.Optional; public interface TodayQuizRepository { TodayQuiz save(TodayQuiz todayQuiz); - Optional findLatest(); + Optional findLatest(); Optional findTodayQuizBy(Long todayQuizId); diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java index 3c3363b6..5eb028b3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java @@ -3,7 +3,7 @@ import static com.dnd.spaced.core.quiz.domain.QTodayQuiz.todayQuiz; import com.dnd.spaced.core.quiz.domain.TodayQuiz; -import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizInfo; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizInfoMapper; import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; @@ -22,7 +22,7 @@ @RequiredArgsConstructor public class TodayQuizGatewayRepository implements TodayQuizRepository { - private static final RowMapper simpleTodayQuizInfoRowMapper = + private static final RowMapper simpleTodayQuizInfoRowMapper = (rs, ignoreRowNum) -> TodayQuizInfoMapper.toDto( rs.getLong(1), rs.getTimestamp(2).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), @@ -48,7 +48,7 @@ public TodayQuiz save(TodayQuiz todayQuiz) { key = "'" + CacheConst.TODAY_QUIZ_CACHE_NAME + "'", cacheManager = "memoryCacheManager" ) - public Optional findLatest() { + public Optional findLatest() { String sql = """ SELECT tq.id, @@ -66,9 +66,9 @@ public Optional findLatest() { ) t left join today_quizzes tq ON t.id = tq.id; """; try { - SimpleTodayQuizInfo simpleTodayQuizInfo = jdbcTemplate.queryForObject(sql, simpleTodayQuizInfoRowMapper); + SimpleTodayQuizDto simpleTodayQuizDto = jdbcTemplate.queryForObject(sql, simpleTodayQuizInfoRowMapper); - return Optional.of(simpleTodayQuizInfo); + return Optional.of(simpleTodayQuizDto); } catch (IncorrectResultSizeDataAccessException ignored) { return Optional.empty(); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java index 1b012b8a..6eabea30 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/presentation/TodayQuizController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.quiz.presentation; -import com.dnd.spaced.core.quiz.application.TodayQuizService; +import com.dnd.spaced.core.quiz.application.TodayQuizServiceFacade; import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.ReadTodayQuizGradedAnswerSearchRequest; import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse; @@ -29,11 +29,11 @@ @RequiredArgsConstructor public class TodayQuizController { - private final TodayQuizService todayQuizService; + private final TodayQuizServiceFacade todayQuizServiceFacade; @GetMapping("/latest") public ResponseEntity readLatestTodayQuiz() { - return ResponseEntity.ok(todayQuizService.readLatestTodayQuiz()); + return ResponseEntity.ok(todayQuizServiceFacade.readLatestTodayQuiz()); } @GetMapping("/{todayQuizId}") @@ -42,7 +42,7 @@ public ResponseEntity readTodayQuiz( @PathVariable Long todayQuizId ) { return ResponseEntity.ok( - todayQuizService.readTodayQuiz(accountId.id(), todayQuizId) + todayQuizServiceFacade.readTodayQuiz(accountId.id(), todayQuizId) ); } @@ -52,7 +52,7 @@ public ResponseEntity gradeTodayQuiz( @PathVariable Long todayQuizId, @Valid @RequestBody GradeTodayQuizRequest request ) { - todayQuizService.grade(accountId.id(), todayQuizId, request); + todayQuizServiceFacade.gradeTodayQuiz(accountId.id(), todayQuizId, request); URI location = UriComponentsBuilder.fromPath("/today-quizzes/{id}/graded-answers") .buildAndExpand(todayQuizId) .toUri(); @@ -66,7 +66,7 @@ public ResponseEntity readTargetTodayQuizGradedAn @CurrentAccount AuthAccountId accountId, @PathVariable Long todayQuizId ) { - return ResponseEntity.ok(todayQuizService.readTargetTodayQuizGradedAnswers(accountId.id(), todayQuizId)); + return ResponseEntity.ok(todayQuizServiceFacade.readTargetTodayQuizGradedAnswers(accountId.id(), todayQuizId)); } @GetMapping("/graded-answers") @@ -76,7 +76,7 @@ public ResponseEntity readTodayQuizGrad @GradedAnswerPageable Pageable pageable ) { return ResponseEntity.ok( - todayQuizService.readTodayQuizGradedAnswers(accountId.id(), request, pageable) + todayQuizServiceFacade.readTodayQuizGradedAnswers(accountId.id(), request, pageable) ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java new file mode 100644 index 00000000..cd5963fa --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java @@ -0,0 +1,79 @@ +package com.dnd.spaced.core.quiz.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; +import com.dnd.spaced.core.quiz.application.exception.AlreadyGradeTodayQuizException; +import com.dnd.spaced.core.quiz.application.exception.TodayQuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizGradedAnswerRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class GradeTodayQuizServiceTest { + + @Autowired + TodayQuizServiceFacade todayQuizServiceFacade; + + @Autowired + TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; + + @Test + void 지정한_오늘의_퀴즈_id가_없다면_퀴즈_정답을_제출할_수_없다() { + // when & then + GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, -999L, request)) + .isInstanceOf(TodayQuizNotFoundException.class) + .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/today_quiz.sql", + "classpath:sql/quiz/today_quiz_graded_answer.sql" + }) + void 이미_푼_오늘의_퀴즈인_경우_정답을_제출할_수_없다() { + // given + GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + + // when & then + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request)) + .isInstanceOf(AlreadyGradeTodayQuizException.class) + .hasMessage("이미 오늘의 퀴즈를 풀었습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/today_quiz.sql" + }) + void 오늘의_퀴즈_정답을_제출한다() { + // when + GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + + todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); + + // then + TodayQuizGradedAnswer actual = todayQuizGradedAnswerRepository.findBy(1L, 1L) + .get(); + + assertAll( + () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(1L), + () -> assertThat(actual.getAccountId()).isEqualTo(1L) + ); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java new file mode 100644 index 00000000..394a4064 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java @@ -0,0 +1,144 @@ +package com.dnd.spaced.core.quiz.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.quiz.application.dto.request.ReadTodayQuizGradedAnswerSearchRequest; +import com.dnd.spaced.core.quiz.application.dto.response.ReadTodayQuizDto; +import com.dnd.spaced.core.quiz.application.exception.TodayQuizNotFoundException; +import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cache.CacheManager; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ReadTodayQuizServiceTest { + + @Autowired + ReadTodayQuizService todayQuizService; + + @Autowired + CacheManager cacheManager; + + @BeforeEach + void setUp() { + cacheManager.getCacheNames() + .forEach(name -> cacheManager.getCache(name).clear()); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/today_quiz.sql" + }) + void 최근에_생성한_오늘의_퀴즈를_조회한다() { + // when + SimpleTodayQuizDto actual = todayQuizService.readLatestTodayQuiz(); + + // then + assertAll( + () -> assertThat(actual.id()).isPositive(), + () -> assertThat(actual.todayQuizAnswerOption()).isNotNull() + ); + } + + @Test + void 오늘의_퀴즈가_생성된_적이_없다면_최근에_생성한_오늘의_퀴즈를_조회할_수_없다() { + // when & then + assertThatThrownBy(() -> todayQuizService.readLatestTodayQuiz()) + .isInstanceOf(TodayQuizNotFoundException.class) + .hasMessage("오늘의 퀴즈가 생성되지 않았습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/today_quiz.sql", + "classpath:sql/quiz/today_quiz_graded_answer.sql" + }) + void 사용자가_제출한_모든_오늘의_퀴즈_채점_결과를_조회한다() { + // given + ReadTodayQuizGradedAnswerSearchRequest request = new ReadTodayQuizGradedAnswerSearchRequest( + null + ); + + // when + List actual = todayQuizService.readTodayQuizGradedAnswers( + 1L, request, PageRequest.of(0, 10) + ); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).getTodayQuiz().getId()).isEqualTo(1L) + ); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/today_quiz.sql", + "classpath:sql/quiz/today_quiz_graded_answer.sql" + }) + void 사용자가_제출한_오늘의_퀴즈_채점_결과를_조회한다() { + // when + TodayQuizGradedAnswer actual = todayQuizService.readTargetTodayQuizGradedAnswers( + 1L, + 1L + ); + + // then + assertAll( + () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(1L), + () -> assertThat(actual.getAccountId()).isEqualTo(1L) + ); + } + + @Test + void 지정한_오늘의_퀴즈_id가_없다면_사용자가_제출한_오늘의_퀴즈_채점_결과를_조회할_수_없다() { + // when & then + assertThatThrownBy(() -> todayQuizService.readTargetTodayQuizGradedAnswers(1L, 1L)) + .isInstanceOf(TodayQuizNotFoundException.class) + .hasMessage("지정한 오늘의 퀴즈 답안지를 찾지 못했습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/word.sql", + "classpath:sql/quiz/today_quiz.sql" + }) + void 지정한_id의_오늘의_퀴즈를_조회한다() { + // when + ReadTodayQuizDto actual = todayQuizService.readTodayQuiz(1L, 1L); + + // then + assertAll( + () -> assertThat(actual.todayQuiz().getId()).isEqualTo(1L), + () -> assertThat(actual.todayQuiz().getTodayQuizQuestion()).isNotNull() + ); + } + + @Test + void 없는_id의_오늘의_퀴즈를_조회할_수_없다() { + // when & then + assertThatThrownBy(() -> todayQuizService.readTodayQuiz(1L, -999L)) + .isInstanceOf(TodayQuizNotFoundException.class) + .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java index bf3c6f4e..96bfd533 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java @@ -38,7 +38,7 @@ class TodayQuizServiceTest { ApplicationEvents events; @Autowired - TodayQuizService todayQuizService; + TodayQuizServiceFacade todayQuizServiceFacade; @Autowired TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; @@ -60,7 +60,7 @@ void setUp() { }) void 최근에_생성한_오늘의_퀴즈를_조회한다() { // when - SimpleTodayQuizResponse actual = todayQuizService.readLatestTodayQuiz(); + SimpleTodayQuizResponse actual = todayQuizServiceFacade.readLatestTodayQuiz(); // then assertAll( @@ -72,7 +72,7 @@ void setUp() { @Test void 오늘의_퀴즈가_생성된_적이_없다면_최근에_생성한_오늘의_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizService.readLatestTodayQuiz()) + assertThatThrownBy(() -> todayQuizServiceFacade.readLatestTodayQuiz()) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("오늘의 퀴즈가 생성되지 않았습니다."); } @@ -82,7 +82,7 @@ void setUp() { // when & then GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); - assertThatThrownBy(() -> todayQuizService.grade(1L, -999L, request)) + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, -999L, request)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); } @@ -97,7 +97,7 @@ void setUp() { // when GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); - todayQuizService.grade(1L, 1L, request); + todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); // then TodayQuizGradedAnswer actual = todayQuizGradedAnswerRepository.findBy(1L, 1L) @@ -117,14 +117,14 @@ void setUp() { "classpath:sql/quiz/today_quiz.sql", "classpath:sql/quiz/today_quiz_graded_answer.sql" }) - void 사용자가_제출한_모든_오늘의_퀴즈_채점_결과를_반환한다() { + void 사용자가_제출한_모든_오늘의_퀴즈_채점_결과를_조회한다() { // given ReadTodayQuizGradedAnswerSearchRequest request = new ReadTodayQuizGradedAnswerSearchRequest( null ); // when - TodayQuizGradedAnswerCollectionResponse actual = todayQuizService.readTodayQuizGradedAnswers( + TodayQuizGradedAnswerCollectionResponse actual = todayQuizServiceFacade.readTodayQuizGradedAnswers( 1L, request, PageRequest.of(0, 10) ); @@ -147,7 +147,7 @@ void setUp() { GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); // when & then - assertThatThrownBy(() -> todayQuizService.grade(1L, 1L, request)) + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request)) .isInstanceOf(AlreadyGradeTodayQuizException.class) .hasMessage("이미 오늘의 퀴즈를 풀었습니다."); } @@ -161,7 +161,7 @@ void setUp() { }) void 사용자가_제출한_오늘의_퀴즈_채점_결과를_조회한다() { // when - TodayQuizGradedAnswerResponse actual = todayQuizService.readTargetTodayQuizGradedAnswers( + TodayQuizGradedAnswerResponse actual = todayQuizServiceFacade.readTargetTodayQuizGradedAnswers( 1L, 1L ); @@ -176,7 +176,7 @@ void setUp() { @Test void 지정한_오늘의_퀴즈_id가_없다면_사용자가_제출한_오늘의_퀴즈_채점_결과를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizService.readTargetTodayQuizGradedAnswers(1L, 1L)) + assertThatThrownBy(() -> todayQuizServiceFacade.readTargetTodayQuizGradedAnswers(1L, 1L)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 오늘의 퀴즈 답안지를 찾지 못했습니다."); } @@ -189,7 +189,7 @@ void setUp() { }) void 지정한_id의_오늘의_퀴즈를_조회한다() { // when - TodayQuizResponse actual = todayQuizService.readTodayQuiz(1L, 1L); + TodayQuizResponse actual = todayQuizServiceFacade.readTodayQuiz(1L, 1L); // then assertAll( @@ -201,7 +201,7 @@ void setUp() { @Test void 없는_id의_오늘의_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizService.readTodayQuiz(1L, -999L)) + assertThatThrownBy(() -> todayQuizServiceFacade.readTodayQuiz(1L, -999L)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/TodayQuizControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/TodayQuizControllerTest.java index 447502f6..2206beb8 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/TodayQuizControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/presentation/TodayQuizControllerTest.java @@ -24,7 +24,7 @@ import com.dnd.spaced.config.common.CommonControllerSliceTest; import com.dnd.spaced.config.docs.link.DocumentLinkGenerator.DocsUrl; -import com.dnd.spaced.core.quiz.application.TodayQuizService; +import com.dnd.spaced.core.quiz.application.TodayQuizServiceFacade; import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.ReadTodayQuizGradedAnswerSearchRequest; import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse; @@ -38,7 +38,6 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -50,7 +49,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { @Autowired - TodayQuizService todayQuizService; + TodayQuizServiceFacade todayQuizServiceFacade; @Test void 최신_오늘의_퀴즈_요청_성공_테스트() throws Exception { @@ -66,7 +65,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { LocalDateTime.now() ); - given(todayQuizService.readLatestTodayQuiz()).willReturn(todayQuizResponse); + given(todayQuizServiceFacade.readLatestTodayQuiz()).willReturn(todayQuizResponse); // when & then ResultActions resultActions = mockMvc.perform( @@ -81,7 +80,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { "인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘") ); - verify(todayQuizService).readLatestTodayQuiz(); + verify(todayQuizServiceFacade).readLatestTodayQuiz(); 최신_오늘의_퀴즈_요청_문서화(resultActions); } @@ -128,7 +127,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { TodayQuizStatus.NOT_SOLVED ); - given(todayQuizService.readTodayQuiz(anyLong(), anyLong())).willReturn(todayQuizResponse); + given(todayQuizServiceFacade.readTodayQuiz(anyLong(), anyLong())).willReturn(todayQuizResponse); // when & then ResultActions resultActions = mockMvc.perform( @@ -146,7 +145,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { jsonPath("todayQuizQuestion.todayQuizOptions[*].content").exists() ); - verify(todayQuizService).readTodayQuiz(anyLong(), anyLong()); + verify(todayQuizServiceFacade).readTodayQuiz(anyLong(), anyLong()); 오늘의_퀴즈_조회_요청_문서화(resultActions); } @@ -195,7 +194,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { @WithMockUser("1") void 오늘의_퀴즈_채점_요청_성공_테스트() throws Exception { // given - willDoNothing().given(todayQuizService).grade(anyLong(), anyLong(), any(GradeTodayQuizRequest.class)); + willDoNothing().given(todayQuizServiceFacade).gradeTodayQuiz(anyLong(), anyLong(), any(GradeTodayQuizRequest.class)); // when & then GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); @@ -211,7 +210,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { header().string("Location", "/today-quizzes/1/graded-answers") ); - verify(todayQuizService).grade(anyLong(), anyLong(), any(GradeTodayQuizRequest.class)); + verify(todayQuizServiceFacade).gradeTodayQuiz(anyLong(), anyLong(), any(GradeTodayQuizRequest.class)); 오늘의_퀴즈_채점_요청_문서화(resultActions); } @@ -258,7 +257,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { ); given( - todayQuizService.readTodayQuizGradedAnswers( + todayQuizServiceFacade.readTodayQuizGradedAnswers( anyLong(), any(ReadTodayQuizGradedAnswerSearchRequest.class), any(Pageable.class) @@ -289,7 +288,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { jsonPath("answers[0].answerQuizOptionContent").value("Authorization") ); - verify(todayQuizService).readTodayQuizGradedAnswers( + verify(todayQuizServiceFacade).readTodayQuizGradedAnswers( anyLong(), any(ReadTodayQuizGradedAnswerSearchRequest.class), any(Pageable.class) @@ -353,7 +352,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { true ); - given(todayQuizService.readTargetTodayQuizGradedAnswers(anyLong(), anyLong())).willReturn( + given(todayQuizServiceFacade.readTargetTodayQuizGradedAnswers(anyLong(), anyLong())).willReturn( todayQuizGradedAnswerResponse); // when & then @@ -374,7 +373,7 @@ class TodayQuizControllerTest extends CommonControllerSliceTest { jsonPath("answerQuizOptionContent").value("Authorization") ); - verify(todayQuizService).readTargetTodayQuizGradedAnswers(anyLong(), anyLong()); + verify(todayQuizServiceFacade).readTargetTodayQuizGradedAnswers(anyLong(), anyLong()); 특정_오늘의_퀴즈에_대한_채점_결과_조회_요청_문서화(resultActions); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java b/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java index 3dd215e3..52aba2d2 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/skill/application/event/listener/GradedQuizEventListenerTest.java @@ -11,7 +11,7 @@ import static org.mockito.Mockito.verify; import com.dnd.spaced.core.quiz.application.QuizServiceFacade; -import com.dnd.spaced.core.quiz.application.TodayQuizService; +import com.dnd.spaced.core.quiz.application.TodayQuizServiceFacade; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest; import com.dnd.spaced.core.quiz.application.dto.request.GradeQuizRequest.SubmitAnswerRequest; import com.dnd.spaced.core.quiz.application.dto.request.GradeTodayQuizRequest; @@ -47,7 +47,7 @@ class GradedQuizEventListenerTest { QuizServiceFacade quizServiceFacade; @Autowired - TodayQuizService todayQuizService; + TodayQuizServiceFacade todayQuizServiceFacade; @Autowired SkillRepository skillRepository; @@ -162,7 +162,7 @@ class GradedQuizEventListenerTest { GradeTodayQuizRequest request = new GradeTodayQuizRequest(2L, "YAML"); // when - todayQuizService.grade(1L, 1L, request); + todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); // then assertAll( @@ -188,7 +188,7 @@ class GradedQuizEventListenerTest { GradeTodayQuizRequest request = new GradeTodayQuizRequest(2L, "YAML"); // when - todayQuizService.grade(1L, 1L, request); + todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); // then assertAll( @@ -212,7 +212,7 @@ class GradedQuizEventListenerTest { GradeTodayQuizRequest request = new GradeTodayQuizRequest(2L, "YAML"); // when - todayQuizService.grade(1L, 1L, request); + todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); // then assertAll( From e601b70e6a0cff2b11d4b81247b274e8f3e9ed51 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 20:06:52 +0900 Subject: [PATCH 014/115] =?UTF-8?q?refactor:=20Report=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EB=82=B4=EC=9A=A9=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/event/listener/AdminReportEventListener.java | 2 +- .../main/java/com/dnd/spaced/core/report/domain/Report.java | 5 ++--- .../dnd/spaced/core/report/domain/enums/ReportStatus.java | 2 +- .../spaced/core/report/domain/enums/ReportStatusTest.java | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminReportEventListener.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminReportEventListener.java index 9a6666bb..a3188655 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminReportEventListener.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminReportEventListener.java @@ -31,7 +31,7 @@ private Comment findComment(ProcessedReportEvent event) { } private void postProcessReportBy(ReportStatus reportStatus, Comment comment) { - if (reportStatus.isProcess()) { + if (reportStatus.isProcessed()) { comment.delete(); return; } diff --git a/space-d/src/main/java/com/dnd/spaced/core/report/domain/Report.java b/space-d/src/main/java/com/dnd/spaced/core/report/domain/Report.java index 1d35a4e4..c8959d3c 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/report/domain/Report.java +++ b/space-d/src/main/java/com/dnd/spaced/core/report/domain/Report.java @@ -34,13 +34,12 @@ public class Report extends CreateTimeEntity { private Long reporterId; @Enumerated(EnumType.STRING) - private ReportStatus reportStatus; + private ReportStatus reportStatus = ReportStatus.PENDING; public Report(ReportReason reportReason, Long commentId, Long reporterId) { this.reportReason = reportReason; this.commentId = commentId; this.reporterId = reporterId; - this.reportStatus = ReportStatus.PENDING; } public void process(ReportStatus reportStatus) { @@ -48,6 +47,6 @@ public void process(ReportStatus reportStatus) { } public boolean isProcessed() { - return this.reportStatus == ReportStatus.PROCESSED; + return this.reportStatus.isProcessed(); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/report/domain/enums/ReportStatus.java b/space-d/src/main/java/com/dnd/spaced/core/report/domain/enums/ReportStatus.java index 3c0e77c5..9eb5fdd9 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/report/domain/enums/ReportStatus.java +++ b/space-d/src/main/java/com/dnd/spaced/core/report/domain/enums/ReportStatus.java @@ -22,7 +22,7 @@ public static Optional findBy(String name) { .findAny(); } - public boolean isProcess() { + public boolean isProcessed() { return this == PROCESSED; } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/report/domain/enums/ReportStatusTest.java b/space-d/src/test/java/com/dnd/spaced/core/report/domain/enums/ReportStatusTest.java index 102ea828..f5b152ec 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/report/domain/enums/ReportStatusTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/report/domain/enums/ReportStatusTest.java @@ -50,7 +50,7 @@ private static Stream findByTestArguments() { ReportStatus reportStatus = ReportStatus.PROCESSED; // when - boolean actual = reportStatus.isProcess(); + boolean actual = reportStatus.isProcessed(); // then assertThat(actual).isTrue(); From b67824826184ead7b412dd63d07cd4c95554f5d2 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 20:20:57 +0900 Subject: [PATCH 015/115] =?UTF-8?q?refactor:=20Skill=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/spaced/core/skill/domain/Skill.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/skill/domain/Skill.java b/space-d/src/main/java/com/dnd/spaced/core/skill/domain/Skill.java index 1235f056..cadea3fa 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/skill/domain/Skill.java +++ b/space-d/src/main/java/com/dnd/spaced/core/skill/domain/Skill.java @@ -27,32 +27,32 @@ public class Skill { private Long accountId; - private long submitQuizQuestionCount = 0; + private long submitQuizQuestionCount = 0L; - private long quizQuestionCorrectCount = 0; + private long quizQuestionCorrectCount = 0L; - private long submitTodayQuizQuestionCount = 0; + private long submitTodayQuizQuestionCount = 0L; - private long todayQuizQuestionCorrectCount = 0; + private long todayQuizQuestionCorrectCount = 0L; public Skill(Long accountId) { this.accountId = accountId; } public double calculateQuizQuestionCorrectPercent(long totalQuizQuestionCount) { - if (totalQuizQuestionCount == 0L || quizQuestionCorrectCount == 0L) { + if (hasNeverAttemptedQuiz(totalQuizQuestionCount)) { return 0.0d; } - return ((double) quizQuestionCorrectCount / totalQuizQuestionCount) * PERCENT; + return calculateQuizCorrectPercent(totalQuizQuestionCount); } public double calculateTodayQuizQuestionCorrectPercent(long totalTodayQuizQuestionCount) { - if (totalTodayQuizQuestionCount == 0L || todayQuizQuestionCorrectCount == 0L) { + if (hasNeverAttemptedTodayQuiz(totalTodayQuizQuestionCount)) { return 0.0d; } - return ((double) todayQuizQuestionCorrectCount / totalTodayQuizQuestionCount) * PERCENT; + return calculateTodayQuizCorrectPercent(totalTodayQuizQuestionCount); } public void addCorrectQuizQuestion(long correctCount) { @@ -64,4 +64,20 @@ public void addCorrectTodayQuizQuestion(long correctCount) { this.submitTodayQuizQuestionCount += TODAY_QUIZ_QUESTION_COUNT; this.todayQuizQuestionCorrectCount += correctCount; } + + private boolean hasNeverAttemptedQuiz(long totalQuizQuestionCount) { + return totalQuizQuestionCount == 0L || quizQuestionCorrectCount == 0L; + } + + private double calculateQuizCorrectPercent(long totalQuizQuestionCount) { + return ((double) quizQuestionCorrectCount / totalQuizQuestionCount) * PERCENT; + } + + private boolean hasNeverAttemptedTodayQuiz(long totalTodayQuizQuestionCount) { + return totalTodayQuizQuestionCount == 0L || todayQuizQuestionCorrectCount == 0L; + } + + private double calculateTodayQuizCorrectPercent(long totalTodayQuizQuestionCount) { + return ((double) todayQuizQuestionCorrectCount / totalTodayQuizQuestionCount) * PERCENT; + } } From ac40ce57e2409444906a57a18b60cc3d8e2d3572 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 20:29:35 +0900 Subject: [PATCH 016/115] =?UTF-8?q?refactor:=20Word=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/spaced/core/word/domain/Word.java | 6 +++--- .../dnd/spaced/core/word/domain/WordExample.java | 4 ---- .../spaced/core/word/domain/WordExampleTest.java | 14 -------------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/Word.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/Word.java index 8c6de4a2..7a83606c 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/Word.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/Word.java @@ -57,12 +57,12 @@ public class Word extends BaseTimeEntity { @Builder private Word(String name, String meaning, String categoryName) { - validateContent(name); + validateName(name); this.name = name; this.wordMeaning = new WordMeaning(meaning); this.category = Category.findBy(categoryName) - .orElseThrow(() -> new InvalidCategoryNameException(String.format(EXCEPTION_FORMAT, name)));; + .orElseThrow(() -> new InvalidCategoryNameException(String.format(EXCEPTION_FORMAT, name))); } public void addPronunciation(Pronunciation pronunciation) { @@ -87,7 +87,7 @@ public void delete() { this.deleted = true; } - private void validateContent(String name) { + private void validateName(String name) { if (isInvalidName(name)) { throw new InvalidWordNameException("용어 이름은 null이거나 비어 있을 수 없습니다."); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/WordExample.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/WordExample.java index 0aa663bf..65cf1c20 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/WordExample.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/WordExample.java @@ -71,8 +71,4 @@ public void changeExample(String changedExample) { public void deleted() { this.deleted = true; } - - public boolean isEqualTo(Long id) { - return this.id.equals(id); - } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/domain/WordExampleTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/domain/WordExampleTest.java index 463454fc..1c622cdb 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/domain/WordExampleTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/domain/WordExampleTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.springframework.test.util.ReflectionTestUtils; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -64,19 +63,6 @@ class WordExampleTest { assertThat(wordExample.getContent()).isEqualTo(changedExample); } - @Test - void 용어_예문의_식별자_여부를_판단한다() { - // given - WordExample wordExample = WordExample.from("시스템 관리자는 신입 직원들에게 회사 내부 네트워크에 대한 Authorization을 부여했다."); - ReflectionTestUtils.setField(wordExample, "id", 1L); - - // when - boolean actual = wordExample.isEqualTo(1L); - - // then - assertThat(actual).isTrue(); - } - @Test void 용어_예문을_삭제한다() { // given From 857d82003c936b025f6527f68e4f597af7bef83d Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 21:21:32 +0900 Subject: [PATCH 017/115] =?UTF-8?q?refactor:=20QuizWordCountValidator=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminTodayQuizService.java | 6 +- .../quiz/application/CreateQuizService.java | 6 +- .../enums/QuizWordCountValidator.java | 36 ---- .../QuizCategoryNotFoundException.java | 2 +- .../schedule/CreateTodayQuizScheduler.java | 5 +- .../service/QuizWordCountValidator.java | 47 +++++ .../enums/QuizWordCountValidatorTest.java | 127 -------------- .../service/QuizWordCountValidatorTest.java | 160 ++++++++++++++++++ 8 files changed, 219 insertions(+), 170 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java rename space-d/src/main/java/com/dnd/spaced/core/quiz/application/{enums => }/exception/QuizCategoryNotFoundException.java (84%) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidator.java delete mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidatorTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java index a4c763e2..a2b169b7 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java @@ -1,7 +1,6 @@ package com.dnd.spaced.core.admin.application; import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; -import com.dnd.spaced.core.quiz.application.enums.QuizWordCountValidator; import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; import com.dnd.spaced.core.quiz.domain.TodayQuiz; @@ -12,6 +11,7 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizOptionRepository; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; +import com.dnd.spaced.core.quiz.domain.service.QuizWordCountValidator; import com.dnd.spaced.core.word.domain.WordMetadata; import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; @@ -71,7 +71,9 @@ private WordMetadata findWordMetadata() { } private void validateQuizWordCount(QuizCategory quizCategory, WordMetadata wordMetadata) { - if (QuizWordCountValidator.isBlocked(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { + QuizWordCountValidator validator = QuizWordCountValidator.create(); + + if (validator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { throw new InvalidTodayQuizWordCountException("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java index 3b3d0a10..c37dcf19 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java @@ -1,7 +1,6 @@ package com.dnd.spaced.core.quiz.application; import com.dnd.spaced.core.quiz.application.dto.request.CreateQuizRequest; -import com.dnd.spaced.core.quiz.application.enums.QuizWordCountValidator; import com.dnd.spaced.core.quiz.application.exception.InvalidQuizWordCountException; import com.dnd.spaced.core.quiz.application.exception.WordMetadataNotFoundException; import com.dnd.spaced.core.quiz.domain.Quiz; @@ -12,6 +11,7 @@ import com.dnd.spaced.core.quiz.domain.repository.QuizOptionRepository; import com.dnd.spaced.core.quiz.domain.repository.QuizQuestionRepository; import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; +import com.dnd.spaced.core.quiz.domain.service.QuizWordCountValidator; import com.dnd.spaced.core.word.domain.WordMetadata; import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; @@ -68,7 +68,9 @@ private WordMetadata findWordMetadata() { } private void validateQuizMetadata(QuizCategory quizCategory, WordMetadata wordMetadata) { - if (QuizWordCountValidator.isBlocked(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + if (quizWordCountValidator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { throw new InvalidQuizWordCountException("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java deleted file mode 100644 index aed08302..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidator.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.dnd.spaced.core.quiz.application.enums; - -import com.dnd.spaced.core.quiz.application.enums.exception.QuizCategoryNotFoundException; -import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; -import com.dnd.spaced.core.word.domain.WordMetadata; -import java.util.Arrays; -import java.util.function.BiPredicate; - -public enum QuizWordCountValidator { - - DEVELOP(QuizCategory.DEVELOP, WordMetadata::canGenerateBusinessQuiz), - DESIGN(QuizCategory.DESIGN, WordMetadata::canGenerateDesignQuiz), - BUSINESS(QuizCategory.BUSINESS, WordMetadata::canGenerateDesignQuiz), - TOTAL(QuizCategory.TOTAL, WordMetadata::canGenerateTotalQuiz); - - private final QuizCategory category; - private final BiPredicate validator; - - QuizWordCountValidator(QuizCategory quizCategory, BiPredicate validator) { - this.category = quizCategory; - this.validator = validator; - } - - public static boolean isValidate(QuizCategory category, WordMetadata wordMetadata, int requiredWordCount) { - return Arrays.stream(QuizWordCountValidator.values()) - .filter(validator -> validator.category.equals(category)) - .findAny() - .orElseThrow(() -> new QuizCategoryNotFoundException("지정한 퀴즈 카테고리를 찾을 수 없습니다.")) - .validator - .test(wordMetadata, requiredWordCount); - } - - public static boolean isBlocked(QuizCategory category, WordMetadata wordMetadata, int requiredWordCount) { - return !isValidate(category, wordMetadata, requiredWordCount); - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/exception/QuizCategoryNotFoundException.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/exception/QuizCategoryNotFoundException.java similarity index 84% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/exception/QuizCategoryNotFoundException.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/application/exception/QuizCategoryNotFoundException.java index 496b4f76..e6e1d877 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/enums/exception/QuizCategoryNotFoundException.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/exception/QuizCategoryNotFoundException.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.quiz.application.enums.exception; +package com.dnd.spaced.core.quiz.application.exception; import com.dnd.spaced.global.exception.base.QuizClientException; import com.dnd.spaced.global.exception.code.QuizErrorCode; diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java index 4d79c751..ba6dc4c3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java @@ -1,7 +1,6 @@ package com.dnd.spaced.core.quiz.application.schedule; import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; -import com.dnd.spaced.core.quiz.application.enums.QuizWordCountValidator; import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; import com.dnd.spaced.core.quiz.domain.TodayQuiz; @@ -12,6 +11,7 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizOptionRepository; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; +import com.dnd.spaced.core.quiz.domain.service.QuizWordCountValidator; import com.dnd.spaced.core.word.domain.WordMetadata; import com.dnd.spaced.core.word.domain.dto.SimpleWord; import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; @@ -110,8 +110,9 @@ private void validateQuizCreation(QuizCategory quizCategory) { .orElseThrow(() -> new WordMetadataNotFoundException( "용어 메타데이터가 정상적으로 설정되지 않았습니다.") ); + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); - if (QuizWordCountValidator.isBlocked(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { + if (quizWordCountValidator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { throw new InvalidTodayQuizWordCountException("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidator.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidator.java new file mode 100644 index 00000000..aa5808e0 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidator.java @@ -0,0 +1,47 @@ +package com.dnd.spaced.core.quiz.domain.service; + +import com.dnd.spaced.core.quiz.application.exception.QuizCategoryNotFoundException; +import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; +import com.dnd.spaced.core.word.domain.WordMetadata; +import java.util.EnumMap; +import java.util.Map; +import java.util.function.BiPredicate; + +public class QuizWordCountValidator { + + private final Map> validators; + + public static QuizWordCountValidator create() { + Map> validators = createValidators(); + + return new QuizWordCountValidator(validators); + } + + private static Map> createValidators() { + Map> validators = new EnumMap<>(QuizCategory.class); + + validators.put(QuizCategory.BUSINESS, WordMetadata::canGenerateBusinessQuiz); + validators.put(QuizCategory.DESIGN, WordMetadata::canGenerateDesignQuiz); + validators.put(QuizCategory.DEVELOP, WordMetadata::canGenerateDevelopQuiz); + validators.put(QuizCategory.TOTAL, WordMetadata::canGenerateTotalQuiz); + return validators; + } + + private QuizWordCountValidator(Map> validators) { + this.validators = validators; + } + + public boolean isValidate(QuizCategory category, WordMetadata wordMetadata, int requiredWordCount) { + BiPredicate validator = validators.get(category); + + if (validator == null) { + throw new QuizCategoryNotFoundException("지정한 퀴즈 카테고리를 찾을 수 없습니다."); + } + + return validator.test(wordMetadata, requiredWordCount); + } + + public boolean isInvalidate(QuizCategory category, WordMetadata wordMetadata, int requiredWordCount) { + return !isValidate(category, wordMetadata, requiredWordCount); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java deleted file mode 100644 index 4ecb3fe8..00000000 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/enums/QuizWordCountValidatorTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.dnd.spaced.core.quiz.application.enums; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; -import com.dnd.spaced.core.quiz.application.enums.exception.QuizCategoryNotFoundException; -import com.dnd.spaced.core.word.domain.WordMetadata; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class QuizWordCountValidatorTest { - - @Test - void 디자인_퀴즈를_생성할_수_있는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isValidate(QuizCategory.DESIGN, wordMetadata, 5); - - // then - assertThat(actual).isFalse(); - } - - @Test - void 디자인_퀴즈를_생성할_수_없는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.DESIGN, wordMetadata, 5); - - // then - assertThat(actual).isTrue(); - } - - @Test - void 비즈니스_퀴즈를_생성할_수_있는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isValidate(QuizCategory.BUSINESS, wordMetadata, 5); - - // then - assertThat(actual).isFalse(); - } - - @Test - void 비즈니스_퀴즈를_생성할_수_없는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.BUSINESS, wordMetadata, 5); - - // then - assertThat(actual).isTrue(); - } - - @Test - void 개발_퀴즈를_생성할_수_있는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isValidate(QuizCategory.DEVELOP, wordMetadata, 5); - - // then - assertThat(actual).isFalse(); - } - - @Test - void 개발_퀴즈를_생성할_수_없는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.DEVELOP, wordMetadata, 5); - - // then - assertThat(actual).isTrue(); - } - - @Test - void 전체_실무_퀴즈를_생성할_수_있는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isValidate(QuizCategory.TOTAL, wordMetadata, 5); - - // then - assertThat(actual).isFalse(); - } - - @Test - void 전체_실무_퀴즈를_생성할_수_없는지_여부를_확인한다() { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when - boolean actual = QuizWordCountValidator.isBlocked(QuizCategory.TOTAL, wordMetadata, 5); - - // then - assertThat(actual).isTrue(); - } - - @ParameterizedTest(name = "카테고리가 {0} 일 때 퀴즈 생성 여부를 판단할 수 없다") - @NullSource - void 유효한_퀴즈_카테고리가_아니라면_퀴즈_생성_가능_여부를_판단할_수_없다(QuizCategory invalidQuizCategory) { - // given - WordMetadata wordMetadata = new WordMetadata(); - - // when & then - assertThatThrownBy( - () -> QuizWordCountValidator.isValidate(invalidQuizCategory, wordMetadata, 5) - ).isInstanceOf(QuizCategoryNotFoundException.class) - .hasMessage("지정한 퀴즈 카테고리를 찾을 수 없습니다."); - } -} diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidatorTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidatorTest.java new file mode 100644 index 00000000..bc2163db --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/domain/service/QuizWordCountValidatorTest.java @@ -0,0 +1,160 @@ +package com.dnd.spaced.core.quiz.domain.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.dnd.spaced.core.quiz.application.exception.QuizCategoryNotFoundException; +import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; +import com.dnd.spaced.core.word.domain.WordMetadata; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class QuizWordCountValidatorTest { + + @Test + void 디자인_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_크거나_같다면_퀴즈를_생성할_수_있다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + wordMetadata.addDesignWordCount(); + wordMetadata.addDesignWordCount(); + wordMetadata.addDesignWordCount(); + wordMetadata.addDesignWordCount(); + wordMetadata.addDesignWordCount(); + + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.DESIGN, wordMetadata, 5); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 디자인_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_적다면_퀴즈를_생성할_수_없다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.DESIGN, wordMetadata, 5); + + // then + assertThat(actual).isFalse(); + } + + @Test + void 비즈니스_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_크거나_같다면_퀴즈를_생성할_수_있다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + wordMetadata.addBusinessWordCount(); + wordMetadata.addBusinessWordCount(); + wordMetadata.addBusinessWordCount(); + wordMetadata.addBusinessWordCount(); + wordMetadata.addBusinessWordCount(); + + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.BUSINESS, wordMetadata, 5); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 비즈니스_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_적다면_퀴즈를_생성할_수_없다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.BUSINESS, wordMetadata, 5); + + // then + assertThat(actual).isFalse(); + } + + @Test + void 개발_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_크거나_같다면_퀴즈를_생성할_수_있다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + wordMetadata.addDevelopWordCount(); + wordMetadata.addDevelopWordCount(); + wordMetadata.addDevelopWordCount(); + wordMetadata.addDevelopWordCount(); + wordMetadata.addDevelopWordCount(); + + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.DEVELOP, wordMetadata, 5); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 개발_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_적다면_퀴즈를_생성할_수_없다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.DEVELOP, wordMetadata, 5); + + // then + assertThat(actual).isFalse(); + } + + @Test + void 전체_실무_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_크거나_같다면_퀴즈를_생성할_수_있다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + wordMetadata.addDevelopWordCount(); + wordMetadata.addDevelopWordCount(); + wordMetadata.addBusinessWordCount(); + wordMetadata.addBusinessWordCount(); + wordMetadata.addDesignWordCount(); + + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.TOTAL, wordMetadata, 5); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 전체_실무_카테고리_용어_개수가_퀴즈_생성_시_필요한_개수보다_적다면_퀴즈를_생성할_수_없다() { + // given + WordMetadata wordMetadata = new WordMetadata(); + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when + boolean actual = quizWordCountValidator.isValidate(QuizCategory.TOTAL, wordMetadata, 5); + + // then + assertThat(actual).isFalse(); + } + + @ParameterizedTest(name = "카테고리가 {0} 일 때 퀴즈 생성 여부를 판단할 수 없다") + @NullSource + void 유효한_퀴즈_카테고리가_아니라면_퀴즈_생성_가능_여부를_판단할_수_없다(QuizCategory invalidQuizCategory) { + // given + WordMetadata wordMetadata = new WordMetadata(); + QuizWordCountValidator quizWordCountValidator = QuizWordCountValidator.create(); + + // when & then + assertThatThrownBy( + () -> quizWordCountValidator.isValidate(invalidQuizCategory, wordMetadata, 5) + ).isInstanceOf(QuizCategoryNotFoundException.class) + .hasMessage("지정한 퀴즈 카테고리를 찾을 수 없습니다."); + } +} From d86ecff959eecba2c0626e941454254ce759166c Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 22:12:08 +0900 Subject: [PATCH 018/115] =?UTF-8?q?refactor:=20PronunciationGatewayReposit?= =?UTF-8?q?ory=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PronunciationCrudRepository.java | 7 ------- .../PronunciationGatewayRepository.java | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 15 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationCrudRepository.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationCrudRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationCrudRepository.java deleted file mode 100644 index de289093..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationCrudRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.dnd.spaced.core.word.infrastructure.persistence; - -import com.dnd.spaced.core.word.domain.Pronunciation; -import org.springframework.data.repository.CrudRepository; - -interface PronunciationCrudRepository extends CrudRepository { -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepository.java index aea814df..daa5b0a0 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepository.java @@ -1,9 +1,8 @@ package com.dnd.spaced.core.word.infrastructure.persistence; -import static com.dnd.spaced.core.word.domain.QPronunciation.*; +import static com.dnd.spaced.core.word.domain.QPronunciation.pronunciation; import com.dnd.spaced.core.word.domain.Pronunciation; -import com.dnd.spaced.core.word.domain.QPronunciation; import com.dnd.spaced.core.word.domain.repository.PronunciationRepository; import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.Timestamp; @@ -24,23 +23,24 @@ public class PronunciationGatewayRepository implements PronunciationRepository { private final Clock clock; private final JPAQueryFactory queryFactory; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - private final PronunciationCrudRepository pronunciationCrudRepository; public PronunciationGatewayRepository( Clock clock, JdbcTemplate jdbcTemplate, - JPAQueryFactory queryFactory, - PronunciationCrudRepository pronunciationCrudRepository + JPAQueryFactory queryFactory ) { this.clock = clock; this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); this.queryFactory = queryFactory; - this.pronunciationCrudRepository = pronunciationCrudRepository; } @Override public Optional findBy(Long pronunciationId) { - return pronunciationCrudRepository.findById(pronunciationId); + Pronunciation result = queryFactory.selectFrom(pronunciation) + .where(pronunciation.id.eq(pronunciationId), pronunciation.deleted.isFalse()) + .fetchOne(); + + return Optional.ofNullable(result); } @Override @@ -68,12 +68,14 @@ INSERT INTO pronunciations(created_at, updated_at, content, pronunciation_type, @Override public long countBy(Long wordId) { String sql = """ - SELECT COUNT(id) FROM pronunciations WHERE word_id = :wordId + SELECT COUNT(id) FROM pronunciations WHERE word_id = :wordId AND deleted = false """; MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("wordId", wordId); Long result = namedParameterJdbcTemplate.queryForObject(sql, parameters, Long.class); + return result != null ? result : 0L; } From afddc8b5b16cf13cb0fea4c345382425638f43e4 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 7 Jul 2025 22:12:20 +0900 Subject: [PATCH 019/115] =?UTF-8?q?test:=20PronunciationGatewayRepository?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PronunciationGatewayRepositoryTest.java | 111 ++++++++++++++++++ .../test/resources/sql/word/pronunciation.sql | 8 ++ 2 files changed, 119 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java create mode 100644 space-d/src/test/resources/sql/word/pronunciation.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java new file mode 100644 index 00000000..74a52956 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java @@ -0,0 +1,111 @@ +package com.dnd.spaced.core.word.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.domain.Pronunciation; +import com.dnd.spaced.core.word.domain.Word; +import com.dnd.spaced.core.word.domain.repository.WordRepository; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class PronunciationGatewayRepositoryTest { + + @Autowired + PronunciationGatewayRepository pronunciationRepository; + + @Autowired + WordRepository wordRepository; + + @Test + @Sql("classpath:sql/word/pronunciation.sql") + void 삭제하지_않은_용어_발음을_조회한다() { + // when + Optional actual = pronunciationRepository.findBy(1L); + + // then + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getId()).isEqualTo(1L) + ); + } + + @Test + @Sql("classpath:sql/word/pronunciation.sql") + void 삭제한_용어_발음은_조회할_수_없다() { + // when + Optional actual = pronunciationRepository.findBy(2L); + + // then + assertThat(actual).isEmpty(); + } + + @Test + void 용어_발음_다수를_영속화_한다() { + // given + Word word = Word.builder() + .name("Authorization") + .meaning("인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘") + .categoryName("개발") + .build(); + + wordRepository.save(word); + + Pronunciation pronunciation1 = Pronunciation.of("어써라이제이션", "한글 발음"); + Pronunciation pronunciation2 = Pronunciation.of("어썰라이제이션", "한글 발음"); + Pronunciation pronunciation3 = Pronunciation.of("오써러제이션", "한글 발음"); + pronunciation1.initWord(word); + pronunciation2.initWord(word); + pronunciation3.initWord(word); + + List pronunciations = List.of(pronunciation1, pronunciation2, pronunciation3); + + // when + pronunciationRepository.saveAll(pronunciations); + + // then + Optional actual1 = pronunciationRepository.findBy(1L); + Optional actual2 = pronunciationRepository.findBy(2L); + Optional actual3 = pronunciationRepository.findBy(3L); + + assertAll( + () -> assertThat(actual1.get().getContent()).isEqualTo("어써라이제이션"), + () -> assertThat(actual2.get().getContent()).isEqualTo("어썰라이제이션"), + () -> assertThat(actual3.get().getContent()).isEqualTo("오써러제이션") + ); + } + + @Test + @Sql("classpath:sql/word/pronunciation.sql") + void 삭제되지_않은_발음_개수를_조회한다() { + // when + long actual = pronunciationRepository.countBy(1L); + + // then + assertThat(actual).isEqualTo(1L); + } + + @Test + @Sql("classpath:sql/word/pronunciation.sql") + @Transactional + void 특정_용어의_모든_용어_발음을_삭제한다() { + // when + pronunciationRepository.deleteAllBy(1L); + + // then + long actual = pronunciationRepository.countBy(1L); + + assertThat(actual).isZero(); + } +} diff --git a/space-d/src/test/resources/sql/word/pronunciation.sql b/space-d/src/test/resources/sql/word/pronunciation.sql new file mode 100644 index 00000000..3825f62f --- /dev/null +++ b/space-d/src/test/resources/sql/word/pronunciation.sql @@ -0,0 +1,8 @@ +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(1, now(), now(), 0, 'DEVELOP', 'Authorization', 0, '인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(1, now(), now(), '어써라이제이션', 'KOREAN', 1, false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(2, now(), now(), '오써러제이션', 'KOREAN', 1, true); From 338f7808690d01270301d6bc229d9b7a9862e404 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 00:41:44 +0900 Subject: [PATCH 020/115] =?UTF-8?q?refactor:=20WordExampleGatewayRepositor?= =?UTF-8?q?y=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordExampleCrudRepository.java | 7 ------- .../WordExampleGatewayRepository.java | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleCrudRepository.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleCrudRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleCrudRepository.java deleted file mode 100644 index f5c8f5cf..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleCrudRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.dnd.spaced.core.word.infrastructure.persistence; - -import com.dnd.spaced.core.word.domain.WordExample; -import org.springframework.data.repository.CrudRepository; - -interface WordExampleCrudRepository extends CrudRepository { -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepository.java index 7bf2fe58..260127f2 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepository.java @@ -24,24 +24,25 @@ public class WordExampleGatewayRepository implements WordExampleRepository { private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final JPAQueryFactory queryFactory; + public WordExampleGatewayRepository(Clock clock, JdbcTemplate jdbcTemplate, JPAQueryFactory queryFactory) { + this.clock = clock; + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + this.queryFactory = queryFactory; + } + @Override public Optional findBy(Long wordExampleId) { WordExample result = queryFactory.selectFrom(wordExample) - .where(wordExample.id.eq(wordExampleId)) + .where(wordExample.id.eq(wordExampleId), wordExample.deleted.isFalse()) .fetchOne(); return Optional.ofNullable(result); } - public WordExampleGatewayRepository(Clock clock, JdbcTemplate jdbcTemplate, JPAQueryFactory queryFactory) { - this.clock = clock; - this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); - this.queryFactory = queryFactory; - } - + @Override public long countBy(Long wordId) { String sql = """ - SELECT COUNT(id) FROM word_examples WHERE word_id = :wordId + SELECT COUNT(id) FROM word_examples WHERE word_id = :wordId AND deleted = false """; MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("wordId", wordId); @@ -54,7 +55,7 @@ SELECT COUNT(id) FROM word_examples WHERE word_id = :wordId public long update(Long wordExampleId, String example) { return queryFactory.update(wordExample) .set(wordExample.content, example) - .where(wordExample.id.eq(wordExampleId)) + .where(wordExample.id.eq(wordExampleId), wordExample.deleted.isFalse()) .execute(); } From 45ab015f59b18cb7c950ac1126714e6f5139a7aa Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 00:42:48 +0900 Subject: [PATCH 021/115] =?UTF-8?q?refactor:=20WordRandomGatewayRepository?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/WordRandomGatewayRepository.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java index bfe979e0..5a1e4dd5 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordRandomGatewayRepository.java @@ -18,7 +18,7 @@ public class WordRandomGatewayRepository implements WordRandomRepository { private static final int RANDOM_BOUND = 1_000_000; - private static final RowMapper simpleWordInfoRowMapper = (rs, ignoreRowNum) -> new SimpleWord( + private static final RowMapper simpleWordRowMapper = (rs, ignoreRowNum) -> new SimpleWord( rs.getLong(1), rs.getString(2), rs.getString(3), @@ -28,10 +28,7 @@ public class WordRandomGatewayRepository implements WordRandomRepository { private final WordRandomCrudRepository wordRandomCrudRepository; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - public WordRandomGatewayRepository( - WordRandomCrudRepository wordRandomCrudRepository, - JdbcTemplate jdbcTemplate - ) { + public WordRandomGatewayRepository(WordRandomCrudRepository wordRandomCrudRepository, JdbcTemplate jdbcTemplate) { this.wordRandomCrudRepository = wordRandomCrudRepository; this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); } @@ -87,7 +84,7 @@ private List findGoe(int random, QuizCategory quizCategory, long lim sqlParameters.addValue("category", quizCategory.name()); } - return namedParameterJdbcTemplate.query(sql, sqlParameters, simpleWordInfoRowMapper); + return namedParameterJdbcTemplate.query(sql, sqlParameters, simpleWordRowMapper); } private List findLoe(int random, QuizCategory quizCategory, long limit) { @@ -120,6 +117,6 @@ private List findLoe(int random, QuizCategory quizCategory, long lim sqlParameters.addValue("category", quizCategory.name()); } - return namedParameterJdbcTemplate.query(sql, sqlParameters, simpleWordInfoRowMapper); + return namedParameterJdbcTemplate.query(sql, sqlParameters, simpleWordRowMapper); } } From 1984a7198178c83f921762e23a992d7fbe576fc9 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 00:43:48 +0900 Subject: [PATCH 022/115] =?UTF-8?q?refactor:=20WordInfo=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/word/application/WordService.java | 18 ++--- .../dto/WordApplicationMapper.java | 14 ++-- .../dto/{WordInfo.java => WordView.java} | 10 +-- ...ordInfoMapper.java => WordViewMapper.java} | 32 ++++----- ...epository.java => WordViewRepository.java} | 10 +-- ...ry.java => WordViewGatewayRepository.java} | 70 ++++++++++--------- 6 files changed, 78 insertions(+), 76 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/{WordInfo.java => WordView.java} (65%) rename space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/{WordInfoMapper.java => WordViewMapper.java} (59%) rename space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/{WordInfoRepository.java => WordViewRepository.java} (63%) rename space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/{WordInfoGatewayRepository.java => WordViewGatewayRepository.java} (79%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java index 1795ecc0..a372c514 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java @@ -9,10 +9,10 @@ import com.dnd.spaced.core.word.application.event.dto.WordViewCountIncrementEvent; import com.dnd.spaced.core.word.application.event.dto.WordViewCountStatisticsEvent; import com.dnd.spaced.core.word.application.exception.WordNotFoundException; -import com.dnd.spaced.core.word.domain.dto.WordInfo; +import com.dnd.spaced.core.word.domain.dto.WordView; import com.dnd.spaced.core.word.domain.enums.Category; import com.dnd.spaced.core.word.domain.repository.PopularWordRepository; -import com.dnd.spaced.core.word.domain.repository.WordInfoRepository; +import com.dnd.spaced.core.word.domain.repository.WordViewRepository; import com.dnd.spaced.core.word.domain.dto.PopularWord; import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchCondition; import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchPageRequest; @@ -29,12 +29,12 @@ public class WordService { private final Clock clock; - private final WordInfoRepository wordInfoRepository; + private final WordViewRepository wordViewRepository; private final PopularWordRepository popularWordRepository; private final ApplicationEventPublisher eventPublisher; public WordResponse readWord(Long wordId) { - WordInfo word = findWord(wordId); + WordView word = findWord(wordId); publishWordViewCountIncrementedEvent(word); return WordApplicationMapper.toPronunciationInfoDto(word); @@ -45,7 +45,7 @@ public WordCollectionResponse readWords(ReadAllWordRequest request, Pageable pag .orElse(null); Category lastCategory = Category.findBy(request.lastCategoryName()) .orElse(null); - List words = wordInfoRepository.findAllBy(category, request.lastWordName(), lastCategory, pageable); + List words = wordViewRepository.findAllBy(category, request.lastWordName(), lastCategory, pageable); return WordApplicationMapper.toWordCollectionDto(words); } @@ -64,7 +64,7 @@ public WordCollectionResponse searchWord(SearchWordRequest request, Pageable pag pageable, request.lastWordName(), lastCategory); - List words = wordInfoRepository.search(wordSearchCondition, wordSearchPageRequest); + List words = wordViewRepository.search(wordSearchCondition, wordSearchPageRequest); return WordApplicationMapper.toWordCollectionDto(words); } @@ -75,12 +75,12 @@ public PopularWordCollectionResponse readPopularWords() { return WordApplicationMapper.toPopularWordCollectionDto(popularWords); } - private WordInfo findWord(Long wordId) { - return wordInfoRepository.findBy(wordId) + private WordView findWord(Long wordId) { + return wordViewRepository.findBy(wordId) .orElseThrow(() -> new WordNotFoundException("지정한 ID에 해당하는 용어를 찾을 수 없습니다.")); } - private void publishWordViewCountIncrementedEvent(WordInfo word) { + private void publishWordViewCountIncrementedEvent(WordView word) { eventPublisher.publishEvent(new WordViewCountIncrementEvent(word.id(), LocalDateTime.now(clock))); eventPublisher.publishEvent(new WordViewCountStatisticsEvent(word.id(), LocalDateTime.now(clock))); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java index fade4d51..7feb343c 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java @@ -5,9 +5,9 @@ import com.dnd.spaced.core.word.application.dto.response.WordCollectionResponse; import com.dnd.spaced.core.word.application.dto.response.WordResponse; import com.dnd.spaced.core.word.application.dto.response.WordResponse.PronunciationResponse; -import com.dnd.spaced.core.word.domain.dto.WordInfo; -import com.dnd.spaced.core.word.domain.dto.WordInfo.PronunciationInfo; -import com.dnd.spaced.core.word.domain.dto.WordInfo.WordExampleInfo; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.dto.WordView.PronunciationView; +import com.dnd.spaced.core.word.domain.dto.WordView.WordExampleView; import com.dnd.spaced.core.word.domain.dto.PopularWord; import java.util.List; import lombok.AccessLevel; @@ -16,7 +16,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class WordApplicationMapper { - public static WordCollectionResponse toWordCollectionDto(List words) { + public static WordCollectionResponse toWordCollectionDto(List words) { if (words.isEmpty()) { return new WordCollectionResponse(List.of(), null); } @@ -28,11 +28,11 @@ public static WordCollectionResponse toWordCollectionDto(List words) { return new WordCollectionResponse(wordResponses, wordResponses.get(wordResponses.size() - 1).name()); } - public static WordResponse toPronunciationInfoDto(WordInfo word) { + public static WordResponse toPronunciationInfoDto(WordView word) { List pronunciations = toPronunciationInfoDto(word.pronunciations()); List examples = word.wordExamples() .stream() - .map(WordExampleInfo::example) + .map(WordExampleView::example) .toList(); return new WordResponse( @@ -61,7 +61,7 @@ public static PopularWordCollectionResponse toPopularWordCollectionDto(List toPronunciationInfoDto(List pronunciations) { + private static List toPronunciationInfoDto(List pronunciations) { return pronunciations.stream() .map( pronunciation -> new PronunciationResponse( diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/WordInfo.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/WordView.java similarity index 65% rename from space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/WordInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/WordView.java index 330661b3..e0712684 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/WordInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/WordView.java @@ -5,20 +5,20 @@ import com.dnd.spaced.core.word.domain.enums.PronunciationType; import java.util.List; -public record WordInfo( +public record WordView( Long id, String name, Category category, WordMeaning wordMeaning, - List pronunciations, - List wordExamples, + List pronunciations, + List wordExamples, long viewCount, long bookmarkCount ) { - public record PronunciationInfo(Long id, Long wordId, String content, PronunciationType type) { + public record PronunciationView(Long id, Long wordId, String content, PronunciationType type) { } - public record WordExampleInfo(Long id, Long wordId, String example) { + public record WordExampleView(Long id, Long wordId, String example) { } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/WordInfoMapper.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/WordViewMapper.java similarity index 59% rename from space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/WordInfoMapper.java rename to space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/WordViewMapper.java index 240775fa..8f4e9ede 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/WordInfoMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/dto/mapper/WordViewMapper.java @@ -1,50 +1,50 @@ package com.dnd.spaced.core.word.domain.dto.mapper; -import static com.dnd.spaced.core.word.domain.dto.WordInfo.PronunciationInfo; +import static com.dnd.spaced.core.word.domain.dto.WordView.PronunciationView; import com.dnd.spaced.core.word.domain.Pronunciation; import com.dnd.spaced.core.word.domain.Word; import com.dnd.spaced.core.word.domain.WordExample; -import com.dnd.spaced.core.word.domain.dto.WordInfo; -import com.dnd.spaced.core.word.domain.dto.WordInfo.WordExampleInfo; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.dto.WordView.WordExampleView; import java.util.List; import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class WordInfoMapper { +public final class WordViewMapper { - public static WordInfo toDto(Word word, List pronunciations) { - List pronunciationInfos = pronunciations.stream() - .map(WordInfoMapper::toPronunciationInfoDto) + public static WordView toDto(Word word, List pronunciations) { + List pronunciationViews = pronunciations.stream() + .map(WordViewMapper::toPronunciationInfoDto) .toList(); - List wordExampleInfos = word.getWordExamples() + List wordExampleViews = word.getWordExamples() .stream() - .map(WordInfoMapper::toWordExampleInfoDto) + .map(WordViewMapper::toWordExampleInfoDto) .toList(); - return new WordInfo( + return new WordView( word.getId(), word.getName(), word.getCategory(), word.getWordMeaning(), - pronunciationInfos, - wordExampleInfos, + pronunciationViews, + wordExampleViews, word.getViewCount(), word.getBookmarkCount() ); } - private static WordExampleInfo toWordExampleInfoDto(WordExample wordExample) { - return new WordExampleInfo( + private static WordExampleView toWordExampleInfoDto(WordExample wordExample) { + return new WordExampleView( wordExample.getId(), wordExample.getWord().getId(), wordExample.getContent() ); } - private static PronunciationInfo toPronunciationInfoDto(Pronunciation pronunciation) { - return new PronunciationInfo( + private static PronunciationView toPronunciationInfoDto(Pronunciation pronunciation) { + return new PronunciationView( pronunciation.getId(), pronunciation.getWord().getId(), pronunciation.getContent(), diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordInfoRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordViewRepository.java similarity index 63% rename from space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordInfoRepository.java rename to space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordViewRepository.java index 972d1172..3c855df9 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordInfoRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordViewRepository.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.word.domain.repository; -import com.dnd.spaced.core.word.domain.dto.WordInfo; +import com.dnd.spaced.core.word.domain.dto.WordView; import com.dnd.spaced.core.word.domain.enums.Category; import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchCondition; import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchPageRequest; @@ -8,11 +8,11 @@ import java.util.Optional; import org.springframework.data.domain.Pageable; -public interface WordInfoRepository { +public interface WordViewRepository { - Optional findBy(Long wordId); + Optional findBy(Long wordId); - List findAllBy(Category category, String lastWordName, Category lastCategory, Pageable pageable); + List findAllBy(Category category, String lastWordName, Category lastCategory, Pageable pageable); - List search(WordSearchCondition condition, WordSearchPageRequest pageRequest); + List search(WordSearchCondition condition, WordSearchPageRequest pageRequest); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordInfoGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java similarity index 79% rename from space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordInfoGatewayRepository.java rename to space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java index 066e024a..4f67924e 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordInfoGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java @@ -6,10 +6,10 @@ import com.dnd.spaced.core.word.domain.Pronunciation; import com.dnd.spaced.core.word.domain.Word; -import com.dnd.spaced.core.word.domain.dto.WordInfo; -import com.dnd.spaced.core.word.domain.dto.mapper.WordInfoMapper; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.dto.mapper.WordViewMapper; import com.dnd.spaced.core.word.domain.enums.Category; -import com.dnd.spaced.core.word.domain.repository.WordInfoRepository; +import com.dnd.spaced.core.word.domain.repository.WordViewRepository; import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchCondition; import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchPageRequest; import com.querydsl.core.types.dsl.BooleanExpression; @@ -27,12 +27,12 @@ @Repository @RequiredArgsConstructor -public class WordInfoGatewayRepository implements WordInfoRepository { +public class WordViewGatewayRepository implements WordViewRepository { private final JPAQueryFactory queryFactory; @Override - public Optional findBy(Long wordId) { + public Optional findBy(Long wordId) { Word result = findWord(wordId); if (result == null) { @@ -41,14 +41,17 @@ public Optional findBy(Long wordId) { List pronunciations = findPronunciations(result); - return Optional.of(WordInfoMapper.toDto(result, pronunciations)); + return Optional.of(WordViewMapper.toDto(result, pronunciations)); } @Override - public List findAllBy(Category category, String lastWordName, Category lastCategory, Pageable pageable) { + public List findAllBy(Category category, String lastWordName, Category lastCategory, Pageable pageable) { List wordIds = queryFactory.select(word.id) .from(word) - .where(buildWordPaginationCondition(category, lastWordName, lastCategory)) + .where( + buildWordPaginationCondition(category, lastWordName, lastCategory), + word.deleted.isFalse() + ) .orderBy(word.name.asc(), word.category.asc(), word.id.desc()) .limit(pageable.getPageSize()) .fetch(); @@ -57,18 +60,18 @@ public List findAllBy(Category category, String lastWordName, Category return Collections.emptyList(); } - return mapToWordInfos(wordIds); + return mapToWordViews(wordIds); } @Override - public List search(WordSearchCondition condition, WordSearchPageRequest pageRequest) { + public List search(WordSearchCondition condition, WordSearchPageRequest pageRequest) { List wordIds = fetchFilteredWordIds(condition, pageRequest); if (wordIds.isEmpty()) { return Collections.emptyList(); } - return mapToWordInfos(wordIds); + return mapToWordViews(wordIds); } private Word findWord(Long wordId) { @@ -102,27 +105,6 @@ private BooleanExpression buildWordPaginationCondition( return null; } - private Map fetchWordsWithExamples(List wordIds) { - return queryFactory.selectFrom(word) - .innerJoin(word.wordExamples, wordExample).fetchJoin() - .where(word.id.in(wordIds), wordExample.deleted.isFalse()) - .fetch() - .stream() - .collect(Collectors.toMap(Word::getId, Function.identity())); - } - - private Map> fetchPronunciations(List wordIds) { - return queryFactory - .selectFrom(pronunciation) - .where(pronunciation.word.id.in(wordIds), pronunciation.deleted.isFalse()) - .fetch() - .stream() - .collect(Collectors.groupingBy( - pronunciation -> pronunciation.getWord().getId(), - Collectors.mapping(Function.identity(), Collectors.toList()) - )); - } - private List fetchFilteredWordIds(WordSearchCondition condition, WordSearchPageRequest pageRequest) { return queryFactory .select(word.id) @@ -168,12 +150,32 @@ private BooleanExpression existsPronunciationContentCondition(String content) { .exists(); } - private List mapToWordInfos(List wordIds) { + private List mapToWordViews(List wordIds) { Map wordMap = fetchWordsWithExamples(wordIds); Map> pronunciationMap = fetchPronunciations(wordIds); return wordIds.stream() - .map(id -> WordInfoMapper.toDto(wordMap.get(id), pronunciationMap.get(id))) + .map(id -> WordViewMapper.toDto(wordMap.get(id), pronunciationMap.get(id))) .toList(); } + + private Map fetchWordsWithExamples(List wordIds) { + return queryFactory.selectFrom(word) + .innerJoin(word.wordExamples, wordExample).fetchJoin() + .where(word.id.in(wordIds), wordExample.deleted.isFalse()) + .fetch() + .stream() + .collect(Collectors.toMap(Word::getId, Function.identity())); + } + + private Map> fetchPronunciations(List wordIds) { + return queryFactory.selectFrom(pronunciation) + .where(pronunciation.word.id.in(wordIds), pronunciation.deleted.isFalse()) + .fetch() + .stream() + .collect(Collectors.groupingBy( + pronunciation -> pronunciation.getWord().getId(), + Collectors.mapping(Function.identity(), Collectors.toList()) + )); + } } From eba83e7690f44d5d3a41b22f5b89d87e5a153aa4 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 00:44:26 +0900 Subject: [PATCH 023/115] =?UTF-8?q?test:=20WordExampleGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordExampleGatewayRepositoryTest.java | 124 ++++++++++++++++++ .../test/resources/sql/word/word_example.sql | 8 ++ 2 files changed, 132 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java create mode 100644 space-d/src/test/resources/sql/word/word_example.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java new file mode 100644 index 00000000..763152d7 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java @@ -0,0 +1,124 @@ +package com.dnd.spaced.core.word.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.domain.Word; +import com.dnd.spaced.core.word.domain.WordExample; +import com.dnd.spaced.core.word.domain.repository.WordRepository; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class WordExampleGatewayRepositoryTest { + + @Autowired + WordExampleGatewayRepository wordExampleRepository; + + @Autowired + WordRepository wordRepository; + + @Test + @Sql("classpath:sql/word/word_example.sql") + void 삭제하지_않은_용어_예문을_조회한다() { + // when + Optional actual = wordExampleRepository.findBy(1L); + + // then + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getId()).isEqualTo(1L) + ); + } + + @Test + @Sql("classpath:sql/word/word_example.sql") + void 삭제한_용어_예문은_조회할_수_없다() { + // when + Optional actual = wordExampleRepository.findBy(2L); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql("classpath:sql/word/word_example.sql") + void 삭제하지_않은_용어_예문_개수를_조회한다() { + // when + long actual = wordExampleRepository.countBy(1L); + + // then + assertThat(actual).isEqualTo(1L); + } + + @Test + @Sql("classpath:sql/word/word_example.sql") + @Transactional + void 삭제하지_않은_용어_예문을_수정한다() { + // when + wordExampleRepository.update(1L, "웹 API 요청 시 사용자 인증을 위해서는 HTTP 헤더에 Authorization 토큰을 포함해야 합니다. 서버는 이 토큰을 검증하여 접근 권한을 확인한 후 요청된 데이터를 반환합니다."); + + // then + WordExample actual = wordExampleRepository.findBy(1L).get(); + + assertThat(actual.getContent()).isEqualTo("웹 API 요청 시 사용자 인증을 위해서는 HTTP 헤더에 Authorization 토큰을 포함해야 합니다. 서버는 이 토큰을 검증하여 접근 권한을 확인한 후 요청된 데이터를 반환합니다."); + } + + @Test + @Sql("classpath:sql/word/word_example.sql") + @Transactional + void 특정_용어의_모든_용어_예문을_삭제한다() { + // when + wordExampleRepository.deleteAllBy(1L); + + // then + long actual = wordExampleRepository.countBy(1L); + + assertThat(actual).isZero(); + } + + @Test + void 용어_예문_다수를_영속화한다() { + // given + Word word = Word.builder() + .name("Authorization") + .meaning("인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘") + .categoryName("개발") + .build(); + + wordRepository.save(word); + + WordExample wordExample1 = WordExample.from("게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다."); + WordExample wordExample2 = WordExample.from("해당 에러는 Authorization 과정이 실패해서 발생했습니다"); + WordExample wordExample3 = WordExample.from("웹 API 요청 시 사용자 인증을 위해서는 HTTP 헤더에 Authorization 토큰을 포함해야 합니다. 서버는 이 토큰을 검증하여 접근 권한을 확인한 후 요청된 데이터를 반환합니다."); + wordExample1.initWord(word); + wordExample2.initWord(word); + wordExample3.initWord(word); + + List wordExamples = List.of(wordExample1, wordExample2, wordExample3); + + // when + wordExampleRepository.saveAll(wordExamples); + + // then + WordExample actual1 = wordExampleRepository.findBy(1L).get(); + WordExample actual2 = wordExampleRepository.findBy(2L).get(); + WordExample actual3 = wordExampleRepository.findBy(3L).get(); + + assertAll( + () -> assertThat(actual1.getContent()).isEqualTo(wordExample1.getContent()), + () -> assertThat(actual2.getContent()).isEqualTo(wordExample2.getContent()), + () -> assertThat(actual3.getContent()).isEqualTo(wordExample3.getContent()) + ); + } +} diff --git a/space-d/src/test/resources/sql/word/word_example.sql b/space-d/src/test/resources/sql/word/word_example.sql new file mode 100644 index 00000000..dc05c88d --- /dev/null +++ b/space-d/src/test/resources/sql/word/word_example.sql @@ -0,0 +1,8 @@ +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(1, now(), now(), 0, 'DEVELOP', 'Authorization', 0, '인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘', false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(1, now(), now(), '게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.', 1, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(2, now(), now(), '해당 에러는 Authorization 과정이 실패해서 발생했습니다', 1, true); From f6df16875c99b9fc1f2250507cb678f6fc0d05f3 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 00:45:14 +0900 Subject: [PATCH 024/115] =?UTF-8?q?fix:=20=EC=9A=A9=EC=96=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=88=98=20=EC=A6=9D=EA=B0=80=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=B4=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EB=8C=80=EC=83=81=EC=9D=80=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/persistence/WordGatewayRepository.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java index 8cc9724d..cf518d3d 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java @@ -37,7 +37,7 @@ public boolean existsBy(Long wordId) { public void updateViewCount(Long wordId) { queryFactory.update(word) .set(word.viewCount, word.viewCount.add(1)) - .where(word.id.eq(wordId)) + .where(word.id.eq(wordId), word.deleted.isFalse()) .execute(); } @@ -46,7 +46,7 @@ public void updateViewCount(List wordViewCountStatis for (WordViewCountStatisticsDto dto : wordViewCountStatisticsDtos) { queryFactory.update(word) .set(word.viewCount, word.viewCount.add(dto.viewCount())) - .where(word.id.eq(dto.id())) + .where(word.id.eq(dto.id()), word.deleted.isFalse()) .execute(); } } @@ -55,7 +55,7 @@ public void updateViewCount(List wordViewCountStatis public void addBookmarkCount(Long wordId) { queryFactory.update(word) .set(word.bookmarkCount, word.bookmarkCount.add(1)) - .where(word.id.eq(wordId)) + .where(word.id.eq(wordId), word.deleted.isFalse()) .execute(); } @@ -63,7 +63,7 @@ public void addBookmarkCount(Long wordId) { public void updateSubtractBookmarkCount(Long wordId) { queryFactory.update(word) .set(word.bookmarkCount, word.bookmarkCount.subtract(1)) - .where(word.id.eq(wordId)) + .where(word.id.eq(wordId), word.deleted.isFalse()) .execute(); } From ce97c8693de87c7027c165da2accb2fa0167fd94 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 00:56:42 +0900 Subject: [PATCH 025/115] =?UTF-8?q?refactor:=20WordRepository=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/listener/WordBookmarkCountListener.java | 2 +- .../event/listener/WordViewCounterEventListener.java | 2 +- .../word/application/schedule/PopularWordScheduler.java | 2 +- .../spaced/core/word/domain/repository/WordRepository.java | 6 +++--- .../infrastructure/persistence/WordGatewayRepository.java | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordBookmarkCountListener.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordBookmarkCountListener.java index a52c03e5..49914c58 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordBookmarkCountListener.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordBookmarkCountListener.java @@ -26,6 +26,6 @@ public void listen(WordBookmarkCountIncrementedEvent event) { @EventListener @Transactional public void listen(WordBookmarkCountDecrementedEvent event) { - wordRepository.updateSubtractBookmarkCount(event.wordId()); + wordRepository.subtractBookmarkCount(event.wordId()); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordViewCounterEventListener.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordViewCounterEventListener.java index ade5e779..74bd4274 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordViewCounterEventListener.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/event/listener/WordViewCounterEventListener.java @@ -26,7 +26,7 @@ public class WordViewCounterEventListener { @EventListener public void listen(WordViewCountIncrementEvent event) { if (!popularWordRepository.existsBy(event.wordId(), event.localDateTime())) { - transactionTemplate.executeWithoutResult(status -> wordRepository.updateViewCount(event.wordId())); + transactionTemplate.executeWithoutResult(status -> wordRepository.addViewCount(event.wordId())); String requestId = MDC.get(REQUEST_ID); log.info("[{}] wordId : {}, localDateTime : {}", requestId, event.wordId(), event.localDateTime()); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/schedule/PopularWordScheduler.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/schedule/PopularWordScheduler.java index 991f5d0e..33f04bb5 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/schedule/PopularWordScheduler.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/schedule/PopularWordScheduler.java @@ -54,7 +54,7 @@ private void updatePopularWordViewCount(LocalDateTime yesterday) { .toList(); List dtos = wordViewCountStatisticsRepository.findAllBy(ids, yesterday); - wordRepository.updateViewCount(dtos); + wordRepository.addViewCount(dtos); } private List calculatePopularWordInfo(List ranking, List names) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRepository.java index 44a67bde..3a401811 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/domain/repository/WordRepository.java @@ -11,13 +11,13 @@ public interface WordRepository { boolean existsBy(Long wordId); - void updateViewCount(Long wordId); + void addViewCount(Long wordId); - void updateViewCount(List wordViewCountStatisticsDtos); + void addViewCount(List wordViewCountStatisticsDtos); void addBookmarkCount(Long wordId); - void updateSubtractBookmarkCount(Long wordId); + void subtractBookmarkCount(Long wordId); List findNameAllBy(Long[] wordIds); diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java index cf518d3d..b9ebdbec 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepository.java @@ -34,7 +34,7 @@ public boolean existsBy(Long wordId) { } @Override - public void updateViewCount(Long wordId) { + public void addViewCount(Long wordId) { queryFactory.update(word) .set(word.viewCount, word.viewCount.add(1)) .where(word.id.eq(wordId), word.deleted.isFalse()) @@ -42,7 +42,7 @@ public void updateViewCount(Long wordId) { } @Override - public void updateViewCount(List wordViewCountStatisticsDtos) { + public void addViewCount(List wordViewCountStatisticsDtos) { for (WordViewCountStatisticsDto dto : wordViewCountStatisticsDtos) { queryFactory.update(word) .set(word.viewCount, word.viewCount.add(dto.viewCount())) @@ -60,7 +60,7 @@ public void addBookmarkCount(Long wordId) { } @Override - public void updateSubtractBookmarkCount(Long wordId) { + public void subtractBookmarkCount(Long wordId) { queryFactory.update(word) .set(word.bookmarkCount, word.bookmarkCount.subtract(1)) .where(word.id.eq(wordId), word.deleted.isFalse()) From 00af5586497f78d50aa8d97f5ed7393bc781b49d Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 23:20:28 +0900 Subject: [PATCH 026/115] =?UTF-8?q?test:=20WordGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordGatewayRepositoryTest.java | 226 ++++++++++++++++++ space-d/src/test/resources/sql/word/word.sql | 9 + 2 files changed, 235 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java new file mode 100644 index 00000000..a3fa27df --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java @@ -0,0 +1,226 @@ +package com.dnd.spaced.core.word.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.domain.Word; +import jakarta.persistence.EntityManager; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class WordGatewayRepositoryTest { + + @Autowired + WordGatewayRepository wordGatewayRepository; + + @Autowired + WordCrudRepository wordCrudRepository; + + @Autowired + EntityManager em; + + @Test + void 용어를_영속화_한다() { + // given + Word word = Word.builder() + .name("Authorization") + .meaning("인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘") + .categoryName("개발") + .build(); + + // when + Word actual = wordGatewayRepository.save(word); + + // then + assertThat(actual.getId()).isPositive(); + } + + @Test + @Sql("classpath:sql/word/word.sql") + void 삭제하지_않은_용어의_영속화_여부를_확인한다() { + // when + boolean actual = wordGatewayRepository.existsBy(1L); + + // then + assertThat(actual).isTrue(); + } + + @Test + @Sql("classpath:sql/word/word.sql") + void 삭제한_용어의_영속화_여부를_확인한다() { + // when + boolean actual = wordGatewayRepository.existsBy(2L); + + // then + assertThat(actual).isFalse(); + } + + @Test + @Sql("classpath:sql/word/word.sql") + void 삭제하지_않은_용어를_조회한다() { + // when + Optional actual = wordGatewayRepository.findBy(1L); + + // then + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getId()).isEqualTo(1L) + ); + } + + @Test + @Sql("classpath:sql/word/word.sql") + void 삭제한_용어는_조회할_수_없다() { + // when + Optional actual = wordGatewayRepository.findBy(2L); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql("classpath:sql/word/word.sql") + @Transactional + void 삭제하지_않은_용어의_조회_수를_1_증가시킨다() { + // given + Word before = wordGatewayRepository.findBy(1L).get(); + + assertThat(before.getViewCount()).isZero(); + + // when + wordGatewayRepository.addViewCount(1L); + + // then + em.clear(); + + Word actual = wordGatewayRepository.findBy(1L).get(); + + assertThat(actual.getViewCount()).isEqualTo(1L); + } + + @Test + @Sql("classpath:sql/word/word.sql") + @Transactional + void 삭제한_용어의_조회_수는_증가되지_않는다() { + // given + Word before = wordCrudRepository.findById(2L).get(); + + assertThat(before.getViewCount()).isZero(); + + // when + wordGatewayRepository.addViewCount(2L); + + // then + em.clear(); + + Word actual = wordGatewayRepository.findBy(1L).get(); + + assertThat(actual.getViewCount()).isZero(); + } + + @Test + @Sql("classpath:sql/word/word.sql") + @Transactional + void 삭제하지_않은_용어의_북마크_수를_1_증가시킨다() { + // given + Word before = wordGatewayRepository.findBy(1L).get(); + + assertThat(before.getBookmarkCount()).isZero(); + + // when + wordGatewayRepository.addBookmarkCount(1L); + + // then + em.clear(); + + Word actual = wordGatewayRepository.findBy(1L).get(); + + assertThat(actual.getBookmarkCount()).isEqualTo(1L); + } + + @Test + @Sql("classpath:sql/word/word.sql") + @Transactional + void 삭제한_용어의_북마크_수는_증가되지_않는다() { + // given + Word before = wordCrudRepository.findById(2L).get(); + + assertThat(before.getBookmarkCount()).isEqualTo(1L); + + // when + wordGatewayRepository.addBookmarkCount(2L); + + // then + em.clear(); + + Word actual = wordCrudRepository.findById(2L).get(); + + assertThat(actual.getBookmarkCount()).isEqualTo(1L); + } + + @Test + @Sql("classpath:sql/word/word.sql") + @Transactional + void 삭제하지_않은_용어의_북마크_수를_1_감소시킨다() { + // given + wordGatewayRepository.addBookmarkCount(1L); + + Word before = wordGatewayRepository.findBy(1L).get(); + + assertThat(before.getBookmarkCount()).isEqualTo(1L); + + // when + wordGatewayRepository.subtractBookmarkCount(1L); + + // then + em.clear(); + + Word actual = wordGatewayRepository.findBy(1L).get(); + + assertThat(actual.getBookmarkCount()).isZero(); + } + + @Test + @Sql("classpath:sql/word/word.sql") + @Transactional + void 삭제한_용어의_북마크_수는_감소되지_않는다() { + // given + Word before = wordCrudRepository.findById(2L).get(); + + assertThat(before.getBookmarkCount()).isEqualTo(1L); + + // when + wordGatewayRepository.subtractBookmarkCount(2L); + + // then + em.clear(); + + Word actual = wordCrudRepository.findById(2L).get(); + + assertThat(actual.getBookmarkCount()).isEqualTo(1L); + } + + @Test + @Sql("classpath:sql/word/word.sql") + void 삭제하지_않은_모든_용어의_이름을_조회한다() { + // when + List actual = wordGatewayRepository.findNameAllBy(new Long[]{1L, 2L}); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0)).isEqualTo("Authorization") + ); + } +} diff --git a/space-d/src/test/resources/sql/word/word.sql b/space-d/src/test/resources/sql/word/word.sql index 0c46de6e..6e2159f4 100644 --- a/space-d/src/test/resources/sql/word/word.sql +++ b/space-d/src/test/resources/sql/word/word.sql @@ -6,3 +6,12 @@ VALUES(1, now(), now(), '어써라이제이션', 'KOREAN', 1, false); INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) VALUES(1, now(), now(), '게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.', 1, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(2, now(), now(), 1, 'DEVELOP', 'YAML', 0, '사람이 읽기 쉬운 데이터 형식으로, 주로 설정 파일에 사용됩니다.', true); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(2, now(), now(), '야믈', 'KOREAN', 2, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(2, now(), now(), 'YAML은 설정 파일이나 데이터 교환 포맷으로 자주 사용됩니다.', 2, false); From 5d44c6f02464537ba9e531acb026fde43beb5e66 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 23:23:48 +0900 Subject: [PATCH 027/115] =?UTF-8?q?refactor:=20CommentRepository=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentService.java | 4 ++-- .../CommentResponseCollectionMapper.java | 16 ++++++------- .../listener/CommentLikeCountListener.java | 4 ++-- ...ikedCommentInfo.java => LikedComment.java} | 4 ++-- .../domain/repository/CommentRepository.java | 8 +++---- .../persistence/CommentGatewayRepository.java | 24 +++++++++---------- .../CommentGatewayRepositoryTest.java | 8 +++---- 7 files changed, 34 insertions(+), 34 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/{LikedCommentInfo.java => LikedComment.java} (69%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java index 744dd28a..ea95c307 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java @@ -8,7 +8,7 @@ import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; import com.dnd.spaced.core.comment.application.exception.WordNotFoundException; import com.dnd.spaced.core.comment.domain.Comment; -import com.dnd.spaced.core.comment.domain.dto.LikedCommentInfo; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; import com.dnd.spaced.core.comment.domain.repository.CommentRepository; import com.dnd.spaced.core.word.domain.repository.WordRepository; import java.util.List; @@ -50,7 +50,7 @@ public void updateComment(Long accountId, Long commentId, UpdateCommentRequest r } public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { - List comments = commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); + List comments = commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); return CommentResponseCollectionMapper.toCollectionDto(comments); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java index c805c0c9..e250c99f 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java @@ -5,7 +5,7 @@ import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse.CommentResponse; import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse.CommentWriterResponse; import com.dnd.spaced.core.comment.domain.Comment; -import com.dnd.spaced.core.comment.domain.dto.LikedCommentInfo; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; import java.util.List; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -13,7 +13,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class CommentResponseCollectionMapper { - public static CommentCollectionResponse toCollectionDto(List comments) { + public static CommentCollectionResponse toCollectionDto(List comments) { if (comments.isEmpty()) { return new CommentCollectionResponse(List.of(), null); } @@ -25,15 +25,15 @@ public static CommentCollectionResponse toCollectionDto(List c return new CommentCollectionResponse(responses, comments.get(comments.size() - 1).comment().getId()); } - private static CommentResponse toCommentResponse(LikedCommentInfo likedCommentInfo) { + private static CommentResponse toCommentResponse(LikedComment likedComment) { return new CommentResponse( - toCommentContentResponse(likedCommentInfo.comment()), + toCommentContentResponse(likedComment.comment()), toCommentWriterResponse( - likedCommentInfo.writerNickname(), - likedCommentInfo.writerProfileImage(), - likedCommentInfo.comment().getWriterId() + likedComment.writerNickname(), + likedComment.writerProfileImage(), + likedComment.comment().getWriterId() ), - likedCommentInfo.isLiked() + likedComment.isLiked() ); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/event/listener/CommentLikeCountListener.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/event/listener/CommentLikeCountListener.java index aa09f41b..2dc54c64 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/event/listener/CommentLikeCountListener.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/event/listener/CommentLikeCountListener.java @@ -17,12 +17,12 @@ public class CommentLikeCountListener { @EventListener @Transactional public void listen(LikedEvent event) { - commentRepository.increaseLikeCount(event.commentId()); + commentRepository.addLikeCount(event.commentId()); } @EventListener @Transactional public void listen(UnlikedEvent event) { - commentRepository.decreaseLikeCount(event.commentId()); + commentRepository.subtractLikeCount(event.commentId()); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/LikedCommentInfo.java b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/LikedComment.java similarity index 69% rename from space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/LikedCommentInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/LikedComment.java index a3de4bac..01e60d02 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/LikedCommentInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/dto/LikedComment.java @@ -2,14 +2,14 @@ import com.dnd.spaced.core.comment.domain.Comment; -public record LikedCommentInfo( +public record LikedComment( Comment comment, boolean isLiked, String writerNickname, String writerProfileImage ) { - public LikedCommentInfo(Comment comment, String writerNickname, String writerProfileImage) { + public LikedComment(Comment comment, String writerNickname, String writerProfileImage) { this(comment, false, writerNickname, writerProfileImage); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java index 2cdc54b8..5d41f74d 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/domain/repository/CommentRepository.java @@ -1,7 +1,7 @@ package com.dnd.spaced.core.comment.domain.repository; import com.dnd.spaced.core.comment.domain.Comment; -import com.dnd.spaced.core.comment.domain.dto.LikedCommentInfo; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Pageable; @@ -12,9 +12,9 @@ public interface CommentRepository { Optional findBy(Long commentId); - List findAllBy(Long accountId, Long wordId, Long lastCommentId, Pageable pageable); + List findAllBy(Long accountId, Long wordId, Long lastCommentId, Pageable pageable); - void increaseLikeCount(Long commentId); + void addLikeCount(Long commentId); - void decreaseLikeCount(Long commentId); + void subtractLikeCount(Long commentId); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java index 46953f44..5dd55529 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java @@ -6,7 +6,7 @@ import com.dnd.spaced.core.comment.domain.Comment; import com.dnd.spaced.core.comment.domain.repository.CommentRepository; -import com.dnd.spaced.core.comment.domain.dto.LikedCommentInfo; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; import com.dnd.spaced.global.consts.AuthConst; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; @@ -40,7 +40,7 @@ public Optional findBy(Long commentId) { } @Override - public List findAllBy(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { + public List findAllBy(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { if (AuthConst.GUEST_ACCOUNT_ID.equals(accountId)) { return findAllWithoutIsLikedBy(wordId, lastCommentId, pageable); } @@ -49,7 +49,7 @@ public List findAllBy(Long accountId, Long wordId, Long lastCo } @Override - public void increaseLikeCount(Long commentId) { + public void addLikeCount(Long commentId) { queryFactory.update(comment) .set(comment.likeCount, comment.likeCount.add(1)) .where(comment.id.eq(commentId), comment.deleted.isFalse()) @@ -57,20 +57,20 @@ public void increaseLikeCount(Long commentId) { } @Override - public void decreaseLikeCount(Long commentId) { + public void subtractLikeCount(Long commentId) { queryFactory.update(comment) .set(comment.likeCount, comment.likeCount.subtract(1)) .where(comment.id.eq(commentId), comment.deleted.isFalse()) .execute(); } - private List findAllWithIsLikedBy( + private List findAllWithIsLikedBy( Long accountId, Long wordId, Long lastCommentId, Pageable pageable ) { - return queryFactory.select(getLikedCommentInfoWithLiked()) + return queryFactory.select(getLikedCommentWithLiked()) .from(comment) .leftJoin(account).on(comment.writerId.eq(account.id)) .leftJoin(like).on(comment.id.eq(like.commentId), like.accountId.eq(accountId)) @@ -84,8 +84,8 @@ private List findAllWithIsLikedBy( .fetch(); } - private List findAllWithoutIsLikedBy(Long wordId, Long lastCommentId, Pageable pageable) { - return queryFactory.select(getLikedCommentInfoWithoutLiked()) + private List findAllWithoutIsLikedBy(Long wordId, Long lastCommentId, Pageable pageable) { + return queryFactory.select(getLikedCommentWithoutLiked()) .from(comment) .leftJoin(account).on(comment.writerId.eq(account.id)) .where( @@ -106,9 +106,9 @@ private BooleanExpression gtLastCommentId(Long commentId) { return comment.id.gt(commentId); } - private ConstructorExpression getLikedCommentInfoWithLiked() { + private ConstructorExpression getLikedCommentWithLiked() { return Projections.constructor( - LikedCommentInfo.class, + LikedComment.class, comment, like.id.isNotNull(), account.profile.nickname, @@ -116,9 +116,9 @@ private ConstructorExpression getLikedCommentInfoWithLiked() { ); } - private ConstructorExpression getLikedCommentInfoWithoutLiked() { + private ConstructorExpression getLikedCommentWithoutLiked() { return Projections.constructor( - LikedCommentInfo.class, + LikedComment.class, comment, account.profile.nickname, account.profile.profileImage diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java index 9b88b6d3..90221688 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java @@ -112,7 +112,7 @@ class CommentGatewayRepositoryTest { @Transactional void 삭제하지_않은_댓글에_좋아요_카운트를_1_증가시킨다() { // when - commentRepository.increaseLikeCount(1L); + commentRepository.addLikeCount(1L); // then Optional actual = commentRepository.findBy(1L); @@ -128,7 +128,7 @@ class CommentGatewayRepositoryTest { @Transactional void 삭제한_댓글에_좋아요_카운트를_증가시킬_수_없다() { // when - commentRepository.increaseLikeCount(2L); + commentRepository.addLikeCount(2L); // then Optional actual = commentCrudRepository.findById(2L); @@ -145,7 +145,7 @@ class CommentGatewayRepositoryTest { @Transactional void 삭제하지_않은_댓글에_좋아요_카운트를_1_감소시킨다() { // when - commentRepository.decreaseLikeCount(1L); + commentRepository.subtractLikeCount(1L); // then Optional actual = commentRepository.findBy(1L); @@ -161,7 +161,7 @@ class CommentGatewayRepositoryTest { @Transactional void 삭제한_댓글에_좋아요_카운트를_감소시킬_수_없다() { // when - commentRepository.decreaseLikeCount(2L); + commentRepository.subtractLikeCount(2L); // then Optional actual = commentCrudRepository.findById(2L); From e7a64a3ee86c9ab0f3d44e3ccfa9cc6bb2312576 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 8 Jul 2025 23:52:11 +0900 Subject: [PATCH 028/115] =?UTF-8?q?test:=20CommentGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentGatewayRepositoryTest.java | 99 +++++++++++++------ .../test/resources/sql/comment/comment.sql | 3 + .../resources/sql/comment/deleted_comment.sql | 2 - .../resources/sql/comment/deleted_word.sql | 8 -- .../src/test/resources/sql/comment/word.sql | 10 ++ 5 files changed, 83 insertions(+), 39 deletions(-) delete mode 100644 space-d/src/test/resources/sql/comment/deleted_comment.sql delete mode 100644 space-d/src/test/resources/sql/comment/deleted_word.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java index 90221688..f4f3a107 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepositoryTest.java @@ -4,7 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.dnd.spaced.core.comment.domain.Comment; -import com.dnd.spaced.core.comment.domain.dto.LikedCommentInfo; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; +import jakarta.persistence.EntityManager; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayNameGeneration; @@ -22,19 +23,29 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class CommentGatewayRepositoryTest { + private static final Long WRITER_ID = 1L; + private static final Long READER_ID = 2L; + private static final Long GUEST_ID = -1L; + private static final Long WORD_ID = 1L; + private static final Long COMMENT_ID = 1L; + private static final Long DELETED_COMMENT_ID = 2L; + @Autowired - CommentGatewayRepository commentRepository; + CommentGatewayRepository commentGatewayRepository; @Autowired CommentCrudRepository commentCrudRepository; + @Autowired + EntityManager em; + @Test void 댓글을_영속화_한다() { // given - Comment comment = new Comment(1L, 1L, "이 용어 언제 쓰는건가요?"); + Comment comment = new Comment(WRITER_ID, WORD_ID, "이 용어 언제 쓰는건가요?"); // when - Comment actual = commentRepository.save(comment); + Comment actual = commentGatewayRepository.save(comment); // then assertThat(actual.getId()).isPositive(); @@ -45,22 +56,25 @@ class CommentGatewayRepositoryTest { "classpath:sql/comment/word.sql", "classpath:sql/comment/comment.sql" }) - void 삭제하지_않은_댓글을_댓글_식별자로_조회한다() { + void 삭제하지_않은_댓글을_id로_조회한다() { // when - Optional actual = commentRepository.findBy(1L); + Optional actual = commentGatewayRepository.findBy(COMMENT_ID); // then - assertThat(actual).isPresent(); + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getId()).isEqualTo(COMMENT_ID) + ); } @Test @Sql(value = { "classpath:sql/comment/word.sql", - "classpath:sql/comment/deleted_comment.sql" + "classpath:sql/comment/comment.sql" }) - void 삭제한_댓글은_댓글_식별자로_조회할_수_없다() { + void 삭제한_댓글은_id로_조회할_수_없다() { // when - Optional actual = commentRepository.findBy(2L); + Optional actual = commentGatewayRepository.findBy(DELETED_COMMENT_ID); // then assertThat(actual).isEmpty(); @@ -70,12 +84,11 @@ class CommentGatewayRepositoryTest { @Sql(value = { "classpath:sql/comment/word.sql", "classpath:sql/comment/comment.sql", - "classpath:sql/comment/deleted_comment.sql", "classpath:sql/comment/like.sql", }) void 로그인하지_않은_상태로_용어의_삭제하지_않은_모든_댓글을_조회한다() { // when - List actual = commentRepository.findAllBy(-1L, 1L, null, PageRequest.of(0, 10)); + List actual = commentGatewayRepository.findAllBy(GUEST_ID, WORD_ID, null, PageRequest.of(0, 10)); // then assertAll( @@ -89,12 +102,11 @@ class CommentGatewayRepositoryTest { @Sql(value = { "classpath:sql/comment/word.sql", "classpath:sql/comment/comment.sql", - "classpath:sql/comment/deleted_comment.sql", "classpath:sql/comment/like.sql", }) void 로그인한_상태로_용어의_삭제하지_않은_모든_댓글을_조회한다() { // when - List actual = commentRepository.findAllBy(2L, 1L, null, PageRequest.of(0, 10)); + List actual = commentGatewayRepository.findAllBy(READER_ID, WORD_ID, null, PageRequest.of(0, 10)); // then assertAll( @@ -107,33 +119,48 @@ class CommentGatewayRepositoryTest { @Test @Sql(value = { "classpath:sql/comment/word.sql", - "classpath:sql/comment/comment.sql" + "classpath:sql/comment/comment.sql", + "classpath:sql/comment/like.sql" }) @Transactional void 삭제하지_않은_댓글에_좋아요_카운트를_1_증가시킨다() { + // given + Comment comment = commentGatewayRepository.findBy(COMMENT_ID).get(); + + assertThat(comment.getLikeCount()).isEqualTo(1L); + // when - commentRepository.addLikeCount(1L); + commentGatewayRepository.addLikeCount(COMMENT_ID); // then - Optional actual = commentRepository.findBy(1L); + em.clear(); - assertThat(actual.get().getLikeCount()).isEqualTo(1L); + Optional actual = commentGatewayRepository.findBy(COMMENT_ID); + + assertThat(actual.get().getLikeCount()).isEqualTo(2L); } @Test @Sql(value = { "classpath:sql/comment/word.sql", - "classpath:sql/comment/deleted_comment.sql" + "classpath:sql/comment/comment.sql" }) @Transactional void 삭제한_댓글에_좋아요_카운트를_증가시킬_수_없다() { + // given + Comment deletedComment = commentCrudRepository.findById(DELETED_COMMENT_ID).get(); + + assertThat(deletedComment.getLikeCount()).isEqualTo(1L); + // when - commentRepository.addLikeCount(2L); + commentGatewayRepository.addLikeCount(DELETED_COMMENT_ID); // then - Optional actual = commentCrudRepository.findById(2L); + em.clear(); - assertThat(actual.get().getLikeCount()).isZero(); + Optional actual = commentCrudRepository.findById(DELETED_COMMENT_ID); + + assertThat(actual.get().getLikeCount()).isEqualTo(1L); } @Test @@ -144,28 +171,42 @@ class CommentGatewayRepositoryTest { }) @Transactional void 삭제하지_않은_댓글에_좋아요_카운트를_1_감소시킨다() { + // given + Comment comment = commentGatewayRepository.findBy(COMMENT_ID).get(); + + assertThat(comment.getLikeCount()).isEqualTo(1L); + // when - commentRepository.subtractLikeCount(1L); + commentGatewayRepository.subtractLikeCount(COMMENT_ID); // then - Optional actual = commentRepository.findBy(1L); + em.clear(); - assertThat(actual.get().getLikeCount()).isZero(); + Comment actual = commentGatewayRepository.findBy(COMMENT_ID).get(); + + assertThat(actual.getLikeCount()).isZero(); } @Test @Sql(value = { "classpath:sql/comment/word.sql", - "classpath:sql/comment/deleted_comment.sql" + "classpath:sql/comment/comment.sql" }) @Transactional void 삭제한_댓글에_좋아요_카운트를_감소시킬_수_없다() { + // given + Comment deletedComment = commentCrudRepository.findById(DELETED_COMMENT_ID).get(); + + assertThat(deletedComment.getLikeCount()).isEqualTo(1L); + // when - commentRepository.subtractLikeCount(2L); + commentGatewayRepository.subtractLikeCount(DELETED_COMMENT_ID); // then - Optional actual = commentCrudRepository.findById(2L); + em.clear(); + + Optional actual = commentCrudRepository.findById(DELETED_COMMENT_ID); - assertThat(actual.get().getLikeCount()).isZero(); + assertThat(actual.get().getLikeCount()).isEqualTo(1L); } } diff --git a/space-d/src/test/resources/sql/comment/comment.sql b/space-d/src/test/resources/sql/comment/comment.sql index b57f7dab..51b9d095 100644 --- a/space-d/src/test/resources/sql/comment/comment.sql +++ b/space-d/src/test/resources/sql/comment/comment.sql @@ -1,2 +1,5 @@ INSERT INTO comments(id, created_at, updated_at, content, deleted, like_count, word_id, writer_id) VALUES (1, now(), now(), '이 용어는 언제 쓰는건가요?', false, 0, 1, 1); + +INSERT INTO comments(id, created_at, updated_at, content, deleted, like_count, word_id, writer_id) +VALUES (2, now(), now(), '이 용어 쓰는걸 본 적이 없는거 같아요', true, 1, 1, 1); diff --git a/space-d/src/test/resources/sql/comment/deleted_comment.sql b/space-d/src/test/resources/sql/comment/deleted_comment.sql deleted file mode 100644 index 2ced3f0f..00000000 --- a/space-d/src/test/resources/sql/comment/deleted_comment.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO comments(id, created_at, updated_at, content, deleted, like_count, word_id, writer_id) -VALUES (2, now(), now(), '이 용어 쓰는걸 본 적이 없는거 같아요', true, 0, 1, 1); diff --git a/space-d/src/test/resources/sql/comment/deleted_word.sql b/space-d/src/test/resources/sql/comment/deleted_word.sql deleted file mode 100644 index 3b7aca38..00000000 --- a/space-d/src/test/resources/sql/comment/deleted_word.sql +++ /dev/null @@ -1,8 +0,0 @@ -INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) -VALUES(2, now(), now(), 0, 'DEVELOP', 'HTTP', 0, 'HyperText Transfer Protocol의 약자.', true); - -INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) -VALUES(2, now(), now(), '에이치티티피', 'KOREAN', 2, true); - -INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) -VALUES(2, now(), now(), 'HTTP 통신이 정상적으로 수행되지 않는 것 같습니다.', 2, true); diff --git a/space-d/src/test/resources/sql/comment/word.sql b/space-d/src/test/resources/sql/comment/word.sql index 0c46de6e..1adc2269 100644 --- a/space-d/src/test/resources/sql/comment/word.sql +++ b/space-d/src/test/resources/sql/comment/word.sql @@ -6,3 +6,13 @@ VALUES(1, now(), now(), '어써라이제이션', 'KOREAN', 1, false); INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) VALUES(1, now(), now(), '게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.', 1, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(2, now(), now(), 0, 'DEVELOP', 'HTTP', 0, 'HyperText Transfer Protocol의 약자.', true); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(2, now(), now(), '에이치티티피', 'KOREAN', 2, true); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(2, now(), now(), 'HTTP 통신이 정상적으로 수행되지 않는 것 같습니다.', 2, true); + From 79379c1259d32edb6b31dd088aefc4b188c29866 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 9 Jul 2025 00:16:26 +0900 Subject: [PATCH 029/115] =?UTF-8?q?test:=20AccountGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AccountGatewayRepositoryTest.java | 72 ++++++++++++------- .../test/resources/sql/account/account.sql | 9 +++ .../resources/sql/account/deleted_account.sql | 2 - .../sql/account/pre_init_account.sql | 4 -- 4 files changed, 54 insertions(+), 33 deletions(-) delete mode 100644 space-d/src/test/resources/sql/account/deleted_account.sql delete mode 100644 space-d/src/test/resources/sql/account/pre_init_account.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java index df0374ad..1e26d137 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java @@ -1,6 +1,7 @@ package com.dnd.spaced.core.account.infrastructure.persistence; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import com.dnd.spaced.core.account.domain.Account; import com.dnd.spaced.core.account.domain.embed.Social; @@ -21,23 +22,32 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class AccountGatewayRepositoryTest { + private static final long ACCOUNT_ID = 1L; + private static final long DELETED_ACCOUNT_ID = 2L; + private static final long PRE_INIT_ACCOUNT_ID = 3L; + private static final long DELETED_PRE_INIT_ACCOUNT_ID = 4L; + private static final RegistrationId REGISTRATION_ID = RegistrationId.KAKAO; + private static final String SOCIAL_ID = "12345"; + private static final String DELETED_SOCIAL_ID = "54321"; + @Autowired AccountRepository accountRepository; @Test @Sql("classpath:sql/account/account.sql") - void 탈퇴하지_않은_회원의_식별자로_영속화_여부를_확인한다() { + void 탈퇴하지_않은_회원의_id로_영속화_여부를_확인한다() { // when - boolean actual = accountRepository.existsBy(1L); + boolean actual = accountRepository.existsBy(ACCOUNT_ID); // then assertThat(actual).isTrue(); } @Test - void 탈퇴한_회원의_식별자로_영속화_여부를_확인한다() { + @Sql("classpath:sql/account/account.sql") + void 탈퇴한_회원의_id로_영속화_여부를_확인한다() { // when - boolean actual = accountRepository.existsBy(2L); + boolean actual = accountRepository.existsBy(DELETED_ACCOUNT_ID); // then assertThat(actual).isFalse(); @@ -45,19 +55,22 @@ class AccountGatewayRepositoryTest { @Test @Sql("classpath:sql/account/account.sql") - void 탈퇴하지_않은_회원을_조회한다() { + void 탈퇴하지_않은_회원을_id로_조회한다() { // when - Optional actual = accountRepository.findBy(1L); + Optional actual = accountRepository.findBy(ACCOUNT_ID); // then - assertThat(actual).isPresent(); + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getId()).isEqualTo(ACCOUNT_ID) + ); } @Test - @Sql("classpath:sql/account/deleted_account.sql") - void 탈퇴한_회원은_조회할_수_없다() { + @Sql("classpath:sql/account/account.sql") + void 탈퇴한_회원은_id로_조회할_수_없다() { // when - Optional actual = accountRepository.findBy(2L); + Optional actual = accountRepository.findBy(DELETED_ACCOUNT_ID); // then assertThat(actual).isEmpty(); @@ -83,24 +96,26 @@ class AccountGatewayRepositoryTest { @Test @Sql("classpath:sql/account/account.sql") - void 회원의_소셜_정보로_탈퇴하지_않은_회원을_조회한다() { + void 회원의_소셜_id로_탈퇴하지_않은_회원을_조회한다() { // given - RegistrationId registrationId = RegistrationId.KAKAO; - Social social = new Social(registrationId, "12345"); + Social social = new Social(REGISTRATION_ID, SOCIAL_ID); // when Optional actual = accountRepository.findBy(social); // then - assertThat(actual).isPresent(); + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getSocial().getRegistrationId()).isEqualTo(REGISTRATION_ID), + () -> assertThat(actual.get().getSocial().getSocialId()).isEqualTo(SOCIAL_ID) + ); } @Test - @Sql("classpath:sql/account/deleted_account.sql") - void 회원의_소셜_정보로_탈퇴한_회원을_조회한다() { + @Sql("classpath:sql/account/account.sql") + void 탈퇴한_회원은_소셜_id로_조회할_수_없다() { // given - RegistrationId registrationId = RegistrationId.KAKAO; - Social social = new Social(registrationId, "54321"); + Social social = new Social(REGISTRATION_ID, DELETED_SOCIAL_ID); // when Optional actual = accountRepository.findBy(social); @@ -110,30 +125,33 @@ class AccountGatewayRepositoryTest { } @Test - @Sql("classpath:sql/account/pre_init_account.sql") - void 초기_설정된_회원을_식별자로_조회한다() { + @Sql("classpath:sql/account/account.sql") + void 초기_설정만_진행한_회원을_id로_조회한다() { // when - Optional actual = accountRepository.findPreInitializationAccountBy(3L); + Optional actual = accountRepository.findPreInitializationAccountBy(PRE_INIT_ACCOUNT_ID); // then - assertThat(actual).isPresent(); + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getId()).isEqualTo(PRE_INIT_ACCOUNT_ID) + ); } @Test @Sql("classpath:sql/account/account.sql") - void 설정을_모두_완료한_회원을_식별자로_조회할_수_없다() { + void 설정을_모두_완료한_회원을_id로_조회할_수_없다() { // when - Optional actual = accountRepository.findPreInitializationAccountBy(1L); + Optional actual = accountRepository.findPreInitializationAccountBy(ACCOUNT_ID); // then assertThat(actual).isEmpty(); } @Test - @Sql("classpath:sql/account/pre_init_account.sql") - void 탈퇴한_회원을_식별자로_조회할_수_없다() { + @Sql("classpath:sql/account/account.sql") + void 초기_설정_진행_도중_탈퇴한_회원을_id로_조회할_수_없다() { // when - Optional actual = accountRepository.findPreInitializationAccountBy(4L); + Optional actual = accountRepository.findPreInitializationAccountBy(DELETED_PRE_INIT_ACCOUNT_ID); // then assertThat(actual).isEmpty(); diff --git a/space-d/src/test/resources/sql/account/account.sql b/space-d/src/test/resources/sql/account/account.sql index 457d5842..803f6ab3 100644 --- a/space-d/src/test/resources/sql/account/account.sql +++ b/space-d/src/test/resources/sql/account/account.sql @@ -1,2 +1,11 @@ INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) VALUES (1, now(), now(), 'KAKAO', '12345', 'STARTUP', 'UNDER_FIRST', 'ETC', false, '재빠른지구001', 'earth.png'); + +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +VALUES (2, now(), now(), 'KAKAO', '54321', 'STARTUP', 'UNDER_FIRST', 'ETC', true, '재빠른지구002', 'earth.png'); + +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +VALUES (3, now(), now(), 'KAKAO', '13245', null, null, null, false, '재빠른지구002', 'earth.png'); + +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +VALUES (4, now(), now(), 'KAKAO', '32145', null, null, null, true, '재빠른지구002', 'earth.png'); diff --git a/space-d/src/test/resources/sql/account/deleted_account.sql b/space-d/src/test/resources/sql/account/deleted_account.sql deleted file mode 100644 index 4f6dd70b..00000000 --- a/space-d/src/test/resources/sql/account/deleted_account.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) -VALUES (2, now(), now(), 'KAKAO', '54321', 'STARTUP', 'UNDER_FIRST', 'ETC', true, '재빠른지구002', 'earth.png'); diff --git a/space-d/src/test/resources/sql/account/pre_init_account.sql b/space-d/src/test/resources/sql/account/pre_init_account.sql deleted file mode 100644 index 793478ff..00000000 --- a/space-d/src/test/resources/sql/account/pre_init_account.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) -VALUES (3, now(), now(), 'KAKAO', '54321', null, null, null, false, '재빠른지구002', 'earth.png'); -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) -VALUES (4, now(), now(), 'KAKAO', '54321', null, null, null, true, '재빠른지구002', 'earth.png'); From 6c4ed37f6068b9b6d764d6461c5f5c1793e8bf0c Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 9 Jul 2025 00:19:44 +0900 Subject: [PATCH 030/115] =?UTF-8?q?test:=20PronunciationGatewayRepository?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PronunciationGatewayRepositoryTest.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java index 74a52956..e9db9200 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/PronunciationGatewayRepositoryTest.java @@ -22,6 +22,10 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class PronunciationGatewayRepositoryTest { + private static final long PRONUNCIATION_ID = 1L; + private static final long DELETED_PRONUNCIATION_ID = 2L; + private static final long WORD_ID = 1L; + @Autowired PronunciationGatewayRepository pronunciationRepository; @@ -30,22 +34,22 @@ class PronunciationGatewayRepositoryTest { @Test @Sql("classpath:sql/word/pronunciation.sql") - void 삭제하지_않은_용어_발음을_조회한다() { + void 삭제하지_않은_용어_발음을_id로_조회한다() { // when - Optional actual = pronunciationRepository.findBy(1L); + Optional actual = pronunciationRepository.findBy(PRONUNCIATION_ID); // then assertAll( () -> assertThat(actual).isPresent(), - () -> assertThat(actual.get().getId()).isEqualTo(1L) + () -> assertThat(actual.get().getId()).isEqualTo(PRONUNCIATION_ID) ); } @Test @Sql("classpath:sql/word/pronunciation.sql") - void 삭제한_용어_발음은_조회할_수_없다() { + void 삭제한_용어_발음은_id로_조회할_수_없다() { // when - Optional actual = pronunciationRepository.findBy(2L); + Optional actual = pronunciationRepository.findBy(DELETED_PRONUNCIATION_ID); // then assertThat(actual).isEmpty(); @@ -90,7 +94,7 @@ class PronunciationGatewayRepositoryTest { @Sql("classpath:sql/word/pronunciation.sql") void 삭제되지_않은_발음_개수를_조회한다() { // when - long actual = pronunciationRepository.countBy(1L); + long actual = pronunciationRepository.countBy(WORD_ID); // then assertThat(actual).isEqualTo(1L); @@ -104,7 +108,7 @@ class PronunciationGatewayRepositoryTest { pronunciationRepository.deleteAllBy(1L); // then - long actual = pronunciationRepository.countBy(1L); + long actual = pronunciationRepository.countBy(WORD_ID); assertThat(actual).isZero(); } From b33df667205079a150c012808afe4dd173a50799 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 9 Jul 2025 00:24:00 +0900 Subject: [PATCH 031/115] =?UTF-8?q?test:=20WordExampleGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordExampleGatewayRepositoryTest.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java index 763152d7..6067d8f7 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordExampleGatewayRepositoryTest.java @@ -22,6 +22,10 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class WordExampleGatewayRepositoryTest { + private static final long WORD_EXAMPLE_ID = 1L; + private static final long DELETED_WORD_EXAMPLE_ID = 2L; + private static final long WORD_ID = 1L; + @Autowired WordExampleGatewayRepository wordExampleRepository; @@ -30,22 +34,22 @@ class WordExampleGatewayRepositoryTest { @Test @Sql("classpath:sql/word/word_example.sql") - void 삭제하지_않은_용어_예문을_조회한다() { + void 삭제하지_않은_용어_예문을_id로_조회한다() { // when - Optional actual = wordExampleRepository.findBy(1L); + Optional actual = wordExampleRepository.findBy(WORD_EXAMPLE_ID); // then assertAll( () -> assertThat(actual).isPresent(), - () -> assertThat(actual.get().getId()).isEqualTo(1L) + () -> assertThat(actual.get().getId()).isEqualTo(WORD_EXAMPLE_ID) ); } @Test @Sql("classpath:sql/word/word_example.sql") - void 삭제한_용어_예문은_조회할_수_없다() { + void 삭제한_용어_예문은_id로_조회할_수_없다() { // when - Optional actual = wordExampleRepository.findBy(2L); + Optional actual = wordExampleRepository.findBy(DELETED_WORD_EXAMPLE_ID); // then assertThat(actual).isEmpty(); @@ -55,7 +59,7 @@ class WordExampleGatewayRepositoryTest { @Sql("classpath:sql/word/word_example.sql") void 삭제하지_않은_용어_예문_개수를_조회한다() { // when - long actual = wordExampleRepository.countBy(1L); + long actual = wordExampleRepository.countBy(WORD_ID); // then assertThat(actual).isEqualTo(1L); @@ -66,10 +70,10 @@ class WordExampleGatewayRepositoryTest { @Transactional void 삭제하지_않은_용어_예문을_수정한다() { // when - wordExampleRepository.update(1L, "웹 API 요청 시 사용자 인증을 위해서는 HTTP 헤더에 Authorization 토큰을 포함해야 합니다. 서버는 이 토큰을 검증하여 접근 권한을 확인한 후 요청된 데이터를 반환합니다."); + wordExampleRepository.update(WORD_EXAMPLE_ID, "웹 API 요청 시 사용자 인증을 위해서는 HTTP 헤더에 Authorization 토큰을 포함해야 합니다. 서버는 이 토큰을 검증하여 접근 권한을 확인한 후 요청된 데이터를 반환합니다."); // then - WordExample actual = wordExampleRepository.findBy(1L).get(); + WordExample actual = wordExampleRepository.findBy(WORD_EXAMPLE_ID).get(); assertThat(actual.getContent()).isEqualTo("웹 API 요청 시 사용자 인증을 위해서는 HTTP 헤더에 Authorization 토큰을 포함해야 합니다. 서버는 이 토큰을 검증하여 접근 권한을 확인한 후 요청된 데이터를 반환합니다."); } @@ -79,10 +83,10 @@ class WordExampleGatewayRepositoryTest { @Transactional void 특정_용어의_모든_용어_예문을_삭제한다() { // when - wordExampleRepository.deleteAllBy(1L); + wordExampleRepository.deleteAllBy(WORD_ID); // then - long actual = wordExampleRepository.countBy(1L); + long actual = wordExampleRepository.countBy(WORD_ID); assertThat(actual).isZero(); } From 5bfd4b27afb72a74993ba76a3e3b51ce8a45a3fc Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 9 Jul 2025 21:50:49 +0900 Subject: [PATCH 032/115] =?UTF-8?q?test:=20WordGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordGatewayRepositoryTest.java | 89 ++++++++++--------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java index a3fa27df..0c88ab2d 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordGatewayRepositoryTest.java @@ -21,6 +21,9 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class WordGatewayRepositoryTest { + private static final long WORD_ID = 1L; + private static final long DELETED_WORD_ID = 2L; + @Autowired WordGatewayRepository wordGatewayRepository; @@ -50,7 +53,7 @@ class WordGatewayRepositoryTest { @Sql("classpath:sql/word/word.sql") void 삭제하지_않은_용어의_영속화_여부를_확인한다() { // when - boolean actual = wordGatewayRepository.existsBy(1L); + boolean actual = wordGatewayRepository.existsBy(WORD_ID); // then assertThat(actual).isTrue(); @@ -60,7 +63,7 @@ class WordGatewayRepositoryTest { @Sql("classpath:sql/word/word.sql") void 삭제한_용어의_영속화_여부를_확인한다() { // when - boolean actual = wordGatewayRepository.existsBy(2L); + boolean actual = wordGatewayRepository.existsBy(DELETED_WORD_ID); // then assertThat(actual).isFalse(); @@ -70,12 +73,12 @@ class WordGatewayRepositoryTest { @Sql("classpath:sql/word/word.sql") void 삭제하지_않은_용어를_조회한다() { // when - Optional actual = wordGatewayRepository.findBy(1L); + Optional actual = wordGatewayRepository.findBy(WORD_ID); // then assertAll( () -> assertThat(actual).isPresent(), - () -> assertThat(actual.get().getId()).isEqualTo(1L) + () -> assertThat(actual.get().getId()).isEqualTo(WORD_ID) ); } @@ -83,7 +86,7 @@ class WordGatewayRepositoryTest { @Sql("classpath:sql/word/word.sql") void 삭제한_용어는_조회할_수_없다() { // when - Optional actual = wordGatewayRepository.findBy(2L); + Optional actual = wordGatewayRepository.findBy(DELETED_WORD_ID); // then assertThat(actual).isEmpty(); @@ -94,17 +97,17 @@ class WordGatewayRepositoryTest { @Transactional void 삭제하지_않은_용어의_조회_수를_1_증가시킨다() { // given - Word before = wordGatewayRepository.findBy(1L).get(); + Word word = wordGatewayRepository.findBy(WORD_ID).get(); + + assertThat(word.getViewCount()).isZero(); - assertThat(before.getViewCount()).isZero(); + em.clear(); // when - wordGatewayRepository.addViewCount(1L); + wordGatewayRepository.addViewCount(WORD_ID); // then - em.clear(); - - Word actual = wordGatewayRepository.findBy(1L).get(); + Word actual = wordGatewayRepository.findBy(WORD_ID).get(); assertThat(actual.getViewCount()).isEqualTo(1L); } @@ -114,17 +117,17 @@ class WordGatewayRepositoryTest { @Transactional void 삭제한_용어의_조회_수는_증가되지_않는다() { // given - Word before = wordCrudRepository.findById(2L).get(); + Word deletedWord = wordCrudRepository.findById(DELETED_WORD_ID).get(); + + assertThat(deletedWord.getViewCount()).isZero(); - assertThat(before.getViewCount()).isZero(); + em.clear(); // when - wordGatewayRepository.addViewCount(2L); + wordGatewayRepository.addViewCount(DELETED_WORD_ID); // then - em.clear(); - - Word actual = wordGatewayRepository.findBy(1L).get(); + Word actual = wordCrudRepository.findById(DELETED_WORD_ID).get(); assertThat(actual.getViewCount()).isZero(); } @@ -134,17 +137,17 @@ class WordGatewayRepositoryTest { @Transactional void 삭제하지_않은_용어의_북마크_수를_1_증가시킨다() { // given - Word before = wordGatewayRepository.findBy(1L).get(); + Word word = wordGatewayRepository.findBy(WORD_ID).get(); + + assertThat(word.getBookmarkCount()).isZero(); - assertThat(before.getBookmarkCount()).isZero(); + em.clear(); // when - wordGatewayRepository.addBookmarkCount(1L); + wordGatewayRepository.addBookmarkCount(WORD_ID); // then - em.clear(); - - Word actual = wordGatewayRepository.findBy(1L).get(); + Word actual = wordGatewayRepository.findBy(WORD_ID).get(); assertThat(actual.getBookmarkCount()).isEqualTo(1L); } @@ -154,17 +157,17 @@ class WordGatewayRepositoryTest { @Transactional void 삭제한_용어의_북마크_수는_증가되지_않는다() { // given - Word before = wordCrudRepository.findById(2L).get(); + Word deletedWord = wordCrudRepository.findById(DELETED_WORD_ID).get(); - assertThat(before.getBookmarkCount()).isEqualTo(1L); + assertThat(deletedWord.getBookmarkCount()).isEqualTo(1L); + + em.clear(); // when - wordGatewayRepository.addBookmarkCount(2L); + wordGatewayRepository.addBookmarkCount(DELETED_WORD_ID); // then - em.clear(); - - Word actual = wordCrudRepository.findById(2L).get(); + Word actual = wordCrudRepository.findById(DELETED_WORD_ID).get(); assertThat(actual.getBookmarkCount()).isEqualTo(1L); } @@ -174,19 +177,19 @@ class WordGatewayRepositoryTest { @Transactional void 삭제하지_않은_용어의_북마크_수를_1_감소시킨다() { // given - wordGatewayRepository.addBookmarkCount(1L); + wordGatewayRepository.addBookmarkCount(WORD_ID); + + Word word = wordGatewayRepository.findBy(WORD_ID).get(); - Word before = wordGatewayRepository.findBy(1L).get(); + assertThat(word.getBookmarkCount()).isEqualTo(1L); - assertThat(before.getBookmarkCount()).isEqualTo(1L); + em.clear(); // when - wordGatewayRepository.subtractBookmarkCount(1L); + wordGatewayRepository.subtractBookmarkCount(WORD_ID); // then - em.clear(); - - Word actual = wordGatewayRepository.findBy(1L).get(); + Word actual = wordGatewayRepository.findBy(WORD_ID).get(); assertThat(actual.getBookmarkCount()).isZero(); } @@ -196,17 +199,17 @@ class WordGatewayRepositoryTest { @Transactional void 삭제한_용어의_북마크_수는_감소되지_않는다() { // given - Word before = wordCrudRepository.findById(2L).get(); + Word deletedWord = wordCrudRepository.findById(DELETED_WORD_ID).get(); + + assertThat(deletedWord.getBookmarkCount()).isEqualTo(1L); - assertThat(before.getBookmarkCount()).isEqualTo(1L); + em.clear(); // when - wordGatewayRepository.subtractBookmarkCount(2L); + wordGatewayRepository.subtractBookmarkCount(DELETED_WORD_ID); // then - em.clear(); - - Word actual = wordCrudRepository.findById(2L).get(); + Word actual = wordCrudRepository.findById(DELETED_WORD_ID).get(); assertThat(actual.getBookmarkCount()).isEqualTo(1L); } @@ -215,7 +218,7 @@ class WordGatewayRepositoryTest { @Sql("classpath:sql/word/word.sql") void 삭제하지_않은_모든_용어의_이름을_조회한다() { // when - List actual = wordGatewayRepository.findNameAllBy(new Long[]{1L, 2L}); + List actual = wordGatewayRepository.findNameAllBy(new Long[]{WORD_ID, DELETED_WORD_ID}); // then assertAll( From 3b914069444803d2e4955764526d700f333e1e9f Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 9 Jul 2025 22:25:43 +0900 Subject: [PATCH 033/115] =?UTF-8?q?test:=20Bookmark=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/bookmark/application/BookmarkServiceTest.java | 5 ++++- .../schedule/DeleteBookmarkSchedulerTest.java | 4 +--- space-d/src/test/resources/sql/bookmark/bookmark.sql | 2 ++ space-d/src/test/resources/sql/bookmark/deleted_word.sql | 8 -------- space-d/src/test/resources/sql/bookmark/word.sql | 9 +++++++++ 5 files changed, 16 insertions(+), 12 deletions(-) delete mode 100644 space-d/src/test/resources/sql/bookmark/deleted_word.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java index 797b87c2..00f1c6aa 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java @@ -89,7 +89,10 @@ class BookmarkServiceTest { } @Test - @Sql("classpath:sql/bookmark/bookmark.sql") + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) void 회원이_생성한_북마크를_모두_조회한다() { // given ReadAllBookmarkRequest request = new ReadAllBookmarkRequest(null); diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java index 30efa6fb..47b9b85b 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java @@ -10,14 +10,12 @@ import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; import com.dnd.spaced.core.word.application.DeletedWordIdRepository; import java.time.Clock; -import java.time.Instant; import java.time.LocalDateTime; import java.util.List; import java.util.Set; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; -import org.mockito.internal.util.MockUtil; import ch.qos.logback.classic.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; @@ -46,7 +44,7 @@ class DeleteBookmarkSchedulerTest { @Test @Sql(scripts = { - "classpath:sql/bookmark/deleted_word.sql", + "classpath:sql/bookmark/word.sql", "classpath:sql/bookmark/bookmark.sql" }) void 삭제한_용어에_등록된_북마크를_삭제한다() { diff --git a/space-d/src/test/resources/sql/bookmark/bookmark.sql b/space-d/src/test/resources/sql/bookmark/bookmark.sql index 7615d6d0..a04ccb6a 100644 --- a/space-d/src/test/resources/sql/bookmark/bookmark.sql +++ b/space-d/src/test/resources/sql/bookmark/bookmark.sql @@ -1 +1,3 @@ INSERT INTO bookmarks(id, created_at, account_id, word_id) VALUES(1, now(), 1, 1); + +INSERT INTO bookmarks(id, created_at, account_id, word_id) VALUES(2, now(), 1, 2); diff --git a/space-d/src/test/resources/sql/bookmark/deleted_word.sql b/space-d/src/test/resources/sql/bookmark/deleted_word.sql deleted file mode 100644 index 465caf54..00000000 --- a/space-d/src/test/resources/sql/bookmark/deleted_word.sql +++ /dev/null @@ -1,8 +0,0 @@ -INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) -VALUES(1, now(), now(), 0, 'DEVELOP', 'Authorization', 0, '인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘', true); - -INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) -VALUES(1, now(), now(), '어써라이제이션', 'KOREAN', 1, false); - -INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) -VALUES(1, now(), now(), '게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.', 1, false); diff --git a/space-d/src/test/resources/sql/bookmark/word.sql b/space-d/src/test/resources/sql/bookmark/word.sql index 0c46de6e..6e2159f4 100644 --- a/space-d/src/test/resources/sql/bookmark/word.sql +++ b/space-d/src/test/resources/sql/bookmark/word.sql @@ -6,3 +6,12 @@ VALUES(1, now(), now(), '어써라이제이션', 'KOREAN', 1, false); INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) VALUES(1, now(), now(), '게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.', 1, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(2, now(), now(), 1, 'DEVELOP', 'YAML', 0, '사람이 읽기 쉬운 데이터 형식으로, 주로 설정 파일에 사용됩니다.', true); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(2, now(), now(), '야믈', 'KOREAN', 2, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(2, now(), now(), 'YAML은 설정 파일이나 데이터 교환 포맷으로 자주 사용됩니다.', 2, false); From 1ee87c8a306178741110a7562fc0d8ea2f8dfb5a Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 9 Jul 2025 23:07:04 +0900 Subject: [PATCH 034/115] =?UTF-8?q?test:=20BookmarkGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BookmarkGatewayRepositoryTest.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepositoryTest.java diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepositoryTest.java new file mode 100644 index 00000000..9c38297d --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/infrastructure/persistence/BookmarkGatewayRepositoryTest.java @@ -0,0 +1,139 @@ +package com.dnd.spaced.core.bookmark.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.bookmark.domain.Bookmark; +import jakarta.persistence.EntityManager; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class BookmarkGatewayRepositoryTest { + + private static final long ACCOUNT_ID = 1L; + private static final long WORD_ID = 1L; + private static final long DELETED_WORD_ID = 2L; + + @Autowired + BookmarkGatewayRepository bookmarkGatewayRepository; + + @Autowired + EntityManager em; + + @Test + @Sql("classpath:sql/bookmark/word.sql") + void 북마크를_영속화_한다() { + // given + Bookmark bookmark = new Bookmark(ACCOUNT_ID, WORD_ID); + + // when + bookmarkGatewayRepository.save(bookmark); + + // then + Optional actual = bookmarkGatewayRepository.findBy(ACCOUNT_ID, WORD_ID); + + assertThat(actual).isPresent(); + } + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 삭제하지_않은_용어에_등록된_북마크를_회원_id와_용어_id로_조회한다() { + // when + Optional actual = bookmarkGatewayRepository.findBy(ACCOUNT_ID, WORD_ID); + + // then + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().getAccountId()).isEqualTo(ACCOUNT_ID), + () -> assertThat(actual.get().getWordId()).isEqualTo(WORD_ID) + ); + } + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 삭제한_용어에_등록된_북마크는_회원_id와_용어_id로_조회할_수_없다() { + // when + Optional actual = bookmarkGatewayRepository.findBy(ACCOUNT_ID, DELETED_WORD_ID); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 삭제하지_않은_용어에_등록된_북마크를_회원_id와_용어_id로_영속화_여부를_확인한다() { + // when + boolean actual = bookmarkGatewayRepository.existsBy(ACCOUNT_ID, WORD_ID); + + // then + assertThat(actual).isTrue(); + } + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 삭제한_않은_용어에_등록된_북마크를_회원_id와_용어_id로_영속화_여부를_확인한다() { + // when + boolean actual = bookmarkGatewayRepository.existsBy(ACCOUNT_ID, DELETED_WORD_ID); + + // then + assertThat(actual).isFalse(); + } + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + @Transactional + void 특정_용어_id에_등록된_모든_북마크를_삭제한다() { + // when + bookmarkGatewayRepository.deleteAllBy(Set.of(WORD_ID)); + + // then + boolean actual = bookmarkGatewayRepository.existsBy(ACCOUNT_ID, WORD_ID); + + assertThat(actual).isFalse(); + } + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 특정_회원이_삭제하지_않은_용어에_등록한_모든_북마크를_조회한다() { + // when + List actual = bookmarkGatewayRepository.findAllBy(ACCOUNT_ID, null, PageRequest.of(0, 10)); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).getAccountId()).isEqualTo(ACCOUNT_ID), + () -> assertThat(actual.get(0).getWordId()).isEqualTo(WORD_ID) + ); + } +} From 75af268b7f024131de9d6d74af9718b34316b9af Mon Sep 17 00:00:00 2001 From: apptie Date: Thu, 10 Jul 2025 22:13:02 +0900 Subject: [PATCH 035/115] =?UTF-8?q?fix:=20=EC=9A=A9=EC=96=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20soft=20delete=20=EB=8C=80=EC=83=81?= =?UTF-8?q?=EC=9D=80=20=EC=A1=B0=ED=9A=8C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordViewGatewayRepository.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java index 4f67924e..580aade5 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java @@ -106,21 +106,21 @@ private BooleanExpression buildWordPaginationCondition( } private List fetchFilteredWordIds(WordSearchCondition condition, WordSearchPageRequest pageRequest) { - return queryFactory - .select(word.id) - .from(word) - .where( - buildWordPaginationCondition( - condition.category(), - pageRequest.lastWordName(), - pageRequest.lastCategory() - ), - startsWithWordName(condition.name()), - buildPronunciationContentCondition(condition) - ) - .orderBy(word.name.asc(), word.category.asc(), word.id.desc()) - .limit(pageRequest.pageable().getPageSize()) - .fetch(); + return queryFactory.select(word.id) + .from(word) + .where( + buildWordPaginationCondition( + condition.category(), + pageRequest.lastWordName(), + pageRequest.lastCategory() + ), + startsWithWordName(condition.name()), + buildPronunciationContentCondition(condition), + word.deleted.isFalse() + ) + .orderBy(word.name.asc(), word.category.asc(), word.id.desc()) + .limit(pageRequest.pageable().getPageSize()) + .fetch(); } private BooleanExpression startsWithWordName(String name) { From e7835bfc5d28c4fb97d7c019364399e3b4e0938f Mon Sep 17 00:00:00 2001 From: apptie Date: Thu, 10 Jul 2025 22:43:22 +0900 Subject: [PATCH 036/115] =?UTF-8?q?test:=20WordViewGatewayRepository=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WordViewGatewayRepositoryTest.java | 318 ++++++++++++++++++ .../src/test/resources/sql/word/word_view.sql | 269 +++++++++++++++ 2 files changed, 587 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepositoryTest.java create mode 100644 space-d/src/test/resources/sql/word/word_view.sql diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepositoryTest.java new file mode 100644 index 00000000..461aafa5 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepositoryTest.java @@ -0,0 +1,318 @@ +package com.dnd.spaced.core.word.infrastructure.persistence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.enums.Category; +import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchCondition; +import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchPageRequest; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class WordViewGatewayRepositoryTest { + + private static final Long WORD_ID = 1L; + private static final Long DELETED_WORD_ID = 2L; + + @Autowired + WordViewGatewayRepository wordViewGatewayRepository; + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 삭제되지_않은_용어를_용어_id로_조회한다() { + // when + Optional actual = wordViewGatewayRepository.findBy(WORD_ID); + + // then + assertAll( + () -> assertThat(actual).isPresent(), + () -> assertThat(actual.get().id()).isEqualTo(WORD_ID) + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 삭제된_용어의_정보는_용어_id로_조회할_수_없다() { + // when + Optional actual = wordViewGatewayRepository.findBy(DELETED_WORD_ID); + + // then + assertThat(actual).isEmpty(); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 삭제되지_않은_용어_목록을_조회한다() { + // when + List actual = wordViewGatewayRepository.findAllBy( + null, + null, + null, + Pageable.ofSize(10) + ); + + // then + assertAll( + () -> assertThat(actual).hasSize(10), + () -> assertThat(convertWordId(actual)).doesNotContain(DELETED_WORD_ID) + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 삭제되지_않고_사전_순서로_debounce_이후_용어_목록을_조회한다() { + // when + List actual = wordViewGatewayRepository.findAllBy( + null, + "debounce", + Category.DEVELOP, + Pageable.ofSize(10) + ); + + // then + assertAll( + () -> assertThat(actual).hasSize(10), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("debounce") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_삭제되지_않은_첫_번째_용어_목록을_조회한다() { + // when + List actual = wordViewGatewayRepository.findAllBy( + Category.DEVELOP, + null, + null, + Pageable.ofSize(10) + ); + + // then + assertAll( + () -> assertThat(actual).hasSize(10), + () -> assertThat(convertWordId(actual)).doesNotContain(DELETED_WORD_ID) + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_사전_순서로_debounce_이후이면서_삭제되지_않은_용어_목록을_조회한다() { + // when + List actual = wordViewGatewayRepository.findAllBy( + Category.DEVELOP, + "debounce", + Category.DEVELOP, + Pageable.ofSize(10) + ); + + // then + assertAll( + () -> assertThat(actual).hasSize(10), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("debounce") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition(null, Category.DEVELOP, null); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(10), null, null); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertAll( + () -> assertThat(actual).hasSize(10), + () -> assertThat(convertWordId(actual)).doesNotContain(DELETED_WORD_ID) + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_사전_순서로_용어_이름이_debounce_이후_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition(null, Category.DEVELOP, null); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(10), "debounce", Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertAll( + () -> assertThat(actual).hasSize(10), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("debounce") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_발음이_디로_시작하면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition(null, Category.DEVELOP, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), null, Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + assertThat(actual).hasSize(5); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_발음이_디로_시작하면서_사전_순으로_용어_이름이_default_이후이면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition(null, Category.DEVELOP, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), "default", Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + assertAll( + () -> assertThat(actual).hasSize(5), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("default") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_발음이_디로_시작하면서_용어_이름이_de로_시작하면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition("de", Category.DEVELOP, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), null, Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertThat(actual).hasSize(5); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 카테고리가_DEVELOP이면서_발음이_디로_시작하면서_용어_이름이_de로_시작하면서_사전_순으로_용어_이름이_design_이후이면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition("de", Category.DEVELOP, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), "design", Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("design") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 발음이_디로_시작하면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition(null, null, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), null, null); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertThat(actual).hasSize(5); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 발음이_디로_시작하면서_사전_순으로_용어_이름이_default_이후이면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition(null, null, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), "default", Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + assertAll( + () -> assertThat(actual).hasSize(5), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("default") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 발음이_디로_시작하면서_용어_이름이_de로_시작하면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition("de", null, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), null, Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertThat(actual).hasSize(5); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 발음이_디로_시작하면서_용어_이름이_de로_시작하면서_사전_순으로_용어_이름이_design_이후이면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition("de", null, "디"); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), "design", Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("design") + ); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 용어_이름이_de로_시작하면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition("de", null, null); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), null, Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertThat(actual).hasSize(5); + } + + @Test + @Sql("classpath:sql/word/word_view.sql") + void 용어_이름이_de로_시작하면서_사전_순으로_용어_이름이_design_이후이면서_삭제되지_않은_용어_목록을_검색한다() { + // given + WordSearchCondition condition = new WordSearchCondition("de", null, null); + WordSearchPageRequest pageRequest = new WordSearchPageRequest(Pageable.ofSize(5), "design", Category.DEVELOP); + + // when + List actual = wordViewGatewayRepository.search(condition, pageRequest); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).name()).usingDefaultComparator().isGreaterThan("design") + ); + } + + private List convertWordId(List wordViews) { + return wordViews.stream() + .map(WordView::id) + .toList(); + } +} diff --git a/space-d/src/test/resources/sql/word/word_view.sql b/space-d/src/test/resources/sql/word/word_view.sql new file mode 100644 index 00000000..c7ba8565 --- /dev/null +++ b/space-d/src/test/resources/sql/word/word_view.sql @@ -0,0 +1,269 @@ +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(1, now(), now(), 0, 'DEVELOP', 'Authorization', 0, '인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(1, now(), now(), '어써라이제이션', 'KOREAN', 1, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(1, now(), now(), '게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.', 1, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(2, now(), now(), 0, 'DEVELOP', 'annotation', 0, '소스 코드에 추가되는 주석이나 설명을 의미하며, 코드의 이해를 돕기 위해 사용됩니다.', true); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(2, now(), now(), '어노테이션', 'KOREAN', 2, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(2, now(), now(), '어노테이션은 소스 코드에 주석을 추가하여 코드의 의미를 명확히 하는 데 사용됩니다.', 2, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(3, now(), now(), 0, 'DEVELOP', 'TOML', 0, '간단하고 가독성이 높은 설정 파일 형식으로, 키-값 쌍을 이용해 데이터를 표현합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(3, now(), now(), '톰엘', 'KOREAN', 3, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(3, now(), now(), 'TOML은 구성 파일에 사용하기 쉬운 데이터 직렬화 언어입니다.', 3, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(4, now(), now(), 0, 'DEVELOP', 'deprecated', 0, '더 이상 사용되지 않거나, 지원되지 않는다는 뜻입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(4, now(), now(), '데프리케이티드', 'KOREAN', 4, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(4, now(), now(), '이 함수는 더 이상 사용되지 않으므로 deprecated되었습니다.', 4, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(5, now(), now(), 0, 'DEVELOP', 'execute', 0, '주로 프로그램이나 코드, 명령을 실행할 때 사용됩니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(5, now(), now(), '엑시큐트', 'KOREAN', 5, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(5, now(), now(), '코드를 실행하려면 Run 버튼을 눌러서 execute시킵니다.', 5, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(6, now(), now(), 0, 'DEVELOP', 'COALESCE', 0, 'SQL에서 인자로 주어진 컬럼들 중에서 NULL이 아닌 첫 번째 값을 반환하는 함수입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(6, now(), now(), '코얼레스', 'KOREAN', 6, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(6, now(), now(), '데이터베이스 쿼리에서 COALESCE 함수를 사용해 NULL 값을 빈 문자열로 대체합니다.', 6, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(7, now(), now(), 0, 'DEVELOP', 'Queue', 0, '대기열을 의미하며, 데이터 구조에서 먼저 들어온 데이터가 먼저 나가는(FIFO) 방식의 대기열을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(7, now(), now(), '큐', 'KOREAN', 7, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(7, now(), now(), '비동기 작업을 처리하기 위해 작업 Queue를 사용하여 작업을 순차적으로 실행합니다.', 7, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(8, now(), now(), 0, 'DEVELOP', 'carousel', 0, '회전목마를 의미하며, UI 중 이미지를 순환하며 보여주는 슬라이더를 지칭합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(8, now(), now(), '캐러셀', 'KOREAN', 8, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(8, now(), now(), '사용자는 이미지 캐러셀을 통해 다양한 사진을 볼 수 있습니다.', 8, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(9, now(), now(), 0, 'DEVELOP', 'Dequeue', 0, 'Queue의 반대 동작으로, 큐에 저장된 데이터 중 첫 번째 요소를 제거하고 반환하는 것을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(9, now(), now(), '디큐', 'KOREAN', 9, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(9, now(), now(), '사용자 요청을 Queue에 저장하고, 순서대로 Dequeue하여 처리합니다.', 9, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(10, now(), now(), 0, 'DEVELOP', 'JWT', 0, 'JWT는 JSON Web Token의 약자로, JSON 형식의 웹 토큰을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(10, now(), now(), '제이더블유티', 'KOREAN', 10, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(10, now(), now(), '사용자가 로그인하면 서버는 JWT를 생성하여 클라이언트에게 반환합니다.', 10, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(11, now(), now(), 0, 'DEVELOP', 'GUI', 0, '그래픽 사용자 인터페이스를 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(11, now(), now(), '구이', 'KOREAN', 11, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(11, now(), now(), '새로 출시된 운영 체제는 직관적인 GUI를 제공하여 사용자가 쉽게 파일을 관리하고 프로그램을 실행할 수 있습니다.', 11, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(12, now(), now(), 0, 'DEVELOP', 'usage', 0, '사용, 용법을 뜻합니다. CPU, 메모리, 네트워크 등 시스템이나 소프트웨어 자원의 사용량을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(12, now(), now(), '유씨지', 'KOREAN', 12, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(12, now(), now(), '시스템의 CPU usage가 80%를 초과했습니다.', 12, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(13, now(), now(), 0, 'DEVELOP', 'SaaS', 0, 'Software as a Service의 약자로, 서비스형 소프트웨어를 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(13, now(), now(), '사스', 'KOREAN', 13, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(13, now(), now(), '회사에 클라우드 기반 SaaS 솔루션을 도입했습니다.', 13, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(14, now(), now(), 0, 'DEVELOP', 'directory', 0, '파일 시스템에서 파일과 폴더를 계층적으로 구성하는 데 사용되는 구조를 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(14, now(), now(), '디렉터리', 'KOREAN', 14, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(14, now(), now(), 'directory 권한 설정을 통해 특정 사용자만 접근할 수 있도록 했습니다.', 14, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(15, now(), now(), 0, 'DEVELOP', 'empty', 0, '비어 있는, 아무것도 없는 상태를 의미하며, 변수나 데이터 구조가 값을 포함하지 않은 상태를 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(15, now(), now(), '엠티', 'KOREAN', 15, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(15, now(), now(), '배열이 empty인지 확인한 후에 데이터 추가 작업을 수행합니다.', 15, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(16, now(), now(), 0, 'DEVELOP', 'redirect', 0, '웹 서버나 애플리케이션에서 사용자가 요청한 URL을 다른 URL로 자동으로 보내는 행위를 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(16, now(), now(), '리다이렉트', 'KOREAN', 16, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(16, now(), now(), 'HTTP 상태 코드 중 301은 영구적으로 다른 URL로 redirect하며, 302는 일시적으로 다른 URL로 redirect했다는 뜻입니다.', 16, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(17, now(), now(), 0, 'DEVELOP', 'jar', 0, '자바 애플리케이션을 패키징하여 배포하는 데 사용되는 파일 형식으로, 여러 클래스 파일과 관련 메타데이터를 포함합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(17, now(), now(), '자르', 'KOREAN', 17, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(17, now(), now(), 'JAR 파일을 실행하여 애플리케이션을 시작합니다', 17, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(18, now(), now(), 0, 'DEVELOP', 'locale', 0, '소프트웨어나 시스템에서 특정 언어와 문화권에 맞춘 설정을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(18, now(), now(), '로캘', 'KOREAN', 18, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(18, now(), now(), '시스템의 locale을 한국어로 설정합니다.', 18, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(19, now(), now(), 0, 'DEVELOP', 'gradient', 0, 'CSS에서 색상이나 밝기가 점진적으로 변하는 효과로, 웹 페이지의 배경이나 요소에 주로 적용합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(19, now(), now(), '그레이디언트', 'KOREAN', 19, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(19, now(), now(), 'CSS에서 linear-gradient와 radial-gradient 속성을 사용하여 다양한 형태의 gradient를 만들 수 있습니다.', 19, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(20, now(), now(), 0, 'DEVELOP', 'status', 0, '시스템, 프로세스, 작업, 또는 소프트웨어의 현재 상태나 진행 상황을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(20, now(), now(), '스테이터스', 'KOREAN', 20, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(20, now(), now(), '클라이언트의 요청이 성공적으로 처리되었음을 나타내기 위해 서버는 200 OK HTTP status 코드를 반환합니다.', 20, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(21, now(), now(), 0, 'DEVELOP', 'digital', 0, '디지털 기술과 관련된 용어로, 아날로그가 아닌 이진 데이터를 사용하는 방식을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(21, now(), now(), '디지털', 'KOREAN', 21, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(21, now(), now(), 'digital 신호 처리는 아날로그 신호를 디지털로 변환하는 과정입니다.', 21, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(22, now(), now(), 0, 'DEVELOP', 'debugging', 0, '프로그램의 오류를 찾아 수정하는 과정입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(22, now(), now(), '디버깅', 'KOREAN', 22, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(22, now(), now(), '코드를 debugging하여 문제를 해결했습니다.', 22, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(23, now(), now(), 0, 'DEVELOP', 'disk', 0, '컴퓨터 저장 장치 중 하나로, 데이터를 저장하는 물리적 매체입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(23, now(), now(), '디스크', 'KOREAN', 23, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(23, now(), now(), 'disk 용량이 부족하여 파일을 삭제했습니다.', 23, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(24, now(), now(), 0, 'DEVELOP', 'directory', 0, '파일 시스템에서 파일과 폴더를 계층적으로 구성하는 구조입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(24, now(), now(), '디렉토리', 'KOREAN', 24, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(24, now(), now(), 'directory 권한을 설정하여 접근을 제한했습니다.', 24, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(25, now(), now(), 0, 'DEVELOP', 'device', 0, '하드웨어 장치를 의미하며, 컴퓨터 주변기기를 포함합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(25, now(), now(), '디바이스', 'KOREAN', 25, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(25, now(), now(), '새로운 device를 연결하여 테스트했습니다.', 25, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(26, now(), now(), 0, 'DEVELOP', 'design', 0, '제품이나 서비스의 외관 및 기능을 계획하고 설계하는 과정입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(26, now(), now(), '디자인', 'KOREAN', 26, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(26, now(), now(), '웹사이트 design을 새롭게 변경했습니다.', 26, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(27, now(), now(), 0, 'DEVELOP', 'default', 0, '기본 설정이나 초기값을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(27, now(), now(), '디폴트', 'KOREAN', 27, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(27, now(), now(), 'default 설정으로 프로그램을 실행했습니다.', 27, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(28, now(), now(), 0, 'DEVELOP', 'decode', 0, '암호화된 데이터를 해독하는 과정을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(28, now(), now(), '디코딩', 'KOREAN', 28, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(28, now(), now(), '비디오 스트림을 decode하여 재생했습니다.', 28, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(29, now(), now(), 0, 'DEVELOP', 'debounce', 0, '입력 신호의 잡음을 제거하는 기술입니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(29, now(), now(), '디바운스', 'KOREAN', 29, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(29, now(), now(), '버튼 클릭 시 debounce 처리를 적용했습니다.', 29, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(30, now(), now(), 0, 'DEVELOP', 'display', 0, '화면에 정보를 출력하는 장치나 기능을 의미합니다.', false); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(30, now(), now(), '디스플레이', 'KOREAN', 30, false); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(30, now(), now(), '고해상도 display를 사용하여 화면을 선명하게 표현했습니다.', 30, false); From 060f0cf6ba1a37e25db6b404e622e31c93b1a167 Mon Sep 17 00:00:00 2001 From: apptie Date: Thu, 10 Jul 2025 23:27:14 +0900 Subject: [PATCH 037/115] =?UTF-8?q?refactor:=20QuizDtoMapper=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{QuizInfoMapper.java => QuizDtoMapper.java} | 14 ++------------ .../persistence/QuizGatewayRepository.java | 4 ++-- 2 files changed, 4 insertions(+), 14 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/{QuizInfoMapper.java => QuizDtoMapper.java} (88%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizDtoMapper.java similarity index 88% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizDtoMapper.java index 5c8d0f07..00e05959 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizInfoMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/QuizDtoMapper.java @@ -13,17 +13,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class QuizInfoMapper { - - public static QuizDto toDto(Quiz quiz) { - return new QuizDto( - quiz.getId(), - quiz.getAccountId(), - quiz.isSolved(), - quiz.getCreatedAt(), - Collections.emptyList() - ); - } +public final class QuizDtoMapper { public static QuizDto toDto(Quiz quiz, Map> quizOptionMap) { List quizQuestions = quiz.getQuizQuestions() @@ -52,7 +42,7 @@ private static QuizQuestionDto toQuizQuestionDto(QuizQuestion quizQuestion, List ); } List quizOptionDtos = quizOptions.stream() - .map(QuizInfoMapper::toQuizOptionDto) + .map(QuizDtoMapper::toQuizOptionDto) .toList(); return new QuizQuestionDto( diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java index 5586730f..1ee57c63 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/QuizGatewayRepository.java @@ -9,7 +9,7 @@ import com.dnd.spaced.core.quiz.domain.dto.QuizDto; import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto.QuizQuestionDto; -import com.dnd.spaced.core.quiz.domain.dto.mapper.QuizInfoMapper; +import com.dnd.spaced.core.quiz.domain.dto.mapper.QuizDtoMapper; import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.quiz.domain.repository.QuizRepository; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -86,7 +86,7 @@ public Optional findBy(Long quizId, Long accountId) { List quizQuestionId = findQuizQuestionIds(result); Map> quizOptionMap = findQuizOptions(quizQuestionId); - QuizDto quizDto = QuizInfoMapper.toDto(result, quizOptionMap); + QuizDto quizDto = QuizDtoMapper.toDto(result, quizOptionMap); return Optional.of(quizDto); } From e833c8789f4190b0382cc8aa97432351b28f9763 Mon Sep 17 00:00:00 2001 From: apptie Date: Thu, 10 Jul 2025 23:42:34 +0900 Subject: [PATCH 038/115] =?UTF-8?q?refactor:=20Profile=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/application/AccountService.java | 4 +- .../dto/mapper/AccountResponseMapper.java | 2 +- .../spaced/core/account/domain/Account.java | 9 +-- .../core/account/domain/embed/Profile.java | 34 +++++----- .../domain/enums/ProfileImageName.java | 9 ++- .../application/internal/SignUpService.java | 2 +- .../persistence/CommentGatewayRepository.java | 4 +- .../core/account/domain/AccountTest.java | 52 +++++++------- .../account/domain/embed/ProfileTest.java | 67 ++----------------- .../domain/enums/ProfileImageNameTest.java | 46 +++++++++++-- .../AccountGatewayRepositoryTest.java | 3 +- .../core/comment/domain/CommentTest.java | 5 +- .../test/resources/sql/account/account.sql | 8 +-- .../src/test/resources/sql/like/account.sql | 2 +- 14 files changed, 114 insertions(+), 133 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java index 84e78a02..3ad116e4 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java @@ -39,9 +39,9 @@ public void changeCareerInfo(Long accountId, ChangeCareerRequest request) { @Transactional public void changeProfileInfo(Long accountId, ChangeProfileRequest request) { Account authorizedAccount = findAuthorizedAccount(accountId); - ProfileImageName changedProfileImageName = ProfileImageName.findBy(request.changedProfileImageKoreanName()); + ProfileImageName changedProfileImageName = ProfileImageName.findByKorean(request.changedProfileImageKoreanName()); - authorizedAccount.changeProfileInfo(request.changedNickname(), changedProfileImageName.getImageName()); + authorizedAccount.changeProfileInfo(request.changedNickname(), changedProfileImageName); } public AccountResponse readAccount(Long accountId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java index 3c47bbeb..89c914c7 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java @@ -16,7 +16,7 @@ public static AccountResponse toDto(Account account) { return new AccountResponse( profile.getNickname(), - profile.getProfileImage(), + profile.getProfileImageName(), career.getJobGroup().getName(), career.getCompany().getName(), career.getExperience().getName() diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java index 32e33268..b712650f 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java @@ -3,6 +3,7 @@ import com.dnd.spaced.core.account.domain.embed.Career; import com.dnd.spaced.core.account.domain.embed.Profile; import com.dnd.spaced.core.account.domain.embed.Social; +import com.dnd.spaced.core.account.domain.enums.ProfileImageName; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.enums.Role; import com.dnd.spaced.global.audit.BaseTimeEntity; @@ -48,12 +49,12 @@ public class Account extends BaseTimeEntity { @Builder private Account( String nickname, - String profileImage, + ProfileImageName profileImageName, Role role, RegistrationId registrationId, String socialIdentifier ) { - this.profile = Profile.of(nickname, profileImage); + this.profile = Profile.of(nickname, profileImageName); this.role = role; this.social = new Social(registrationId, socialIdentifier); } @@ -73,8 +74,8 @@ public void changeCareer( .build(); } - public void changeProfileInfo(String changedNickname, String changedProfileImage) { - this.profile = Profile.of(changedNickname, changedProfileImage); + public void changeProfileInfo(String changedNickname, ProfileImageName changedProfileImageName) { + this.profile = Profile.of(changedNickname, changedProfileImageName); } public boolean isEqualTo(Long id) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java index 5ee415d6..28d12aaa 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/embed/Profile.java @@ -2,6 +2,7 @@ import com.dnd.spaced.core.account.domain.embed.exception.InvalidNicknameException; import com.dnd.spaced.core.account.domain.embed.exception.InvalidProfileImageException; +import com.dnd.spaced.core.account.domain.enums.ProfileImageName; import jakarta.persistence.Embeddable; import lombok.AccessLevel; import lombok.Getter; @@ -21,20 +22,24 @@ public class Profile { ); private String nickname; - private String profileImage; + private String profileImageName; - public static Profile of(String nickname, String profileImage) { - validateContent(nickname, profileImage); + public static Profile of(String nickname, ProfileImageName profileImageName) { + validateNickname(nickname); + validateProfileImageName(profileImageName); - return new Profile(nickname, profileImage); + return new Profile(nickname, profileImageName.getImageName()); } - private static void validateContent(String nickname, String profileImage) { + private static void validateNickname(String nickname) { if (isInvalidNickname(nickname)) { throw new InvalidNicknameException(NICKNAME_EXCEPTION_MESSAGE); } - if (isInvalidProfileImage(profileImage)) { - throw new InvalidProfileImageException("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); + } + + private static void validateProfileImageName(ProfileImageName profileImageName) { + if (isInvalidProfileImageName(profileImageName)) { + throw new InvalidProfileImageException("프로필 이미지 정보는 null일 수 없습니다."); } } @@ -43,19 +48,12 @@ private static boolean isInvalidNickname(String nickname) { || nickname.length() < NICKNAME_MIN_LENGTH || nickname.length() > NICKNAME_MAX_LENGTH; } - private static boolean isInvalidProfileImage(String profileImage) { - return profileImage == null || profileImage.isBlank(); + private static boolean isInvalidProfileImageName(ProfileImageName profileImageName) { + return profileImageName == null; } - private Profile(String nickname, String profileImage) { + private Profile(String nickname, String profileImageName) { this.nickname = nickname; - this.profileImage = profileImage; - } - - public void changeProfileInfo(String changedNickname, String changedProfileImage) { - validateContent(changedNickname, changedProfileImage); - - this.nickname = changedNickname; - this.profileImage = changedProfileImage; + this.profileImageName = profileImageName; } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/enums/ProfileImageName.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/enums/ProfileImageName.java index af53f745..0fd6f893 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/enums/ProfileImageName.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/enums/ProfileImageName.java @@ -32,10 +32,17 @@ public static ProfileImageName findRandom() { .orElse(EARTH); } - public static ProfileImageName findBy(String korean) { + public static ProfileImageName findByKorean(String korean) { return Arrays.stream(ProfileImageName.values()) .filter(profileImageName -> profileImageName.korean.equals(korean)) .findAny() .orElseThrow(() -> new InvalidProfileImageNameException(String.format(EXCEPTION_MESSAGE, korean))); } + + public static ProfileImageName findByImageName(String imageName) { + return Arrays.stream(ProfileImageName.values()) + .filter(profileImageName -> profileImageName.imageName.equals(imageName)) + .findAny() + .orElseThrow(() -> new InvalidProfileImageNameException(String.format(EXCEPTION_MESSAGE, imageName))); + } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java index 9c629be2..5db617d2 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java @@ -69,7 +69,7 @@ private Account persistAccount( .socialIdentifier(socialIdentifier) .nickname(formattedNickname) .role(DEFAULT_ROLE) - .profileImage(profileImageName) + .profileImageName(ProfileImageName.findByImageName(profileImageName)) .build(); return accountRepository.save(newAccount); diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java index 5dd55529..9238b715 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/infrastructure/persistence/CommentGatewayRepository.java @@ -112,7 +112,7 @@ private ConstructorExpression getLikedCommentWithLiked() { comment, like.id.isNotNull(), account.profile.nickname, - account.profile.profileImage + account.profile.profileImageName ); } @@ -121,7 +121,7 @@ private ConstructorExpression getLikedCommentWithoutLiked() { LikedComment.class, comment, account.profile.nickname, - account.profile.profileImage + account.profile.profileImageName ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java index d547ea2b..c4c995c0 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java @@ -11,6 +11,7 @@ import com.dnd.spaced.core.account.domain.enums.Company; import com.dnd.spaced.core.account.domain.enums.Experience; import com.dnd.spaced.core.account.domain.enums.JobGroup; +import com.dnd.spaced.core.account.domain.enums.ProfileImageName; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.enums.Role; import com.dnd.spaced.core.account.domain.enums.exception.InvalidCompanyException; @@ -38,7 +39,7 @@ class AccountTest { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build() ); @@ -47,7 +48,7 @@ class AccountTest { () -> assertThat(actual.getSocial().getRegistrationId()).isEqualTo(RegistrationId.KAKAO), () -> assertThat(actual.getSocial().getSocialId()).isEqualTo("12345"), () -> assertThat(actual.getProfile().getNickname()).isEqualTo("재빠른지구001"), - () -> assertThat(actual.getProfile().getProfileImage()).isEqualTo("earth.png"), + () -> assertThat(actual.getProfile().getProfileImageName()).isEqualTo("earth.png"), () -> assertThat(actual.getRole()).isEqualTo(Role.ROLE_USER) ); } @@ -71,27 +72,26 @@ private static Stream builderTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname(invalidNickname) - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build() ).isInstanceOf(InvalidNicknameException.class) .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); } - @ParameterizedTest(name = "프로필 이미지가 {0}일 때 예외가 발생한다") - @NullAndEmptySource - void 비어_있는_프로필_이미지_경로라면_회원을_초기화할_수_없다(String invalidProfileImage) { + @Test + void 비어_있는_프로필_이미지_경로라면_회원을_초기화할_수_없다() { // when & then assertThatThrownBy( () -> Account.builder() .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage(invalidProfileImage) + .profileImageName(null) .role(Role.ROLE_USER) .build() ).isInstanceOf(InvalidProfileImageException.class) - .hasMessage("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); + .hasMessage("프로필 이미지 정보는 null일 수 없습니다."); } @Test @@ -101,7 +101,7 @@ private static Stream builderTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); @@ -126,7 +126,7 @@ private static Stream builderTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); @@ -150,7 +150,7 @@ private static Stream builderTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); @@ -174,7 +174,7 @@ private static Stream builderTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); @@ -197,39 +197,35 @@ private static Stream builderTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); // when - String changedNickname = "행복한화성001"; - String changedProfileImage = "mars.png"; - - account.changeProfileInfo(changedNickname, changedProfileImage); + account.changeProfileInfo("행복한화성001", ProfileImageName.MARS); // then assertAll( - () -> assertThat(account.getProfile().getNickname()).isEqualTo(changedNickname), - () -> assertThat(account.getProfile().getProfileImage()).isEqualTo(changedProfileImage) + () -> assertThat(account.getProfile().getNickname()).isEqualTo("행복한화성001"), + () -> assertThat(account.getProfile().getProfileImageName()).isEqualTo(ProfileImageName.MARS.getImageName()) ); } - @ParameterizedTest(name = "프로필 이미지가 {0}일 때 예외가 발생한다") - @NullAndEmptySource - void 프로필_이미지_경로가_비어_있으면_회원_프로필_정보를_변환할_수_없다(String invalidProfileImage) { + @Test + void 프로필_이미지_경로가_비어_있으면_회원_프로필_정보를_변환할_수_없다() { // given Account account = Account.builder() .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); // when & then - assertThatThrownBy(() -> account.changeProfileInfo("행복한화성001", invalidProfileImage)) + assertThatThrownBy(() -> account.changeProfileInfo("행복한화성001", null)) .isInstanceOf(InvalidProfileImageException.class) - .hasMessage("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); + .hasMessage("프로필 이미지 정보는 null일 수 없습니다."); } private static Stream changeProfileInfoTestWithInvalidNickname() { @@ -250,12 +246,12 @@ private static Stream changeProfileInfoTestWithInvalidNickname() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); // when & then - assertThatThrownBy(() -> account.changeProfileInfo(invalidNickname, "mars.png")) + assertThatThrownBy(() -> account.changeProfileInfo(invalidNickname, ProfileImageName.MARS)) .isInstanceOf(InvalidNicknameException.class) .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); } @@ -275,7 +271,7 @@ private static Stream isEqualToTestArguments() { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); ReflectionTestUtils.setField(account, "id", 1L); diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java index d8445dd9..0dfc55a0 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/embed/ProfileTest.java @@ -1,12 +1,11 @@ package com.dnd.spaced.core.account.domain.embed; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.dnd.spaced.core.account.domain.embed.exception.InvalidNicknameException; import com.dnd.spaced.core.account.domain.embed.exception.InvalidProfileImageException; +import com.dnd.spaced.core.account.domain.enums.ProfileImageName; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -14,7 +13,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.NullAndEmptySource; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -23,7 +21,7 @@ class ProfileTest { @Test void 프로필_정보를_초기화한다() { // when & then - assertDoesNotThrow(() -> Profile.of("재빠른지구001", "earth.png")); + assertDoesNotThrow(() -> Profile.of("재빠른지구001", ProfileImageName.EARTH)); } private static Stream constructorTestWithInvalidNickname() { @@ -40,69 +38,16 @@ private static Stream constructorTestWithInvalidNickname() { @MethodSource("constructorTestWithInvalidNickname") void 유효한_길이의_닉네임이_아니라면_프로필_정보를_초기화할_수_없다(String invalidNickname) { // when & then - assertThatThrownBy(() -> Profile.of(invalidNickname, "earth.png")) + assertThatThrownBy(() -> Profile.of(invalidNickname, ProfileImageName.EARTH)) .isInstanceOf(InvalidNicknameException.class) .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); } - @ParameterizedTest(name = "프로필 이미지가 {0}일 때 프로필 정보를 초기화할 수 없다") - @NullAndEmptySource - void 비어_있는_프로필_이미지_경로라면_프로필_정보를_초기화할_수_없다(String invalidProfileImage) { - // when & then - assertThatThrownBy(() -> Profile.of("행복한지구001", invalidProfileImage)) - .isInstanceOf(InvalidProfileImageException.class) - .hasMessage("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); - } - @Test - void 프로필_정보를_변경한다() { - // given - Profile profile = Profile.of("재빠른지구001", "earth.png"); - - // when - String changedNickname = "행복한화성001"; - String changedProfileImage = "mars.png"; - - profile.changeProfileInfo(changedNickname, changedProfileImage); - - // then - assertAll( - () -> assertThat(profile.getNickname()).isEqualTo(changedNickname), - () -> assertThat(profile.getProfileImage()).isEqualTo(changedProfileImage) - ); - } - - @ParameterizedTest(name = "프로필 이미지가 {0}일 때 프로필 정보를 변경할 수 없다") - @NullAndEmptySource - void 비어_있는_프로필_이미지_경로라면_프로필_정보를_변경할_수_없다(String invalidProfileImage) { - // given - Profile profile = Profile.of("재빠른지구001", "earth.png"); - + void 비어_있는_프로필_이미지_경로라면_프로필_정보를_초기화할_수_없다() { // when & then - assertThatThrownBy(() -> profile.changeProfileInfo("행복한화성001", invalidProfileImage)) + assertThatThrownBy(() -> Profile.of("행복한지구001", null)) .isInstanceOf(InvalidProfileImageException.class) - .hasMessage("프로필 이미지 정보는 null이거나 비어 있을 수 없습니다."); - } - - private static Stream changeProfileInfoTestWithInvalidNickname() { - return Stream.of( - Arguments.of((Object) null), - Arguments.of(""), - Arguments.of(" "), - Arguments.of("1234"), - Arguments.of("12345678901") - ); - } - - @ParameterizedTest(name = "닉네임이 {0}일 때 프로필 정보를 변경할 수 없다") - @MethodSource("changeProfileInfoTestWithInvalidNickname") - void 유효한_닉네임_길이가_아니라면_프로필_정보를_변경할_수_없다(String invalidNickname) { - // given - Profile profile = Profile.of("nickname", "profileImage"); - - // when & then - assertThatThrownBy(() -> profile.changeProfileInfo(invalidNickname, "profileImage")) - .isInstanceOf(InvalidNicknameException.class) - .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); + .hasMessage("프로필 이미지 정보는 null일 수 없습니다."); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java index 61cef830..2dbabd65 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java @@ -24,7 +24,7 @@ class ProfileImageNameTest { assertDoesNotThrow(ProfileImageName::findRandom); } - private static Stream findByTestWithProfileImageKoreanName() { + private static Stream findByKoreanTestWithKoreanName() { return Stream.of( Arguments.of("수성", ProfileImageName.MERCURY), Arguments.of("금성", ProfileImageName.VENUS), @@ -37,11 +37,11 @@ private static Stream findByTestWithProfileImageKoreanName() { ); } - @ParameterizedTest(name = "프로필 이미지 이름이 {0}일 때 {1}을 반환한다") - @MethodSource("findByTestWithProfileImageKoreanName") - void 프로필_이미지를_찾는다(String korean, ProfileImageName expected) { + @ParameterizedTest(name = "프로필 이미지의 한글 이름이 {0}일 때 {1}을 반환한다") + @MethodSource("findByKoreanTestWithKoreanName") + void 한글_이름으로_프로필_이미지를_찾는다(String korean, ProfileImageName expected) { // when - ProfileImageName actual = ProfileImageName.findBy(korean); + ProfileImageName actual = ProfileImageName.findByKorean(korean); // then assertThat(actual).isEqualTo(expected); @@ -49,9 +49,41 @@ private static Stream findByTestWithProfileImageKoreanName() { @ParameterizedTest @NullAndEmptySource - void 유효한_이름이_아니라면_프로필_이미지를_찾을_수_없다(String invalidKoreanName) { + void 유효한_한글_이름이_아니라면_프로필_이미지를_찾을_수_없다(String invalidKoreanName) { // when & then - assertThatThrownBy(() -> ProfileImageName.findBy(invalidKoreanName)) + assertThatThrownBy(() -> ProfileImageName.findByKorean(invalidKoreanName)) + .isInstanceOf(InvalidProfileImageNameException.class) + .hasMessageContaining("잘못된 프로필 이미지 이름"); + } + + private static Stream findByImageNameTestWithImageName() { + return Stream.of( + Arguments.of("mercury.png", ProfileImageName.MERCURY), + Arguments.of("venus.png", ProfileImageName.VENUS), + Arguments.of("earth.png", ProfileImageName.EARTH), + Arguments.of("mars.png", ProfileImageName.MARS), + Arguments.of("jupiter.png", ProfileImageName.JUPITER), + Arguments.of("saturn.png", ProfileImageName.SATURN), + Arguments.of("uranus.png", ProfileImageName.URANUS), + Arguments.of("neptune.png", ProfileImageName.NEPTUNE) + ); + } + + @ParameterizedTest(name = "프로필 이미지의 이미지 이름이 {0}일 때 {1}을 반환한다") + @MethodSource("findByImageNameTestWithImageName") + void 이미지_이름으로_프로필_이미지를_찾는다(String korean, ProfileImageName expected) { + // when + ProfileImageName actual = ProfileImageName.findByImageName(korean); + + // then + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @NullAndEmptySource + void 유효한_이미지_이름이_아니라면_프로필_이미지를_찾을_수_없다(String invalidImageName) { + // when & then + assertThatThrownBy(() -> ProfileImageName.findByKorean(invalidImageName)) .isInstanceOf(InvalidProfileImageNameException.class) .hasMessageContaining("잘못된 프로필 이미지 이름"); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java index 1e26d137..338033c9 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/infrastructure/persistence/AccountGatewayRepositoryTest.java @@ -5,6 +5,7 @@ import com.dnd.spaced.core.account.domain.Account; import com.dnd.spaced.core.account.domain.embed.Social; +import com.dnd.spaced.core.account.domain.enums.ProfileImageName; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.enums.Role; import com.dnd.spaced.core.account.domain.repository.AccountRepository; @@ -83,7 +84,7 @@ class AccountGatewayRepositoryTest { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java index 879c5639..fa56e82f 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/domain/CommentTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.dnd.spaced.core.account.domain.Account; +import com.dnd.spaced.core.account.domain.enums.ProfileImageName; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.enums.Role; import com.dnd.spaced.core.comment.domain.exception.InvalidCommentContentException; @@ -50,7 +51,7 @@ class CommentTest { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); ReflectionTestUtils.setField(account, "id", 1L); @@ -70,7 +71,7 @@ class CommentTest { .registrationId(RegistrationId.KAKAO) .socialIdentifier("12345") .nickname("재빠른지구001") - .profileImage("earth.png") + .profileImageName(ProfileImageName.EARTH) .role(Role.ROLE_USER) .build(); ReflectionTestUtils.setField(writer, "id", 1L); diff --git a/space-d/src/test/resources/sql/account/account.sql b/space-d/src/test/resources/sql/account/account.sql index 803f6ab3..23c140f6 100644 --- a/space-d/src/test/resources/sql/account/account.sql +++ b/space-d/src/test/resources/sql/account/account.sql @@ -1,11 +1,11 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image_name) VALUES (1, now(), now(), 'KAKAO', '12345', 'STARTUP', 'UNDER_FIRST', 'ETC', false, '재빠른지구001', 'earth.png'); -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image_name) VALUES (2, now(), now(), 'KAKAO', '54321', 'STARTUP', 'UNDER_FIRST', 'ETC', true, '재빠른지구002', 'earth.png'); -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image_name) VALUES (3, now(), now(), 'KAKAO', '13245', null, null, null, false, '재빠른지구002', 'earth.png'); -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image_name) VALUES (4, now(), now(), 'KAKAO', '32145', null, null, null, true, '재빠른지구002', 'earth.png'); diff --git a/space-d/src/test/resources/sql/like/account.sql b/space-d/src/test/resources/sql/like/account.sql index 457d5842..61d7dbff 100644 --- a/space-d/src/test/resources/sql/like/account.sql +++ b/space-d/src/test/resources/sql/like/account.sql @@ -1,2 +1,2 @@ -INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image) +INSERT INTO accounts(id, created_at, updated_at, registration_id, social_id, company, experience, job_group, deleted, nickname, profile_image_name) VALUES (1, now(), now(), 'KAKAO', '12345', 'STARTUP', 'UNDER_FIRST', 'ETC', false, '재빠른지구001', 'earth.png'); From 5ca2fc387d1077ff5b035a72b87da3f67b698cb7 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 20:58:22 +0900 Subject: [PATCH 039/115] =?UTF-8?q?fix:=20=EC=9A=A9=EC=96=B4=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=BF=BC=EB=A6=AC=20=EC=9A=A9?= =?UTF-8?q?=EC=96=B4=20=EB=B0=9C=EC=9D=8C=20=EC=A1=B0=EA=B1=B4=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/persistence/WordViewGatewayRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java index 580aade5..4ae286b7 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java @@ -115,8 +115,8 @@ private List fetchFilteredWordIds(WordSearchCondition condition, WordSearc pageRequest.lastCategory() ), startsWithWordName(condition.name()), - buildPronunciationContentCondition(condition), - word.deleted.isFalse() + word.deleted.isFalse(), + buildPronunciationContentCondition(condition) ) .orderBy(word.name.asc(), word.category.asc(), word.id.desc()) .limit(pageRequest.pageable().getPageSize()) From 9784c93dfc08e0f49143b5bfff2e2301dd3a930f Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 21:16:54 +0900 Subject: [PATCH 040/115] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20Account=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20DTO=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/AccountInfoResponse.java | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/account/presentation/dto/response/AccountInfoResponse.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/dto/response/AccountInfoResponse.java b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/dto/response/AccountInfoResponse.java deleted file mode 100644 index ef1a0c6f..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/dto/response/AccountInfoResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.dnd.spaced.core.account.presentation.dto.response; - -import com.dnd.spaced.core.account.application.dto.response.AccountResponse; - -public record AccountInfoResponse( - String nickname, - String profileImage, - String jobGroupName, - String companyName, - String experienceName -) { - - public static AccountInfoResponse from(AccountResponse dto) { - return new AccountInfoResponse( - dto.nickname(), - dto.profileImage(), - dto.jobGroupName(), - dto.companyName(), - dto.experienceName() - ); - } -} From 93a893a6f999d1f81ad9c94d84352dc5dcf5d2c1 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 21:20:07 +0900 Subject: [PATCH 041/115] =?UTF-8?q?refactor:=20Account=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20DTO=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/InitAccountCareerService.java | 4 ++-- ...nfoRequest.java => InitAccountCareerRequest.java} | 2 +- .../application/dto/response/LoggedInAccountDto.java | 4 ++++ .../dto/response/LoggedInAccountInfoDto.java | 4 ---- .../core/auth/application/internal/LoginService.java | 8 ++++---- .../core/auth/presentation/AuthController.java | 4 ++-- .../auth/security/handler/OAuth2SuccessHandler.java | 4 ++-- .../application/InitAccountCareerServiceTest.java | 12 ++++++------ .../auth/application/internal/LoginServiceTest.java | 6 +++--- .../core/auth/presentation/AuthControllerTest.java | 8 ++++---- 10 files changed, 28 insertions(+), 28 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/{InitAccountCareerInfoRequest.java => InitAccountCareerRequest.java} (85%) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountDto.java delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountInfoDto.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java index e46fea01..9c0ce54f 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java @@ -2,7 +2,7 @@ import com.dnd.spaced.core.account.domain.Account; import com.dnd.spaced.core.account.domain.repository.AccountRepository; -import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerInfoRequest; +import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerRequest; import com.dnd.spaced.core.auth.application.exception.ForbiddenInitCareerInfoException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -15,7 +15,7 @@ public class InitAccountCareerService { private final AccountRepository accountRepository; @Transactional - public void initCareer(Long accountId, InitAccountCareerInfoRequest request) { + public void initCareer(Long accountId, InitAccountCareerRequest request) { Account account = findPreInitializationAccount(accountId); account.changeCareer(request.jobGroupName(), request.companyName(), request.experienceName()); diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/InitAccountCareerInfoRequest.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/InitAccountCareerRequest.java similarity index 85% rename from space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/InitAccountCareerInfoRequest.java rename to space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/InitAccountCareerRequest.java index f1dfd565..40f53961 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/InitAccountCareerInfoRequest.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/request/InitAccountCareerRequest.java @@ -2,7 +2,7 @@ import jakarta.validation.constraints.NotBlank; -public record InitAccountCareerInfoRequest( +public record InitAccountCareerRequest( @NotBlank String jobGroupName, diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountDto.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountDto.java new file mode 100644 index 00000000..e8de14f4 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountDto.java @@ -0,0 +1,4 @@ +package com.dnd.spaced.core.auth.application.dto.response; + +public record LoggedInAccountDto(Long id, String roleName, boolean isSignUp) { +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountInfoDto.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountInfoDto.java deleted file mode 100644 index 1347afec..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/dto/response/LoggedInAccountInfoDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.dnd.spaced.core.auth.application.dto.response; - -public record LoggedInAccountInfoDto(Long id, String roleName, boolean isSignUp) { -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java index 14b01069..7c9236e2 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java @@ -4,7 +4,7 @@ import com.dnd.spaced.core.account.domain.embed.Social; import com.dnd.spaced.core.account.domain.enums.RegistrationId; import com.dnd.spaced.core.account.domain.repository.AccountRepository; -import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountInfoDto; +import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -15,13 +15,13 @@ public class LoginService { private final AccountRepository accountRepository; private final SignUpService signUpService; - public LoggedInAccountInfoDto login(String registrationIdName, String socialIdentifier) { + public LoggedInAccountDto login(String registrationIdName, String socialIdentifier) { RegistrationId registrationId = RegistrationId.findBy(registrationIdName); Social social = new Social(registrationId, socialIdentifier); return accountRepository.findBy(social) .map(account -> - new LoggedInAccountInfoDto( + new LoggedInAccountDto( account.getId(), account.getRole().name(), false) @@ -29,7 +29,7 @@ public LoggedInAccountInfoDto login(String registrationIdName, String socialIden .orElseGet(() -> { Account signedUpAccount = signUpService.signUp(registrationId, socialIdentifier); - return new LoggedInAccountInfoDto( + return new LoggedInAccountDto( signedUpAccount.getId(), signedUpAccount.getRole().name(), true diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java index 6ed45bdb..0fda689b 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java @@ -2,7 +2,7 @@ import com.dnd.spaced.core.auth.application.InitAccountCareerService; import com.dnd.spaced.core.auth.application.RefreshTokenService; -import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerInfoRequest; +import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerRequest; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; import com.dnd.spaced.core.auth.presentation.dto.response.AccessTokenResponse; import com.dnd.spaced.core.auth.presentation.exception.RefreshTokenNotFoundException; @@ -40,7 +40,7 @@ public class AuthController { @PostMapping("/profile") public ResponseEntity initAccountCareerInfo( @CurrentAccount AuthAccountId accountId, - @Valid @RequestBody InitAccountCareerInfoRequest request + @Valid @RequestBody InitAccountCareerRequest request ) { initAccountCareerService.initCareer(accountId.id(), request); diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java b/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java index a6e22ac8..0749a7ed 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java @@ -2,7 +2,7 @@ import com.dnd.spaced.core.auth.application.internal.GenerateTokenService; import com.dnd.spaced.core.auth.application.internal.LoginService; -import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountInfoDto; +import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountDto; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; import com.dnd.spaced.global.auth.exception.InvalidResponseWriteException; import com.dnd.spaced.global.auth.security.dto.response.LoginResponse; @@ -45,7 +45,7 @@ public void onAuthenticationSuccess( String socialIdentifier = (String) oAuth2User.getAttributes() .get(StandardClaimNames.SUB); String registrationId = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(); - LoggedInAccountInfoDto accountInfoDto = loginService.login(registrationId, socialIdentifier); + LoggedInAccountDto accountInfoDto = loginService.login(registrationId, socialIdentifier); TokenDto tokenDto = generateTokenService.generate(accountInfoDto.id(), accountInfoDto.roleName()); writeResponse(response, tokenDto, accountInfoDto.isSignUp()); diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java index 6ad8d8d9..7fa2a31f 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/InitAccountCareerServiceTest.java @@ -6,7 +6,7 @@ import com.dnd.spaced.core.account.domain.enums.exception.InvalidCompanyException; import com.dnd.spaced.core.account.domain.enums.exception.InvalidExperienceException; import com.dnd.spaced.core.account.domain.enums.exception.InvalidJobGroupException; -import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerInfoRequest; +import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerRequest; import com.dnd.spaced.core.auth.application.exception.ForbiddenInitCareerInfoException; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -30,7 +30,7 @@ class InitAccountCareerServiceTest { @Sql("classpath:sql/auth/account.sql") void 경력_정보를_초기화한다() { // given - InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( + InitAccountCareerRequest request = new InitAccountCareerRequest( "개발자", "비공개", "1~2년 차" @@ -45,7 +45,7 @@ class InitAccountCareerServiceTest { @Sql("classpath:sql/auth/account.sql") void 유효한_회사명이_아닌_경우_경력_정보를_초기화할_수_없다(String invalidCompanyName) { // given - InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( + InitAccountCareerRequest request = new InitAccountCareerRequest( "개발자", invalidCompanyName, "1~2년 차" @@ -62,7 +62,7 @@ class InitAccountCareerServiceTest { @Sql("classpath:sql/auth/account.sql") void 유효한_직군이_아닌_경우_경력_정보를_초기화할_수_없다(String invalidJobGroupName) { // given - InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( + InitAccountCareerRequest request = new InitAccountCareerRequest( invalidJobGroupName, "비공개", "1~2년 차" @@ -79,7 +79,7 @@ class InitAccountCareerServiceTest { @Sql("classpath:sql/auth/account.sql") void 유효한_경력이_아닌_경우_경력_정보를_초기화할_수_없다(String invalidExperienceName) { // given - InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( + InitAccountCareerRequest request = new InitAccountCareerRequest( "개발자", "비공개", invalidExperienceName @@ -94,7 +94,7 @@ class InitAccountCareerServiceTest { @Test void 회원_ID가_없거나_이미_탈퇴한_경우_경력_정보를_초기화할_수_없다() { // given - InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( + InitAccountCareerRequest request = new InitAccountCareerRequest( "개발자", "비공개", "1~2년 차" diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java index dd38cf62..d85f85bf 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java @@ -4,7 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountInfoDto; +import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountDto; import com.dnd.spaced.core.auth.application.exception.NicknameMetadataNotFoundException; import com.dnd.spaced.core.skill.application.event.dto.InitializedAccountEvent; import org.junit.jupiter.api.DisplayNameGeneration; @@ -33,7 +33,7 @@ class LoginServiceTest { @Sql("classpath:sql/auth/nickname_metadata.sql") void 회원가입하지_않은_회원이_로그인하면_회원_가입과_로그인_절차를_진행한다() { // when - LoggedInAccountInfoDto actual = loginService.login("kakao", "12345"); + LoggedInAccountDto actual = loginService.login("kakao", "12345"); // then assertAll( @@ -52,7 +52,7 @@ class LoginServiceTest { void 회원가입한_회원이_로그인하면_로그인_절차를_진행한다() { // given // when - LoggedInAccountInfoDto actual = loginService.login("kakao", "12345"); + LoggedInAccountDto actual = loginService.login("kakao", "12345"); // then assertAll( diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java index 0c5e0bee..8e1d90e3 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/presentation/AuthControllerTest.java @@ -26,7 +26,7 @@ import com.dnd.spaced.config.docs.link.DocumentLinkGenerator.DocsUrl; import com.dnd.spaced.core.auth.application.InitAccountCareerService; import com.dnd.spaced.core.auth.application.RefreshTokenService; -import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerInfoRequest; +import com.dnd.spaced.core.auth.application.dto.request.InitAccountCareerRequest; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.Test; @@ -50,8 +50,8 @@ class AuthControllerTest extends CommonControllerSliceTest { void 회원_프로필_초기화_요청_성공_테스트() throws Exception { // given willDoNothing().given(initAccountCareerService) - .initCareer(anyLong(), any(InitAccountCareerInfoRequest.class)); - InitAccountCareerInfoRequest request = new InitAccountCareerInfoRequest( + .initCareer(anyLong(), any(InitAccountCareerRequest.class)); + InitAccountCareerRequest request = new InitAccountCareerRequest( "개발자", "중소기업", "1년 차 미만" @@ -66,7 +66,7 @@ class AuthControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(initAccountCareerService).initCareer(anyLong(), any(InitAccountCareerInfoRequest.class)); + verify(initAccountCareerService).initCareer(anyLong(), any(InitAccountCareerRequest.class)); 회원_프로필_초기화_요청_문서화(resultActions); } From 2985efc04b08e3f5544aeaf9fc474888d372b74b Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 21:24:25 +0900 Subject: [PATCH 042/115] =?UTF-8?q?refactor:=20AccountController=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spaced/core/account/presentation/AccountController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java index 319c2966..3b9e8a64 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java @@ -32,7 +32,7 @@ public ResponseEntity withdrawal(@CurrentAccount AuthAccountId accountId) } @PutMapping("/career-info") - public ResponseEntity changeCareerInfo( + public ResponseEntity changeCareer( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ChangeCareerRequest request ) { @@ -42,7 +42,7 @@ public ResponseEntity changeCareerInfo( } @PutMapping("/profile-info") - public ResponseEntity changeProfileInfo( + public ResponseEntity changeProfile( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ChangeProfileRequest request ) { From 650a15542b6018a217548a0390a9c9e3df2b3b42 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 21:29:23 +0900 Subject: [PATCH 043/115] =?UTF-8?q?refactor:=20AccountService=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/application/AccountService.java | 56 +++++++++++++------ .../spaced/core/account/domain/Account.java | 2 +- .../presentation/AccountController.java | 4 +- .../application/AccountServiceTest.java | 16 +++--- .../core/account/domain/AccountTest.java | 6 +- .../presentation/AccountControllerTest.java | 4 +- 6 files changed, 56 insertions(+), 32 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java index 3ad116e4..17c6fc7d 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java @@ -20,37 +20,61 @@ public class AccountService { @Transactional public void withdrawal(Long accountId) { - Account authorizedAccount = findAuthorizedAccount(accountId); + Account account = findAccount(accountId); - authorizedAccount.withdrawal(); + withdrawAccount(account); } @Transactional - public void changeCareerInfo(Long accountId, ChangeCareerRequest request) { - Account authorizedAccount = findAuthorizedAccount(accountId); + public void changeCareer(Long accountId, ChangeCareerRequest request) { + Account account = findAccount(accountId); - authorizedAccount.changeCareer( - request.changedJobGroupName(), - request.changedCompanyName(), - request.changedExperienceName() - ); + updateAccountCareer(account, request); } @Transactional - public void changeProfileInfo(Long accountId, ChangeProfileRequest request) { - Account authorizedAccount = findAuthorizedAccount(accountId); - ProfileImageName changedProfileImageName = ProfileImageName.findByKorean(request.changedProfileImageKoreanName()); + public void changeProfile(Long accountId, ChangeProfileRequest request) { + Account account = findAccount(accountId); + ProfileImageName changedProfileImageName = findProfileImageName(request); - authorizedAccount.changeProfileInfo(request.changedNickname(), changedProfileImageName); + updateAccountProfile(account, request, changedProfileImageName); } public AccountResponse readAccount(Long accountId) { - Account authorizedAccount = findAuthorizedAccount(accountId); + Account account = findAccount(accountId); + + return convertAccountResponse(account); + } + + private void withdrawAccount(Account account) { + account.withdrawal(); + } + + private ProfileImageName findProfileImageName(ChangeProfileRequest request) { + return ProfileImageName.findByKorean(request.changedProfileImageKoreanName()); + } + + private void updateAccountProfile( + Account account, + ChangeProfileRequest request, + ProfileImageName changedProfileImageName + ) { + account.changeProfile(request.changedNickname(), changedProfileImageName); + } + + private void updateAccountCareer(Account account, ChangeCareerRequest request) { + account.changeCareer( + request.changedJobGroupName(), + request.changedCompanyName(), + request.changedExperienceName() + ); + } - return AccountResponseMapper.toDto(authorizedAccount); + private AccountResponse convertAccountResponse(Account account) { + return AccountResponseMapper.toDto(account); } - private Account findAuthorizedAccount(Long accountId) { + private Account findAccount(Long accountId) { return accountRepository.findBy(accountId) .orElseThrow(() -> new ForbiddenAccountException("존재하지 않는 회원이거나 이미 탈퇴한 회원입니다.")); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java b/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java index b712650f..f127583c 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/domain/Account.java @@ -74,7 +74,7 @@ public void changeCareer( .build(); } - public void changeProfileInfo(String changedNickname, ProfileImageName changedProfileImageName) { + public void changeProfile(String changedNickname, ProfileImageName changedProfileImageName) { this.profile = Profile.of(changedNickname, changedProfileImageName); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java index 3b9e8a64..81b52bf6 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/presentation/AccountController.java @@ -36,7 +36,7 @@ public ResponseEntity changeCareer( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ChangeCareerRequest request ) { - accountService.changeCareerInfo(accountId.id(), request); + accountService.changeCareer(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } @@ -46,7 +46,7 @@ public ResponseEntity changeProfile( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody ChangeProfileRequest request ) { - accountService.changeProfileInfo(accountId.id(), request); + accountService.changeProfile(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java index ef388bd5..f30a39e5 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/application/AccountServiceTest.java @@ -62,7 +62,7 @@ class AccountServiceTest { ); // when - accountService.changeCareerInfo(1L, request); + accountService.changeCareer(1L, request); // then AccountResponse actual = accountService.readAccount(1L); @@ -86,7 +86,7 @@ class AccountServiceTest { ); // when & then - assertThatThrownBy(() -> accountService.changeCareerInfo(1L, request)) + assertThatThrownBy(() -> accountService.changeCareer(1L, request)) .isInstanceOf(InvalidJobGroupException.class) .hasMessageContaining("잘못된 직군 이름"); } @@ -103,7 +103,7 @@ class AccountServiceTest { ); // when & then - assertThatThrownBy(() -> accountService.changeCareerInfo(1L, request)) + assertThatThrownBy(() -> accountService.changeCareer(1L, request)) .isInstanceOf(InvalidCompanyException.class) .hasMessageContaining("잘못된 회사 이름"); } @@ -120,7 +120,7 @@ class AccountServiceTest { ); // when & then - assertThatThrownBy(() -> accountService.changeCareerInfo(1L, request)) + assertThatThrownBy(() -> accountService.changeCareer(1L, request)) .isInstanceOf(InvalidExperienceException.class) .hasMessageContaining("잘못된 경력"); } @@ -135,7 +135,7 @@ class AccountServiceTest { ); // when & then - assertThatThrownBy(() -> accountService.changeCareerInfo(-999L, request)) + assertThatThrownBy(() -> accountService.changeCareer(-999L, request)) .isInstanceOf(ForbiddenAccountException.class) .hasMessage("존재하지 않는 회원이거나 이미 탈퇴한 회원입니다."); } @@ -156,7 +156,7 @@ private static Stream changeProfileInfoTestWithProfileImageKoreanName ); // when - accountService.changeProfileInfo(1L, request); + accountService.changeProfile(1L, request); // then AccountResponse actual = accountService.readAccount(1L); @@ -178,7 +178,7 @@ private static Stream changeProfileInfoTestWithProfileImageKoreanName ); // when & then - assertThatThrownBy(() -> accountService.changeProfileInfo(1L, request)) + assertThatThrownBy(() -> accountService.changeProfile(1L, request)) .isInstanceOf(InvalidProfileImageNameException.class) .hasMessageContaining("잘못된 프로필 이미지 이름"); } @@ -192,7 +192,7 @@ private static Stream changeProfileInfoTestWithProfileImageKoreanName ); // when & then - assertThatThrownBy(() -> accountService.changeProfileInfo(-999L, request)) + assertThatThrownBy(() -> accountService.changeProfile(-999L, request)) .isInstanceOf(ForbiddenAccountException.class) .hasMessage("존재하지 않는 회원이거나 이미 탈퇴한 회원입니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java index c4c995c0..a054795b 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/AccountTest.java @@ -202,7 +202,7 @@ private static Stream builderTestWithInvalidNickname() { .build(); // when - account.changeProfileInfo("행복한화성001", ProfileImageName.MARS); + account.changeProfile("행복한화성001", ProfileImageName.MARS); // then assertAll( @@ -223,7 +223,7 @@ private static Stream builderTestWithInvalidNickname() { .build(); // when & then - assertThatThrownBy(() -> account.changeProfileInfo("행복한화성001", null)) + assertThatThrownBy(() -> account.changeProfile("행복한화성001", null)) .isInstanceOf(InvalidProfileImageException.class) .hasMessage("프로필 이미지 정보는 null일 수 없습니다."); } @@ -251,7 +251,7 @@ private static Stream changeProfileInfoTestWithInvalidNickname() { .build(); // when & then - assertThatThrownBy(() -> account.changeProfileInfo(invalidNickname, ProfileImageName.MARS)) + assertThatThrownBy(() -> account.changeProfile(invalidNickname, ProfileImageName.MARS)) .isInstanceOf(InvalidNicknameException.class) .hasMessage("닉네임은 최소 5글자 이상, 최대 10글자 이하여야 합니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java index 1d645c0c..b8803717 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/presentation/AccountControllerTest.java @@ -77,7 +77,7 @@ class AccountControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(accountService).changeCareerInfo(anyLong(), any(ChangeCareerRequest.class)); + verify(accountService).changeCareer(anyLong(), any(ChangeCareerRequest.class)); 회원_경력_정보_변경_요청_문서화(resultActions); } @@ -113,7 +113,7 @@ class AccountControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(accountService).changeProfileInfo(anyLong(), any(ChangeProfileRequest.class)); + verify(accountService).changeProfile(anyLong(), any(ChangeProfileRequest.class)); 회원_프로필_정보_변경_요청_문서화(resultActions); } From 8fde0530de8ba0c1609707fb4c19094dfe6d6e08 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 22:35:41 +0900 Subject: [PATCH 044/115] =?UTF-8?q?refactor:=20AccountResponseMapper?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84?= =?UTF-8?q?=EB=A7=81=20=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/account/application/AccountService.java | 3 ++- .../dto/mapper/AccountResponseMapper.java | 9 ++++----- .../java/com/dnd/spaced/global/mapper/Mapper.java | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 space-d/src/main/java/com/dnd/spaced/global/mapper/Mapper.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java index 17c6fc7d..1a829b06 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/AccountService.java @@ -17,6 +17,7 @@ public class AccountService { private final AccountRepository accountRepository; + private final AccountResponseMapper mapper; @Transactional public void withdrawal(Long accountId) { @@ -71,7 +72,7 @@ private void updateAccountCareer(Account account, ChangeCareerRequest request) { } private AccountResponse convertAccountResponse(Account account) { - return AccountResponseMapper.toDto(account); + return mapper.toDto(account); } private Account findAccount(Long accountId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java index 89c914c7..d7846a83 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/account/application/dto/mapper/AccountResponseMapper.java @@ -4,13 +4,12 @@ import com.dnd.spaced.core.account.domain.Account; import com.dnd.spaced.core.account.domain.embed.Career; import com.dnd.spaced.core.account.domain.embed.Profile; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; +import com.dnd.spaced.global.mapper.Mapper; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class AccountResponseMapper { +@Mapper +public class AccountResponseMapper { - public static AccountResponse toDto(Account account) { + public AccountResponse toDto(Account account) { Profile profile = account.getProfile(); Career career = account.getCareer(); diff --git a/space-d/src/main/java/com/dnd/spaced/global/mapper/Mapper.java b/space-d/src/main/java/com/dnd/spaced/global/mapper/Mapper.java new file mode 100644 index 00000000..7c9e0746 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/global/mapper/Mapper.java @@ -0,0 +1,14 @@ +package com.dnd.spaced.global.mapper; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.stereotype.Component; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface Mapper { + +} From 0e905a9025114214974262a9513e02cce85daf7b Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 22:43:21 +0900 Subject: [PATCH 045/115] =?UTF-8?q?test:=20ProfileImageName=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spaced/core/account/domain/enums/ProfileImageNameTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java b/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java index 2dbabd65..ba34cd94 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/account/domain/enums/ProfileImageNameTest.java @@ -83,7 +83,7 @@ private static Stream findByImageNameTestWithImageName() { @NullAndEmptySource void 유효한_이미지_이름이_아니라면_프로필_이미지를_찾을_수_없다(String invalidImageName) { // when & then - assertThatThrownBy(() -> ProfileImageName.findByKorean(invalidImageName)) + assertThatThrownBy(() -> ProfileImageName.findByImageName(invalidImageName)) .isInstanceOf(InvalidProfileImageNameException.class) .hasMessageContaining("잘못된 프로필 이미지 이름"); } From c3d710321a0c06ee10612332b6f7d18129152ba9 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 22:57:53 +0900 Subject: [PATCH 046/115] =?UTF-8?q?refactor:=20AdminReportService=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/admin/application/AdminReportService.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java index 250fc370..5dd4bf64 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java @@ -29,7 +29,7 @@ public void processReport(Long reportId, ProcessReportRequest request) { Report report = findReport(reportId); ReportStatus reportStatus = findReportStatus(request); - report.process(reportStatus); + processReport(report, reportStatus); publishProcessedReportEvent(reportStatus, report); } @@ -37,7 +37,7 @@ public ReportCollectionResponse readReports(ReadAllReportSearchRequest request, ReportStatus reportStatus = findReportStatus(request); List reports = findAllReportsBy(request, reportStatus, pageable); - return ReportInfoMapper.toDto(reports); + return convertReportCollectionResponse(reports); } private Report findReport(Long reportId) { @@ -59,6 +59,10 @@ private ReportStatus findReportStatus(ReadAllReportSearchRequest request) { .orElse(null); } + private void processReport(Report report, ReportStatus reportStatus) { + report.process(reportStatus); + } + private void publishProcessedReportEvent(ReportStatus reportStatus, Report report) { eventPublisher.publishEvent(new ProcessedReportEvent(reportStatus, report.getCommentId())); } @@ -70,4 +74,8 @@ private List findAllReportsBy( ) { return reportRepository.findAllBy(reportStatus, request.lastReportId(), pageable); } + + private ReportCollectionResponse convertReportCollectionResponse(List reports) { + return ReportInfoMapper.toDto(reports); + } } From defa1eea6aec124c85e595ded301675d41c8e798 Mon Sep 17 00:00:00 2001 From: apptie Date: Fri, 11 Jul 2025 23:42:49 +0900 Subject: [PATCH 047/115] =?UTF-8?q?refactor:=20ReportResponseMapper?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84?= =?UTF-8?q?=EB=A7=81=20=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/admin/application/AdminReportService.java | 5 +++-- ...ortInfoMapper.java => ReportResponseMapper.java} | 13 ++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/{ReportInfoMapper.java => ReportResponseMapper.java} (71%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java index 5dd4bf64..1ec78588 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminReportService.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.admin.application; -import com.dnd.spaced.core.admin.application.dto.mapper.ReportInfoMapper; +import com.dnd.spaced.core.admin.application.dto.mapper.ReportResponseMapper; import com.dnd.spaced.core.admin.application.dto.request.ProcessReportRequest; import com.dnd.spaced.core.admin.application.dto.request.ReadAllReportSearchRequest; import com.dnd.spaced.core.admin.application.dto.resposne.ReportCollectionResponse; @@ -22,6 +22,7 @@ public class AdminReportService { private final ReportRepository reportRepository; + private final ReportResponseMapper mapper; private final ApplicationEventPublisher eventPublisher; @Transactional @@ -76,6 +77,6 @@ private List findAllReportsBy( } private ReportCollectionResponse convertReportCollectionResponse(List reports) { - return ReportInfoMapper.toDto(reports); + return mapper.toDto(reports); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/ReportInfoMapper.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/ReportResponseMapper.java similarity index 71% rename from space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/ReportInfoMapper.java rename to space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/ReportResponseMapper.java index 3b550789..b7c22a95 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/ReportInfoMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/mapper/ReportResponseMapper.java @@ -3,26 +3,25 @@ import com.dnd.spaced.core.admin.application.dto.resposne.ReportCollectionResponse; import com.dnd.spaced.core.admin.application.dto.resposne.ReportCollectionResponse.ReportResponse; import com.dnd.spaced.core.report.domain.Report; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ReportInfoMapper { +@Mapper +public class ReportResponseMapper { - public static ReportCollectionResponse toDto(List reports) { + public ReportCollectionResponse toDto(List reports) { if (reports.isEmpty()) { return new ReportCollectionResponse(List.of(), null); } List reportResponses = reports.stream() - .map(ReportInfoMapper::toReportDto) + .map(this::toReportDto) .toList(); return new ReportCollectionResponse(reportResponses, reports.get(reports.size() - 1).getId()); } - private static ReportResponse toReportDto(Report report) { + private ReportResponse toReportDto(Report report) { return new ReportResponse( report.getId(), report.getCommentId(), From af15c2b17421e13bc1806f6efd2e1ada527f7b1a Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 12 Jul 2025 00:34:04 +0900 Subject: [PATCH 048/115] =?UTF-8?q?feat:=20=EC=98=A4=EB=8A=98=EC=9D=98=20?= =?UTF-8?q?=ED=80=B4=EC=A6=88=20=EC=83=9D=EC=84=B1=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CreateTodayQuizService.java | 130 ++++++++++++++++++ .../CreateTodayQuizServiceTest.java | 58 ++++++++ 2 files changed, 188 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java new file mode 100644 index 00000000..4cd39f6c --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java @@ -0,0 +1,130 @@ +package com.dnd.spaced.core.admin.application; + +import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; +import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; +import com.dnd.spaced.core.quiz.domain.TodayQuiz; +import com.dnd.spaced.core.quiz.domain.TodayQuizOption; +import com.dnd.spaced.core.quiz.domain.embed.TodayQuizAnswerOption; +import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; +import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizOptionRepository; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; +import com.dnd.spaced.core.quiz.domain.service.QuizWordCountValidator; +import com.dnd.spaced.core.word.domain.WordMetadata; +import com.dnd.spaced.core.word.domain.dto.SimpleWord; +import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; +import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; +import com.dnd.spaced.global.config.properties.QuizQuestionProperties; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CreateTodayQuizService { + + private static final Long DEFAULT_WORD_METADATA_ID = 1L; + private static final int REQUIRED_TODAY_QUIZ_WORD_COUNT = 4; + private static final int ANSWER_OPTION_INDEX = 0; + + private final TodayQuizRepository todayQuizRepository; + private final WordRandomRepository wordRandomRepository; + private final WordMetadataRepository wordMetadataRepository; + private final TodayQuizOptionRepository todayQuizOptionRepository; + private final QuizQuestionProperties quizQuestionProperties; + + public TodayQuiz createTodayQuiz() { + QuizCategory quizCategory = findRandomQuizCategory(); + + validateQuizCreationRequirements(quizCategory); + + return createTodayQuiz(quizCategory); + } + + private QuizCategory findRandomQuizCategory() { + return QuizCategory.findRandom(); + } + + private void validateQuizCreationRequirements(QuizCategory quizCategory) { + WordMetadata wordMetadata = findWordMetadata(); + + validateWordCount(quizCategory, wordMetadata); + } + + private WordMetadata findWordMetadata() { + return wordMetadataRepository.findBy(DEFAULT_WORD_METADATA_ID) + .orElseThrow( + () -> new WordMetadataNotFoundException("용어 메타데이터가 정상적으로 설정되지 않았습니다.") + ); + } + + private void validateWordCount(QuizCategory quizCategory, WordMetadata wordMetadata) { + QuizWordCountValidator validator = QuizWordCountValidator.create(); + + if (validator.isInvalidate(quizCategory, wordMetadata, REQUIRED_TODAY_QUIZ_WORD_COUNT)) { + throw new InvalidTodayQuizWordCountException("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); + } + } + + private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { + List randomWords = findRandomWords(quizCategory); + TodayQuiz todayQuiz = initTodayQuiz(quizCategory, randomWords); + TodayQuiz persistedTodayQuiz = persistTodayQuiz(todayQuiz); + + persistTodayQuizOptions(randomWords, todayQuiz); + return persistedTodayQuiz; + } + + private List findRandomWords(QuizCategory quizCategory) { + return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_TODAY_QUIZ_WORD_COUNT); + } + + private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { + SimpleWord answerWord = randomWords.get(ANSWER_OPTION_INDEX); + TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption( + answerWord.id(), + answerWord.name() + ); + TodayQuizQuestion todayQuizQuestion = TodayQuizQuestion.of( + quizCategory, + quizQuestionProperties.getQuestion(), + answerWord.meaning(), + todayQuizAnswerOption + ); + + return new TodayQuiz(todayQuizQuestion); + } + + private TodayQuiz persistTodayQuiz(TodayQuiz todayQuiz) { + return todayQuizRepository.save(todayQuiz); + } + + private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { + List shuffledWords = shuffleRandomWords(randomWords); + List todayQuizOptions = initTodayQuizOptions(shuffledWords, todayQuiz); + + saveAllTodayQuizOptions(todayQuizOptions); + } + + private List shuffleRandomWords(List randomWords) { + Collections.shuffle(randomWords); + + return randomWords; + } + + private List initTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { + return IntStream.range(0, randomWords.size()) + .mapToObj(i -> { + SimpleWord simpleWord = randomWords.get(i); + + return TodayQuizOption.of(simpleWord.id(), simpleWord.name(), i, todayQuiz); + }) + .toList(); + } + + private void saveAllTodayQuizOptions(List todayQuizOptions) { + todayQuizOptionRepository.saveAll(todayQuizOptions); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java new file mode 100644 index 00000000..54dfefb2 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java @@ -0,0 +1,58 @@ +package com.dnd.spaced.core.admin.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; +import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; +import com.dnd.spaced.core.quiz.domain.TodayQuiz; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CreateTodayQuizServiceTest { + + @Autowired + CreateTodayQuizService createTodayQuizService; + + @Test + void 용어_메타데이터가_정상적으로_설정되지_않다면_오늘의_퀴즈를_생성할_수_없다() { + // when & then + assertThatThrownBy(() -> createTodayQuizService.createTodayQuiz()) + .isInstanceOf(WordMetadataNotFoundException.class) + .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/quiz/word_metadata.sql", + "classpath:sql/admin/quiz/quiz_metadata.sql" + }) + void 등록된_용어_수가_퀴즈_생성_시_필요한_용어_수보다_적으면_퀴즈를_생성할_수_없다() { + // when & then + assertThatThrownBy(() -> createTodayQuizService.createTodayQuiz()) + .isInstanceOf(InvalidTodayQuizWordCountException.class) + .hasMessage("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/quiz/word_metadata.sql", + "classpath:sql/admin/quiz/quiz_metadata.sql", + "classpath:sql/admin/quiz/word.sql" + }) + void 오늘의_퀴즈를_생성한다() { + // when + TodayQuiz actual = createTodayQuizService.createTodayQuiz(); + + // then + assertThat(actual.getId()).isPositive(); + } +} From 3643a17f31edcb359754a11de4bd77acda10d8be Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 12 Jul 2025 00:43:35 +0900 Subject: [PATCH 049/115] =?UTF-8?q?test:=20AdminTodayQuizService=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminTodayQuizServiceTest.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java index 3b0fb8a9..b4d60bb5 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java @@ -2,15 +2,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; +import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; +import com.dnd.spaced.global.consts.CacheConst; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cache.CacheManager; import org.springframework.test.context.event.ApplicationEvents; import org.springframework.test.context.event.RecordApplicationEvents; import org.springframework.test.context.jdbc.Sql; @@ -27,6 +31,9 @@ class AdminTodayQuizServiceTest { @Autowired AdminTodayQuizService adminTodayQuizService; + @Autowired + CacheManager memoryCacheManager; + @Test void 용어_메타데이터가_정상적으로_설정되지_않다면_오늘의_퀴즈를_생성할_수_없다() { // when & then @@ -58,6 +65,10 @@ class AdminTodayQuizServiceTest { Long savedTodayQuizId = adminTodayQuizService.createTodayQuiz(); // then - assertThat(savedTodayQuizId).isPositive(); + assertAll( + () -> assertThat(savedTodayQuizId).isPositive(), + () -> assertThat(events.stream(AddedTodayQuizQuestionEvent.class).count()).isOne(), + () -> assertThat(memoryCacheManager.getCache(CacheConst.TODAY_QUIZ_CACHE_NAME)).isNotNull() + ); } } From 05368a7c26279ee91653d0994a2cc7eba243d8f8 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 12 Jul 2025 01:01:02 +0900 Subject: [PATCH 050/115] =?UTF-8?q?refactor:=20AdminTodayQuizService=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminTodayQuizService.java | 137 ------------------ .../AdminTodayQuizServiceFacade.java | 44 ++++++ .../AdminTodayQuizController.java | 6 +- ...a => AdminTodayQuizServiceFacadeTest.java} | 8 +- .../AdminTodayQuizControllerTest.java | 9 +- 5 files changed, 55 insertions(+), 149 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java rename space-d/src/test/java/com/dnd/spaced/core/admin/application/{AdminTodayQuizServiceTest.java => AdminTodayQuizServiceFacadeTest.java} (90%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java deleted file mode 100644 index a2b169b7..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizService.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.dnd.spaced.core.admin.application; - -import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; -import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; -import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; -import com.dnd.spaced.core.quiz.domain.TodayQuiz; -import com.dnd.spaced.core.quiz.domain.TodayQuizOption; -import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizInfoMapper; -import com.dnd.spaced.core.quiz.domain.embed.TodayQuizAnswerOption; -import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; -import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; -import com.dnd.spaced.core.quiz.domain.repository.TodayQuizOptionRepository; -import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; -import com.dnd.spaced.core.quiz.domain.service.QuizWordCountValidator; -import com.dnd.spaced.core.word.domain.WordMetadata; -import com.dnd.spaced.core.word.domain.dto.SimpleWord; -import com.dnd.spaced.core.word.domain.repository.WordMetadataRepository; -import com.dnd.spaced.core.word.domain.repository.WordRandomRepository; -import com.dnd.spaced.global.config.properties.QuizQuestionProperties; -import com.dnd.spaced.global.consts.CacheConst; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class AdminTodayQuizService { - - private static final Long DEFAULT_WORD_METADATA_ID = 1L; - private static final int REQUIRED_QUIZ_WORD_COUNT = 4; - private static final int ANSWER_OPTION_INDEX = 0; - - private final TodayQuizRepository todayQuizRepository; - private final WordRandomRepository wordRandomRepository; - private final WordMetadataRepository wordMetadataRepository; - private final TodayQuizOptionRepository todayQuizOptionRepository; - private final QuizQuestionProperties quizQuestionProperties; - private final CacheManager memoryCacheManager; - private final ApplicationEventPublisher eventPublisher; - - @Transactional - public Long createTodayQuiz() { - QuizCategory quizCategory = QuizCategory.findRandom(); - - validateQuizCreation(quizCategory); - - TodayQuiz todayQuiz = createTodayQuiz(quizCategory); - - persistMemoryCache(todayQuiz); - - return todayQuiz.getId(); - } - - private void validateQuizCreation(QuizCategory quizCategory) { - WordMetadata wordMetadata = findWordMetadata(); - - validateQuizWordCount(quizCategory, wordMetadata); - } - - private WordMetadata findWordMetadata() { - return wordMetadataRepository.findBy(DEFAULT_WORD_METADATA_ID) - .orElseThrow( - () -> new WordMetadataNotFoundException("용어 메타데이터가 정상적으로 설정되지 않았습니다.") - ); - } - - private void validateQuizWordCount(QuizCategory quizCategory, WordMetadata wordMetadata) { - QuizWordCountValidator validator = QuizWordCountValidator.create(); - - if (validator.isInvalidate(quizCategory, wordMetadata, REQUIRED_QUIZ_WORD_COUNT)) { - throw new InvalidTodayQuizWordCountException("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); - } - } - - private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { - List randomWords = findRandomWords(quizCategory); - TodayQuiz todayQuiz = initTodayQuiz(quizCategory, randomWords); - TodayQuiz savedTodayQuiz = todayQuizRepository.save(todayQuiz); - - persistTodayQuizOptions(randomWords, todayQuiz); - publishAddedTodayQuizQuestionEvent(); - return savedTodayQuiz; - } - - private List findRandomWords(QuizCategory quizCategory) { - return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_QUIZ_WORD_COUNT); - } - - private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { - SimpleWord answerWord = randomWords.get(ANSWER_OPTION_INDEX); - TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption( - answerWord.id(), - answerWord.name() - ); - TodayQuizQuestion todayQuizQuestion = TodayQuizQuestion.of( - quizCategory, - quizQuestionProperties.getQuestion(), - answerWord.meaning(), - todayQuizAnswerOption - ); - - return new TodayQuiz(todayQuizQuestion); - } - - private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { - Collections.shuffle(randomWords); - - List todayQuizOptions = new ArrayList<>(); - for (int i = 0; i < randomWords.size(); i++) { - SimpleWord word = randomWords.get(i); - - TodayQuizOption todayQuizOption = TodayQuizOption.of(word.id(), word.name(), i, todayQuiz); - todayQuizOptions.add(todayQuizOption); - } - - todayQuizOptionRepository.saveAll(todayQuizOptions); - } - - private void publishAddedTodayQuizQuestionEvent() { - eventPublisher.publishEvent(new AddedTodayQuizQuestionEvent()); - } - - private void persistMemoryCache(TodayQuiz todayQuiz) { - Cache cache = memoryCacheManager.getCache(CacheConst.TODAY_QUIZ_CACHE_NAME); - - if (cache != null) { - cache.clear(); - cache.put(CacheConst.TODAY_QUIZ_CACHE_NAME, TodayQuizInfoMapper.toDto(todayQuiz)); - } - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java new file mode 100644 index 00000000..37d6e318 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java @@ -0,0 +1,44 @@ +package com.dnd.spaced.core.admin.application; + +import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; +import com.dnd.spaced.core.quiz.domain.TodayQuiz; +import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizInfoMapper; +import com.dnd.spaced.global.consts.CacheConst; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AdminTodayQuizServiceFacade { + + private final CreateTodayQuizService createTodayQuizService; + private final CacheManager memoryCacheManager; + private final ApplicationEventPublisher eventPublisher; + + @Transactional + public Long createTodayQuiz() { + TodayQuiz todayQuiz = createTodayQuizService.createTodayQuiz(); + + publishAddedTodayQuizQuestionEvent(); + persistMemoryCache(todayQuiz); + + return todayQuiz.getId(); + } + + private void publishAddedTodayQuizQuestionEvent() { + eventPublisher.publishEvent(new AddedTodayQuizQuestionEvent()); + } + + private void persistMemoryCache(TodayQuiz todayQuiz) { + Cache cache = memoryCacheManager.getCache(CacheConst.TODAY_QUIZ_CACHE_NAME); + + if (cache != null) { + cache.clear(); + cache.put(CacheConst.TODAY_QUIZ_CACHE_NAME, TodayQuizInfoMapper.toDto(todayQuiz)); + } + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizController.java b/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizController.java index 0e654bf2..bc676a88 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.admin.presentation; -import com.dnd.spaced.core.admin.application.AdminTodayQuizService; +import com.dnd.spaced.core.admin.application.AdminTodayQuizServiceFacade; import java.net.URI; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -14,11 +14,11 @@ @RequiredArgsConstructor public class AdminTodayQuizController { - private final AdminTodayQuizService adminTodayQuizService; + private final AdminTodayQuizServiceFacade adminTodayQuizServiceFacade; @PostMapping public ResponseEntity createTodayQuiz() { - Long todayQuizId = adminTodayQuizService.createTodayQuiz(); + Long todayQuizId = adminTodayQuizServiceFacade.createTodayQuiz(); URI location = UriComponentsBuilder.fromPath("/today-quizzes/{todayQuizId}") .buildAndExpand(todayQuizId) .toUri(); diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java similarity index 90% rename from space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java index b4d60bb5..7dbef612 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java @@ -29,7 +29,7 @@ class AdminTodayQuizServiceTest { ApplicationEvents events; @Autowired - AdminTodayQuizService adminTodayQuizService; + AdminTodayQuizServiceFacade adminTodayQuizServiceFacade; @Autowired CacheManager memoryCacheManager; @@ -37,7 +37,7 @@ class AdminTodayQuizServiceTest { @Test void 용어_메타데이터가_정상적으로_설정되지_않다면_오늘의_퀴즈를_생성할_수_없다() { // when & then - assertThatThrownBy(() -> adminTodayQuizService.createTodayQuiz()) + assertThatThrownBy(() -> adminTodayQuizServiceFacade.createTodayQuiz()) .isInstanceOf(WordMetadataNotFoundException.class) .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); } @@ -49,7 +49,7 @@ class AdminTodayQuizServiceTest { }) void 등록된_용어_수가_퀴즈_생성_시_필요한_용어_수보다_적으면_퀴즈를_생성할_수_없다() { // when & then - assertThatThrownBy(() -> adminTodayQuizService.createTodayQuiz()) + assertThatThrownBy(() -> adminTodayQuizServiceFacade.createTodayQuiz()) .isInstanceOf(InvalidTodayQuizWordCountException.class) .hasMessage("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } @@ -62,7 +62,7 @@ class AdminTodayQuizServiceTest { }) void 오늘의_퀴즈를_생성한다() { // when - Long savedTodayQuizId = adminTodayQuizService.createTodayQuiz(); + Long savedTodayQuizId = adminTodayQuizServiceFacade.createTodayQuiz(); // then assertAll( diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizControllerTest.java index 42178ea8..5a69b264 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminTodayQuizControllerTest.java @@ -10,10 +10,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.dnd.spaced.config.common.CommonControllerSliceTest; -import com.dnd.spaced.core.admin.application.AdminTodayQuizService; +import com.dnd.spaced.core.admin.application.AdminTodayQuizServiceFacade; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.ResultActions; @@ -22,13 +21,13 @@ class AdminTodayQuizControllerTest extends CommonControllerSliceTest { @Autowired - AdminTodayQuizService adminTodayQuizService; + AdminTodayQuizServiceFacade adminTodayQuizServiceFacade; @Test @WithMockUser(value = "1", roles = "ADMIN") void 오늘의_퀴즈_수동_생성_요청_성공_테스트() throws Exception { // given - given(adminTodayQuizService.createTodayQuiz()).willReturn(1L); + given(adminTodayQuizServiceFacade.createTodayQuiz()).willReturn(1L); // when & then ResultActions resultActions = mockMvc.perform( @@ -38,7 +37,7 @@ class AdminTodayQuizControllerTest extends CommonControllerSliceTest { header().string("Location", "/today-quizzes/1") ); - verify(adminTodayQuizService).createTodayQuiz(); + verify(adminTodayQuizServiceFacade).createTodayQuiz(); 오늘의_퀴즈_수동_생성_요청_문서화(resultActions); } From c0dd304145e2c60c7f48f2a7961c7d4426f0f4fb Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 12 Jul 2025 01:06:27 +0900 Subject: [PATCH 051/115] =?UTF-8?q?fix:=20=EC=9A=A9=EC=96=B4=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=BF=BC=EB=A6=AC=20=EC=9A=A9?= =?UTF-8?q?=EC=96=B4=20=EC=9D=B4=EB=A6=84=20=EC=A1=B0=EA=B1=B4=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/persistence/WordViewGatewayRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java index 4ae286b7..aa192552 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/WordViewGatewayRepository.java @@ -109,12 +109,12 @@ private List fetchFilteredWordIds(WordSearchCondition condition, WordSearc return queryFactory.select(word.id) .from(word) .where( + startsWithWordName(condition.name()), buildWordPaginationCondition( condition.category(), pageRequest.lastWordName(), pageRequest.lastCategory() - ), - startsWithWordName(condition.name()), + ), word.deleted.isFalse(), buildPronunciationContentCondition(condition) ) From 2c0ae84d7fd37f81cc3f42e91403cb23878aa588 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 13 Jul 2025 15:17:58 +0900 Subject: [PATCH 052/115] =?UTF-8?q?test:=20AdminTodayQuizServiceFacade=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/admin/application/AdminTodayQuizServiceFacadeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java index 7dbef612..911a4a24 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacadeTest.java @@ -23,7 +23,7 @@ @RecordApplicationEvents @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class AdminTodayQuizServiceTest { +class AdminTodayQuizServiceFacadeTest { @Autowired ApplicationEvents events; From 7a0bde8cf99b9ed83eb3c4da6af89d3e04193a64 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 13 Jul 2025 15:35:40 +0900 Subject: [PATCH 053/115] =?UTF-8?q?refactor:=20CreateTodayQuizService=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spaced/core/admin/application/CreateTodayQuizService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java index 4cd39f6c..aad1420a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java @@ -23,7 +23,7 @@ @Service @RequiredArgsConstructor -public class CreateTodayQuizService { +class CreateTodayQuizService { private static final Long DEFAULT_WORD_METADATA_ID = 1L; private static final int REQUIRED_TODAY_QUIZ_WORD_COUNT = 4; From deb2d894eb0dae92435712eef8619b5aa090eef8 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 13 Jul 2025 15:41:18 +0900 Subject: [PATCH 054/115] =?UTF-8?q?refactor:=20CreateTodayQuizService=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/CreateTodayQuizService.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java index aad1420a..62ba601e 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java @@ -116,14 +116,16 @@ private List shuffleRandomWords(List randomWords) { private List initTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { return IntStream.range(0, randomWords.size()) - .mapToObj(i -> { - SimpleWord simpleWord = randomWords.get(i); - - return TodayQuizOption.of(simpleWord.id(), simpleWord.name(), i, todayQuiz); - }) + .mapToObj(i -> initTodayQuizOption(randomWords, todayQuiz, i)) .toList(); } + private TodayQuizOption initTodayQuizOption(List randomWords, TodayQuiz todayQuiz, int index) { + SimpleWord simpleWord = randomWords.get(index); + + return TodayQuizOption.of(simpleWord.id(), simpleWord.name(), index, todayQuiz); + } + private void saveAllTodayQuizOptions(List todayQuizOptions) { todayQuizOptionRepository.saveAll(todayQuizOptions); } From 4aba22f365399a9812b6fde1c4bd10ff1b331150 Mon Sep 17 00:00:00 2001 From: apptie Date: Sun, 13 Jul 2025 16:08:05 +0900 Subject: [PATCH 055/115] =?UTF-8?q?refactor:=20CreateTodayQuizService=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminTodayQuizServiceFacade.java | 2 +- .../application/CreateTodayQuizService.java | 28 ++++++++++--------- .../CreateTodayQuizServiceTest.java | 6 ++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java index 37d6e318..829f6d00 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java @@ -21,7 +21,7 @@ public class AdminTodayQuizServiceFacade { @Transactional public Long createTodayQuiz() { - TodayQuiz todayQuiz = createTodayQuizService.createTodayQuiz(); + TodayQuiz todayQuiz = createTodayQuizService.assembleTodayQuiz(); publishAddedTodayQuizQuestionEvent(); persistMemoryCache(todayQuiz); diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java index 62ba601e..06ce4c96 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java @@ -20,6 +20,7 @@ import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -35,12 +36,13 @@ class CreateTodayQuizService { private final TodayQuizOptionRepository todayQuizOptionRepository; private final QuizQuestionProperties quizQuestionProperties; - public TodayQuiz createTodayQuiz() { + @Transactional + public TodayQuiz assembleTodayQuiz() { QuizCategory quizCategory = findRandomQuizCategory(); validateQuizCreationRequirements(quizCategory); - return createTodayQuiz(quizCategory); + return assembleTodayQuiz(quizCategory); } private QuizCategory findRandomQuizCategory() { @@ -68,12 +70,12 @@ private void validateWordCount(QuizCategory quizCategory, WordMetadata wordMetad } } - private TodayQuiz createTodayQuiz(QuizCategory quizCategory) { + private TodayQuiz assembleTodayQuiz(QuizCategory quizCategory) { List randomWords = findRandomWords(quizCategory); - TodayQuiz todayQuiz = initTodayQuiz(quizCategory, randomWords); + TodayQuiz todayQuiz = buildTodayQuiz(quizCategory, randomWords); TodayQuiz persistedTodayQuiz = persistTodayQuiz(todayQuiz); - persistTodayQuizOptions(randomWords, todayQuiz); + setupTodayQuizOptions(randomWords, todayQuiz); return persistedTodayQuiz; } @@ -81,7 +83,7 @@ private List findRandomWords(QuizCategory quizCategory) { return wordRandomRepository.findRandomAllBy(quizCategory, REQUIRED_TODAY_QUIZ_WORD_COUNT); } - private TodayQuiz initTodayQuiz(QuizCategory quizCategory, List randomWords) { + private TodayQuiz buildTodayQuiz(QuizCategory quizCategory, List randomWords) { SimpleWord answerWord = randomWords.get(ANSWER_OPTION_INDEX); TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption( answerWord.id(), @@ -101,11 +103,11 @@ private TodayQuiz persistTodayQuiz(TodayQuiz todayQuiz) { return todayQuizRepository.save(todayQuiz); } - private void persistTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { + private void setupTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { List shuffledWords = shuffleRandomWords(randomWords); - List todayQuizOptions = initTodayQuizOptions(shuffledWords, todayQuiz); + List todayQuizOptions = buildTodayQuizOptions(shuffledWords, todayQuiz); - saveAllTodayQuizOptions(todayQuizOptions); + setupTodayQuizOptions(todayQuizOptions); } private List shuffleRandomWords(List randomWords) { @@ -114,19 +116,19 @@ private List shuffleRandomWords(List randomWords) { return randomWords; } - private List initTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { + private List buildTodayQuizOptions(List randomWords, TodayQuiz todayQuiz) { return IntStream.range(0, randomWords.size()) - .mapToObj(i -> initTodayQuizOption(randomWords, todayQuiz, i)) + .mapToObj(i -> buildTodayQuizOption(randomWords, todayQuiz, i)) .toList(); } - private TodayQuizOption initTodayQuizOption(List randomWords, TodayQuiz todayQuiz, int index) { + private TodayQuizOption buildTodayQuizOption(List randomWords, TodayQuiz todayQuiz, int index) { SimpleWord simpleWord = randomWords.get(index); return TodayQuizOption.of(simpleWord.id(), simpleWord.name(), index, todayQuiz); } - private void saveAllTodayQuizOptions(List todayQuizOptions) { + private void setupTodayQuizOptions(List todayQuizOptions) { todayQuizOptionRepository.saveAll(todayQuizOptions); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java index 54dfefb2..00ec5a2a 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java @@ -25,7 +25,7 @@ class CreateTodayQuizServiceTest { @Test void 용어_메타데이터가_정상적으로_설정되지_않다면_오늘의_퀴즈를_생성할_수_없다() { // when & then - assertThatThrownBy(() -> createTodayQuizService.createTodayQuiz()) + assertThatThrownBy(() -> createTodayQuizService.assembleTodayQuiz()) .isInstanceOf(WordMetadataNotFoundException.class) .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); } @@ -37,7 +37,7 @@ class CreateTodayQuizServiceTest { }) void 등록된_용어_수가_퀴즈_생성_시_필요한_용어_수보다_적으면_퀴즈를_생성할_수_없다() { // when & then - assertThatThrownBy(() -> createTodayQuizService.createTodayQuiz()) + assertThatThrownBy(() -> createTodayQuizService.assembleTodayQuiz()) .isInstanceOf(InvalidTodayQuizWordCountException.class) .hasMessage("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } @@ -50,7 +50,7 @@ class CreateTodayQuizServiceTest { }) void 오늘의_퀴즈를_생성한다() { // when - TodayQuiz actual = createTodayQuizService.createTodayQuiz(); + TodayQuiz actual = createTodayQuizService.assembleTodayQuiz(); // then assertThat(actual.getId()).isPositive(); From 6ee802704c48be599372cbf6edcd364f0ad047d0 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 14:00:02 +0900 Subject: [PATCH 056/115] =?UTF-8?q?test:=20AdminWordService=20=EC=9A=A9?= =?UTF-8?q?=EC=96=B4=20=EC=B6=94=EA=B0=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/admin/application/AdminWordServiceTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java index 8868a82f..6b77484b 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; @@ -11,6 +12,7 @@ import com.dnd.spaced.core.admin.application.exception.PronunciationNotFoundException; import com.dnd.spaced.core.admin.application.exception.WordExampleDeletionNotAllowedException; import com.dnd.spaced.core.admin.application.exception.WordExampleNotFoundException; +import com.dnd.spaced.core.word.application.event.dto.PersistedWordEvent; import com.dnd.spaced.core.word.application.exception.WordNotFoundException; import com.dnd.spaced.core.word.domain.exception.InvalidWordExampleContentException; import java.util.List; @@ -58,7 +60,10 @@ class AdminWordServiceTest { Long actual = adminWordService.createWord(request); // then - assertThat(actual).isPositive(); + assertAll( + () -> assertThat(actual).isPositive(), + () -> assertThat(events.stream(PersistedWordEvent.class).count()).isOne() + ); } @Test From 2ac3a8a143a2e36274ab8e3dd676fe9d279615a0 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:18:12 +0900 Subject: [PATCH 057/115] =?UTF-8?q?feat:=20=EC=9A=A9=EC=96=B4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/CreateWordService.java | 107 ++++++++++++++++++ .../dto/resposne/PersistWordDto.java | 6 + .../application/CreateWordServiceTest.java | 52 +++++++++ 3 files changed, 165 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateWordService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/resposne/PersistWordDto.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateWordServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateWordService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateWordService.java new file mode 100644 index 00000000..05675c9e --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateWordService.java @@ -0,0 +1,107 @@ +package com.dnd.spaced.core.admin.application; + +import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; +import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest.CreatePronunciationRequest; +import com.dnd.spaced.core.admin.application.dto.resposne.PersistWordDto; +import com.dnd.spaced.core.word.domain.Pronunciation; +import com.dnd.spaced.core.word.domain.Word; +import com.dnd.spaced.core.word.domain.WordExample; +import com.dnd.spaced.core.word.domain.repository.PronunciationRepository; +import com.dnd.spaced.core.word.domain.repository.WordExampleRepository; +import com.dnd.spaced.core.word.domain.repository.WordRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +class CreateWordService { + + private final WordRepository wordRepository; + private final WordExampleRepository wordExampleRepository; + private final PronunciationRepository pronunciationRepository; + + @Transactional + public PersistWordDto createWord(CreateWordRequest createWordRequest) { + Word word = setupWord(createWordRequest); + + setupWordExamples(word, createWordRequest); + setupPronunciations(word, createWordRequest); + + return convertPersistedWordDto(word); + } + + private Word setupWord(CreateWordRequest createWordRequest) { + Word word = buildWord(createWordRequest); + + return persistWord(word); + } + + private Word persistWord(Word word) { + return wordRepository.save(word); + } + + private Word buildWord(CreateWordRequest request) { + return Word.builder() + .name(request.name()) + .meaning(request.meaning()) + .categoryName(request.categoryName()) + .build(); + } + + private void setupWordExamples(Word word, CreateWordRequest request) { + List wordExamples = buildWordExamples(word, request); + + persistWordExamples(wordExamples); + } + + private List buildWordExamples(Word word, CreateWordRequest request) { + return request.examples() + .stream() + .map(example -> buildWordExample(word, example)) + .toList(); + } + + private WordExample buildWordExample(Word word, String example) { + WordExample wordExample = WordExample.from(example); + + wordExample.initWord(word); + return wordExample; + } + + private void persistWordExamples(List wordExamples) { + wordExampleRepository.saveAll(wordExamples); + } + + private void setupPronunciations(Word word, CreateWordRequest request) { + List pronunciations = buildPronunciations(word, request); + + persistPronunciations(pronunciations); + } + + private void persistPronunciations(List pronunciations) { + pronunciationRepository.saveAll(pronunciations); + } + + private List buildPronunciations(Word word, CreateWordRequest wordRequest) { + return wordRequest.pronunciations() + .stream() + .map(pronunciationRequest -> buildPronunciation(word, pronunciationRequest)) + .toList(); + } + + private Pronunciation buildPronunciation(Word word, CreatePronunciationRequest pronunciationRequest) { + Pronunciation pronunciation = Pronunciation.of( + pronunciationRequest.pronunciation(), + pronunciationRequest.typeName() + ); + + pronunciation.initWord(word); + return pronunciation; + } + + private PersistWordDto convertPersistedWordDto(Word word) { + return new PersistWordDto(word.getId(), word.getCategory()); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/resposne/PersistWordDto.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/resposne/PersistWordDto.java new file mode 100644 index 00000000..9d278d07 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/dto/resposne/PersistWordDto.java @@ -0,0 +1,6 @@ +package com.dnd.spaced.core.admin.application.dto.resposne; + +import com.dnd.spaced.core.word.domain.enums.Category; + +public record PersistWordDto(Long id, Category category) { +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateWordServiceTest.java new file mode 100644 index 00000000..20c5608c --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateWordServiceTest.java @@ -0,0 +1,52 @@ +package com.dnd.spaced.core.admin.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; +import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest.CreatePronunciationRequest; +import com.dnd.spaced.core.admin.application.dto.resposne.PersistWordDto; +import com.dnd.spaced.core.word.domain.enums.Category; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CreateWordServiceTest { + + @Autowired + CreateWordService createWordService; + + @Test + @Sql("classpath:sql/admin/word/word_metadata.sql") + void 용어를_추가한다() { + // given + List createPronunciationRequests = List.of( + new CreatePronunciationRequest("어써라이제이션", "한글 발음") + ); + List examples = List.of("게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다."); + CreateWordRequest request = new CreateWordRequest( + "Authorization", + "Authorization(권한 부여)은 인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘", + "개발", + createPronunciationRequests, + examples + ); + + // when + PersistWordDto actual = createWordService.createWord(request); + + // then + assertAll( + () -> assertThat(actual.id()).isPositive(), + () -> assertThat(actual.category()).isEqualTo(Category.DEVELOP) + ); + } +} From 124875e5202f8eb657380384392073b8ab07a20b Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:19:54 +0900 Subject: [PATCH 058/115] =?UTF-8?q?feat:=20=EC=9A=A9=EC=96=B4=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/UpdateWordService.java | 33 +++++++++++++ .../application/UpdateWordServiceTest.java | 47 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/UpdateWordService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/admin/application/UpdateWordServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/UpdateWordService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/UpdateWordService.java new file mode 100644 index 00000000..0eb2f700 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/UpdateWordService.java @@ -0,0 +1,33 @@ +package com.dnd.spaced.core.admin.application; + +import com.dnd.spaced.core.admin.application.exception.WordExampleNotFoundException; +import com.dnd.spaced.core.word.domain.WordExample; +import com.dnd.spaced.core.word.domain.repository.WordExampleRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +class UpdateWordService { + + private final WordExampleRepository wordExampleRepository; + + @Transactional + public void updateWordExample(Long wordExampleId, String example) { + WordExample wordExample = findWordExample(wordExampleId); + + executeWordExampleUpdate(example, wordExample); + } + + private WordExample findWordExample(Long wordExampleId) { + return wordExampleRepository.findBy(wordExampleId) + .orElseThrow(() -> new WordExampleNotFoundException( + "지정한 용어 예문을 찾을 수 없습니다.") + ); + } + + private void executeWordExampleUpdate(String example, WordExample wordExample) { + wordExample.changeExample(example); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/UpdateWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/UpdateWordServiceTest.java new file mode 100644 index 00000000..1942d249 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/UpdateWordServiceTest.java @@ -0,0 +1,47 @@ +package com.dnd.spaced.core.admin.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.admin.application.exception.WordExampleNotFoundException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class UpdateWordServiceTest { + + @Autowired + UpdateWordService updateWordService; + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 용어_예문을_변경한다() { + // when & then + assertDoesNotThrow(() -> updateWordService.updateWordExample( + 1L, + "이 기능은 일반 사용자의 Authorization 범위를 벗어나므로, 관리자 권한이 필요합니다.") + ); + } + + @Test + void 잘못된_용어_예문_ID라면_용어_예문을_변경할_수_없다() { + // when & then + assertThatThrownBy( + () -> updateWordService.updateWordExample( + -999L, + "이 기능은 일반 사용자의 Authorization 범위를 벗어나므로, 관리자 권한이 필요합니다." + ) + ).isInstanceOf(WordExampleNotFoundException.class) + .hasMessage("지정한 용어 예문을 찾을 수 없습니다."); + } +} From d1d699d0d7365c0eed395fbabc8e127da431d7f1 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:20:01 +0900 Subject: [PATCH 059/115] =?UTF-8?q?feat:=20=EC=9A=A9=EC=96=B4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ordService.java => DeleteWordService.java} | 93 +++----------- .../application/DeleteWordServiceTest.java | 118 ++++++++++++++++++ 2 files changed, 134 insertions(+), 77 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/admin/application/{AdminWordService.java => DeleteWordService.java} (54%) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/admin/application/DeleteWordServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/DeleteWordService.java similarity index 54% rename from space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordService.java rename to space-d/src/main/java/com/dnd/spaced/core/admin/application/DeleteWordService.java index 13f9655a..1df98052 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/DeleteWordService.java @@ -1,13 +1,9 @@ package com.dnd.spaced.core.admin.application; -import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; -import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest.CreatePronunciationRequest; -import com.dnd.spaced.core.admin.application.event.dto.DeletedWordEvent; import com.dnd.spaced.core.admin.application.exception.PronunciationDeletionNotAllowedException; import com.dnd.spaced.core.admin.application.exception.PronunciationNotFoundException; import com.dnd.spaced.core.admin.application.exception.WordExampleDeletionNotAllowedException; import com.dnd.spaced.core.admin.application.exception.WordExampleNotFoundException; -import com.dnd.spaced.core.word.application.event.dto.PersistedWordEvent; import com.dnd.spaced.core.word.application.exception.WordNotFoundException; import com.dnd.spaced.core.word.domain.Pronunciation; import com.dnd.spaced.core.word.domain.Word; @@ -15,16 +11,13 @@ import com.dnd.spaced.core.word.domain.repository.PronunciationRepository; import com.dnd.spaced.core.word.domain.repository.WordExampleRepository; import com.dnd.spaced.core.word.domain.repository.WordRepository; -import java.util.ArrayList; -import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -public class AdminWordService { +class DeleteWordService { private static final long WORD_EXAMPLE_MIN_COUNT = 1L; private static final long PRONUNCIATION_MIN_COUNT = 1L; @@ -32,33 +25,20 @@ public class AdminWordService { private final WordRepository wordRepository; private final WordExampleRepository wordExampleRepository; private final PronunciationRepository pronunciationRepository; - private final ApplicationEventPublisher eventPublisher; @Transactional - public Long createWord(CreateWordRequest createWordRequest) { - Word word = buildWordFromRequest(createWordRequest); - Word savedWord = wordRepository.save(word); - - persistExamples(savedWord, createWordRequest); - persistPronunciations(savedWord, createWordRequest); - publishPersistedEvent(savedWord); - - return savedWord.getId(); - } - - @Transactional - public void updateWordExample(Long wordExampleId, String example) { - WordExample wordExample = findWordExample(wordExampleId); + public void deleteWord(Long wordId) { + Word word = findWord(wordId); - wordExample.changeExample(example); + executeWordDeletion(word); } @Transactional public void deleteWordExample(Long wordId, Long wordExampleId) { WordExample wordExample = findWordExample(wordExampleId); - validateExampleCount(wordId); - wordExample.deleted(); + validateWordExampleCount(wordId); + executeWordExampleDeletion(wordExample); } @Transactional @@ -66,15 +46,7 @@ public void deletePronunciation(Long wordId, Long pronunciationId) { Pronunciation pronunciation = findPronunciation(pronunciationId); validatePronunciationCount(wordId); - pronunciation.deleted(); - } - - @Transactional - public void deleteWord(Long wordId) { - Word word = findWord(wordId); - - word.delete(); - publishDeletedWordEvent(wordId); + executePronunciationDeletion(pronunciation); } private Word findWord(Long wordId) { @@ -82,43 +54,6 @@ private Word findWord(Long wordId) { .orElseThrow(() -> new WordNotFoundException("지정한 용어를 찾을 수 없습니다.")); } - private Word buildWordFromRequest(CreateWordRequest request) { - return Word.builder() - .name(request.name()) - .meaning(request.meaning()) - .categoryName(request.categoryName()) - .build(); - } - - private void persistExamples(Word word, CreateWordRequest request) { - List wordExamples = new ArrayList<>(); - - for (String example : request.examples()) { - WordExample wordExample = WordExample.from(example); - - wordExample.initWord(word); - wordExamples.add(wordExample); - } - - wordExampleRepository.saveAll(wordExamples); - } - - private void persistPronunciations(Word word, CreateWordRequest request) { - List pronunciations = new ArrayList<>(); - - for (CreatePronunciationRequest pronunciationInfo : request.pronunciations()) { - Pronunciation pronunciation = Pronunciation.of( - pronunciationInfo.pronunciation(), - pronunciationInfo.typeName() - ); - - pronunciation.initWord(word); - pronunciations.add(pronunciation); - } - - pronunciationRepository.saveAll(pronunciations); - } - private WordExample findWordExample(Long wordExampleId) { return wordExampleRepository.findBy(wordExampleId) .orElseThrow(() -> new WordExampleNotFoundException( @@ -133,7 +68,7 @@ private Pronunciation findPronunciation(Long pronunciationId) { ); } - private void validateExampleCount(Long wordId) { + private void validateWordExampleCount(Long wordId) { if (wordExampleRepository.countBy(wordId) <= WORD_EXAMPLE_MIN_COUNT) { throw new WordExampleDeletionNotAllowedException("해당 용어의 예문 개수가 최소치입니다."); } @@ -145,11 +80,15 @@ private void validatePronunciationCount(Long wordId) { } } - private void publishDeletedWordEvent(Long wordId) { - eventPublisher.publishEvent(new DeletedWordEvent(wordId)); + private void executeWordDeletion(Word word) { + word.delete(); } - private void publishPersistedEvent(Word word) { - eventPublisher.publishEvent(new PersistedWordEvent(word.getId(), word.getCategory())); + private void executeWordExampleDeletion(WordExample wordExample) { + wordExample.deleted(); + } + + private void executePronunciationDeletion(Pronunciation pronunciation) { + pronunciation.deleted(); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/DeleteWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/DeleteWordServiceTest.java new file mode 100644 index 00000000..c97b9923 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/DeleteWordServiceTest.java @@ -0,0 +1,118 @@ +package com.dnd.spaced.core.admin.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.admin.application.exception.PronunciationDeletionNotAllowedException; +import com.dnd.spaced.core.admin.application.exception.PronunciationNotFoundException; +import com.dnd.spaced.core.admin.application.exception.WordExampleDeletionNotAllowedException; +import com.dnd.spaced.core.admin.application.exception.WordExampleNotFoundException; +import com.dnd.spaced.core.word.application.exception.WordNotFoundException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class DeleteWordServiceTest { + + @Autowired + DeleteWordService deleteWordService; + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 용어_예문을_삭제한다() { + // when & then + assertDoesNotThrow( + () -> deleteWordService.deleteWordExample(1L, 1L) + ); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 잘못된_용어_예문_ID로_용어_예문을_삭제할_수_없다() { + // when & then + assertThatThrownBy(() -> deleteWordService.deleteWordExample(1L, -999L)) + .isInstanceOf(WordExampleNotFoundException.class) + .hasMessage("지정한 용어 예문을 찾을 수 없습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 용어_예문의_개수가_최소치라면_용어_예문을_삭제할_수_없다() { + // when & then + assertThatThrownBy( + () -> deleteWordService.deleteWordExample(2L, 3L) + ).isInstanceOf(WordExampleDeletionNotAllowedException.class) + .hasMessage("해당 용어의 예문 개수가 최소치입니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 잘못된_용어_발음_ID로_용어_발음을_삭제할_수_없다() { + // when & then + assertThatThrownBy(() -> deleteWordService.deletePronunciation(1L, -999L)) + .isInstanceOf(PronunciationNotFoundException.class) + .hasMessage("지정한 발음을 찾지 못했습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 용어_발음_정보를_삭제한다() { + // when & then + assertDoesNotThrow( + () -> deleteWordService.deletePronunciation(1L, 1L) + ); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 용어_발음_정보의_개수가_최소치라면_용어_발음_정보를_삭제할_수_없다() { + // when & then + assertThatThrownBy( + () -> deleteWordService.deletePronunciation(2L, 1L) + ).isInstanceOf(PronunciationDeletionNotAllowedException.class) + .hasMessage("해당 용어의 발음 정보 개수가 최소치입니다."); + } + + @Test + void 유효하지_않은_용어_ID로_용어를_삭제할_수_없다() { + // when & then + assertThatThrownBy(() -> deleteWordService.deleteWord(-999L)) + .isInstanceOf(WordNotFoundException.class) + .hasMessage("지정한 용어를 찾을 수 없습니다."); + } + + @Test + @Sql(scripts = { + "classpath:sql/admin/word/word_metadata.sql", + "classpath:sql/admin/word/word.sql" + }) + void 용어를_삭제한다() { + // when & then + assertDoesNotThrow(() -> deleteWordService.deleteWord(1L)); + } +} From c49e6b9cc18a5f08e5aec0e5cade98ad1dabb014 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:22:41 +0900 Subject: [PATCH 060/115] =?UTF-8?q?refactor:=20AdminWordService=20Facade?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminWordServiceFacade.java | 58 +++++++++++++++++++ .../presentation/AdminWordController.java | 14 ++--- ...t.java => AdminWordServiceFacadeTest.java} | 28 ++++----- .../presentation/AdminWordControllerTest.java | 16 ++--- .../WordPersistEventListenerTest.java | 10 ++-- 5 files changed, 92 insertions(+), 34 deletions(-) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java rename space-d/src/test/java/com/dnd/spaced/core/admin/application/{AdminWordServiceTest.java => AdminWordServiceFacadeTest.java} (88%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java new file mode 100644 index 00000000..607ff44b --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java @@ -0,0 +1,58 @@ +package com.dnd.spaced.core.admin.application; + +import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; +import com.dnd.spaced.core.admin.application.dto.resposne.PersistWordDto; +import com.dnd.spaced.core.admin.application.event.dto.DeletedWordEvent; +import com.dnd.spaced.core.word.application.event.dto.PersistedWordEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AdminWordServiceFacade { + + private final CreateWordService createWordservice; + private final UpdateWordService updateWordService; + private final DeleteWordService deleteWordService; + private final ApplicationEventPublisher eventPublisher; + + @Transactional + public Long createWord(CreateWordRequest createWordRequest) { + PersistWordDto wordDto = createWordservice.createWord(createWordRequest); + + publishPersistedWordEvent(wordDto); + return wordDto.id(); + } + + @Transactional + public void updateWordExample(Long wordExampleId, String example) { + updateWordService.updateWordExample(wordExampleId, example); + } + + @Transactional + public void deleteWordExample(Long wordId, Long wordExampleId) { + deleteWordService.deleteWordExample(wordId, wordExampleId); + } + + @Transactional + public void deletePronunciation(Long wordId, Long pronunciationId) { + deleteWordService.deletePronunciation(wordId, pronunciationId); + } + + @Transactional + public void deleteWord(Long wordId) { + deleteWordService.deleteWord(wordId); + + publishDeletedWordEvent(wordId); + } + + private void publishDeletedWordEvent(Long wordId) { + eventPublisher.publishEvent(new DeletedWordEvent(wordId)); + } + + private void publishPersistedWordEvent(PersistWordDto wordDto) { + eventPublisher.publishEvent(new PersistedWordEvent(wordDto.id(), wordDto.category())); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminWordController.java b/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminWordController.java index 083a985c..793da365 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminWordController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/presentation/AdminWordController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.admin.presentation; -import com.dnd.spaced.core.admin.application.AdminWordService; +import com.dnd.spaced.core.admin.application.AdminWordServiceFacade; import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; import com.dnd.spaced.core.admin.application.dto.request.UpdateWordExampleRequest; import com.dnd.spaced.global.consts.controller.ResponseEntityConst; @@ -22,11 +22,11 @@ @RequiredArgsConstructor public class AdminWordController { - private final AdminWordService adminWordService; + private final AdminWordServiceFacade adminWordServiceFacade; @PostMapping public ResponseEntity createWord(@Valid @RequestBody CreateWordRequest request) { - Long wordId = adminWordService.createWord(request); + Long wordId = adminWordServiceFacade.createWord(request); URI location = UriComponentsBuilder.fromPath("/words/{wordId}") .buildAndExpand(wordId) .toUri(); @@ -40,28 +40,28 @@ public ResponseEntity updateWordExample( @PathVariable Long wordExampleId, @Valid @RequestBody UpdateWordExampleRequest request ) { - adminWordService.updateWordExample(wordExampleId, request.content()); + adminWordServiceFacade.updateWordExample(wordExampleId, request.content()); return ResponseEntityConst.NO_CONTENT; } @DeleteMapping("/{wordId}") public ResponseEntity deleteWord(@PathVariable Long wordId) { - adminWordService.deleteWord(wordId); + adminWordServiceFacade.deleteWord(wordId); return ResponseEntityConst.NO_CONTENT; } @DeleteMapping("/{wordId}/examples/{wordExampleId}") public ResponseEntity deleteWordExample(@PathVariable Long wordId, @PathVariable Long wordExampleId) { - adminWordService.deleteWordExample(wordId, wordExampleId); + adminWordServiceFacade.deleteWordExample(wordId, wordExampleId); return ResponseEntityConst.NO_CONTENT; } @DeleteMapping("/{wordId}/pronunciations/{pronunciationId}") public ResponseEntity deletePronunciation(@PathVariable Long wordId, @PathVariable Long pronunciationId) { - adminWordService.deletePronunciation(wordId, pronunciationId); + adminWordServiceFacade.deletePronunciation(wordId, pronunciationId); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacadeTest.java similarity index 88% rename from space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacadeTest.java index 6b77484b..7d8347c0 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacadeTest.java @@ -32,10 +32,10 @@ @SuppressWarnings("NonAsciiCharacters") @RecordApplicationEvents @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class AdminWordServiceTest { +class AdminWordServiceFacadeTest { @Autowired - AdminWordService adminWordService; + AdminWordServiceFacade adminWordServiceFacade; @Autowired ApplicationEvents events; @@ -57,7 +57,7 @@ class AdminWordServiceTest { ); // when - Long actual = adminWordService.createWord(request); + Long actual = adminWordServiceFacade.createWord(request); // then assertAll( @@ -73,7 +73,7 @@ class AdminWordServiceTest { }) void 용어_예문을_변경한다() { // when & then - assertDoesNotThrow(() -> adminWordService.updateWordExample( + assertDoesNotThrow(() -> adminWordServiceFacade.updateWordExample( 1L, "이 기능은 일반 사용자의 Authorization 범위를 벗어나므로, 관리자 권한이 필요합니다.") ); @@ -83,7 +83,7 @@ class AdminWordServiceTest { void 잘못된_용어_예문_ID라면_용어_예문을_변경할_수_없다() { // when & then assertThatThrownBy( - () -> adminWordService.updateWordExample( + () -> adminWordServiceFacade.updateWordExample( -999L, "이 기능은 일반 사용자의 Authorization 범위를 벗어나므로, 관리자 권한이 필요합니다." ) @@ -100,7 +100,7 @@ class AdminWordServiceTest { void 유효하지_않은_길이의_예문으로_용어_예문을_변경할_수_없다(String invalidContent) { // when & then assertThatThrownBy( - () -> adminWordService.updateWordExample( + () -> adminWordServiceFacade.updateWordExample( 1L, invalidContent ) @@ -116,7 +116,7 @@ class AdminWordServiceTest { void 용어_예문을_삭제한다() { // when & then assertDoesNotThrow( - () -> adminWordService.deleteWordExample(1L, 1L) + () -> adminWordServiceFacade.deleteWordExample(1L, 1L) ); } @@ -127,7 +127,7 @@ class AdminWordServiceTest { }) void 잘못된_용어_예문_ID로_용어_예문을_삭제할_수_없다() { // when & then - assertThatThrownBy(() -> adminWordService.deleteWordExample(1L, -999L)) + assertThatThrownBy(() -> adminWordServiceFacade.deleteWordExample(1L, -999L)) .isInstanceOf(WordExampleNotFoundException.class) .hasMessage("지정한 용어 예문을 찾을 수 없습니다."); } @@ -140,7 +140,7 @@ class AdminWordServiceTest { void 용어_예문의_개수가_최소치라면_용어_예문을_삭제할_수_없다() { // when & then assertThatThrownBy( - () -> adminWordService.deleteWordExample(2L, 3L) + () -> adminWordServiceFacade.deleteWordExample(2L, 3L) ).isInstanceOf(WordExampleDeletionNotAllowedException.class) .hasMessage("해당 용어의 예문 개수가 최소치입니다."); } @@ -152,7 +152,7 @@ class AdminWordServiceTest { }) void 잘못된_용어_발음_ID로_용어_발음을_삭제할_수_없다() { // when & then - assertThatThrownBy(() -> adminWordService.deletePronunciation(1L, -999L)) + assertThatThrownBy(() -> adminWordServiceFacade.deletePronunciation(1L, -999L)) .isInstanceOf(PronunciationNotFoundException.class) .hasMessage("지정한 발음을 찾지 못했습니다."); } @@ -165,7 +165,7 @@ class AdminWordServiceTest { void 용어_발음_정보를_삭제한다() { // when & then assertDoesNotThrow( - () -> adminWordService.deletePronunciation(1L, 1L) + () -> adminWordServiceFacade.deletePronunciation(1L, 1L) ); } @@ -177,7 +177,7 @@ class AdminWordServiceTest { void 용어_발음_정보의_개수가_최소치라면_용어_발음_정보를_삭제할_수_없다() { // when & then assertThatThrownBy( - () -> adminWordService.deletePronunciation(2L, 1L) + () -> adminWordServiceFacade.deletePronunciation(2L, 1L) ).isInstanceOf(PronunciationDeletionNotAllowedException.class) .hasMessage("해당 용어의 발음 정보 개수가 최소치입니다."); } @@ -185,7 +185,7 @@ class AdminWordServiceTest { @Test void 유효하지_않은_용어_ID로_용어를_삭제할_수_없다() { // when & then - assertThatThrownBy(() -> adminWordService.deleteWord(-999L)) + assertThatThrownBy(() -> adminWordServiceFacade.deleteWord(-999L)) .isInstanceOf(WordNotFoundException.class) .hasMessage("지정한 용어를 찾을 수 없습니다."); } @@ -197,7 +197,7 @@ class AdminWordServiceTest { }) void 용어를_삭제한다() { // when & then - assertDoesNotThrow(() -> adminWordService.deleteWord(1L)); + assertDoesNotThrow(() -> adminWordServiceFacade.deleteWord(1L)); assertThat(events.stream(DeletedWordEvent.class).count()).isOne(); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminWordControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminWordControllerTest.java index 5676a241..96988505 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminWordControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/presentation/AdminWordControllerTest.java @@ -22,7 +22,7 @@ import com.dnd.spaced.config.common.CommonControllerSliceTest; import com.dnd.spaced.config.docs.link.DocumentLinkGenerator.DocsUrl; -import com.dnd.spaced.core.admin.application.AdminWordService; +import com.dnd.spaced.core.admin.application.AdminWordServiceFacade; import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest.CreatePronunciationRequest; import com.dnd.spaced.core.admin.application.dto.request.UpdateWordExampleRequest; @@ -38,7 +38,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { @Autowired - AdminWordService adminWordService; + AdminWordServiceFacade adminWordServiceFacade; @Test @WithMockUser(value = "1", roles = "ADMIN") @@ -56,7 +56,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { example ); - given(adminWordService.createWord(any(CreateWordRequest.class))).willReturn(1L); + given(adminWordServiceFacade.createWord(any(CreateWordRequest.class))).willReturn(1L); // when & then ResultActions resultActions = mockMvc.perform( @@ -68,7 +68,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { header().stringValues("Location", "/words/1") ); - verify(adminWordService).createWord(any(CreateWordRequest.class)); + verify(adminWordServiceFacade).createWord(any(CreateWordRequest.class)); 용어_등록_요청_문서화(resultActions); } @@ -116,7 +116,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(adminWordService).updateWordExample(anyLong(), anyString()); + verify(adminWordServiceFacade).updateWordExample(anyLong(), anyString()); 용어_예문_변경_요청_문서화(resultActions); } @@ -148,7 +148,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(adminWordService).deleteWordExample(anyLong(), anyLong()); + verify(adminWordServiceFacade).deleteWordExample(anyLong(), anyLong()); 용어_예문_삭제_요청_문서화(resultActions); } @@ -178,7 +178,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(adminWordService).deletePronunciation(anyLong(), anyLong()); + verify(adminWordServiceFacade).deletePronunciation(anyLong(), anyLong()); 용어_발음_정보_삭제_요청_문서화(resultAction); } @@ -206,7 +206,7 @@ class AdminWordControllerTest extends CommonControllerSliceTest { .header(HttpHeaders.AUTHORIZATION, "Bearer AccessToken") ).andExpectAll(status().isNoContent()); - verify(adminWordService).deleteWord(anyLong()); + verify(adminWordServiceFacade).deleteWord(anyLong()); 용어_삭제_요청_문서화(resultAction); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/event/listener/WordPersistEventListenerTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/event/listener/WordPersistEventListenerTest.java index 1d9190b7..cc40515e 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/application/event/listener/WordPersistEventListenerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/event/listener/WordPersistEventListenerTest.java @@ -12,7 +12,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.dnd.spaced.core.admin.application.AdminWordService; +import com.dnd.spaced.core.admin.application.AdminWordServiceFacade; import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest; import com.dnd.spaced.core.admin.application.dto.request.CreateWordRequest.CreatePronunciationRequest; import com.dnd.spaced.core.word.application.event.dto.FailedWordPersistedEvent; @@ -52,7 +52,7 @@ class WordPersistEventListenerTest { PronunciationRepository pronunciationRepository; @Autowired - AdminWordService adminWordService; + AdminWordServiceFacade adminWordServiceFacade; @Autowired WordRandomRepository wordRandomRepository; @@ -82,7 +82,7 @@ class WordPersistEventListenerTest { List.of("게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.") ); - adminWordService.createWord(request); + adminWordServiceFacade.createWord(request); assertAll( () -> assertThat(events.stream(PersistedWordEvent.class).count()).isOne(), @@ -114,7 +114,7 @@ class WordPersistEventListenerTest { List.of("게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.") ); - adminWordService.createWord(request); + adminWordServiceFacade.createWord(request); assertAll( () -> assertThat(events.stream(PersistedWordEvent.class).count()).isOne(), @@ -140,7 +140,7 @@ class WordPersistEventListenerTest { List.of("게시글 삭제는 작성자와 관리자만 Authorization이 있도록 구현했습니다.") ); - adminWordService.createWord(request); + adminWordServiceFacade.createWord(request); assertAll( () -> assertThat(events.stream(PersistedWordEvent.class).count()).isOne(), From 0fa7566ec627add63fa1c7bc0a22e2b7c899ae00 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:33:47 +0900 Subject: [PATCH 061/115] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/{internal => }/GenerateTokenService.java | 2 +- .../core/auth/application/{internal => }/LoginService.java | 2 +- .../dnd/spaced/core/auth/application/RefreshTokenService.java | 1 - .../core/auth/application/{internal => }/SignUpService.java | 4 ++-- .../global/auth/security/handler/OAuth2SuccessHandler.java | 4 ++-- .../java/com/dnd/spaced/global/config/SecurityConfig.java | 4 ++-- .../{internal => }/GenerateRefreshTokenServiceTest.java | 2 +- .../auth/application/{internal => }/LoginServiceTest.java | 2 +- .../auth/application/{internal => }/SignUpServiceTest.java | 2 +- 9 files changed, 11 insertions(+), 12 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/auth/application/{internal => }/GenerateTokenService.java (95%) rename space-d/src/main/java/com/dnd/spaced/core/auth/application/{internal => }/LoginService.java (96%) rename space-d/src/main/java/com/dnd/spaced/core/auth/application/{internal => }/SignUpService.java (97%) rename space-d/src/test/java/com/dnd/spaced/core/auth/application/{internal => }/GenerateRefreshTokenServiceTest.java (95%) rename space-d/src/test/java/com/dnd/spaced/core/auth/application/{internal => }/LoginServiceTest.java (98%) rename space-d/src/test/java/com/dnd/spaced/core/auth/application/{internal => }/SignUpServiceTest.java (97%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/GenerateTokenService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/GenerateTokenService.java similarity index 95% rename from space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/GenerateTokenService.java rename to space-d/src/main/java/com/dnd/spaced/core/auth/application/GenerateTokenService.java index 8d1df029..707f5375 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/GenerateTokenService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/GenerateTokenService.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.auth.application.internal; +package com.dnd.spaced.core.auth.application; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; import com.dnd.spaced.core.auth.domain.enums.TokenScheme; diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java similarity index 96% rename from space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java rename to space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java index 7c9236e2..06156641 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/LoginService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.auth.application.internal; +package com.dnd.spaced.core.auth.application; import com.dnd.spaced.core.account.domain.Account; import com.dnd.spaced.core.account.domain.embed.Social; diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java index e8411e6b..0898b810 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java @@ -4,7 +4,6 @@ import com.dnd.spaced.core.auth.application.exception.BlockedTokenException; import com.dnd.spaced.core.auth.application.exception.ExpiredTokenException; import com.dnd.spaced.core.auth.application.exception.RotationRefreshTokenMismatchException; -import com.dnd.spaced.core.auth.application.internal.GenerateTokenService; import com.dnd.spaced.core.auth.domain.PrivateClaims; import com.dnd.spaced.core.auth.domain.TokenDecoder; import com.dnd.spaced.core.auth.domain.enums.TokenType; diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java similarity index 97% rename from space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java rename to space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java index 5db617d2..ec7642ae 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/internal/SignUpService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.auth.application.internal; +package com.dnd.spaced.core.auth.application; import com.dnd.spaced.core.account.domain.Account; import com.dnd.spaced.core.account.domain.NicknameMetadata; @@ -18,7 +18,7 @@ @Service @RequiredArgsConstructor @Transactional -public class SignUpService { +class SignUpService { private static final Role DEFAULT_ROLE = Role.ROLE_USER; diff --git a/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java b/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java index 0749a7ed..2cdee914 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java +++ b/space-d/src/main/java/com/dnd/spaced/global/auth/security/handler/OAuth2SuccessHandler.java @@ -1,7 +1,7 @@ package com.dnd.spaced.global.auth.security.handler; -import com.dnd.spaced.core.auth.application.internal.GenerateTokenService; -import com.dnd.spaced.core.auth.application.internal.LoginService; +import com.dnd.spaced.core.auth.application.GenerateTokenService; +import com.dnd.spaced.core.auth.application.LoginService; import com.dnd.spaced.core.auth.application.dto.response.LoggedInAccountDto; import com.dnd.spaced.core.auth.application.dto.response.TokenDto; import com.dnd.spaced.global.auth.exception.InvalidResponseWriteException; diff --git a/space-d/src/main/java/com/dnd/spaced/global/config/SecurityConfig.java b/space-d/src/main/java/com/dnd/spaced/global/config/SecurityConfig.java index 64946ffb..9d1c8d15 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/config/SecurityConfig.java +++ b/space-d/src/main/java/com/dnd/spaced/global/config/SecurityConfig.java @@ -1,8 +1,8 @@ package com.dnd.spaced.global.config; import com.dnd.spaced.core.auth.application.BlacklistTokenService; -import com.dnd.spaced.core.auth.application.internal.GenerateTokenService; -import com.dnd.spaced.core.auth.application.internal.LoginService; +import com.dnd.spaced.core.auth.application.GenerateTokenService; +import com.dnd.spaced.core.auth.application.LoginService; import com.dnd.spaced.core.auth.domain.TokenDecoder; import com.dnd.spaced.global.auth.security.core.OAuth2UserDetailsService; import com.dnd.spaced.global.auth.security.filter.OAuth2AuthenticationFilter; diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/GenerateRefreshTokenServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/GenerateRefreshTokenServiceTest.java similarity index 95% rename from space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/GenerateRefreshTokenServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/auth/application/GenerateRefreshTokenServiceTest.java index b2b7c093..9d197365 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/GenerateRefreshTokenServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/GenerateRefreshTokenServiceTest.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.auth.application.internal; +package com.dnd.spaced.core.auth.application; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/LoginServiceTest.java similarity index 98% rename from space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/auth/application/LoginServiceTest.java index d85f85bf..f18e2c15 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/LoginServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/LoginServiceTest.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.auth.application.internal; +package com.dnd.spaced.core.auth.application; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/auth/application/SignUpServiceTest.java similarity index 97% rename from space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/auth/application/SignUpServiceTest.java index 779c0263..f7cd9491 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/auth/application/internal/SignUpServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/auth/application/SignUpServiceTest.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.auth.application.internal; +package com.dnd.spaced.core.auth.application; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; From ad5f899c42282550629062ee36b0cf27fb0913e2 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:43:53 +0900 Subject: [PATCH 062/115] =?UTF-8?q?refactor:=20InitAccountCareerService=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/InitAccountCareerService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java index 9c0ce54f..606e4a93 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/InitAccountCareerService.java @@ -16,12 +16,12 @@ public class InitAccountCareerService { @Transactional public void initCareer(Long accountId, InitAccountCareerRequest request) { - Account account = findPreInitializationAccount(accountId); + Account account = findPreInitAccount(accountId); - account.changeCareer(request.jobGroupName(), request.companyName(), request.experienceName()); + executeCareerInit(request, account); } - private Account findPreInitializationAccount(Long accountId) { + private Account findPreInitAccount(Long accountId) { return accountRepository.findPreInitializationAccountBy(accountId) .orElseThrow( () -> new ForbiddenInitCareerInfoException( @@ -29,4 +29,8 @@ private Account findPreInitializationAccount(Long accountId) { ) ); } + + private void executeCareerInit(InitAccountCareerRequest request, Account account) { + account.changeCareer(request.jobGroupName(), request.companyName(), request.experienceName()); + } } From ed1a909921be72bd169cb4c025d0b245afbbaa2d Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 15:55:17 +0900 Subject: [PATCH 063/115] =?UTF-8?q?refactor:=20LoginService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/auth/application/LoginService.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java index 06156641..ea5fa24b 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/LoginService.java @@ -12,28 +12,25 @@ @RequiredArgsConstructor public class LoginService { - private final AccountRepository accountRepository; private final SignUpService signUpService; + private final AccountRepository accountRepository; public LoggedInAccountDto login(String registrationIdName, String socialIdentifier) { RegistrationId registrationId = RegistrationId.findBy(registrationIdName); Social social = new Social(registrationId, socialIdentifier); return accountRepository.findBy(social) - .map(account -> - new LoggedInAccountDto( - account.getId(), - account.getRole().name(), - false) - ) - .orElseGet(() -> { - Account signedUpAccount = signUpService.signUp(registrationId, socialIdentifier); - - return new LoggedInAccountDto( - signedUpAccount.getId(), - signedUpAccount.getRole().name(), - true - ); - }); + .map(this::buildLoggedInAccount) + .orElseGet(() -> buildSignUpAccount(registrationId, socialIdentifier)); + } + + private LoggedInAccountDto buildLoggedInAccount(Account account) { + return new LoggedInAccountDto(account.getId(), account.getRole().name(), false); + } + + private LoggedInAccountDto buildSignUpAccount(RegistrationId registrationId, String socialIdentifier) { + Account signedUpAccount = signUpService.signUp(registrationId, socialIdentifier); + + return new LoggedInAccountDto(signedUpAccount.getId(), signedUpAccount.getRole().name(), true); } } From 7058ada09eae29472f1d7bd7668185d924a26646 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 16:01:57 +0900 Subject: [PATCH 064/115] =?UTF-8?q?refactor:=20RefreshTokenService=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=ED=8A=B8=EB=9E=9C?= =?UTF-8?q?=EC=9E=AD=EC=85=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/RefreshTokenService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java index 0898b810..3480227a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/RefreshTokenService.java @@ -10,7 +10,6 @@ import com.dnd.spaced.core.auth.domain.repository.RefreshTokenRotationRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -21,18 +20,13 @@ public class RefreshTokenService { private final BlacklistTokenService blacklistTokenService; private final RefreshTokenRotationRepository refreshTokenRotationRepository; - @Transactional public TokenDto refreshToken(String refreshToken) { PrivateClaims privateClaims = convertTokenPrivateClaims(refreshToken); validateBlacklistToken(privateClaims); validateRotationRefreshToken(refreshToken, privateClaims); - TokenDto tokenDto = generateTokenService.generate(privateClaims.accountId(), privateClaims.roleName()); - - refreshTokenRotationRepository.save(privateClaims.accountId(), tokenDto.refreshToken()); - - return tokenDto; + return generateRefreshToken(privateClaims); } private PrivateClaims convertTokenPrivateClaims(String refreshToken) { @@ -67,4 +61,11 @@ private void validateRefreshToken(String refreshToken, PrivateClaims privateClai throw new RotationRefreshTokenMismatchException("기존 Refresh Token과 일치하지 않습니다."); } } + + private TokenDto generateRefreshToken(PrivateClaims privateClaims) { + TokenDto tokenDto = generateTokenService.generate(privateClaims.accountId(), privateClaims.roleName()); + + refreshTokenRotationRepository.save(privateClaims.accountId(), tokenDto.refreshToken()); + return tokenDto; + } } From 2e5bb9ef5d238a2c082dadb99e32b4841ac30ed9 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 16:07:15 +0900 Subject: [PATCH 065/115] =?UTF-8?q?refactor:=20SignUpService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/auth/application/SignUpService.java | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java b/space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java index ec7642ae..89561d25 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/application/SignUpService.java @@ -27,18 +27,18 @@ class SignUpService { private final NicknameMetadataRepository nicknameMetadataRepository; private final ApplicationEventPublisher eventPublisher; - Account signUp(RegistrationId registrationId, String socialIdentifier) { + public Account signUp(RegistrationId registrationId, String socialIdentifier) { String profileImageName = findRandomProfileImage(); String formattedNickname = formatNickname(); - Account persistedAccount = persistAccount( + Account account = setupAccount( registrationId, socialIdentifier, formattedNickname, profileImageName ); - eventPublisher.publishEvent(new InitializedAccountEvent(persistedAccount.getId())); - return persistedAccount; + publishSignedUpAccountEvent(account); + return account; } private String findRandomProfileImage() { @@ -49,7 +49,8 @@ private String findRandomProfileImage() { private String formatNickname() { String nickname = nicknameProperties.generate(); NicknameMetadata metadata = nicknameMetadataRepository.findBy(nickname) - .orElseThrow(() -> new NicknameMetadataNotFoundException("닉네임 메타데이터가 정상적으로 초기화되지 않았습니다.")); + .orElseThrow(() -> new NicknameMetadataNotFoundException( + "닉네임 메타데이터가 정상적으로 초기화되지 않았습니다.")); metadata.addCount(); return nicknameProperties.format( @@ -58,20 +59,34 @@ private String formatNickname() { ); } - private Account persistAccount( + private Account setupAccount( RegistrationId registrationId, String socialIdentifier, String formattedNickname, String profileImageName ) { - Account newAccount = Account.builder() - .registrationId(registrationId) - .socialIdentifier(socialIdentifier) - .nickname(formattedNickname) - .role(DEFAULT_ROLE) - .profileImageName(ProfileImageName.findByImageName(profileImageName)) - .build(); + Account newAccount = buildAccount(registrationId, socialIdentifier, formattedNickname, + profileImageName); + return persistAccount(newAccount); + } + + private Account buildAccount(RegistrationId registrationId, String socialIdentifier, String formattedNickname, + String profileImageName) { + return Account.builder() + .registrationId(registrationId) + .socialIdentifier(socialIdentifier) + .nickname(formattedNickname) + .role(DEFAULT_ROLE) + .profileImageName(ProfileImageName.findByImageName(profileImageName)) + .build(); + } + + private Account persistAccount(Account newAccount) { return accountRepository.save(newAccount); } + + private void publishSignedUpAccountEvent(Account account) { + eventPublisher.publishEvent(new InitializedAccountEvent(account.getId())); + } } From 2268fdf7b40a18c18a10a8400f81c4976c9fbce9 Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 18:11:53 +0900 Subject: [PATCH 066/115] =?UTF-8?q?refactor:=20JwtDecoder=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/infrastructure/jwt/JwtDecoder.java | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtDecoder.java b/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtDecoder.java index 96814e51..7ba43015 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtDecoder.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtDecoder.java @@ -47,29 +47,47 @@ private void validateToken(String token) { private Optional parse(TokenType tokenType, String token) { try { - JWEObject jweObject = JWEObject.parse(token); + return extractClaimsSet(tokenType, token); + } catch (JOSEException e) { + throw new FailedDecodeTokenException("토큰 디코딩에 실패했습니다", e); + } catch (ParseException e) { + throw new InvalidTokenException("유효한 토큰이 아닙니다.", e); + } + } - jweObject.decrypt(jweDecrypter); + private Optional extractClaimsSet(TokenType tokenType, String token) + throws ParseException, JOSEException { + JWTClaimsSet claimsSet = findJWTClaimsSet(tokenType, token); - SignedJWT signedJwt = jweObject.getPayload() - .toSignedJWT(); - JWSVerifier jwsVerifier = jwsVerifierFinder.findByTokenType(tokenType); + validateIssuer(claimsSet); - validateSign(signedJwt, jwsVerifier); + return findClaimsSet(claimsSet); + } - JWTClaimsSet claims = signedJwt.getJWTClaimsSet(); + private JWTClaimsSet findJWTClaimsSet(TokenType tokenType, String token) throws ParseException, JOSEException { + JWEObject jweObject = findJWEObject(token); + SignedJWT signedJwt = findSignedJWT(jweObject); + JWSVerifier jwsVerifier = findJWSVerifier(tokenType); - validateIssuer(claims.getIssuer()); - if (isExpiredToken(claims.getExpirationTime())) { - return Optional.empty(); - } + validateSign(signedJwt, jwsVerifier); - return Optional.of(claims); - } catch (JOSEException e) { - throw new FailedDecodeTokenException("토큰 디코딩에 실패했습니다", e); - } catch (ParseException e) { - throw new InvalidTokenException("유효한 토큰이 아닙니다.", e); - } + return signedJwt.getJWTClaimsSet(); + } + + private JWEObject findJWEObject(String token) throws ParseException, JOSEException { + JWEObject jweObject = JWEObject.parse(token); + + jweObject.decrypt(jweDecrypter); + return jweObject; + } + + private SignedJWT findSignedJWT(JWEObject jweObject) { + return jweObject.getPayload() + .toSignedJWT(); + } + + private JWSVerifier findJWSVerifier(TokenType tokenType) { + return jwsVerifierFinder.findByTokenType(tokenType); } private void validateSign(SignedJWT signedJwt, JWSVerifier jwsVerifier) throws JOSEException { @@ -85,12 +103,20 @@ private boolean isExpiredToken(Date expirationTime) { return expirationDate.isBefore(now); } - private void validateIssuer(String issuer) { - if (!tokenProperties.issuer().equals(issuer)) { + private void validateIssuer(JWTClaimsSet claimsSet) { + if (!tokenProperties.issuer().equals(claimsSet.getIssuer())) { throw new InvalidTokenException("서비스에서 발급한 토큰이 아닙니다."); } } + private Optional findClaimsSet(JWTClaimsSet claimsSet) { + if (isExpiredToken(claimsSet.getExpirationTime())) { + return Optional.empty(); + } + + return Optional.of(claimsSet); + } + private PrivateClaims convert(JWTClaimsSet claims) { Date issueTime = claims.getIssueTime(); From cdf0213d441bd35d932a6638640fb89cfed7d22a Mon Sep 17 00:00:00 2001 From: apptie Date: Mon, 14 Jul 2025 18:32:20 +0900 Subject: [PATCH 067/115] =?UTF-8?q?refactor:=20JwtEncoder=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B0=8F=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/infrastructure/jwt/JwtEncoder.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtEncoder.java b/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtEncoder.java index 082cf166..f9a15b58 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtEncoder.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/infrastructure/jwt/JwtEncoder.java @@ -37,13 +37,7 @@ public class JwtEncoder implements TokenEncoder { @Override public String encode(LocalDateTime publishTime, TokenType tokenType, Long accountId, String roleName) { try { - JWEHeader header = createJweHeader(); - JWTClaimsSet claims = createJwtPayload(tokenType, accountId, roleName, publishTime); - SignedJWT signedJwt = createSignedJwt(claims, tokenType); - JWEObject jweObject = createJweObject(header, signedJwt); - - jweObject.encrypt(jweEncrypter); - return jweObject.serialize(); + return serializeToken(publishTime, tokenType, accountId, roleName); } catch (KeyLengthException e) { throw new FailedEncodeTokenException("키 길이를 지원하지 않는 환경입니다.", e); } catch (JOSEException e) { @@ -51,6 +45,16 @@ public String encode(LocalDateTime publishTime, TokenType tokenType, Long accoun } } + private String serializeToken(LocalDateTime publishTime, TokenType tokenType, Long accountId, String roleName) + throws JOSEException { + JWEHeader header = createJweHeader(); + JWTClaimsSet claims = createJwtPayload(tokenType, accountId, roleName, publishTime); + SignedJWT signedJwt = setupSignedJwt(claims, tokenType); + JWEObject jweObject = setupJweObject(header, signedJwt); + + return jweObject.serialize(); + } + private JWEHeader createJweHeader() { return new JWEHeader.Builder(JWEAlgorithm.A256KW, EncryptionMethod.A256GCM) .contentType(TOKEN_CONTENT_TYPE) @@ -74,7 +78,7 @@ private JWTClaimsSet createJwtPayload( .build(); } - private SignedJWT createSignedJwt(JWTClaimsSet claims, TokenType tokenType) throws JOSEException { + private SignedJWT setupSignedJwt(JWTClaimsSet claims, TokenType tokenType) throws JOSEException { JWSHeader jwsHeader = new Builder(JWSAlgorithm.HS256).build(); SignedJWT signedJwt = new SignedJWT(jwsHeader, claims); @@ -82,8 +86,12 @@ private SignedJWT createSignedJwt(JWTClaimsSet claims, TokenType tokenType) thro return signedJwt; } - private JWEObject createJweObject(JWEHeader header, SignedJWT signedJwt) { - return new JWEObject(header, new Payload(signedJwt)); + private JWEObject setupJweObject(JWEHeader header, SignedJWT signedJwt) throws JOSEException { + Payload payload = new Payload(signedJwt); + JWEObject jweObject = new JWEObject(header, payload); + + jweObject.encrypt(jweEncrypter); + return jweObject; } private Date convertDate(LocalDateTime target) { From 313ea73357f2ba566a5c29bb39210e422b419ea7 Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 15 Jul 2025 12:00:30 +0900 Subject: [PATCH 068/115] =?UTF-8?q?refactor:=20AuthController=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/spaced/core/auth/presentation/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java index 0fda689b..cceb798e 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/auth/presentation/AuthController.java @@ -38,7 +38,7 @@ public class AuthController { private final InitAccountCareerService initAccountCareerService; @PostMapping("/profile") - public ResponseEntity initAccountCareerInfo( + public ResponseEntity initAccountCareer( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody InitAccountCareerRequest request ) { From 6dcf55263a13af5b5d0fdcdeefca714cbfea54db Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 15:18:39 +0900 Subject: [PATCH 069/115] =?UTF-8?q?fix:=20BookmarkService=20=EB=9D=BD=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/bookmark/application/BookmarkService.java | 5 +++-- .../exception/BookmarkInterruptedException.java | 11 +++++++++++ .../global/exception/code/BookmarkErrorCode.java | 3 ++- .../translator/BookmarkExceptionTranslator.java | 5 +++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkInterruptedException.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java index cdcaeba1..0d25e6c8 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java @@ -6,7 +6,7 @@ import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; import com.dnd.spaced.core.bookmark.application.exception.AlreadyExistsBookmarkException; -import com.dnd.spaced.core.bookmark.application.exception.BookmarkLockException; +import com.dnd.spaced.core.bookmark.application.exception.BookmarkInterruptedException; import com.dnd.spaced.core.bookmark.application.exception.WordNotFoundException; import com.dnd.spaced.core.bookmark.domain.Bookmark; import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; @@ -55,7 +55,8 @@ public void createBookmark(Long accountId, CreateBookmarkRequest request) { bookmarkRepository.save(bookmark); publishAddedBookmarkEvent(bookmark); } catch (InterruptedException e) { - throw new BookmarkLockException("북마크 생성 중 인터럽트 발생", e); + Thread.currentThread().interrupt(); + throw new BookmarkInterruptedException("북마크 생성 중 인터럽트 발생", e); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkInterruptedException.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkInterruptedException.java new file mode 100644 index 00000000..5326d895 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkInterruptedException.java @@ -0,0 +1,11 @@ +package com.dnd.spaced.core.bookmark.application.exception; + +import com.dnd.spaced.global.exception.base.BookmarkServerException; +import com.dnd.spaced.global.exception.code.BookmarkErrorCode; + +public class BookmarkInterruptedException extends BookmarkServerException { + + public BookmarkInterruptedException(String message, Throwable e) { + super(BookmarkErrorCode.BOOKMARK_INTERRUPTED_EXCEPTION, message, e); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/global/exception/code/BookmarkErrorCode.java b/space-d/src/main/java/com/dnd/spaced/global/exception/code/BookmarkErrorCode.java index 7e3fef27..bb06d88f 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/exception/code/BookmarkErrorCode.java +++ b/space-d/src/main/java/com/dnd/spaced/global/exception/code/BookmarkErrorCode.java @@ -5,5 +5,6 @@ public enum BookmarkErrorCode implements ErrorCode { BOOKMARK_NOT_FOUND_EXCEPTION, WORD_NOT_FOUND_EXCEPTION, ALREADY_EXISTS_BOOKMARK_EXCEPTION, - BOOKMARK_LOCK_EXCEPTION + BOOKMARK_LOCK_EXCEPTION, + BOOKMARK_INTERRUPTED_EXCEPTION } diff --git a/space-d/src/main/java/com/dnd/spaced/global/exception/translator/BookmarkExceptionTranslator.java b/space-d/src/main/java/com/dnd/spaced/global/exception/translator/BookmarkExceptionTranslator.java index 53d30016..3bf30add 100644 --- a/space-d/src/main/java/com/dnd/spaced/global/exception/translator/BookmarkExceptionTranslator.java +++ b/space-d/src/main/java/com/dnd/spaced/global/exception/translator/BookmarkExceptionTranslator.java @@ -28,6 +28,11 @@ public enum BookmarkExceptionTranslator implements ExceptionTranslator { BookmarkErrorCode.BOOKMARK_LOCK_EXCEPTION, HttpStatus.INTERNAL_SERVER_ERROR, "북마크 생성 도중 서버에 문제가 발생했습니다." + ), + BOOKMARK_INTERRUPTED_EXCEPTION( + BookmarkErrorCode.BOOKMARK_INTERRUPTED_EXCEPTION, + HttpStatus.INTERNAL_SERVER_ERROR, + "북마크 생성 도중 서버에 문제가 발생했습니다." ); private final ErrorCode errorCode; From ec083617013d0982c67a9218d4ff6aaa2801a790 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 16:42:41 +0900 Subject: [PATCH 070/115] =?UTF-8?q?test:=20BookmarkService=20=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcurrencyBookmarkServiceTest.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java index 3749e4db..a8d1a1e6 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java @@ -1,5 +1,6 @@ package com.dnd.spaced.core.bookmark.application; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -12,6 +13,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -25,6 +27,9 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class ConcurrencyBookmarkServiceTest { + private static final Long WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + @Autowired BookmarkService bookmarkService; @@ -36,29 +41,31 @@ class ConcurrencyBookmarkServiceTest { void 동시에_동일한_용어에_북마크_생성_요청을_하더라도_단_하나의_북마크만_생성되어야_한다() throws InterruptedException { int numberOfThreads = 10; ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); - CountDownLatch latch = new CountDownLatch(numberOfThreads); - CreateBookmarkRequest request = new CreateBookmarkRequest(1L); + CountDownLatch startLatch = new CountDownLatch(numberOfThreads); + CountDownLatch completionLatch = new CountDownLatch(numberOfThreads); + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); for (int i = 0; i < numberOfThreads; i++) { executorService.submit(() -> { try { - latch.countDown(); - latch.await(); + startLatch.countDown(); + startLatch.await(); - bookmarkService.createBookmark(1L, request); + bookmarkService.createBookmark(ACCOUNT_ID, request); } catch (Exception e) { throw new RuntimeException(e); + } finally { + completionLatch.countDown(); } }); } executorService.shutdown(); - while (!executorService.isTerminated()) { - Thread.sleep(100); - } + boolean isCompleted = completionLatch.await(5, TimeUnit.SECONDS); assertAll( + () -> assertThat(isCompleted).isTrue(), () -> verify(bookmarkRepository, times(10)).existsBy(anyLong(), anyLong()), () -> verify(bookmarkRepository).save(any(Bookmark.class)) ); From a0f64722688e2fa595c47235f601bf7c4b5513a2 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 16:50:38 +0900 Subject: [PATCH 071/115] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=97=90=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/com/dnd/spaced/config/RetryTestConfig.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/config/RetryTestConfig.java b/space-d/src/test/java/com/dnd/spaced/config/RetryTestConfig.java index 6f94949d..8a0c4892 100644 --- a/space-d/src/test/java/com/dnd/spaced/config/RetryTestConfig.java +++ b/space-d/src/test/java/com/dnd/spaced/config/RetryTestConfig.java @@ -1,15 +1,10 @@ package com.dnd.spaced.config; -import com.dnd.spaced.global.exception.base.BaseClientException; -import com.dnd.spaced.global.exception.base.BaseServerException; import java.util.HashMap; import java.util.Map; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; -import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.TransientDataAccessException; From bced82faceccc117463f240e1e2e7ffca6532234 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 16:53:17 +0900 Subject: [PATCH 072/115] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20JWT=20Decoder=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/spaced/config/TestSecurityConfig.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/config/TestSecurityConfig.java diff --git a/space-d/src/test/java/com/dnd/spaced/config/TestSecurityConfig.java b/space-d/src/test/java/com/dnd/spaced/config/TestSecurityConfig.java new file mode 100644 index 00000000..76065491 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/config/TestSecurityConfig.java @@ -0,0 +1,15 @@ +package com.dnd.spaced.config; + +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.jwt.JwtDecoderFactory; + +@Profile("test") +@Configuration +public class TestSecurityConfig { + + @MockBean + JwtDecoderFactory jwtDecoderFactory; +} From 1508398bbd1135acb01c5eb267313cc37f428091 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 16:54:45 +0900 Subject: [PATCH 073/115] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CreateBookmarkService.java | 99 ++++++++++++ .../exception/BookmarkLockException.java | 4 + .../ConcurrencyCreateBookmarkServiceTest.java | 73 +++++++++ .../CreateBookmarkServiceTest.java | 74 +++++++++ .../TryLockCreateBookmarkServiceTest.java | 151 ++++++++++++++++++ 5 files changed, 401 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyCreateBookmarkServiceTest.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/bookmark/application/TryLockCreateBookmarkServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java new file mode 100644 index 00000000..269cabc7 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java @@ -0,0 +1,99 @@ +package com.dnd.spaced.core.bookmark.application; + +import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.exception.AlreadyExistsBookmarkException; +import com.dnd.spaced.core.bookmark.application.exception.BookmarkInterruptedException; +import com.dnd.spaced.core.bookmark.application.exception.BookmarkLockException; +import com.dnd.spaced.core.bookmark.application.exception.WordNotFoundException; +import com.dnd.spaced.core.bookmark.domain.Bookmark; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import com.dnd.spaced.core.word.domain.repository.WordRepository; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.redisson.client.RedisException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@RequiredArgsConstructor +class CreateBookmarkService { + + private static final int LOCK_WAIT_TIME = 1; + private static final int LOCK_LEASE_TIME = 1; + private static final String LOCK_SEPARATOR = ":"; + private static final String LOCK_PREFIX = "bookmark" + LOCK_SEPARATOR + "create" + LOCK_SEPARATOR; + + private final RedissonClient redissonClient; + private final WordRepository wordRepository; + private final BookmarkRepository bookmarkRepository; + private final TransactionTemplate transactionTemplate; + + public void createBookmark(Long accountId, CreateBookmarkRequest request) { + validateWordId(request); + executeBookmarkCreationWithTransaction(accountId, request); + } + + private void executeBookmarkCreationWithTransaction(Long accountId, CreateBookmarkRequest request) { + RLock lock = redissonClient.getLock(calculateLockName(accountId, request)); + + if (!tryLock(lock)) { + throw new BookmarkLockException("북마크 생성 중 락 획득 실패"); + } + + try { + transactionTemplate.executeWithoutResult(action -> doBookmarkCreation(accountId, request)); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + private boolean tryLock(RLock lock) { + try { + return lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); + } catch (RedisException e) { + if (e.getCause() instanceof InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + throw new BookmarkInterruptedException("북마크 생성 중 인터럽트 발생", interruptedException); + } + throw new BookmarkLockException("북마크 생성 중 락 획득 실패", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BookmarkInterruptedException("북마크 생성 중 인터럽트 발생", e); + } + } + + private void doBookmarkCreation(Long accountId, CreateBookmarkRequest request) { + validateExistsBookmark(accountId, request); + persistBookmark(accountId, request); + } + + private String calculateLockName(Long accountId, CreateBookmarkRequest request) { + return LOCK_PREFIX + accountId + LOCK_SEPARATOR + request.wordId(); + } + + private void validateExistsBookmark(Long accountId, CreateBookmarkRequest request) { + if (bookmarkRepository.existsBy(accountId, request.wordId())) { + throw new AlreadyExistsBookmarkException("이미 북마크에 추가된 용어입니다."); + } + } + + private void persistBookmark(Long accountId, CreateBookmarkRequest request) { + Bookmark bookmark = new Bookmark(accountId, request.wordId()); + + bookmarkRepository.save(bookmark); + } + + private void validateWordId(CreateBookmarkRequest request) { + if (isMissingWord(request.wordId())) { + throw new WordNotFoundException("지정한 식별자의 용어를 찾지 못했습니다."); + } + } + + private boolean isMissingWord(Long wordId) { + return !wordRepository.existsBy(wordId); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkLockException.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkLockException.java index d1263a3d..7e58bd50 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkLockException.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/exception/BookmarkLockException.java @@ -5,6 +5,10 @@ public class BookmarkLockException extends BookmarkServerException { + public BookmarkLockException(String message) { + super(BookmarkErrorCode.BOOKMARK_LOCK_EXCEPTION, message); + } + public BookmarkLockException(String message, Throwable e) { super(BookmarkErrorCode.BOOKMARK_LOCK_EXCEPTION, message, e); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyCreateBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyCreateBookmarkServiceTest.java new file mode 100644 index 00000000..06684bf8 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyCreateBookmarkServiceTest.java @@ -0,0 +1,73 @@ +package com.dnd.spaced.core.bookmark.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; +import com.dnd.spaced.core.bookmark.domain.Bookmark; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ConcurrencyCreateBookmarkServiceTest { + + private static final Long WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + + @Autowired + CreateBookmarkService createBookmarkService; + + @Autowired + BookmarkRepository bookmarkRepository; + + @Test + @Sql("classpath:sql/bookmark/word.sql") + void 동시에_동일한_용어에_북마크_생성_요청을_하더라도_단_하나의_북마크만_생성되어야_한다() throws InterruptedException { + int numberOfThreads = 10; + ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); + CountDownLatch startLatch = new CountDownLatch(numberOfThreads); + CountDownLatch completionLatch = new CountDownLatch(numberOfThreads); + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); + + for (int i = 0; i < numberOfThreads; i++) { + executorService.submit(() -> { + try { + startLatch.countDown(); + startLatch.await(); + + createBookmarkService.createBookmark(ACCOUNT_ID, request); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + completionLatch.countDown(); + } + }); + } + + executorService.shutdown(); + + boolean isCompleted = completionLatch.await(5, TimeUnit.SECONDS); + + assertAll( + () -> assertThat(isCompleted).isTrue(), + () -> verify(bookmarkRepository, times(10)).existsBy(anyLong(), anyLong()), + () -> verify(bookmarkRepository).save(any(Bookmark.class)) + ); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java new file mode 100644 index 00000000..cae84c27 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java @@ -0,0 +1,74 @@ +package com.dnd.spaced.core.bookmark.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.exception.AlreadyExistsBookmarkException; +import com.dnd.spaced.core.bookmark.application.exception.WordNotFoundException; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CreateBookmarkServiceTest { + + private static final Long WORD_ID = 1L; + private static final Long NOT_FOUND_WORD_ID = -999L; + private static final Long ACCOUNT_ID = 1L; + + @Autowired + CreateBookmarkService createBookmarkService; + + @Autowired + BookmarkRepository bookmarkRepository; + + @Test + @Sql("classpath:sql/bookmark/word.sql") + void 북마크를_추가한다() { + // given + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); + + // when + createBookmarkService.createBookmark(ACCOUNT_ID, request); + + // then + boolean actual = bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID); + + assertThat(actual).isTrue(); + } + + @Test + @Sql(scripts = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 이미_북마크에_추가된_용어를_북마에_추가할_수_없다() { + // given + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); + + // when & then + assertThatThrownBy(() -> createBookmarkService.createBookmark(ACCOUNT_ID, request)) + .isInstanceOf(AlreadyExistsBookmarkException.class) + .hasMessage("이미 북마크에 추가된 용어입니다."); + } + + @Test + void 지정한_용어_식별자로_용어를_찾지_못하면_북마크를_추가할_수_없다() { + // given + CreateBookmarkRequest request = new CreateBookmarkRequest(NOT_FOUND_WORD_ID); + + // when & then + assertThatThrownBy(() -> createBookmarkService.createBookmark(ACCOUNT_ID, request)) + .isInstanceOf(WordNotFoundException.class) + .hasMessage("지정한 식별자의 용어를 찾지 못했습니다."); + + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/TryLockCreateBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/TryLockCreateBookmarkServiceTest.java new file mode 100644 index 00000000..b1bd292b --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/TryLockCreateBookmarkServiceTest.java @@ -0,0 +1,151 @@ +package com.dnd.spaced.core.bookmark.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.exception.BookmarkLockException; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class TryLockCreateBookmarkServiceTest { + + private static final Long WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + + @Autowired + CreateBookmarkService createBookmarkService; + + @Autowired + RedissonClient redissonClient; + + @Autowired + BookmarkRepository bookmarkRepository; + + @Test + @Sql("classpath:sql/bookmark/word.sql") + void 락_획득에_성공하면_북마크를_추가할_수_있다() { + // given + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); + + // when & then + assertDoesNotThrow(() -> createBookmarkService.createBookmark(ACCOUNT_ID, request)); + + assertThat(bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID)).isTrue(); + } + + @Test + @Sql("classpath:sql/bookmark/word.sql") + void 락_획득에_실패하면_북마크를_추가할_수_없다() throws InterruptedException { + // given + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); + + String lockName = "bookmark:create:" + WORD_ID + ":" + ACCOUNT_ID; + RLock lock = redissonClient.getLock(lockName); + + CountDownLatch lockAcquired = new CountDownLatch(1); + CountDownLatch testCompleted = new CountDownLatch(1); + + Thread lockHolder = new Thread(() -> { + lock.lock(); + lockAcquired.countDown(); + + try { + testCompleted.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + lock.unlock(); + } + }); + + lockHolder.start(); + lockAcquired.await(); + + // when & then + try { + assertThatThrownBy(() -> createBookmarkService.createBookmark(ACCOUNT_ID, request)) + .isInstanceOf(BookmarkLockException.class) + .hasMessage("북마크 생성 중 락 획득 실패"); + + assertThat(bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID)).isFalse(); + } finally { + testCompleted.countDown(); + lockHolder.join(); + } + } + + @Test + @Sql("classpath:sql/bookmark/word.sql") + void 락_대기_중_인터럽트_발생_예외_세부_검증() throws Exception { + // given + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); + + String lockName = "bookmark:create:" + WORD_ID + ":" + ACCOUNT_ID; + RLock lock = redissonClient.getLock(lockName); + + CountDownLatch lockAcquired = new CountDownLatch(1); + CountDownLatch serviceStarted = new CountDownLatch(1); + CountDownLatch testCompleted = new CountDownLatch(1); + + AtomicReference exceptionHolder = new AtomicReference<>(); + + Thread lockHolder = new Thread(() -> { + lock.lock(); + lockAcquired.countDown(); + try { + testCompleted.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + lock.unlock(); + } + }); + + Thread serviceThread = new Thread(() -> { + try { + serviceStarted.countDown(); + createBookmarkService.createBookmark(ACCOUNT_ID, request); + } catch (Exception e) { + exceptionHolder.set(e); + } + }); + + lockHolder.start(); + lockAcquired.await(); + + serviceThread.start(); + serviceStarted.await(); + + // when + serviceThread.interrupt(); + serviceThread.join(2000); + + // then + Exception exception = exceptionHolder.get(); + + assertAll( + () -> assertThat(exception).isNotNull(), + () -> assertThat(exception.getCause()).isInstanceOf(InterruptedException.class), + () -> assertThat(bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID)).isFalse() + ); + + testCompleted.countDown(); + lockHolder.join(); + } +} From 56ee91974e6776a1f63074bef7084b3a4c33638a Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 17:06:13 +0900 Subject: [PATCH 074/115] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/DeleteBookmarkService.java | 19 ++++++++ .../DeleteBookmarkServiceTest.java | 47 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkService.java new file mode 100644 index 00000000..762a9c63 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkService.java @@ -0,0 +1,19 @@ +package com.dnd.spaced.core.bookmark.application; + +import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +class DeleteBookmarkService { + + private final BookmarkRepository bookmarkRepository; + + @Transactional + public void deleteBookmark(Long accountId, DeleteBookmarkRequest request) { + bookmarkRepository.delete(accountId, request.wordId()); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkServiceTest.java new file mode 100644 index 00000000..d9424ae4 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/DeleteBookmarkServiceTest.java @@ -0,0 +1,47 @@ +package com.dnd.spaced.core.bookmark.application; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class DeleteBookmarkServiceTest { + + private static final Long WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + + @Autowired + DeleteBookmarkService deleteBookmarkService; + + @Autowired + BookmarkRepository bookmarkRepository; + + @Test + @Sql(scripts = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 북마크를_삭제한다() { + // given + assertThat(bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID)).isTrue(); + + DeleteBookmarkRequest request = new DeleteBookmarkRequest(WORD_ID); + + // when + deleteBookmarkService.deleteBookmark(ACCOUNT_ID, request); + + // then + assertThat(bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID)).isFalse(); + } + +} From 323b3ad83d31bbfa132b01d4a1b003281da73901 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 17:12:52 +0900 Subject: [PATCH 075/115] =?UTF-8?q?refactor:=20BookmarkMapper=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84=EB=A7=81=20?= =?UTF-8?q?=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/bookmark/application/BookmarkService.java | 5 +++-- ...rkApplicationMapper.java => BookmarkMapper.java} | 13 ++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/{BookmarkApplicationMapper.java => BookmarkMapper.java} (73%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java index 0d25e6c8..74da4c86 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.bookmark.application; -import com.dnd.spaced.core.bookmark.application.dto.mapper.BookmarkApplicationMapper; +import com.dnd.spaced.core.bookmark.application.dto.mapper.BookmarkMapper; import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; @@ -28,6 +28,7 @@ @RequiredArgsConstructor public class BookmarkService { + private final BookmarkMapper mapper; private final RedissonClient redissonClient; private final WordRepository wordRepository; private final BookmarkRepository bookmarkRepository; @@ -79,7 +80,7 @@ public BookmarkCollectionResponse readBookmarks( ) { List bookmarks = bookmarkRepository.findAllBy(accountId, request.lastBookmarkId(), pageable); - return BookmarkApplicationMapper.toDto(bookmarks); + return mapper.toDto(bookmarks); } private void validateWordId(CreateBookmarkRequest request) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/BookmarkApplicationMapper.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/BookmarkMapper.java similarity index 73% rename from space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/BookmarkApplicationMapper.java rename to space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/BookmarkMapper.java index eed90c2f..dc2dcbcb 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/BookmarkApplicationMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/dto/mapper/BookmarkMapper.java @@ -3,25 +3,24 @@ import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse.BookmarkResponse; import com.dnd.spaced.core.bookmark.domain.Bookmark; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class BookmarkApplicationMapper { +@Mapper +public class BookmarkMapper { - public static BookmarkCollectionResponse toDto(List bookmarks) { + public BookmarkCollectionResponse toDto(List bookmarks) { if (bookmarks.isEmpty()) { return new BookmarkCollectionResponse(List.of(), null); } List bookmarkResponses = bookmarks.stream() - .map(BookmarkApplicationMapper::toBookmarkResponse) + .map(this::toBookmarkResponse) .toList(); return new BookmarkCollectionResponse(bookmarkResponses, bookmarks.get(bookmarks.size() - 1).getId()); } - private static BookmarkResponse toBookmarkResponse(Bookmark bookmark) { + private BookmarkResponse toBookmarkResponse(Bookmark bookmark) { return new BookmarkResponse( bookmark.getId(), bookmark.getAccountId(), From 48ac5f64f17049daa0fa771e4c5409448680f39f Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 17:19:46 +0900 Subject: [PATCH 076/115] =?UTF-8?q?test:=20CommentService=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20DI=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/comment/application/CommentServiceTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java index 80e1d5a6..b8645755 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java @@ -12,8 +12,6 @@ import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; import com.dnd.spaced.core.comment.application.exception.WordNotFoundException; import com.dnd.spaced.core.comment.domain.exception.InvalidCommentContentException; -import com.dnd.spaced.core.comment.domain.repository.CommentRepository; -import com.dnd.spaced.core.like.domain.repository.LikeRepository; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -33,12 +31,6 @@ class CommentServiceTest { @Autowired CommentService commentService; - @Autowired - CommentRepository commentRepository; - - @Autowired - LikeRepository likeRepository; - @Test void 댓글을_작성할_용어가_없는_경우_댓글을_작성할_수_없다() { // given From 478f4ec97023c3b46fe7a4fa0e7be539503d163c Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 17:25:42 +0900 Subject: [PATCH 077/115] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReadBookmarkService.java | 29 +++++++++++ .../application/ReadBookmarkServiceTest.java | 51 +++++++++++++++++++ .../test/resources/sql/bookmark/bookmark.sql | 2 + .../src/test/resources/sql/bookmark/word.sql | 9 ++++ 4 files changed, 91 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java new file mode 100644 index 00000000..12e64f67 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java @@ -0,0 +1,29 @@ +package com.dnd.spaced.core.bookmark.application; + +import com.dnd.spaced.core.bookmark.application.dto.mapper.BookmarkMapper; +import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; +import com.dnd.spaced.core.bookmark.domain.Bookmark; +import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReadBookmarkService { + + private final BookmarkMapper mapper; + private final BookmarkRepository bookmarkRepository; + + public BookmarkCollectionResponse readBookmarks( + Long accountId, + ReadAllBookmarkRequest request, + Pageable pageable + ) { + List bookmarks = bookmarkRepository.findAllBy(accountId, request.lastBookmarkId(), pageable); + + return mapper.toDto(bookmarks); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java new file mode 100644 index 00000000..e3ffd30b --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java @@ -0,0 +1,51 @@ +package com.dnd.spaced.core.bookmark.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ReadBookmarkServiceTest { + + private static final long WORD_ID = 1L; + private static final long ACCOUNT_ID = 1L; + private static final long BOOKMARK_ID = 1L; + private static final long LAST_BOOKMARK_ID = 1L; + + @Autowired + ReadBookmarkService readBookmarkService; + + @Test + @Sql(value = { + "classpath:sql/bookmark/word.sql", + "classpath:sql/bookmark/bookmark.sql" + }) + void 회원이_생성한_북마크를_모두_조회한다() { + // given + ReadAllBookmarkRequest request = new ReadAllBookmarkRequest(null); + + // when + BookmarkCollectionResponse actual = readBookmarkService.readBookmarks(ACCOUNT_ID, request, PageRequest.of(0, 10)); + + // then + assertAll( + () -> assertThat(actual.bookmarks()).hasSize(1), + () -> assertThat(actual.lastBookmarkId()).isEqualTo(LAST_BOOKMARK_ID), + () -> assertThat(actual.bookmarks().get(0).bookmarkId()).isEqualTo(BOOKMARK_ID), + () -> assertThat(actual.bookmarks().get(0).accountId()).isEqualTo(ACCOUNT_ID), + () -> assertThat(actual.bookmarks().get(0).wordId()).isEqualTo(WORD_ID) + ); + } +} diff --git a/space-d/src/test/resources/sql/bookmark/bookmark.sql b/space-d/src/test/resources/sql/bookmark/bookmark.sql index a04ccb6a..fc14a66c 100644 --- a/space-d/src/test/resources/sql/bookmark/bookmark.sql +++ b/space-d/src/test/resources/sql/bookmark/bookmark.sql @@ -1,3 +1,5 @@ INSERT INTO bookmarks(id, created_at, account_id, word_id) VALUES(1, now(), 1, 1); INSERT INTO bookmarks(id, created_at, account_id, word_id) VALUES(2, now(), 1, 2); + +INSERT INTO bookmarks(id, created_at, account_id, word_id) VALUES(3, now(), 1, 3); diff --git a/space-d/src/test/resources/sql/bookmark/word.sql b/space-d/src/test/resources/sql/bookmark/word.sql index 6e2159f4..d3eb6977 100644 --- a/space-d/src/test/resources/sql/bookmark/word.sql +++ b/space-d/src/test/resources/sql/bookmark/word.sql @@ -15,3 +15,12 @@ VALUES(2, now(), now(), '야믈', 'KOREAN', 2, false); INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) VALUES(2, now(), now(), 'YAML은 설정 파일이나 데이터 교환 포맷으로 자주 사용됩니다.', 2, false); + +INSERT INTO words(id, created_at, updated_at, bookmark_count, category, name, view_count, meaning, deleted) +VALUES(3, now(), now(), 0, 'DEVELOP', 'HTTP', 0, 'HyperText Transfer Protocol의 약자.', true); + +INSERT INTO pronunciations(id, created_at, updated_at, content, pronunciation_type, word_id, deleted) +VALUES(3, now(), now(), '에이치티티피', 'KOREAN', 2, true); + +INSERT INTO word_examples(id, created_at, updated_at, content, word_id, deleted) +VALUES(3, now(), now(), 'HTTP 통신이 정상적으로 수행되지 않는 것 같습니다.', 2, true); From 1afd425ec1914078020810a82e7318b030f0bdbc Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 17:42:08 +0900 Subject: [PATCH 078/115] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EC=83=9D=EC=84=B1=EB=90=9C=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CreateBookmarkService.java | 15 ++++++++------- .../application/CreateBookmarkServiceTest.java | 16 ++++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java index 269cabc7..a95fc7bf 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkService.java @@ -30,12 +30,12 @@ class CreateBookmarkService { private final BookmarkRepository bookmarkRepository; private final TransactionTemplate transactionTemplate; - public void createBookmark(Long accountId, CreateBookmarkRequest request) { + public Bookmark createBookmark(Long accountId, CreateBookmarkRequest request) { validateWordId(request); - executeBookmarkCreationWithTransaction(accountId, request); + return executeBookmarkCreationWithTransaction(accountId, request); } - private void executeBookmarkCreationWithTransaction(Long accountId, CreateBookmarkRequest request) { + private Bookmark executeBookmarkCreationWithTransaction(Long accountId, CreateBookmarkRequest request) { RLock lock = redissonClient.getLock(calculateLockName(accountId, request)); if (!tryLock(lock)) { @@ -43,7 +43,7 @@ private void executeBookmarkCreationWithTransaction(Long accountId, CreateBookma } try { - transactionTemplate.executeWithoutResult(action -> doBookmarkCreation(accountId, request)); + return transactionTemplate.execute(action -> doBookmarkCreation(accountId, request)); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); @@ -66,9 +66,9 @@ private boolean tryLock(RLock lock) { } } - private void doBookmarkCreation(Long accountId, CreateBookmarkRequest request) { + private Bookmark doBookmarkCreation(Long accountId, CreateBookmarkRequest request) { validateExistsBookmark(accountId, request); - persistBookmark(accountId, request); + return persistBookmark(accountId, request); } private String calculateLockName(Long accountId, CreateBookmarkRequest request) { @@ -81,10 +81,11 @@ private void validateExistsBookmark(Long accountId, CreateBookmarkRequest reques } } - private void persistBookmark(Long accountId, CreateBookmarkRequest request) { + private Bookmark persistBookmark(Long accountId, CreateBookmarkRequest request) { Bookmark bookmark = new Bookmark(accountId, request.wordId()); bookmarkRepository.save(bookmark); + return bookmark; } private void validateWordId(CreateBookmarkRequest request) { diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java index cae84c27..3885e193 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/CreateBookmarkServiceTest.java @@ -2,11 +2,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; import com.dnd.spaced.core.bookmark.application.exception.AlreadyExistsBookmarkException; import com.dnd.spaced.core.bookmark.application.exception.WordNotFoundException; -import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; +import com.dnd.spaced.core.bookmark.domain.Bookmark; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -27,9 +28,6 @@ class CreateBookmarkServiceTest { @Autowired CreateBookmarkService createBookmarkService; - @Autowired - BookmarkRepository bookmarkRepository; - @Test @Sql("classpath:sql/bookmark/word.sql") void 북마크를_추가한다() { @@ -37,12 +35,14 @@ class CreateBookmarkServiceTest { CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); // when - createBookmarkService.createBookmark(ACCOUNT_ID, request); + Bookmark actual = createBookmarkService.createBookmark(ACCOUNT_ID, request); // then - boolean actual = bookmarkRepository.existsBy(ACCOUNT_ID, WORD_ID); - - assertThat(actual).isTrue(); + assertAll( + () -> assertThat(actual.getId()).isPositive(), + () -> assertThat(actual.getAccountId()).isEqualTo(ACCOUNT_ID), + () -> assertThat(actual.getWordId()).isEqualTo(WORD_ID) + ); } @Test From 97a360a09d74cc13956afce76934268e762079b5 Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 17:57:27 +0900 Subject: [PATCH 079/115] =?UTF-8?q?refactor:=20BookmarkService=20Facade=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/application/BookmarkService.java | 117 ------------------ .../application/BookmarkServiceFacade.java | 52 ++++++++ .../presentation/BookmarkController.java | 10 +- ...st.java => BookmarkServiceFacadeTest.java} | 36 +++--- ...ConcurrencyBookmarkServiceFacadeTest.java} | 6 +- .../presentation/BookmarkControllerTest.java | 13 +- 6 files changed, 87 insertions(+), 147 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacade.java rename space-d/src/test/java/com/dnd/spaced/core/bookmark/application/{BookmarkServiceTest.java => BookmarkServiceFacadeTest.java} (80%) rename space-d/src/test/java/com/dnd/spaced/core/bookmark/application/{ConcurrencyBookmarkServiceTest.java => ConcurrencyBookmarkServiceFacadeTest.java} (94%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java deleted file mode 100644 index 74da4c86..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkService.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.dnd.spaced.core.bookmark.application; - -import com.dnd.spaced.core.bookmark.application.dto.mapper.BookmarkMapper; -import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; -import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; -import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; -import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; -import com.dnd.spaced.core.bookmark.application.exception.AlreadyExistsBookmarkException; -import com.dnd.spaced.core.bookmark.application.exception.BookmarkInterruptedException; -import com.dnd.spaced.core.bookmark.application.exception.WordNotFoundException; -import com.dnd.spaced.core.bookmark.domain.Bookmark; -import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; -import com.dnd.spaced.core.word.application.event.dto.WordBookmarkCountDecrementedEvent; -import com.dnd.spaced.core.word.application.event.dto.WordBookmarkCountIncrementedEvent; -import com.dnd.spaced.core.word.domain.repository.WordRepository; -import java.util.List; -import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; -import org.redisson.api.RLock; -import org.redisson.api.RedissonClient; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionTemplate; - -@Service -@RequiredArgsConstructor -public class BookmarkService { - - private final BookmarkMapper mapper; - private final RedissonClient redissonClient; - private final WordRepository wordRepository; - private final BookmarkRepository bookmarkRepository; - private final TransactionTemplate transactionTemplate; - private final ApplicationEventPublisher eventPublisher; - - public void createBookmark(Long accountId, CreateBookmarkRequest request) { - validateWordId(request); - - transactionTemplate.executeWithoutResult( - transactionStatus -> { - RLock lock = redissonClient.getLock(calculateLockName(accountId, request)); - - try { - boolean acquireLock = lock.tryLock(1, 1, TimeUnit.SECONDS); - - if (!acquireLock) { - return; - } - - validateExistsBookmark(accountId, request); - - Bookmark bookmark = new Bookmark(accountId, request.wordId()); - - bookmarkRepository.save(bookmark); - publishAddedBookmarkEvent(bookmark); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new BookmarkInterruptedException("북마크 생성 중 인터럽트 발생", e); - } finally { - if (lock.isHeldByCurrentThread()) { - lock.unlock(); - } - } - } - ); - } - - @Transactional - public void deleteBookmark(Long accountId, DeleteBookmarkRequest request) { - bookmarkRepository.delete(accountId, request.wordId()); - publishDeletedBookmarkEvent(request.wordId()); - } - - public BookmarkCollectionResponse readBookmarks( - Long accountId, - ReadAllBookmarkRequest request, - Pageable pageable - ) { - List bookmarks = bookmarkRepository.findAllBy(accountId, request.lastBookmarkId(), pageable); - - return mapper.toDto(bookmarks); - } - - private void validateWordId(CreateBookmarkRequest request) { - if (isExistsWord(request.wordId())) { - throw new WordNotFoundException("지정한 식별자의 용어를 찾지 못했습니다."); - } - } - - private String calculateLockName(Long accountId, CreateBookmarkRequest request) { - return accountId + ":" + request.wordId(); - } - - private boolean isExistsWord(Long wordId) { - return !wordRepository.existsBy(wordId); - } - - private void validateExistsBookmark(Long accountId, CreateBookmarkRequest request) { - if (isExistsBookmark(accountId, request.wordId())) { - throw new AlreadyExistsBookmarkException("이미 북마크에 추가된 용어입니다."); - } - } - - private boolean isExistsBookmark(Long accountId, Long wordId) { - return bookmarkRepository.existsBy(accountId, wordId); - } - - private void publishAddedBookmarkEvent(Bookmark bookmark) { - eventPublisher.publishEvent(new WordBookmarkCountIncrementedEvent(bookmark.getWordId())); - } - - private void publishDeletedBookmarkEvent(Long wordId) { - eventPublisher.publishEvent(new WordBookmarkCountDecrementedEvent(wordId)); - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacade.java new file mode 100644 index 00000000..3739defc --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacade.java @@ -0,0 +1,52 @@ +package com.dnd.spaced.core.bookmark.application; + +import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; +import com.dnd.spaced.core.bookmark.application.dto.response.BookmarkCollectionResponse; +import com.dnd.spaced.core.bookmark.domain.Bookmark; +import com.dnd.spaced.core.word.application.event.dto.WordBookmarkCountDecrementedEvent; +import com.dnd.spaced.core.word.application.event.dto.WordBookmarkCountIncrementedEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class BookmarkServiceFacade { + + private final CreateBookmarkService createBookmarkService; + private final ReadBookmarkService readBookmarkService; + private final DeleteBookmarkService deleteBookmarkService; + private final ApplicationEventPublisher eventPublisher; + + public void createBookmark(Long accountId, CreateBookmarkRequest request) { + Bookmark bookmark = createBookmarkService.createBookmark(accountId, request); + + publishAddedBookmarkEvent(bookmark); + } + + @Transactional + public void deleteBookmark(Long accountId, DeleteBookmarkRequest request) { + deleteBookmarkService.deleteBookmark(accountId, request); + publishDeletedBookmarkEvent(request.wordId()); + } + + public BookmarkCollectionResponse readBookmarks( + Long accountId, + ReadAllBookmarkRequest request, + Pageable pageable + ) { + return readBookmarkService.readBookmarks(accountId, request, pageable); + } + + private void publishAddedBookmarkEvent(Bookmark bookmark) { + eventPublisher.publishEvent(new WordBookmarkCountIncrementedEvent(bookmark.getWordId())); + } + + private void publishDeletedBookmarkEvent(Long wordId) { + eventPublisher.publishEvent(new WordBookmarkCountDecrementedEvent(wordId)); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java index 7335a117..b3c80a44 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/presentation/BookmarkController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.bookmark.presentation; -import com.dnd.spaced.core.bookmark.application.BookmarkService; +import com.dnd.spaced.core.bookmark.application.BookmarkServiceFacade; import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; @@ -25,7 +25,7 @@ @RequiredArgsConstructor public class BookmarkController { - private final BookmarkService bookmarkService; + private final BookmarkServiceFacade bookmarkServiceFacade; @GetMapping public ResponseEntity readBookmarks( @@ -33,7 +33,7 @@ public ResponseEntity readBookmarks( ReadAllBookmarkRequest request, @BookmarkPageable Pageable pageable ) { - BookmarkCollectionResponse response = bookmarkService.readBookmarks(accountId.id(), request, pageable); + BookmarkCollectionResponse response = bookmarkServiceFacade.readBookmarks(accountId.id(), request, pageable); return ResponseEntity.ok(response); } @@ -43,7 +43,7 @@ public ResponseEntity createBookmark( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody CreateBookmarkRequest request ) { - bookmarkService.createBookmark(accountId.id(), request); + bookmarkServiceFacade.createBookmark(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } @@ -53,7 +53,7 @@ public ResponseEntity deleteBookmark( @CurrentAccount AuthAccountId accountId, @Valid @RequestBody DeleteBookmarkRequest request ) { - bookmarkService.deleteBookmark(accountId.id(), request); + bookmarkServiceFacade.deleteBookmark(accountId.id(), request); return ResponseEntityConst.NO_CONTENT; } diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacadeTest.java similarity index 80% rename from space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacadeTest.java index 00f1c6aa..03c81868 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/BookmarkServiceFacadeTest.java @@ -27,22 +27,28 @@ @RecordApplicationEvents @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class BookmarkServiceTest { +class BookmarkServiceFacadeTest { + + private static final Long ACCOUNT_ID = 1L; + private static final Long NOT_FOUND_WORD_ID = -999L; + private static final Long WORD_ID = 1L; + private static final Long BOOKMARK_ID = 1L; + private static final Long LAST_BOOKMARK_ID = 1L; @Autowired ApplicationEvents events; @Autowired - BookmarkService bookmarkService; + BookmarkServiceFacade bookmarkServiceFacade; @Test @Sql("classpath:sql/bookmark/word.sql") void 북마크를_추가한다() { // given - CreateBookmarkRequest request = new CreateBookmarkRequest(1L); + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); // when - bookmarkService.createBookmark(1L, request); + bookmarkServiceFacade.createBookmark(ACCOUNT_ID, request); // then assertThat(events.stream(WordBookmarkCountIncrementedEvent.class).count()).isOne(); @@ -55,10 +61,10 @@ class BookmarkServiceTest { }) void 이미_북마크에_추가된_용어를_북마에_추가할_수_없다() { // given - CreateBookmarkRequest request = new CreateBookmarkRequest(1L); + CreateBookmarkRequest request = new CreateBookmarkRequest(WORD_ID); // when & then - assertThatThrownBy(() -> bookmarkService.createBookmark(1L, request)) + assertThatThrownBy(() -> bookmarkServiceFacade.createBookmark(ACCOUNT_ID, request)) .isInstanceOf(AlreadyExistsBookmarkException.class) .hasMessage("이미 북마크에 추가된 용어입니다."); } @@ -66,10 +72,10 @@ class BookmarkServiceTest { @Test void 지정한_용어_식별자로_용어를_찾지_못하면_북마크를_추가할_수_없다() { // given - CreateBookmarkRequest request = new CreateBookmarkRequest(-999L); + CreateBookmarkRequest request = new CreateBookmarkRequest(NOT_FOUND_WORD_ID); // when & then - assertThatThrownBy(() -> bookmarkService.createBookmark(1L, request)) + assertThatThrownBy(() -> bookmarkServiceFacade.createBookmark(ACCOUNT_ID, request)) .isInstanceOf(WordNotFoundException.class) .hasMessage("지정한 식별자의 용어를 찾지 못했습니다."); @@ -79,10 +85,10 @@ class BookmarkServiceTest { @Sql("classpath:sql/bookmark/bookmark.sql") void 북마크를_삭제한다() { // given - DeleteBookmarkRequest request = new DeleteBookmarkRequest(1L); + DeleteBookmarkRequest request = new DeleteBookmarkRequest(WORD_ID); // when - bookmarkService.deleteBookmark(1L, request); + bookmarkServiceFacade.deleteBookmark(ACCOUNT_ID, request); // then assertThat(events.stream(WordBookmarkCountDecrementedEvent.class).count()).isOne(); @@ -98,15 +104,15 @@ class BookmarkServiceTest { ReadAllBookmarkRequest request = new ReadAllBookmarkRequest(null); // when - BookmarkCollectionResponse actual = bookmarkService.readBookmarks(1L, request, PageRequest.of(0, 10)); + BookmarkCollectionResponse actual = bookmarkServiceFacade.readBookmarks(ACCOUNT_ID, request, PageRequest.of(0, 10)); // then assertAll( () -> assertThat(actual.bookmarks()).hasSize(1), - () -> assertThat(actual.lastBookmarkId()).isEqualTo(1L), - () -> assertThat(actual.bookmarks().get(0).bookmarkId()).isEqualTo(1L), - () -> assertThat(actual.bookmarks().get(0).accountId()).isEqualTo(1L), - () -> assertThat(actual.bookmarks().get(0).wordId()).isEqualTo(1L) + () -> assertThat(actual.lastBookmarkId()).isEqualTo(LAST_BOOKMARK_ID), + () -> assertThat(actual.bookmarks().get(0).bookmarkId()).isEqualTo(BOOKMARK_ID), + () -> assertThat(actual.bookmarks().get(0).accountId()).isEqualTo(ACCOUNT_ID), + () -> assertThat(actual.bookmarks().get(0).wordId()).isEqualTo(WORD_ID) ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceFacadeTest.java similarity index 94% rename from space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceFacadeTest.java index a8d1a1e6..476a8149 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ConcurrencyBookmarkServiceFacadeTest.java @@ -25,13 +25,13 @@ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class ConcurrencyBookmarkServiceTest { +class ConcurrencyBookmarkServiceFacadeTest { private static final Long WORD_ID = 1L; private static final Long ACCOUNT_ID = 1L; @Autowired - BookmarkService bookmarkService; + BookmarkServiceFacade bookmarkServiceFacade; @Autowired BookmarkRepository bookmarkRepository; @@ -51,7 +51,7 @@ class ConcurrencyBookmarkServiceTest { startLatch.countDown(); startLatch.await(); - bookmarkService.createBookmark(ACCOUNT_ID, request); + bookmarkServiceFacade.createBookmark(ACCOUNT_ID, request); } catch (Exception e) { throw new RuntimeException(e); } finally { diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/presentation/BookmarkControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/presentation/BookmarkControllerTest.java index 39000b83..d5059527 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/presentation/BookmarkControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/presentation/BookmarkControllerTest.java @@ -17,7 +17,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.dnd.spaced.config.common.CommonControllerSliceTest; -import com.dnd.spaced.core.bookmark.application.BookmarkService; +import com.dnd.spaced.core.bookmark.application.BookmarkServiceFacade; import com.dnd.spaced.core.bookmark.application.dto.request.CreateBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.DeleteBookmarkRequest; import com.dnd.spaced.core.bookmark.application.dto.request.ReadAllBookmarkRequest; @@ -27,7 +27,6 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -39,7 +38,7 @@ class BookmarkControllerTest extends CommonControllerSliceTest { @Autowired - BookmarkService bookmarkService; + BookmarkServiceFacade bookmarkServiceFacade; @Test @WithMockUser("1") @@ -55,7 +54,7 @@ class BookmarkControllerTest extends CommonControllerSliceTest { .content(objectMapper.writeValueAsString(request)) ).andExpectAll(status().isNoContent()); - verify(bookmarkService).createBookmark(anyLong(), any(CreateBookmarkRequest.class)); + verify(bookmarkServiceFacade).createBookmark(anyLong(), any(CreateBookmarkRequest.class)); 북마크_생성_요청_문서화(resultActions); } @@ -86,7 +85,7 @@ class BookmarkControllerTest extends CommonControllerSliceTest { .content(objectMapper.writeValueAsString(request)) ).andExpectAll(status().isNoContent()); - verify(bookmarkService).deleteBookmark(anyLong(), any(DeleteBookmarkRequest.class)); + verify(bookmarkServiceFacade).deleteBookmark(anyLong(), any(DeleteBookmarkRequest.class)); 북마크_삭제_요청_문서화(resultActions); } @@ -111,7 +110,7 @@ class BookmarkControllerTest extends CommonControllerSliceTest { BookmarkResponse bookmarkResponse = new BookmarkResponse(1L, 1L, 1L, LocalDateTime.now()); BookmarkCollectionResponse response = new BookmarkCollectionResponse(List.of(bookmarkResponse), 1L); - given(bookmarkService.readBookmarks(anyLong(), any(ReadAllBookmarkRequest.class), any(Pageable.class))) + given(bookmarkServiceFacade.readBookmarks(anyLong(), any(ReadAllBookmarkRequest.class), any(Pageable.class))) .willReturn(response); // when & then @@ -127,7 +126,7 @@ class BookmarkControllerTest extends CommonControllerSliceTest { jsonPath("lastBookmarkId", is(1L), Long.class) ); - verify(bookmarkService).readBookmarks(anyLong(), any(ReadAllBookmarkRequest.class), any(Pageable.class)); + verify(bookmarkServiceFacade).readBookmarks(anyLong(), any(ReadAllBookmarkRequest.class), any(Pageable.class)); 북마크_목록_조회_요청_문서화(resultActions); } From 5ed58fbcb4abeb8523111f420fcdb1578b31971b Mon Sep 17 00:00:00 2001 From: apptie Date: Wed, 16 Jul 2025 18:26:08 +0900 Subject: [PATCH 080/115] =?UTF-8?q?test:=20ReadBookmarkService=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=88=98=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/application/ReadBookmarkServiceTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java index e3ffd30b..bac18229 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkServiceTest.java @@ -19,10 +19,10 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class ReadBookmarkServiceTest { - private static final long WORD_ID = 1L; - private static final long ACCOUNT_ID = 1L; - private static final long BOOKMARK_ID = 1L; - private static final long LAST_BOOKMARK_ID = 1L; + private static final Long WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + private static final Long BOOKMARK_ID = 1L; + private static final Long LAST_BOOKMARK_ID = 1L; @Autowired ReadBookmarkService readBookmarkService; From 6abc57b63673936cb9d9010aba02bd32f3b270ae Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 19 Jul 2025 14:01:58 +0900 Subject: [PATCH 081/115] =?UTF-8?q?refactor:=20ReadBookmarkService=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spaced/core/bookmark/application/ReadBookmarkService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java index 12e64f67..59eff9b5 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/ReadBookmarkService.java @@ -12,7 +12,7 @@ @Service @RequiredArgsConstructor -public class ReadBookmarkService { +class ReadBookmarkService { private final BookmarkMapper mapper; private final BookmarkRepository bookmarkRepository; From 8db9c5e539a21b38e0a87ad3aabe3875267b2f0b Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 19 Jul 2025 14:10:29 +0900 Subject: [PATCH 082/115] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CreateCommentService.java | 37 +++++++++++ .../application/CreateCommentServiceTest.java | 64 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/comment/application/CreateCommentService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/comment/application/CreateCommentServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CreateCommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CreateCommentService.java new file mode 100644 index 00000000..d6000d6a --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CreateCommentService.java @@ -0,0 +1,37 @@ +package com.dnd.spaced.core.comment.application; + +import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; +import com.dnd.spaced.core.comment.application.exception.WordNotFoundException; +import com.dnd.spaced.core.comment.domain.Comment; +import com.dnd.spaced.core.comment.domain.repository.CommentRepository; +import com.dnd.spaced.core.word.domain.repository.WordRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +class CreateCommentService { + + private final WordRepository wordRepository; + private final CommentRepository commentRepository; + + @Transactional + public void createComment(Long accountId, Long wordId, CreateCommentRequest request) { + validateWordId(wordId); + + persistComment(accountId, wordId, request); + } + + private void validateWordId(Long wordId) { + if (!wordRepository.existsBy(wordId)) { + throw new WordNotFoundException("댓글과 관련된 용어를 찾을 수 없습니다."); + } + } + + private void persistComment(Long accountId, Long wordId, CreateCommentRequest request) { + Comment comment = new Comment(accountId, wordId, request.content()); + + commentRepository.save(comment); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/CreateCommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/CreateCommentServiceTest.java new file mode 100644 index 00000000..2b427dc5 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/CreateCommentServiceTest.java @@ -0,0 +1,64 @@ +package com.dnd.spaced.core.comment.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; +import com.dnd.spaced.core.comment.application.exception.WordNotFoundException; +import com.dnd.spaced.core.comment.domain.exception.InvalidCommentContentException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CreateCommentServiceTest { + + private static final Long ACCOUNT_ID = 1L; + private static final Long WORD_ID = 1L; + private static final Long NOT_FOUND_WORD_ID = -1L; + + @Autowired + CreateCommentService createCommentService; + + @Test + void 댓글을_작성할_용어_ID로_용어를_찾지_못하는_경우_댓글을_작성할_수_없다() { + // given + CreateCommentRequest request = new CreateCommentRequest("이 용어는 언제 쓰는건가요?"); + + // when & then + assertThatThrownBy(() -> createCommentService.createComment(ACCOUNT_ID, NOT_FOUND_WORD_ID, request)) + .isInstanceOf(WordNotFoundException.class) + .hasMessage("댓글과 관련된 용어를 찾을 수 없습니다."); + } + + @ParameterizedTest(name = "댓글 내용이 {0}일 때 댓글을 작성할 수 없다") + @NullAndEmptySource + @Sql("classpath:sql/comment/word.sql") + void 유효한_길이의_댓글_내용이_아니라면_댓글을_작성할_수_없다(String invalidContent) { + // given + CreateCommentRequest request = new CreateCommentRequest(invalidContent); + + // when & then + assertThatThrownBy(() -> createCommentService.createComment(ACCOUNT_ID, WORD_ID, request)) + .isInstanceOf(InvalidCommentContentException.class) + .hasMessage("댓글 내용은 최소 1글자 이상, 최소 100글자 이하여야 합니다"); + } + + @Test + @Sql("classpath:sql/comment/word.sql") + void 댓글을_작성한다() { + // given + CreateCommentRequest request = new CreateCommentRequest("이 용어는 언제 쓰는건가요?"); + + // when + assertDoesNotThrow(() -> createCommentService.createComment(ACCOUNT_ID, WORD_ID, request)); + } +} From 5c74f8288e8cf9d2d9c62b46dded58ae7c498b22 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 19 Jul 2025 14:26:34 +0900 Subject: [PATCH 083/115] =?UTF-8?q?refactor:=20CommentResponseCollectionMa?= =?UTF-8?q?pper=EB=A5=BC=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=94=84=EB=A7=81=20=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentService.java | 3 ++- .../mapper/CommentResponseCollectionMapper.java | 17 ++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java index ea95c307..ffa40908 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java @@ -23,6 +23,7 @@ public class CommentService { private final WordRepository wordRepository; private final CommentRepository commentRepository; + private final CommentResponseCollectionMapper mapper; @Transactional public void createComment(Long accountId, Long wordId, CreateCommentRequest request) { @@ -52,7 +53,7 @@ public void updateComment(Long accountId, Long commentId, UpdateCommentRequest r public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { List comments = commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); - return CommentResponseCollectionMapper.toCollectionDto(comments); + return mapper.toDto(comments); } private void validateWordId(Long wordId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java index e250c99f..6c79dd6c 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java @@ -6,26 +6,25 @@ import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse.CommentWriterResponse; import com.dnd.spaced.core.comment.domain.Comment; import com.dnd.spaced.core.comment.domain.dto.LikedComment; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class CommentResponseCollectionMapper { +@Mapper +public class CommentResponseCollectionMapper { - public static CommentCollectionResponse toCollectionDto(List comments) { + public CommentCollectionResponse toDto(List comments) { if (comments.isEmpty()) { return new CommentCollectionResponse(List.of(), null); } List responses = comments.stream() - .map(CommentResponseCollectionMapper::toCommentResponse) + .map(this::toCommentResponse) .toList(); return new CommentCollectionResponse(responses, comments.get(comments.size() - 1).comment().getId()); } - private static CommentResponse toCommentResponse(LikedComment likedComment) { + private CommentResponse toCommentResponse(LikedComment likedComment) { return new CommentResponse( toCommentContentResponse(likedComment.comment()), toCommentWriterResponse( @@ -37,7 +36,7 @@ private static CommentResponse toCommentResponse(LikedComment likedComment) { ); } - private static CommentContentResponse toCommentContentResponse(Comment comment) { + private CommentContentResponse toCommentContentResponse(Comment comment) { return new CommentContentResponse( comment.getId(), comment.getWordId(), @@ -46,7 +45,7 @@ private static CommentContentResponse toCommentContentResponse(Comment comment) ); } - private static CommentWriterResponse toCommentWriterResponse( + private CommentWriterResponse toCommentWriterResponse( String writerNickname, String writerProfileImage, Long writerId From c1ef44fdca37095a0b185757d0ea186c6b75370a Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 19 Jul 2025 14:28:49 +0900 Subject: [PATCH 084/115] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/DeleteCommentService.java | 39 +++++++++++++ .../application/DeleteCommentServiceTest.java | 58 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/comment/application/DeleteCommentService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/comment/application/DeleteCommentServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/DeleteCommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/DeleteCommentService.java new file mode 100644 index 00000000..dd30ad3e --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/DeleteCommentService.java @@ -0,0 +1,39 @@ +package com.dnd.spaced.core.comment.application; + +import com.dnd.spaced.core.comment.application.exception.CommentNotFoundException; +import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; +import com.dnd.spaced.core.comment.domain.Comment; +import com.dnd.spaced.core.comment.domain.repository.CommentRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +class DeleteCommentService { + + private final CommentRepository commentRepository; + + @Transactional + public void deleteComment(Long accountId, Long commentId) { + Comment comment = findComment(commentId); + + validateCommentDeletePermission(comment, accountId); + executeCommentDeletion(comment); + } + + private Comment findComment(Long commentId) { + return commentRepository.findBy(commentId) + .orElseThrow(() -> new CommentNotFoundException("지정한 ID에 해당하는 댓글이 없습니다.")); + } + + private void validateCommentDeletePermission(Comment comment, Long accountId) { + if (comment.isReader(accountId)) { + throw new ForbiddenCommentException("댓글을 삭제할 권한이 없습니다."); + } + } + + private void executeCommentDeletion(Comment comment) { + comment.delete(); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/DeleteCommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/DeleteCommentServiceTest.java new file mode 100644 index 00000000..e5189d28 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/DeleteCommentServiceTest.java @@ -0,0 +1,58 @@ +package com.dnd.spaced.core.comment.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.comment.application.exception.CommentNotFoundException; +import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class DeleteCommentServiceTest { + + private static final Long WRITER_ID = 1L; + private static final Long READER_ID = 2L; + private static final Long COMMENT_ID = 1L; + private static final Long NOT_FOUND_COMMENT_ID = -999L; + + @Autowired + DeleteCommentService commentService; + + @Test + void 없는_댓글_ID를_통해_댓글을_삭제할_수_없다() { + // when & then + assertThatThrownBy(() -> commentService.deleteComment(WRITER_ID, NOT_FOUND_COMMENT_ID)) + .isInstanceOf(CommentNotFoundException.class) + .hasMessage("지정한 ID에 해당하는 댓글이 없습니다."); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + void 댓글_작성자가_아니라면_댓글을_삭제할_수_없다() { + // when & then + assertThatThrownBy(() -> commentService.deleteComment(READER_ID, COMMENT_ID)) + .isInstanceOf(ForbiddenCommentException.class) + .hasMessage("댓글을 삭제할 권한이 없습니다."); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + void 댓글을_삭제한다() { + // when & then + assertDoesNotThrow(() -> commentService.deleteComment(WRITER_ID, COMMENT_ID)); + } +} From 81e59677a96243ca2888a93f4e2408d174980101 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 19 Jul 2025 14:39:50 +0900 Subject: [PATCH 085/115] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/UpdateCommentService.java | 40 +++++++++ .../application/UpdateCommentServiceTest.java | 87 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/comment/application/UpdateCommentService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/comment/application/UpdateCommentServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/UpdateCommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/UpdateCommentService.java new file mode 100644 index 00000000..4e47a330 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/UpdateCommentService.java @@ -0,0 +1,40 @@ +package com.dnd.spaced.core.comment.application; + +import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; +import com.dnd.spaced.core.comment.application.exception.CommentNotFoundException; +import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; +import com.dnd.spaced.core.comment.domain.Comment; +import com.dnd.spaced.core.comment.domain.repository.CommentRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +class UpdateCommentService { + + private final CommentRepository commentRepository; + + @Transactional + public void updateComment(Long accountId, Long commentId, UpdateCommentRequest request) { + Comment comment = findComment(commentId); + + validateCommentUpdatePermission(comment, accountId); + executeCommentUpdate(request, comment); + } + + private Comment findComment(Long commentId) { + return commentRepository.findBy(commentId) + .orElseThrow(() -> new CommentNotFoundException("지정한 ID에 해당하는 댓글이 없습니다.")); + } + + private void validateCommentUpdatePermission(Comment comment, Long accountId) { + if (comment.isReader(accountId)) { + throw new ForbiddenCommentException("댓글을 수정할 권한이 없습니다."); + } + } + + private void executeCommentUpdate(UpdateCommentRequest request, Comment comment) { + comment.changeContent(request.content()); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/UpdateCommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/UpdateCommentServiceTest.java new file mode 100644 index 00000000..e9bd835c --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/UpdateCommentServiceTest.java @@ -0,0 +1,87 @@ +package com.dnd.spaced.core.comment.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; +import com.dnd.spaced.core.comment.application.exception.CommentNotFoundException; +import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; +import com.dnd.spaced.core.comment.domain.exception.InvalidCommentContentException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class UpdateCommentServiceTest { + + private static final Long WRITER_ID = 1L; + private static final Long READER_ID = 2L; + private static final Long COMMENT_ID = 1L; + private static final Long NOT_FOUND_COMMENT_ID = -999L; + + @Autowired + UpdateCommentService updateCommentService; + + @Test + void 댓글_ID로_찾을_수_없는_댓글은_수정할_수_없다() { + // given + UpdateCommentRequest request = new UpdateCommentRequest("처음 보는 용어인데 잘 쓰지는 않나보네요"); + + // when & then + assertThatThrownBy(() -> updateCommentService.updateComment(WRITER_ID, NOT_FOUND_COMMENT_ID, request)) + .isInstanceOf(CommentNotFoundException.class) + .hasMessage("지정한 ID에 해당하는 댓글이 없습니다."); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + void 댓글_작성자가_아니라면_댓글을_수정할_수_없다() { + // given + UpdateCommentRequest request = new UpdateCommentRequest("처음 보는 용어인데 잘 쓰지는 않나보네요"); + + // when & then + assertThatThrownBy(() -> updateCommentService.updateComment(READER_ID, COMMENT_ID, request)) + .isInstanceOf(ForbiddenCommentException.class) + .hasMessage("댓글을 수정할 권한이 없습니다."); + } + + @ParameterizedTest(name = "댓글 내용이 {0}일 때 예외가 발생한다") + @NullAndEmptySource + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + void 비어_있는_내용으로_댓글을_수정할_수_없다(String invalidContent) { + // given + UpdateCommentRequest request = new UpdateCommentRequest(invalidContent); + + // when & then + assertThatThrownBy(() -> updateCommentService.updateComment(WRITER_ID, COMMENT_ID, request)) + .isInstanceOf(InvalidCommentContentException.class) + .hasMessage("댓글 내용은 최소 1글자 이상, 최소 100글자 이하여야 합니다"); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql" + }) + void 댓글을_수정한다() { + // given + UpdateCommentRequest request = new UpdateCommentRequest("처음 보는 용어인데 잘 쓰지는 않나보네요"); + + // when & then + assertDoesNotThrow(() -> updateCommentService.updateComment(WRITER_ID, COMMENT_ID, request)); + } +} From e8646d47588b68cfd0f88b5b15f9b170ed982fa3 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 19 Jul 2025 14:47:30 +0900 Subject: [PATCH 086/115] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReadCommentService.java | 24 ++++++ .../application/ReadCommentServiceTest.java | 73 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java new file mode 100644 index 00000000..7e956cf1 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java @@ -0,0 +1,24 @@ +package com.dnd.spaced.core.comment.application; + +import com.dnd.spaced.core.comment.application.dto.mapper.CommentResponseCollectionMapper; +import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; +import com.dnd.spaced.core.comment.domain.repository.CommentRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +class ReadCommentService { + + private final CommentRepository commentRepository; + private final CommentResponseCollectionMapper mapper; + + public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { + List comments = commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); + + return mapper.toDto(comments); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java new file mode 100644 index 00000000..8ffd7670 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java @@ -0,0 +1,73 @@ +package com.dnd.spaced.core.comment.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ReadCommentServiceTest { + + private static final Long GUEST_ID = -1L; + private static final Long READER_ID = 2L; + private static final Long WORD_ID = 1L; + + @Autowired + ReadCommentService readCommentService; + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql", + "classpath:sql/comment/like.sql" + }) + void 로그인_하지_않고_특정_용어의_댓글_목록을_조회한다() { + // when + CommentCollectionResponse actual = readCommentService.readComments( + GUEST_ID, + WORD_ID, + null, + PageRequest.of(0, 10) + ); + + // then + assertAll( + () -> assertThat(actual.comments()).hasSize(1), + () -> assertThat(actual.comments().get(0).commentContent().content()).isEqualTo("이 용어는 언제 쓰는건가요?"), + () -> assertThat(actual.comments().get(0).liked()).isFalse() + ); + } + + @Test + @Sql(value = { + "classpath:sql/comment/word.sql", + "classpath:sql/comment/comment.sql", + "classpath:sql/comment/like.sql" + }) + void 로그인하고_특정_용어의_댓글_목록을_조회한다() { + // when + CommentCollectionResponse actual = readCommentService.readComments( + READER_ID, + WORD_ID, + null, + PageRequest.of(0, 10) + ); + + // then + assertAll( + () -> assertThat(actual.comments()).hasSize(1), + () -> assertThat(actual.comments().get(0).commentContent().content()).isEqualTo("이 용어는 언제 쓰는건가요?"), + () -> assertThat(actual.comments().get(0).liked()).isTrue() + ); + } +} From 2815c5154ca6c05d714bc4275f6647bd8312c088 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 09:26:52 +0900 Subject: [PATCH 087/115] =?UTF-8?q?refactor:=20CommentService=20Facade=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentService.java | 81 ------------------- .../application/CommentServiceFacade.java | 38 +++++++++ .../presentation/CommentController.java | 12 +-- ...est.java => CommentServiceFacadeTest.java} | 28 +++---- .../presentation/CommentControllerTest.java | 15 ++-- 5 files changed, 65 insertions(+), 109 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java rename space-d/src/test/java/com/dnd/spaced/core/comment/application/{CommentServiceTest.java => CommentServiceFacadeTest.java} (86%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java deleted file mode 100644 index ffa40908..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentService.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.dnd.spaced.core.comment.application; - -import com.dnd.spaced.core.comment.application.dto.mapper.CommentResponseCollectionMapper; -import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; -import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; -import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; -import com.dnd.spaced.core.comment.application.exception.CommentNotFoundException; -import com.dnd.spaced.core.comment.application.exception.ForbiddenCommentException; -import com.dnd.spaced.core.comment.application.exception.WordNotFoundException; -import com.dnd.spaced.core.comment.domain.Comment; -import com.dnd.spaced.core.comment.domain.dto.LikedComment; -import com.dnd.spaced.core.comment.domain.repository.CommentRepository; -import com.dnd.spaced.core.word.domain.repository.WordRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class CommentService { - - private final WordRepository wordRepository; - private final CommentRepository commentRepository; - private final CommentResponseCollectionMapper mapper; - - @Transactional - public void createComment(Long accountId, Long wordId, CreateCommentRequest request) { - validateWordId(wordId); - - Comment comment = new Comment(accountId, wordId, request.content()); - - commentRepository.save(comment); - } - - @Transactional - public void deleteComment(Long accountId, Long commentId) { - Comment comment = findComment(commentId); - - validateDeleteAuthority(comment, accountId); - comment.delete(); - } - - @Transactional - public void updateComment(Long accountId, Long commentId, UpdateCommentRequest request) { - Comment comment = findComment(commentId); - - validateUpdateAuthority(comment, accountId); - comment.changeContent(request.content()); - } - - public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { - List comments = commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); - - return mapper.toDto(comments); - } - - private void validateWordId(Long wordId) { - if (!wordRepository.existsBy(wordId)) { - throw new WordNotFoundException("댓글과 관련된 용어를 찾을 수 없습니다."); - } - } - - private Comment findComment(Long commentId) { - return commentRepository.findBy(commentId) - .orElseThrow(() -> new CommentNotFoundException("지정한 ID에 해당하는 댓글이 없습니다.")); - } - - private void validateDeleteAuthority(Comment comment, Long accountId) { - if (comment.isReader(accountId)) { - throw new ForbiddenCommentException("댓글을 삭제할 권한이 없습니다."); - } - } - - private void validateUpdateAuthority(Comment comment, Long accountId) { - if (comment.isReader(accountId)) { - throw new ForbiddenCommentException("댓글을 수정할 권한이 없습니다."); - } - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java new file mode 100644 index 00000000..e6ea28f6 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java @@ -0,0 +1,38 @@ +package com.dnd.spaced.core.comment.application; + +import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; +import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; +import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CommentServiceFacade { + + private final CreateCommentService createCommentService; + private final ReadCommentService readCommentService; + private final UpdateCommentService updateCommentService; + private final DeleteCommentService deleteCommentService; + + @Transactional + public void createComment(Long accountId, Long wordId, CreateCommentRequest request) { + createCommentService.createComment(accountId, wordId, request); + } + + @Transactional + public void deleteComment(Long accountId, Long commentId) { + deleteCommentService.deleteComment(accountId, commentId); + } + + @Transactional + public void updateComment(Long accountId, Long commentId, UpdateCommentRequest request) { + updateCommentService.updateComment(accountId, commentId, request); + } + + public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { + return readCommentService.readComments(accountId, wordId, lastCommentId, pageable); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java b/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java index c29f17a3..b824c4ac 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/presentation/CommentController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.comment.presentation; -import com.dnd.spaced.core.comment.application.CommentService; +import com.dnd.spaced.core.comment.application.CommentServiceFacade; import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; import com.dnd.spaced.core.comment.application.dto.request.ReadAllCommentRequest; import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; @@ -28,7 +28,7 @@ @RequiredArgsConstructor public class CommentController { - private final CommentService commentService; + private final CommentServiceFacade commentServiceFacade; @PostMapping("/words/{wordId}/comments") public ResponseEntity creteComment( @@ -36,7 +36,7 @@ public ResponseEntity creteComment( @Valid @RequestBody CreateCommentRequest request, @PathVariable Long wordId ) { - commentService.createComment(accountId.id(), wordId, request); + commentServiceFacade.createComment(accountId.id(), wordId, request); URI location = UriComponentsBuilder.fromPath("/words/{wordId}") .buildAndExpand(wordId) @@ -48,7 +48,7 @@ public ResponseEntity creteComment( @DeleteMapping("/comments/{commentId}") public ResponseEntity deleteComment(@CurrentAccount AuthAccountId accountId, @PathVariable Long commentId) { - commentService.deleteComment(accountId.id(), commentId); + commentServiceFacade.deleteComment(accountId.id(), commentId); return ResponseEntityConst.NO_CONTENT; } @@ -59,7 +59,7 @@ public ResponseEntity update( @Valid @RequestBody UpdateCommentRequest request, @PathVariable Long commentId ) { - commentService.updateComment(accountId.id(), commentId, request); + commentServiceFacade.updateComment(accountId.id(), commentId, request); return ResponseEntityConst.NO_CONTENT; } @@ -71,7 +71,7 @@ public ResponseEntity readComments( ReadAllCommentRequest request, @CommentPageable Pageable pageable ) { - CommentCollectionResponse response = commentService.readComments( + CommentCollectionResponse response = commentServiceFacade.readComments( accountId.id(), wordId, request.lastCommentId(), diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceFacadeTest.java similarity index 86% rename from space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceFacadeTest.java index b8645755..3cd0cae2 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/CommentServiceFacadeTest.java @@ -26,10 +26,10 @@ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class CommentServiceTest { +class CommentServiceFacadeTest { @Autowired - CommentService commentService; + CommentServiceFacade commentServiceFacade; @Test void 댓글을_작성할_용어가_없는_경우_댓글을_작성할_수_없다() { @@ -37,7 +37,7 @@ class CommentServiceTest { CreateCommentRequest request = new CreateCommentRequest("이 용어는 언제 쓰는건가요?"); // when & then - assertThatThrownBy(() -> commentService.createComment(1L, -1L, request)) + assertThatThrownBy(() -> commentServiceFacade.createComment(1L, -1L, request)) .isInstanceOf(WordNotFoundException.class) .hasMessage("댓글과 관련된 용어를 찾을 수 없습니다."); } @@ -50,7 +50,7 @@ class CommentServiceTest { CreateCommentRequest request = new CreateCommentRequest(invalidContent); // when & then - assertThatThrownBy(() -> commentService.createComment(1L, 1L, request)) + assertThatThrownBy(() -> commentServiceFacade.createComment(1L, 1L, request)) .isInstanceOf(InvalidCommentContentException.class) .hasMessage("댓글 내용은 최소 1글자 이상, 최소 100글자 이하여야 합니다"); } @@ -62,13 +62,13 @@ class CommentServiceTest { CreateCommentRequest request = new CreateCommentRequest("이 용어는 언제 쓰는건가요?"); // when - assertDoesNotThrow(() -> commentService.createComment(1L, 1L, request)); + assertDoesNotThrow(() -> commentServiceFacade.createComment(1L, 1L, request)); } @Test void 없는_댓글_식별자를_통해_댓글을_삭제할_수_없다() { // when & then - assertThatThrownBy(() -> commentService.deleteComment(1L, -999L)) + assertThatThrownBy(() -> commentServiceFacade.deleteComment(1L, -999L)) .isInstanceOf(CommentNotFoundException.class) .hasMessage("지정한 ID에 해당하는 댓글이 없습니다."); } @@ -80,7 +80,7 @@ class CommentServiceTest { }) void 댓글_작성자가_아니라면_댓글을_삭제할_수_없다() { // when & then - assertThatThrownBy(() -> commentService.deleteComment(2L, 1L)) + assertThatThrownBy(() -> commentServiceFacade.deleteComment(2L, 1L)) .isInstanceOf(ForbiddenCommentException.class) .hasMessage("댓글을 삭제할 권한이 없습니다."); } @@ -92,7 +92,7 @@ class CommentServiceTest { }) void 댓글을_삭제한다() { // when & then - assertDoesNotThrow(() -> commentService.deleteComment(1L, 1L)); + assertDoesNotThrow(() -> commentServiceFacade.deleteComment(1L, 1L)); } @Test @@ -101,7 +101,7 @@ class CommentServiceTest { UpdateCommentRequest request = new UpdateCommentRequest("처음 보는 용어인데 잘 쓰지는 않나보네요"); // when & then - assertThatThrownBy(() -> commentService.updateComment(1L, -999L, request)) + assertThatThrownBy(() -> commentServiceFacade.updateComment(1L, -999L, request)) .isInstanceOf(CommentNotFoundException.class) .hasMessage("지정한 ID에 해당하는 댓글이 없습니다."); } @@ -116,7 +116,7 @@ class CommentServiceTest { UpdateCommentRequest request = new UpdateCommentRequest("처음 보는 용어인데 잘 쓰지는 않나보네요"); // when & then - assertThatThrownBy(() -> commentService.updateComment(2L, 1L, request)) + assertThatThrownBy(() -> commentServiceFacade.updateComment(2L, 1L, request)) .isInstanceOf(ForbiddenCommentException.class) .hasMessage("댓글을 수정할 권한이 없습니다."); } @@ -132,7 +132,7 @@ class CommentServiceTest { UpdateCommentRequest request = new UpdateCommentRequest(invalidContent); // when & then - assertThatThrownBy(() -> commentService.updateComment(1L, 1L, request)) + assertThatThrownBy(() -> commentServiceFacade.updateComment(1L, 1L, request)) .isInstanceOf(InvalidCommentContentException.class) .hasMessage("댓글 내용은 최소 1글자 이상, 최소 100글자 이하여야 합니다"); } @@ -147,7 +147,7 @@ class CommentServiceTest { UpdateCommentRequest request = new UpdateCommentRequest("처음 보는 용어인데 잘 쓰지는 않나보네요"); // when & then - assertDoesNotThrow(() -> commentService.updateComment(1L, 1L, request)); + assertDoesNotThrow(() -> commentServiceFacade.updateComment(1L, 1L, request)); } @Test @@ -158,7 +158,7 @@ class CommentServiceTest { }) void 로그인_하지_않고_특정_용어의_댓글_목록을_조회한다() { // when - CommentCollectionResponse actual = commentService.readComments( + CommentCollectionResponse actual = commentServiceFacade.readComments( -1L, 1L, null, @@ -181,7 +181,7 @@ class CommentServiceTest { }) void 로그인하고_특정_용어의_댓글_목록을_조회한다() { // when - CommentCollectionResponse actual = commentService.readComments( + CommentCollectionResponse actual = commentServiceFacade.readComments( 2L, 1L, null, diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/presentation/CommentControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/presentation/CommentControllerTest.java index c4cd3b8c..b500f8ea 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/presentation/CommentControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/presentation/CommentControllerTest.java @@ -23,7 +23,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.dnd.spaced.config.common.CommonControllerSliceTest; -import com.dnd.spaced.core.comment.application.CommentService; +import com.dnd.spaced.core.comment.application.CommentServiceFacade; import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; @@ -33,7 +33,6 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -45,7 +44,7 @@ class CommentControllerTest extends CommonControllerSliceTest { @Autowired - CommentService commentService; + CommentServiceFacade commentServiceFacade; @Test @WithMockUser("1") @@ -63,7 +62,7 @@ class CommentControllerTest extends CommonControllerSliceTest { header().string("Location", "/words/1") ); - verify(commentService).createComment(anyLong(), anyLong(), any(CreateCommentRequest.class)); + verify(commentServiceFacade).createComment(anyLong(), anyLong(), any(CreateCommentRequest.class)); 댓글_작성_요청_문서화(resultActions); } @@ -94,7 +93,7 @@ class CommentControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(commentService).deleteComment(anyLong(), anyLong()); + verify(commentServiceFacade).deleteComment(anyLong(), anyLong()); 댓글_삭제_요청_문서화(resultActions); } @@ -127,7 +126,7 @@ class CommentControllerTest extends CommonControllerSliceTest { status().isNoContent() ); - verify(commentService).updateComment(anyLong(), anyLong(), any(UpdateCommentRequest.class)); + verify(commentServiceFacade).updateComment(anyLong(), anyLong(), any(UpdateCommentRequest.class)); 댓글_수정_요청_문서화(resultActions); } @@ -156,7 +155,7 @@ class CommentControllerTest extends CommonControllerSliceTest { CommentResponse commentResponse = new CommentResponse(commentContentResponse, commentWriterResponse, false); CommentCollectionResponse response = new CommentCollectionResponse(List.of(commentResponse), 1L); - given(commentService.readComments(anyLong(), anyLong(), eq(null), any())).willReturn(response); + given(commentServiceFacade.readComments(anyLong(), anyLong(), eq(null), any())).willReturn(response); // when & then ResultActions resultActions = mockMvc.perform( @@ -177,7 +176,7 @@ class CommentControllerTest extends CommonControllerSliceTest { jsonPath("lastCommentId", is(1L), Long.class) ); - verify(commentService).readComments(any(), anyLong(), any(), any(Pageable.class)); + verify(commentServiceFacade).readComments(any(), anyLong(), any(), any(Pageable.class)); 댓글_전체_조회_문서화(resultActions); } From 0e3064ae5907942ec5e9118e24f924238ceb6124 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 09:34:39 +0900 Subject: [PATCH 088/115] =?UTF-8?q?refactor:=20LikeService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/like/application/LikeService.java | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/like/application/LikeService.java b/space-d/src/main/java/com/dnd/spaced/core/like/application/LikeService.java index dcdea36e..baedf922 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/like/application/LikeService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/like/application/LikeService.java @@ -22,35 +22,29 @@ public class LikeService { @Transactional public void processLike(Long accountId, Long commentId) { - Comment targetComment = findTargetComment(commentId); + Comment comment = findComment(commentId); - likeRepository.findBy(accountId, targetComment.getId()) + likeRepository.findBy(accountId, comment.getId()) .ifPresentOrElse( - like -> processDeleteLike(like, targetComment), - () -> processAddLike(accountId, targetComment) + like -> deleteLike(like, comment), + () -> addLike(accountId, comment) ); } - private Comment findTargetComment(Long commentId) { + private Comment findComment(Long commentId) { return commentRepository.findBy(commentId) .orElseThrow(() -> new AssociationCommentNotFoundException("좋아요 대상인 댓글을 찾을 수 없습니다.")); } - private void processDeleteLike(Like like, Comment comment) { + private void deleteLike(Like like, Comment comment) { likeRepository.delete(like); - publishDeletedLikeEvent(comment); - } - - private void processAddLike(Long accountId, Comment comment) { - likeRepository.save(new Like(accountId, comment.getId())); - publishAddedLikeEvent(comment); - } - - private void publishDeletedLikeEvent(Comment comment) { eventPublisher.publishEvent(new UnlikedEvent(comment.getId())); } - private void publishAddedLikeEvent(Comment comment) { + private void addLike(Long accountId, Comment comment) { + Like like = new Like(accountId, comment.getId()); + + likeRepository.save(like); eventPublisher.publishEvent(new LikedEvent(comment.getId())); } } From 678b37352922cb67b723c63fda77c1d3eaad8958 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 09:55:12 +0900 Subject: [PATCH 089/115] =?UTF-8?q?refactor:=20ReportService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81=20=EB=B0=8F=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/report/application/ReportService.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/report/application/ReportService.java b/space-d/src/main/java/com/dnd/spaced/core/report/application/ReportService.java index 9d51c8d5..365e1f8b 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/report/application/ReportService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/report/application/ReportService.java @@ -33,12 +33,16 @@ private Comment findComment(ReportRequest request) { } private void processReport(Comment comment, Long reporterId, ReportRequest request) { - validateReportedComment(comment, reporterId); + validateSelfReporting(comment, reporterId); ReportReason reportReason = findReportReason(request); - Report report = new Report(reportReason, request.commentId(), reporterId); + persistReport(reporterId, request, reportReason); + } - reportRepository.save(report); + private void validateSelfReporting(Comment comment, Long reporterId) { + if (comment.isWriter(reporterId)) { + throw new CannotReportOwnCommentException("자신이 작성한 댓글은 신고할 수 없습니다."); + } } private ReportReason findReportReason(ReportRequest request) { @@ -50,9 +54,9 @@ private ReportReason findReportReason(ReportRequest request) { ); } - private void validateReportedComment(Comment comment, Long reporterId) { - if (comment.isWriter(reporterId)) { - throw new CannotReportOwnCommentException("자신이 작성한 댓글은 신고할 수 없습니다."); - } + private void persistReport(Long reporterId, ReportRequest request, ReportReason reportReason) { + Report report = new Report(reportReason, request.commentId(), reporterId); + + reportRepository.save(report); } } From 241d749a02bca812945c1acfdecdc39a3392d8bc Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 09:58:45 +0900 Subject: [PATCH 090/115] =?UTF-8?q?refactor:=20SkillService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/spaced/core/skill/application/SkillService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java b/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java index f0198902..8eaaafae 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java @@ -23,11 +23,11 @@ public SkillResponse readSkill(Long accountId) { QuizMetadata quizMetadata = findQuizMetadata(); return skillRepository.findBy(accountId) - .map(skill -> handleFoundSkill(skill, quizMetadata)) + .map(skill -> buildSkillResponse(skill, quizMetadata)) .orElseGet(() -> SkillApplicationMapper.toDto(accountId)); } - private SkillResponse handleFoundSkill(Skill skill, QuizMetadata quizMetadata) { + private SkillResponse buildSkillResponse(Skill skill, QuizMetadata quizMetadata) { double totalQuizQuestionCorrectPercent = skill.calculateQuizQuestionCorrectPercent( quizMetadata.getTotalQuizQuestionCount() ); From e49d80911a73423fd998779f86d9a10d248a6952 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 10:04:39 +0900 Subject: [PATCH 091/115] =?UTF-8?q?refactor:=20SkillApplicationMapper?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84?= =?UTF-8?q?=EB=A7=81=20=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spaced/core/skill/application/SkillService.java | 5 +++-- .../skill/application/dto/SkillApplicationMapper.java | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java b/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java index 8eaaafae..100d2af8 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/skill/application/SkillService.java @@ -18,13 +18,14 @@ public class SkillService { private final SkillRepository skillRepository; private final QuizMetadataRepository quizMetadataRepository; + private final SkillApplicationMapper mapper; public SkillResponse readSkill(Long accountId) { QuizMetadata quizMetadata = findQuizMetadata(); return skillRepository.findBy(accountId) .map(skill -> buildSkillResponse(skill, quizMetadata)) - .orElseGet(() -> SkillApplicationMapper.toDto(accountId)); + .orElseGet(() -> mapper.toDefaultDto(accountId)); } private SkillResponse buildSkillResponse(Skill skill, QuizMetadata quizMetadata) { @@ -35,7 +36,7 @@ private SkillResponse buildSkillResponse(Skill skill, QuizMetadata quizMetadata) quizMetadata.getTotalTodayQuizQuestionCount() ); - return SkillApplicationMapper.toDto( + return mapper.toDefaultDto( skill, totalQuizQuestionCorrectPercent, totalTodayQuizQuestionCorrectPercent diff --git a/space-d/src/main/java/com/dnd/spaced/core/skill/application/dto/SkillApplicationMapper.java b/space-d/src/main/java/com/dnd/spaced/core/skill/application/dto/SkillApplicationMapper.java index ae0b9e3e..7198389b 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/skill/application/dto/SkillApplicationMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/skill/application/dto/SkillApplicationMapper.java @@ -2,13 +2,12 @@ import com.dnd.spaced.core.skill.application.dto.response.SkillResponse; import com.dnd.spaced.core.skill.domain.Skill; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; +import com.dnd.spaced.global.mapper.Mapper; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class SkillApplicationMapper { +@Mapper +public class SkillApplicationMapper { - public static SkillResponse toDto( + public SkillResponse toDefaultDto( Skill skill, double totalQuizQuestionCorrectPercent, double totalTodayQuizQuestionCorrectPercent @@ -24,7 +23,7 @@ public static SkillResponse toDto( ); } - public static SkillResponse toDto(Long accountId) { + public SkillResponse toDefaultDto(Long accountId) { return new SkillResponse( accountId, 0L, From ca40cff2cc56a56f538faeaa18967deaf7843396 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 10:13:50 +0900 Subject: [PATCH 092/115] =?UTF-8?q?refactor:=20WordApplicationMapper=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84=EB=A7=81=20?= =?UTF-8?q?=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/word/application/WordService.java | 9 +++++---- .../application/dto/WordApplicationMapper.java | 17 ++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java index a372c514..08d845dc 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java @@ -31,13 +31,14 @@ public class WordService { private final Clock clock; private final WordViewRepository wordViewRepository; private final PopularWordRepository popularWordRepository; + private final WordApplicationMapper mapper; private final ApplicationEventPublisher eventPublisher; public WordResponse readWord(Long wordId) { WordView word = findWord(wordId); publishWordViewCountIncrementedEvent(word); - return WordApplicationMapper.toPronunciationInfoDto(word); + return mapper.toPronunciationResponse(word); } public WordCollectionResponse readWords(ReadAllWordRequest request, Pageable pageable) { @@ -47,7 +48,7 @@ public WordCollectionResponse readWords(ReadAllWordRequest request, Pageable pag .orElse(null); List words = wordViewRepository.findAllBy(category, request.lastWordName(), lastCategory, pageable); - return WordApplicationMapper.toWordCollectionDto(words); + return mapper.toWordCollectionResponse(words); } public WordCollectionResponse searchWord(SearchWordRequest request, Pageable pageable) { @@ -66,13 +67,13 @@ public WordCollectionResponse searchWord(SearchWordRequest request, Pageable pag lastCategory); List words = wordViewRepository.search(wordSearchCondition, wordSearchPageRequest); - return WordApplicationMapper.toWordCollectionDto(words); + return mapper.toWordCollectionResponse(words); } public PopularWordCollectionResponse readPopularWords() { List popularWords = popularWordRepository.findAllBy(LocalDateTime.now(clock)); - return WordApplicationMapper.toPopularWordCollectionDto(popularWords); + return mapper.toPopularWordCollectionResponse(popularWords); } private WordView findWord(Long wordId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java index 7feb343c..691eba16 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/dto/WordApplicationMapper.java @@ -9,27 +9,26 @@ import com.dnd.spaced.core.word.domain.dto.WordView.PronunciationView; import com.dnd.spaced.core.word.domain.dto.WordView.WordExampleView; import com.dnd.spaced.core.word.domain.dto.PopularWord; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Mapper public final class WordApplicationMapper { - public static WordCollectionResponse toWordCollectionDto(List words) { + public WordCollectionResponse toWordCollectionResponse(List words) { if (words.isEmpty()) { return new WordCollectionResponse(List.of(), null); } List wordResponses = words.stream() - .map(WordApplicationMapper::toPronunciationInfoDto) + .map(this::toPronunciationResponse) .toList(); return new WordCollectionResponse(wordResponses, wordResponses.get(wordResponses.size() - 1).name()); } - public static WordResponse toPronunciationInfoDto(WordView word) { - List pronunciations = toPronunciationInfoDto(word.pronunciations()); + public WordResponse toPronunciationResponse(WordView word) { + List pronunciations = toPronunciationResponse(word.pronunciations()); List examples = word.wordExamples() .stream() .map(WordExampleView::example) @@ -47,7 +46,7 @@ public static WordResponse toPronunciationInfoDto(WordView word) { ); } - public static PopularWordCollectionResponse toPopularWordCollectionDto(List popularWords) { + public PopularWordCollectionResponse toPopularWordCollectionResponse(List popularWords) { List responses = popularWords.stream() .map( popularWord -> new PopularWordResponse( @@ -61,7 +60,7 @@ public static PopularWordCollectionResponse toPopularWordCollectionDto(List toPronunciationInfoDto(List pronunciations) { + private List toPronunciationResponse(List pronunciations) { return pronunciations.stream() .map( pronunciation -> new PronunciationResponse( From 1fd51b7a82ce0e4d707cdbb380c69a9788db935a Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 10:30:14 +0900 Subject: [PATCH 093/115] =?UTF-8?q?feat:=20=EC=9A=A9=EC=96=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../word/application/ReadWordViewService.java | 38 ++++++++++ .../application/ReadWordViewServiceTest.java | 72 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java new file mode 100644 index 00000000..42a64370 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java @@ -0,0 +1,38 @@ +package com.dnd.spaced.core.word.application; + +import com.dnd.spaced.core.word.application.dto.request.ReadAllWordRequest; +import com.dnd.spaced.core.word.application.exception.WordNotFoundException; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.enums.Category; +import com.dnd.spaced.core.word.domain.repository.WordViewRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReadWordViewService { + + private static final Category NO_CATEGORY_FILTER = null; + private static final Category NO_LAST_CATEGORY_FILTER = null; + + private final WordViewRepository wordViewRepository; + + public WordView readWord(Long wordId) { + return wordViewRepository.findBy(wordId) + .orElseThrow( + () -> new WordNotFoundException( + "지정한 ID에 해당하는 용어를 찾을 수 없습니다." + ) + ); + } + + public List readWords(ReadAllWordRequest request, Pageable pageable) { + Category category = Category.findBy(request.categoryName()) + .orElse(NO_CATEGORY_FILTER); + Category lastCategory = Category.findBy(request.lastCategoryName()) + .orElse(NO_LAST_CATEGORY_FILTER); + return wordViewRepository.findAllBy(category, request.lastWordName(), lastCategory, pageable); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java new file mode 100644 index 00000000..f11c63a6 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java @@ -0,0 +1,72 @@ +package com.dnd.spaced.core.word.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.application.dto.request.ReadAllWordRequest; +import com.dnd.spaced.core.word.application.exception.WordNotFoundException; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.enums.Category; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ReadWordViewServiceTest { + + private static final Long NOT_FOUND_WORD_ID = -999L; + + @Autowired + ReadWordViewService wordService; + + @Test + @Sql("classpath:sql/word/word.sql") + void 용어를_조회한다() { + // when + WordView actual = wordService.readWord(1L); + + // then + assertAll( + () -> assertThat(actual.name()).isEqualTo("Authorization"), + () -> assertThat(actual.category()).isEqualTo(Category.DEVELOP), + () -> assertThat(actual.wordMeaning().getMeaning()).isEqualTo("인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘") + ); + } + + @Test + void 용어_ID로_용어를_찾지_못하면_예외가_발생한다() { + // when & then + assertThatThrownBy(() -> wordService.readWord(NOT_FOUND_WORD_ID)) + .isInstanceOf(WordNotFoundException.class) + .hasMessage("지정한 ID에 해당하는 용어를 찾을 수 없습니다."); + } + + @Test + @Sql("classpath:sql/word/word.sql") + void 용어_목록을_조회한다() { + // given + ReadAllWordRequest request = new ReadAllWordRequest( + null, + null, + null + ); + + // when + List actual = wordService.readWords(request, Pageable.ofSize(10)); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).name()).isEqualTo("Authorization") + ); + } +} From 7eb361e85c22bbeccd8cdf5ff7ebf560a7f8616a Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 11:12:29 +0900 Subject: [PATCH 094/115] =?UTF-8?q?style:=20ReadQuizService=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=ED=83=80=EC=9E=85=EA=B3=BC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EA=B3=B5=EB=B0=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/spaced/core/quiz/application/ReadQuizService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java index 418ec1a3..1f7113f9 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java @@ -32,7 +32,7 @@ List readGradedAnswers( ); } - ListreadGradedAnswers(Long accountId, Long quizId) { + List readGradedAnswers(Long accountId, Long quizId) { return quizGradedAnswerRepository.findAllBy(accountId, quizId); } From 2380cd0e56ad1981a40752289e3a4354b3bb380d Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 11:16:16 +0900 Subject: [PATCH 095/115] =?UTF-8?q?test:=20=ED=80=B4=EC=A6=88=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A7=A4=EC=A7=81=20=EB=84=98=EB=B2=84=EB=A5=BC=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CreateQuizServiceTest.java | 8 ++-- .../application/GradeQuizServiceTest.java | 13 ++++-- .../application/QuizServiceFacadeTest.java | 40 +++++++++++-------- .../quiz/application/ReadQuizServiceTest.java | 31 +++++++++----- .../resources/sql/quiz/quiz_graded_answer.sql | 10 ++--- .../test/resources/sql/quiz/solved_quiz.sql | 12 +++--- 6 files changed, 68 insertions(+), 46 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java index a5ad4a05..45feede7 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/CreateQuizServiceTest.java @@ -19,6 +19,8 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class CreateQuizServiceTest { + private static final Long QUIZ_CREATOR_ID = 1L; + @Autowired CreateQuizService createQuizService; @@ -28,7 +30,7 @@ class CreateQuizServiceTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when & then - assertThatThrownBy(() -> createQuizService.createQuiz(1L, request)) + assertThatThrownBy(() -> createQuizService.createQuiz(QUIZ_CREATOR_ID, request)) .isInstanceOf(WordMetadataNotFoundException.class) .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); } @@ -40,7 +42,7 @@ class CreateQuizServiceTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when & then - assertThatThrownBy(() -> createQuizService.createQuiz(1L, request)) + assertThatThrownBy(() -> createQuizService.createQuiz(QUIZ_CREATOR_ID, request)) .isInstanceOf(InvalidQuizWordCountException.class) .hasMessage("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } @@ -52,7 +54,7 @@ class CreateQuizServiceTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when - Long actual = createQuizService.createQuiz(1L, request); + Long actual = createQuizService.createQuiz(QUIZ_CREATOR_ID, request); // then assertThat(actual).isPositive(); diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java index 510fedcb..de8d3f80 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeQuizServiceTest.java @@ -20,11 +20,16 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class GradeQuizServiceTest { + private static final Long QUIZ_CREATOR_ID = 1L; + private static final Long UNSOLVED_QUIZ_ID = 1L; + private static final Long SOLVED_QUIZ_ID = 2L; + private static final Long NOT_FOUND_QUIZ_ID = -999L; + @Autowired GradeQuizService gradeQuizService; @Test - void 유효하지_않는_퀴즈_id로_퀴즈_답을_제출할_수_없다() { + void 유효하지_않는_퀴즈_ID로_퀴즈_답을_제출할_수_없다() { // given SubmitAnswerRequest[] submitAnswers = { new SubmitAnswerRequest(1L, "Authorization"), @@ -36,7 +41,7 @@ class GradeQuizServiceTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertThatThrownBy(() -> gradeQuizService.gradeQuiz(1L, -999L, request)) + assertThatThrownBy(() -> gradeQuizService.gradeQuiz(QUIZ_CREATOR_ID, NOT_FOUND_QUIZ_ID, request)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @@ -59,7 +64,7 @@ class GradeQuizServiceTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertDoesNotThrow(() -> gradeQuizService.gradeQuiz(1L, 1L, request)); + assertDoesNotThrow(() -> gradeQuizService.gradeQuiz(QUIZ_CREATOR_ID, UNSOLVED_QUIZ_ID, request)); } @Test @@ -80,7 +85,7 @@ class GradeQuizServiceTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertThatThrownBy(() -> gradeQuizService.gradeQuiz(1L, 1L, request)) + assertThatThrownBy(() -> gradeQuizService.gradeQuiz(QUIZ_CREATOR_ID, SOLVED_QUIZ_ID, request)) .isInstanceOf(AlreadyGradeQuizException.class) .hasMessage("이미 풀었던 퀴즈입니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java index 6debb34f..0abfe1e7 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/QuizServiceFacadeTest.java @@ -36,6 +36,12 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class QuizServiceFacadeTest { + private static final Long QUIZ_CREATOR_ID = 1L; + private static final Long UNSOLVED_QUIZ_ID = 1L; + private static final Long SOLVED_QUIZ_ID = 2L; + private static final Long NOT_FOUND_QUIZ_ID = -999L; + private static final Long NON_QUIZ_CREATOR_ID = 5L; + @Autowired ApplicationEvents events; @@ -48,7 +54,7 @@ class QuizServiceFacadeTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when & then - assertThatThrownBy(() -> quizServiceFacade.createQuiz(1L, request)) + assertThatThrownBy(() -> quizServiceFacade.createQuiz(QUIZ_CREATOR_ID, request)) .isInstanceOf(WordMetadataNotFoundException.class) .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); } @@ -60,7 +66,7 @@ class QuizServiceFacadeTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when & then - assertThatThrownBy(() -> quizServiceFacade.createQuiz(1L, request)) + assertThatThrownBy(() -> quizServiceFacade.createQuiz(QUIZ_CREATOR_ID, request)) .isInstanceOf(InvalidQuizWordCountException.class) .hasMessage("퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } @@ -73,12 +79,12 @@ class QuizServiceFacadeTest { }) void 퀴즈를_조회한다() { // when - QuizResponse actual = quizServiceFacade.readQuiz(1L, 1L); + QuizResponse actual = quizServiceFacade.readQuiz(QUIZ_CREATOR_ID, UNSOLVED_QUIZ_ID); // then assertAll( - () -> assertThat(actual.id()).isEqualTo(1L), - () -> assertThat(actual.accountId()).isEqualTo(1L), + () -> assertThat(actual.id()).isEqualTo(UNSOLVED_QUIZ_ID), + () -> assertThat(actual.accountId()).isEqualTo(QUIZ_CREATOR_ID), () -> assertThat(actual.quizQuestions()).hasSize(5), () -> assertThat(actual.quizQuestions().get(0).quizOptions()).hasSize(4), () -> assertThat(actual.quizQuestions().get(1).quizOptions()).hasSize(4), @@ -98,7 +104,7 @@ class QuizServiceFacadeTest { CreateQuizRequest request = new CreateQuizRequest("전체 실무"); // when - Long actual = quizServiceFacade.createQuiz(1L, request); + Long actual = quizServiceFacade.createQuiz(QUIZ_CREATOR_ID, request); // then assertAll( @@ -110,7 +116,7 @@ class QuizServiceFacadeTest { @Test void 유효하지_않는_퀴즈_id로_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> quizServiceFacade.readQuiz(1L, -999L)) + assertThatThrownBy(() -> quizServiceFacade.readQuiz(QUIZ_CREATOR_ID, NOT_FOUND_QUIZ_ID)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @@ -120,15 +126,15 @@ class QuizServiceFacadeTest { "classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/quiz.sql" }) - void 회원이_생성한_퀴즈가_아니라면_존재하는_퀴즈_id더라도_퀴즈_정보를_조회할_수_없다() { + void 회원이_생성한_퀴즈가_아니라면_존재하는_퀴즈_ID더라도_퀴즈_정보를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> quizServiceFacade.readQuiz(5L, 1L)) + assertThatThrownBy(() -> quizServiceFacade.readQuiz(NON_QUIZ_CREATOR_ID, UNSOLVED_QUIZ_ID)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @Test - void 유효하지_않는_퀴즈_id로_퀴즈_답을_제출할_수_없다() { + void 유효하지_않는_퀴즈_ID로_퀴즈_답을_제출할_수_없다() { // given SubmitAnswerRequest[] submitAnswers = { new SubmitAnswerRequest(1L, "Authorization"), @@ -140,7 +146,7 @@ class QuizServiceFacadeTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertThatThrownBy(() -> quizServiceFacade.grade(1L, -999L, request)) + assertThatThrownBy(() -> quizServiceFacade.grade(QUIZ_CREATOR_ID, NOT_FOUND_QUIZ_ID, request)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @@ -163,7 +169,7 @@ class QuizServiceFacadeTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertDoesNotThrow(() -> quizServiceFacade.grade(1L, 1L, request)); + assertDoesNotThrow(() -> quizServiceFacade.grade(QUIZ_CREATOR_ID, UNSOLVED_QUIZ_ID, request)); } @Test @@ -184,7 +190,7 @@ class QuizServiceFacadeTest { GradeQuizRequest request = new GradeQuizRequest(submitAnswers); // when & then - assertThatThrownBy(() -> quizServiceFacade.grade(1L, 1L, request)) + assertThatThrownBy(() -> quizServiceFacade.grade(QUIZ_CREATOR_ID, SOLVED_QUIZ_ID, request)) .isInstanceOf(AlreadyGradeQuizException.class) .hasMessage("이미 풀었던 퀴즈입니다."); } @@ -220,12 +226,12 @@ class QuizServiceFacadeTest { @Sql(scripts = { "classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/word.sql", - "classpath:sql/quiz/quiz.sql", + "classpath:sql/quiz/solved_quiz.sql", "classpath:sql/quiz/quiz_graded_answer.sql" }) void 특정_퀴즈의_제출했던_답을_조회한다() { // when - QuizGradedAnswerCollectionResponse actual = quizServiceFacade.readGradedAnswers(1L, 1L); + QuizGradedAnswerCollectionResponse actual = quizServiceFacade.readGradedAnswers(QUIZ_CREATOR_ID, SOLVED_QUIZ_ID); // then assertAll( @@ -249,12 +255,12 @@ class QuizServiceFacadeTest { ReadAllQuizRequest request = new ReadAllQuizRequest(null); // when - QuizCollectionResponse actual = quizServiceFacade.readQuizzes(1L, request, Pageable.ofSize(10)); + QuizCollectionResponse actual = quizServiceFacade.readQuizzes(QUIZ_CREATOR_ID, request, Pageable.ofSize(10)); // then assertAll( () -> assertThat(actual.quizzes()).hasSize(1), - () -> assertThat(actual.lastQuizId()).isEqualTo(1L) + () -> assertThat(actual.lastQuizId()).isEqualTo(UNSOLVED_QUIZ_ID) ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java index 8d496699..eff0a5b0 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadQuizServiceTest.java @@ -26,6 +26,12 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class ReadQuizServiceTest { + private static final Long QUIZ_CREATOR_ID = 1L; + private static final Long UNSOLVED_QUIZ_ID = 1L; + private static final Long SOLVED_QUIZ_ID = 2L; + private static final Long NOT_FOUND_QUIZ_ID = -999L; + private static final Long NON_QUIZ_CREATOR_ID = 5L; + @Autowired ReadQuizService readQuizService; @@ -37,12 +43,12 @@ class ReadQuizServiceTest { }) void 퀴즈를_조회한다() { // when - QuizDto actual = readQuizService.readQuiz(1L, 1L); + QuizDto actual = readQuizService.readQuiz(QUIZ_CREATOR_ID, UNSOLVED_QUIZ_ID); // then assertAll( - () -> assertThat(actual.id()).isEqualTo(1L), - () -> assertThat(actual.accountId()).isEqualTo(1L), + () -> assertThat(actual.id()).isEqualTo(UNSOLVED_QUIZ_ID), + () -> assertThat(actual.accountId()).isEqualTo(QUIZ_CREATOR_ID), () -> assertThat(actual.quizQuestions()).hasSize(5), () -> assertThat(actual.quizQuestions().get(0).quizOptions()).hasSize(4), () -> assertThat(actual.quizQuestions().get(1).quizOptions()).hasSize(4), @@ -53,18 +59,21 @@ class ReadQuizServiceTest { } @Test - void 유효하지_않는_퀴즈_id로_퀴즈를_조회할_수_없다() { + void 유효하지_않는_퀴즈_ID로_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> readQuizService.readQuiz(1L, -999L)) + assertThatThrownBy(() -> readQuizService.readQuiz(QUIZ_CREATOR_ID, NOT_FOUND_QUIZ_ID)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @Test - @Sql(scripts = {"classpath:sql/quiz/word_metadata.sql", "classpath:sql/quiz/quiz.sql"}) - void 회원이_생성한_퀴즈가_아니라면_존재하는_퀴즈_id더라도_퀴즈_정보를_조회할_수_없다() { + @Sql(scripts = { + "classpath:sql/quiz/word_metadata.sql", + "classpath:sql/quiz/quiz.sql" + }) + void 회원이_생성한_퀴즈가_아니라면_존재하는_퀴즈_ID더라도_퀴즈_정보를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> readQuizService.readQuiz(5L, 1L)) + assertThatThrownBy(() -> readQuizService.readQuiz(NON_QUIZ_CREATOR_ID, UNSOLVED_QUIZ_ID)) .isInstanceOf(QuizNotFoundException.class) .hasMessage("지정한 id의 퀴즈를 찾지 못했습니다."); } @@ -105,7 +114,7 @@ class ReadQuizServiceTest { }) void 특정_퀴즈의_제출했던_답을_조회한다() { // when - List actual = readQuizService.readGradedAnswers(1L, 1L); + List actual = readQuizService.readGradedAnswers(QUIZ_CREATOR_ID, SOLVED_QUIZ_ID); // then assertAll( @@ -129,12 +138,12 @@ class ReadQuizServiceTest { ReadAllQuizRequest request = new ReadAllQuizRequest(null); // when - List actual = readQuizService.readQuizzes(1L, request, Pageable.ofSize(10)); + List actual = readQuizService.readQuizzes(QUIZ_CREATOR_ID, request, Pageable.ofSize(10)); // then assertAll( () -> assertThat(actual).hasSize(1), - () -> assertThat(actual.get(0).id()).isEqualTo(1L) + () -> assertThat(actual.get(0).id()).isEqualTo(UNSOLVED_QUIZ_ID) ); } } diff --git a/space-d/src/test/resources/sql/quiz/quiz_graded_answer.sql b/space-d/src/test/resources/sql/quiz/quiz_graded_answer.sql index cf5d0c35..a3803a62 100644 --- a/space-d/src/test/resources/sql/quiz/quiz_graded_answer.sql +++ b/space-d/src/test/resources/sql/quiz/quiz_graded_answer.sql @@ -1,5 +1,5 @@ -INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(1, now(), 1, 1, 1, 1, 'Authorization'); -INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(2, now(), 1, 1, 2, 2, 'YAML'); -INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(3, now(), 1, 1, 3, 3, 'TOML'); -INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(4, now(), 1, 1, 4, 4, 'deprecated'); -INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(5, now(), 1, 1, 5, 5, 'execute'); +INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(1, now(), 1, 2, 1, 1, 'Authorization'); +INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(2, now(), 1, 2, 2, 2, 'YAML'); +INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(3, now(), 1, 2, 3, 3, 'TOML'); +INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(4, now(), 1, 2, 4, 4, 'deprecated'); +INSERT INTO quiz_graded_answers(id, created_at, account_id, quiz_id, quiz_question_id, selected_word_id, selected_content) VALUES(5, now(), 1, 2, 5, 5, 'execute'); diff --git a/space-d/src/test/resources/sql/quiz/solved_quiz.sql b/space-d/src/test/resources/sql/quiz/solved_quiz.sql index 32469b0b..fb4e7ae7 100644 --- a/space-d/src/test/resources/sql/quiz/solved_quiz.sql +++ b/space-d/src/test/resources/sql/quiz/solved_quiz.sql @@ -1,12 +1,12 @@ SET REFERENTIAL_INTEGRITY FALSE; -INSERT INTO quizzes(id, created_at, account_id, solved) VALUES(1, now(), 1, true); +INSERT INTO quizzes(id, created_at, account_id, solved) VALUES(2, now(), 1, true); -INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(1, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘', 'Authorization', 1, 'DEVELOP', 1); -INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(2, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '사람이 읽기 쉬운 데이터 형식으로, 주로 설정 파일에 사용', 'YAML', 2, 'DEVELOP', 1); -INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(3, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '간단하고 가독성이 높은 설정 파일 형식으로, 키-값 쌍을 이용해 데이터를 표현', 'TOML', 3, 'DEVELOP', 1); -INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(4, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '더 이상 사용되지 않거나, 지원되지 않는다는 뜻', 'deprecated', 4, 'DEVELOP', 1); -INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(5, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', ' 주로 프로그램이나 코드, 명령을 실행할 때 사용', 'execute', 5, 'DEVELOP', 1); +INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(1, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 확인하고 제어하는 보안 메커니즘', 'Authorization', 1, 'DEVELOP', 2); +INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(2, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '사람이 읽기 쉬운 데이터 형식으로, 주로 설정 파일에 사용', 'YAML', 2, 'DEVELOP', 2); +INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(3, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '간단하고 가독성이 높은 설정 파일 형식으로, 키-값 쌍을 이용해 데이터를 표현', 'TOML', 3, 'DEVELOP', 2); +INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(4, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', '더 이상 사용되지 않거나, 지원되지 않는다는 뜻', 'deprecated', 4, 'DEVELOP', 2); +INSERT INTO quiz_questions(id, question, passage, answer_content, answer_word_id, quiz_category, quiz_id) VALUES(5, '다음 예문을 보고 예문에 맞는 용어를 선택해주세요.', ' 주로 프로그램이나 코드, 명령을 실행할 때 사용', 'execute', 5, 'DEVELOP', 2); INSERT INTO quiz_options(id, content, option_order, word_id, quiz_question_id) VALUES(1, 'Authorization', 1, 1, 1); INSERT INTO quiz_options(id, content, option_order, word_id, quiz_question_id) VALUES(2, 'COALESCE', 2, 6, 1); From 328f30620b8456f8a229aa900fef5d1d4748ae21 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 12:31:15 +0900 Subject: [PATCH 096/115] =?UTF-8?q?test:=20=EC=98=A4=EB=8A=98=EC=9D=98=20?= =?UTF-8?q?=ED=80=B4=EC=A6=88=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20=EB=A7=A4=EC=A7=81=20=EB=84=98?= =?UTF-8?q?=EB=B2=84=EB=A5=BC=20=EC=83=81=EC=88=98=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GradeTodayQuizServiceTest.java | 23 +++++---- .../application/ReadTodayQuizServiceTest.java | 26 +++++----- ...t.java => TodayQuizServiceFacadeTest.java} | 49 ++++++++++--------- 3 files changed, 56 insertions(+), 42 deletions(-) rename space-d/src/test/java/com/dnd/spaced/core/quiz/application/{TodayQuizServiceTest.java => TodayQuizServiceFacadeTest.java} (86%) diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java index cd5963fa..1e06a2a8 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizServiceTest.java @@ -22,6 +22,11 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class GradeTodayQuizServiceTest { + private static final Long SELECTED_WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + private static final Long NOT_FOUND_TODAY_QUIZ_ID = -999L; + private static final Long TODAY_QUIZ_ID = 1L; + @Autowired TodayQuizServiceFacade todayQuizServiceFacade; @@ -31,9 +36,9 @@ class GradeTodayQuizServiceTest { @Test void 지정한_오늘의_퀴즈_id가_없다면_퀴즈_정답을_제출할_수_없다() { // when & then - GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + GradeTodayQuizRequest request = new GradeTodayQuizRequest(SELECTED_WORD_ID, "Authorization"); - assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, -999L, request)) + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(ACCOUNT_ID, NOT_FOUND_TODAY_QUIZ_ID, request)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); } @@ -47,10 +52,10 @@ class GradeTodayQuizServiceTest { }) void 이미_푼_오늘의_퀴즈인_경우_정답을_제출할_수_없다() { // given - GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + GradeTodayQuizRequest request = new GradeTodayQuizRequest(SELECTED_WORD_ID, "Authorization"); // when & then - assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request)) + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(ACCOUNT_ID, TODAY_QUIZ_ID, request)) .isInstanceOf(AlreadyGradeTodayQuizException.class) .hasMessage("이미 오늘의 퀴즈를 풀었습니다."); } @@ -63,17 +68,17 @@ class GradeTodayQuizServiceTest { }) void 오늘의_퀴즈_정답을_제출한다() { // when - GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + GradeTodayQuizRequest request = new GradeTodayQuizRequest(SELECTED_WORD_ID, "Authorization"); - todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); + todayQuizServiceFacade.gradeTodayQuiz(ACCOUNT_ID, TODAY_QUIZ_ID, request); // then - TodayQuizGradedAnswer actual = todayQuizGradedAnswerRepository.findBy(1L, 1L) + TodayQuizGradedAnswer actual = todayQuizGradedAnswerRepository.findBy(ACCOUNT_ID, TODAY_QUIZ_ID) .get(); assertAll( - () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(1L), - () -> assertThat(actual.getAccountId()).isEqualTo(1L) + () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(TODAY_QUIZ_ID), + () -> assertThat(actual.getAccountId()).isEqualTo(ACCOUNT_ID) ); } } diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java index 394a4064..c30115eb 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizServiceTest.java @@ -26,6 +26,10 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class ReadTodayQuizServiceTest { + private static final Long ACCOUNT_ID = 1L; + private static final Long NOT_FOUND_TODAY_QUIZ_ID = -999L; + private static final Long TODAY_QUIZ_ID = 1L; + @Autowired ReadTodayQuizService todayQuizService; @@ -78,13 +82,13 @@ void setUp() { // when List actual = todayQuizService.readTodayQuizGradedAnswers( - 1L, request, PageRequest.of(0, 10) + ACCOUNT_ID, request, PageRequest.of(0, 10) ); // then assertAll( () -> assertThat(actual).hasSize(1), - () -> assertThat(actual.get(0).getTodayQuiz().getId()).isEqualTo(1L) + () -> assertThat(actual.get(0).getTodayQuiz().getId()).isEqualTo(TODAY_QUIZ_ID) ); } @@ -98,21 +102,21 @@ void setUp() { void 사용자가_제출한_오늘의_퀴즈_채점_결과를_조회한다() { // when TodayQuizGradedAnswer actual = todayQuizService.readTargetTodayQuizGradedAnswers( - 1L, - 1L + ACCOUNT_ID, + TODAY_QUIZ_ID ); // then assertAll( - () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(1L), - () -> assertThat(actual.getAccountId()).isEqualTo(1L) + () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(TODAY_QUIZ_ID), + () -> assertThat(actual.getAccountId()).isEqualTo(ACCOUNT_ID) ); } @Test void 지정한_오늘의_퀴즈_id가_없다면_사용자가_제출한_오늘의_퀴즈_채점_결과를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizService.readTargetTodayQuizGradedAnswers(1L, 1L)) + assertThatThrownBy(() -> todayQuizService.readTargetTodayQuizGradedAnswers(ACCOUNT_ID, TODAY_QUIZ_ID)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 오늘의 퀴즈 답안지를 찾지 못했습니다."); } @@ -125,19 +129,19 @@ void setUp() { }) void 지정한_id의_오늘의_퀴즈를_조회한다() { // when - ReadTodayQuizDto actual = todayQuizService.readTodayQuiz(1L, 1L); + ReadTodayQuizDto actual = todayQuizService.readTodayQuiz(ACCOUNT_ID, TODAY_QUIZ_ID); // then assertAll( - () -> assertThat(actual.todayQuiz().getId()).isEqualTo(1L), + () -> assertThat(actual.todayQuiz().getId()).isEqualTo(TODAY_QUIZ_ID), () -> assertThat(actual.todayQuiz().getTodayQuizQuestion()).isNotNull() ); } @Test - void 없는_id의_오늘의_퀴즈를_조회할_수_없다() { + void 없는_ID의_오늘의_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizService.readTodayQuiz(1L, -999L)) + assertThatThrownBy(() -> todayQuizService.readTodayQuiz(ACCOUNT_ID, NOT_FOUND_TODAY_QUIZ_ID)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacadeTest.java similarity index 86% rename from space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacadeTest.java index 96bfd533..efdba79d 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacadeTest.java @@ -32,7 +32,12 @@ @RecordApplicationEvents @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class TodayQuizServiceTest { +class TodayQuizServiceFacadeTest { + + private static final Long SELECTED_WORD_ID = 1L; + private static final Long ACCOUNT_ID = 1L; + private static final Long NOT_FOUND_TODAY_QUIZ_ID = -999L; + private static final Long TODAY_QUIZ_ID = 1L; @Autowired ApplicationEvents events; @@ -78,11 +83,11 @@ void setUp() { } @Test - void 지정한_오늘의_퀴즈_id가_없다면_퀴즈_정답을_제출할_수_없다() { + void 지정한_오늘의_퀴즈_ID가_없다면_퀴즈_정답을_제출할_수_없다() { // when & then - GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + GradeTodayQuizRequest request = new GradeTodayQuizRequest(SELECTED_WORD_ID, "Authorization"); - assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, -999L, request)) + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(ACCOUNT_ID, NOT_FOUND_TODAY_QUIZ_ID, request)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); } @@ -95,17 +100,17 @@ void setUp() { }) void 오늘의_퀴즈_정답을_제출한다() { // when - GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + GradeTodayQuizRequest request = new GradeTodayQuizRequest(SELECTED_WORD_ID, "Authorization"); - todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request); + todayQuizServiceFacade.gradeTodayQuiz(ACCOUNT_ID, TODAY_QUIZ_ID, request); // then - TodayQuizGradedAnswer actual = todayQuizGradedAnswerRepository.findBy(1L, 1L) + TodayQuizGradedAnswer actual = todayQuizGradedAnswerRepository.findBy(ACCOUNT_ID, TODAY_QUIZ_ID) .get(); assertAll( - () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(1L), - () -> assertThat(actual.getAccountId()).isEqualTo(1L), + () -> assertThat(actual.getTodayQuiz().getId()).isEqualTo(TODAY_QUIZ_ID), + () -> assertThat(actual.getAccountId()).isEqualTo(ACCOUNT_ID), () -> assertThat(events.stream(GradedTodayQuizEvent.class).count()).isOne() ); } @@ -125,13 +130,13 @@ void setUp() { // when TodayQuizGradedAnswerCollectionResponse actual = todayQuizServiceFacade.readTodayQuizGradedAnswers( - 1L, request, PageRequest.of(0, 10) + ACCOUNT_ID, request, PageRequest.of(0, 10) ); // then assertAll( () -> assertThat(actual.answers()).hasSize(1), - () -> assertThat(actual.answers().get(0).todayQuizId()).isEqualTo(1L) + () -> assertThat(actual.answers().get(0).todayQuizId()).isEqualTo(TODAY_QUIZ_ID) ); } @@ -144,10 +149,10 @@ void setUp() { }) void 이미_푼_오늘의_퀴즈인_경우_정답을_제출할_수_없다() { // given - GradeTodayQuizRequest request = new GradeTodayQuizRequest(1L, "Authorization"); + GradeTodayQuizRequest request = new GradeTodayQuizRequest(SELECTED_WORD_ID, "Authorization"); // when & then - assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(1L, 1L, request)) + assertThatThrownBy(() -> todayQuizServiceFacade.gradeTodayQuiz(ACCOUNT_ID, TODAY_QUIZ_ID, request)) .isInstanceOf(AlreadyGradeTodayQuizException.class) .hasMessage("이미 오늘의 퀴즈를 풀었습니다."); } @@ -162,21 +167,21 @@ void setUp() { void 사용자가_제출한_오늘의_퀴즈_채점_결과를_조회한다() { // when TodayQuizGradedAnswerResponse actual = todayQuizServiceFacade.readTargetTodayQuizGradedAnswers( - 1L, - 1L + ACCOUNT_ID, + TODAY_QUIZ_ID ); // then assertAll( - () -> assertThat(actual.todayQuizId()).isEqualTo(1L), - () -> assertThat(actual.accountId()).isEqualTo(1L) + () -> assertThat(actual.todayQuizId()).isEqualTo(TODAY_QUIZ_ID), + () -> assertThat(actual.accountId()).isEqualTo(ACCOUNT_ID) ); } @Test void 지정한_오늘의_퀴즈_id가_없다면_사용자가_제출한_오늘의_퀴즈_채점_결과를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizServiceFacade.readTargetTodayQuizGradedAnswers(1L, 1L)) + assertThatThrownBy(() -> todayQuizServiceFacade.readTargetTodayQuizGradedAnswers(ACCOUNT_ID, TODAY_QUIZ_ID)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 오늘의 퀴즈 답안지를 찾지 못했습니다."); } @@ -189,19 +194,19 @@ void setUp() { }) void 지정한_id의_오늘의_퀴즈를_조회한다() { // when - TodayQuizResponse actual = todayQuizServiceFacade.readTodayQuiz(1L, 1L); + TodayQuizResponse actual = todayQuizServiceFacade.readTodayQuiz(ACCOUNT_ID, TODAY_QUIZ_ID); // then assertAll( - () -> assertThat(actual.id()).isEqualTo(1L), + () -> assertThat(actual.id()).isEqualTo(TODAY_QUIZ_ID), () -> assertThat(actual.todayQuizQuestion()).isNotNull() ); } @Test - void 없는_id의_오늘의_퀴즈를_조회할_수_없다() { + void 없는_ID의_오늘의_퀴즈를_조회할_수_없다() { // when & then - assertThatThrownBy(() -> todayQuizServiceFacade.readTodayQuiz(1L, -999L)) + assertThatThrownBy(() -> todayQuizServiceFacade.readTodayQuiz(ACCOUNT_ID, NOT_FOUND_TODAY_QUIZ_ID)) .isInstanceOf(TodayQuizNotFoundException.class) .hasMessage("지정한 id의 오늘의 퀴즈를 찾지 못했습니다."); } From a81fa3ec7fe7e06f0ce91826740d094cc47ae403 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 12:38:27 +0900 Subject: [PATCH 097/115] =?UTF-8?q?test:=20ReadWordViewServiceTest=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EB=A7=A4=EC=A7=81=20=EB=84=98=EB=B2=84?= =?UTF-8?q?=EB=A5=BC=20=EC=83=81=EC=88=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/word/application/ReadWordViewServiceTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java index f11c63a6..9a5716eb 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadWordViewServiceTest.java @@ -23,16 +23,17 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class ReadWordViewServiceTest { + private static final Long WORD_ID = 1L; private static final Long NOT_FOUND_WORD_ID = -999L; @Autowired - ReadWordViewService wordService; + ReadWordViewService readWordViewService; @Test @Sql("classpath:sql/word/word.sql") void 용어를_조회한다() { // when - WordView actual = wordService.readWord(1L); + WordView actual = readWordViewService.readWord(WORD_ID); // then assertAll( @@ -45,7 +46,7 @@ class ReadWordViewServiceTest { @Test void 용어_ID로_용어를_찾지_못하면_예외가_발생한다() { // when & then - assertThatThrownBy(() -> wordService.readWord(NOT_FOUND_WORD_ID)) + assertThatThrownBy(() -> readWordViewService.readWord(NOT_FOUND_WORD_ID)) .isInstanceOf(WordNotFoundException.class) .hasMessage("지정한 ID에 해당하는 용어를 찾을 수 없습니다."); } @@ -61,7 +62,7 @@ class ReadWordViewServiceTest { ); // when - List actual = wordService.readWords(request, Pageable.ofSize(10)); + List actual = readWordViewService.readWords(request, Pageable.ofSize(10)); // then assertAll( From 874ac4776bda96ceb9c0f677be40745193f7fb7e Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 12:43:38 +0900 Subject: [PATCH 098/115] =?UTF-8?q?feat:=20=EC=9A=A9=EC=96=B4=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SearchWordViewService.java | 52 +++++++++++++++++++ .../SearchWordViewServiceTest.java | 47 +++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/application/SearchWordViewService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/application/SearchWordViewServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/SearchWordViewService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/SearchWordViewService.java new file mode 100644 index 00000000..c4a5b5b1 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/SearchWordViewService.java @@ -0,0 +1,52 @@ +package com.dnd.spaced.core.word.application; + +import com.dnd.spaced.core.word.application.dto.request.SearchWordRequest; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.enums.Category; +import com.dnd.spaced.core.word.domain.repository.WordViewRepository; +import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchCondition; +import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchPageRequest; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +class SearchWordViewService { + + private static final Category NO_CATEGORY_FILTER = null; + private static final Category NO_LAST_CATEGORY_FILTER = null; + + private final WordViewRepository wordViewRepository; + + public List searchWord(SearchWordRequest request, Pageable pageable) { + WordSearchCondition wordSearchCondition = buildWordSearchCondition( + request); + WordSearchPageRequest wordSearchPageRequest = buildWordSearchPageRequest( + request, pageable); + return wordViewRepository.search(wordSearchCondition, wordSearchPageRequest); + } + + private WordSearchCondition buildWordSearchCondition(SearchWordRequest request) { + Category category = Category.findBy(request.categoryName()) + .orElse(NO_CATEGORY_FILTER); + + return new WordSearchCondition( + request.name(), + category, + request.pronunciation() + ); + } + + private WordSearchPageRequest buildWordSearchPageRequest(SearchWordRequest request, Pageable pageable) { + Category lastCategory = Category.findBy(request.lastCategoryName()) + .orElse(NO_LAST_CATEGORY_FILTER); + + return new WordSearchPageRequest( + pageable, + request.lastWordName(), + lastCategory + ); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/SearchWordViewServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/SearchWordViewServiceTest.java new file mode 100644 index 00000000..037aa6b5 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/SearchWordViewServiceTest.java @@ -0,0 +1,47 @@ +package com.dnd.spaced.core.word.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.application.dto.request.SearchWordRequest; +import com.dnd.spaced.core.word.domain.dto.WordView; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class SearchWordViewServiceTest { + + @Autowired + SearchWordViewService wordService; + + @Test + @Sql("classpath:sql/word/word.sql") + void 용어를_검색한다() { + // given + SearchWordRequest request = new SearchWordRequest( + "Authorization", + null, + null, + null, + null + ); + + // when + List actual = wordService.searchWord(request, Pageable.ofSize(10)); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).name()).isEqualTo("Authorization") + ); + } +} From 9ce9891cf7f66cf35202d2b4a672a807a194886b Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 12:44:02 +0900 Subject: [PATCH 099/115] =?UTF-8?q?refactor:=20ReadWordViewService=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=EB=A5=BC=20package-private=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/spaced/core/word/application/ReadWordViewService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java index 42a64370..f3fa11ef 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadWordViewService.java @@ -12,7 +12,7 @@ @Service @RequiredArgsConstructor -public class ReadWordViewService { +class ReadWordViewService { private static final Category NO_CATEGORY_FILTER = null; private static final Category NO_LAST_CATEGORY_FILTER = null; From dc73e1717c469ba25351c3673a4eeca3a20c2510 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 13:14:44 +0900 Subject: [PATCH 100/115] =?UTF-8?q?feat:=20=EB=A7=8E=EC=9D=B4=20=EC=B0=BE?= =?UTF-8?q?=EC=95=84=EB=B3=B8=20=EC=9A=A9=EC=96=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReadPopularWordService.java | 21 +++++++++ .../ReadPopularWordServiceTest.java | 47 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/application/ReadPopularWordService.java create mode 100644 space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadPopularWordService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadPopularWordService.java new file mode 100644 index 00000000..4535a016 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/ReadPopularWordService.java @@ -0,0 +1,21 @@ +package com.dnd.spaced.core.word.application; + +import com.dnd.spaced.core.word.domain.dto.PopularWord; +import com.dnd.spaced.core.word.domain.repository.PopularWordRepository; +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +class ReadPopularWordService { + + private final Clock clock; + private final PopularWordRepository popularWordRepository; + + public List readPopularWords() { + return popularWordRepository.findAllBy(LocalDateTime.now(clock)); + } +} diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java new file mode 100644 index 00000000..bb8f4738 --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java @@ -0,0 +1,47 @@ +package com.dnd.spaced.core.word.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.dnd.spaced.core.word.domain.dto.PopularWord; +import com.dnd.spaced.core.word.domain.repository.PopularWordRepository; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class ReadPopularWordServiceTest { + + private static final Long WORD_ID = 1L; + + @Autowired + ReadPopularWordService wordService; + + @Autowired + PopularWordRepository popularWordRepository; + + @Test + void 많이_찾아본_용어_목록을_조회한다() { + // given + PopularWord popularWord = new PopularWord(1, WORD_ID, "Authorization"); + popularWordRepository.saveAll(List.of(popularWord), LocalDateTime.now()); + + // when + List actual = wordService.readPopularWords(); + + // then + assertAll( + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).rank()).isEqualTo(popularWord.rank()), + () -> assertThat(actual.get(0).name()).isEqualTo(popularWord.name()), + () -> assertThat(actual.get(0).wordId()).isEqualTo(popularWord.wordId()) + ); + } +} From cfde5306a919634b4697f9e42c0d924e2bee6e3a Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 13:16:32 +0900 Subject: [PATCH 101/115] =?UTF-8?q?test:=20ReadPopularWordServiceTest=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/word/application/ReadPopularWordServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java index bb8f4738..2b5b07d6 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/ReadPopularWordServiceTest.java @@ -22,7 +22,7 @@ class ReadPopularWordServiceTest { private static final Long WORD_ID = 1L; @Autowired - ReadPopularWordService wordService; + ReadPopularWordService readPopularWordService; @Autowired PopularWordRepository popularWordRepository; @@ -34,7 +34,7 @@ class ReadPopularWordServiceTest { popularWordRepository.saveAll(List.of(popularWord), LocalDateTime.now()); // when - List actual = wordService.readPopularWords(); + List actual = readPopularWordService.readPopularWords(); // then assertAll( From c4466716c7383ec503bccc7e2e92dec782746560 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 13:21:30 +0900 Subject: [PATCH 102/115] =?UTF-8?q?refactor:=20WordService=20Facade=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/word/application/WordService.java | 88 ------------------- .../word/application/WordServiceFacade.java | 65 ++++++++++++++ .../word/presentation/WordController.java | 12 +-- ...ceTest.java => WordServiceFacadeTest.java} | 19 ++-- .../word/presentation/WordControllerTest.java | 13 ++- 5 files changed, 88 insertions(+), 109 deletions(-) delete mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java create mode 100644 space-d/src/main/java/com/dnd/spaced/core/word/application/WordServiceFacade.java rename space-d/src/test/java/com/dnd/spaced/core/word/application/{WordServiceTest.java => WordServiceFacadeTest.java} (86%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java deleted file mode 100644 index 08d845dc..00000000 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordService.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.dnd.spaced.core.word.application; - -import com.dnd.spaced.core.word.application.dto.WordApplicationMapper; -import com.dnd.spaced.core.word.application.dto.request.ReadAllWordRequest; -import com.dnd.spaced.core.word.application.dto.request.SearchWordRequest; -import com.dnd.spaced.core.word.application.dto.response.PopularWordCollectionResponse; -import com.dnd.spaced.core.word.application.dto.response.WordCollectionResponse; -import com.dnd.spaced.core.word.application.dto.response.WordResponse; -import com.dnd.spaced.core.word.application.event.dto.WordViewCountIncrementEvent; -import com.dnd.spaced.core.word.application.event.dto.WordViewCountStatisticsEvent; -import com.dnd.spaced.core.word.application.exception.WordNotFoundException; -import com.dnd.spaced.core.word.domain.dto.WordView; -import com.dnd.spaced.core.word.domain.enums.Category; -import com.dnd.spaced.core.word.domain.repository.PopularWordRepository; -import com.dnd.spaced.core.word.domain.repository.WordViewRepository; -import com.dnd.spaced.core.word.domain.dto.PopularWord; -import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchCondition; -import com.dnd.spaced.core.word.domain.repository.dto.request.WordSearchPageRequest; -import java.time.Clock; -import java.time.LocalDateTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class WordService { - - private final Clock clock; - private final WordViewRepository wordViewRepository; - private final PopularWordRepository popularWordRepository; - private final WordApplicationMapper mapper; - private final ApplicationEventPublisher eventPublisher; - - public WordResponse readWord(Long wordId) { - WordView word = findWord(wordId); - - publishWordViewCountIncrementedEvent(word); - return mapper.toPronunciationResponse(word); - } - - public WordCollectionResponse readWords(ReadAllWordRequest request, Pageable pageable) { - Category category = Category.findBy(request.categoryName()) - .orElse(null); - Category lastCategory = Category.findBy(request.lastCategoryName()) - .orElse(null); - List words = wordViewRepository.findAllBy(category, request.lastWordName(), lastCategory, pageable); - - return mapper.toWordCollectionResponse(words); - } - - public WordCollectionResponse searchWord(SearchWordRequest request, Pageable pageable) { - Category category = Category.findBy(request.categoryName()) - .orElse(null); - Category lastCategory = Category.findBy(request.lastCategoryName()) - .orElse(null); - WordSearchCondition wordSearchCondition = new WordSearchCondition( - request.name(), - category, - request.pronunciation() - ); - WordSearchPageRequest wordSearchPageRequest = new WordSearchPageRequest( - pageable, - request.lastWordName(), - lastCategory); - List words = wordViewRepository.search(wordSearchCondition, wordSearchPageRequest); - - return mapper.toWordCollectionResponse(words); - } - - public PopularWordCollectionResponse readPopularWords() { - List popularWords = popularWordRepository.findAllBy(LocalDateTime.now(clock)); - - return mapper.toPopularWordCollectionResponse(popularWords); - } - - private WordView findWord(Long wordId) { - return wordViewRepository.findBy(wordId) - .orElseThrow(() -> new WordNotFoundException("지정한 ID에 해당하는 용어를 찾을 수 없습니다.")); - } - - private void publishWordViewCountIncrementedEvent(WordView word) { - eventPublisher.publishEvent(new WordViewCountIncrementEvent(word.id(), LocalDateTime.now(clock))); - eventPublisher.publishEvent(new WordViewCountStatisticsEvent(word.id(), LocalDateTime.now(clock))); - } -} diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/WordServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordServiceFacade.java new file mode 100644 index 00000000..122c3694 --- /dev/null +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/WordServiceFacade.java @@ -0,0 +1,65 @@ +package com.dnd.spaced.core.word.application; + +import com.dnd.spaced.core.word.application.dto.WordApplicationMapper; +import com.dnd.spaced.core.word.application.dto.request.ReadAllWordRequest; +import com.dnd.spaced.core.word.application.dto.request.SearchWordRequest; +import com.dnd.spaced.core.word.application.dto.response.PopularWordCollectionResponse; +import com.dnd.spaced.core.word.application.dto.response.WordCollectionResponse; +import com.dnd.spaced.core.word.application.dto.response.WordResponse; +import com.dnd.spaced.core.word.application.event.dto.WordViewCountIncrementEvent; +import com.dnd.spaced.core.word.application.event.dto.WordViewCountStatisticsEvent; +import com.dnd.spaced.core.word.domain.dto.WordView; +import com.dnd.spaced.core.word.domain.dto.PopularWord; +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class WordServiceFacade { + + private final Clock clock; + private final ReadWordViewService readWordViewService; + private final SearchWordViewService searchWordViewService; + private final ReadPopularWordService readPopularWordService; + private final WordApplicationMapper mapper; + private final ApplicationEventPublisher eventPublisher; + + public WordResponse readWord(Long wordId) { + WordView wordView = readWordView(wordId); + + publishWordViewCountIncrementedEvent(wordView); + return mapper.toPronunciationResponse(wordView); + } + + public WordCollectionResponse readWords(ReadAllWordRequest request, Pageable pageable) { + List words = readWordViewService.readWords(request, pageable); + + return mapper.toWordCollectionResponse(words); + } + + public WordCollectionResponse searchWord(SearchWordRequest request, Pageable pageable) { + List words = searchWordViewService.searchWord(request, pageable); + + return mapper.toWordCollectionResponse(words); + } + + public PopularWordCollectionResponse readPopularWords() { + List popularWords = readPopularWordService.readPopularWords(); + + return mapper.toPopularWordCollectionResponse(popularWords); + } + + private WordView readWordView(Long wordId) { + return readWordViewService.readWord(wordId); + } + + private void publishWordViewCountIncrementedEvent(WordView word) { + eventPublisher.publishEvent(new WordViewCountIncrementEvent(word.id(), LocalDateTime.now(clock))); + eventPublisher.publishEvent(new WordViewCountStatisticsEvent(word.id(), LocalDateTime.now(clock))); + } +} diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/presentation/WordController.java b/space-d/src/main/java/com/dnd/spaced/core/word/presentation/WordController.java index adfb5c84..5ad0071e 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/presentation/WordController.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/presentation/WordController.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.word.presentation; -import com.dnd.spaced.core.word.application.WordService; +import com.dnd.spaced.core.word.application.WordServiceFacade; import com.dnd.spaced.core.word.application.dto.request.ReadAllWordRequest; import com.dnd.spaced.core.word.application.dto.request.SearchWordRequest; import com.dnd.spaced.core.word.application.dto.response.PopularWordCollectionResponse; @@ -20,11 +20,11 @@ @RequiredArgsConstructor public class WordController { - private final WordService wordService; + private final WordServiceFacade wordServiceFacade; @GetMapping("/{wordId}") public ResponseEntity readWord(@PathVariable Long wordId) { - WordResponse response = wordService.readWord(wordId); + WordResponse response = wordServiceFacade.readWord(wordId); return ResponseEntity.ok(response); } @@ -34,7 +34,7 @@ public ResponseEntity readWords( ReadAllWordRequest request, @WordPageable Pageable pageable ) { - WordCollectionResponse response = wordService.readWords(request, pageable); + WordCollectionResponse response = wordServiceFacade.readWords(request, pageable); return ResponseEntity.ok(response); } @@ -44,14 +44,14 @@ public ResponseEntity searchWords( SearchWordRequest request, @WordPageable Pageable pageable ) { - WordCollectionResponse response = wordService.searchWord(request, pageable); + WordCollectionResponse response = wordServiceFacade.searchWord(request, pageable); return ResponseEntity.ok(response); } @GetMapping("/popular") public ResponseEntity readPopularWords() { - PopularWordCollectionResponse response = wordService.readPopularWords(); + PopularWordCollectionResponse response = wordServiceFacade.readPopularWords(); return ResponseEntity.ok(response); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/application/WordServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/application/WordServiceFacadeTest.java similarity index 86% rename from space-d/src/test/java/com/dnd/spaced/core/word/application/WordServiceTest.java rename to space-d/src/test/java/com/dnd/spaced/core/word/application/WordServiceFacadeTest.java index ef13028f..d838caae 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/application/WordServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/application/WordServiceFacadeTest.java @@ -31,10 +31,13 @@ @RecordApplicationEvents @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class WordServiceTest { +class WordServiceFacadeTest { + + private static final long WORD_ID = 1L; + private static final long NOT_FOUND_WORD = -999L; @Autowired - WordService wordService; + WordServiceFacade wordServiceFacade; @Autowired PopularWordRepository popularWordRepository; @@ -46,7 +49,7 @@ class WordServiceTest { @Sql("classpath:sql/word/word.sql") void 용어를_조회한다() { // when - WordResponse actual = wordService.readWord(1L); + WordResponse actual = wordServiceFacade.readWord(WORD_ID); // then assertAll( @@ -61,7 +64,7 @@ class WordServiceTest { @Test void 용어_식별자로_용어를_찾지_못하면_예외가_발생한다() { // when & then - assertThatThrownBy(() -> wordService.readWord(-999L)) + assertThatThrownBy(() -> wordServiceFacade.readWord(NOT_FOUND_WORD)) .isInstanceOf(WordNotFoundException.class) .hasMessage("지정한 ID에 해당하는 용어를 찾을 수 없습니다."); } @@ -77,7 +80,7 @@ class WordServiceTest { ); // when - WordCollectionResponse actual = wordService.readWords(request, Pageable.ofSize(10)); + WordCollectionResponse actual = wordServiceFacade.readWords(request, Pageable.ofSize(10)); // then assertAll( @@ -99,7 +102,7 @@ class WordServiceTest { ); // when - WordCollectionResponse actual = wordService.searchWord(request, Pageable.ofSize(10)); + WordCollectionResponse actual = wordServiceFacade.searchWord(request, Pageable.ofSize(10)); // then assertAll( @@ -111,11 +114,11 @@ class WordServiceTest { @Test void 많이_찾아본_용어_목록을_조회한다() { // given - PopularWord popularWord = new PopularWord(1, 1L, "Authorization"); + PopularWord popularWord = new PopularWord(1, WORD_ID, "Authorization"); popularWordRepository.saveAll(List.of(popularWord), LocalDateTime.now()); // when - PopularWordCollectionResponse actual = wordService.readPopularWords(); + PopularWordCollectionResponse actual = wordServiceFacade.readPopularWords(); // then assertAll( diff --git a/space-d/src/test/java/com/dnd/spaced/core/word/presentation/WordControllerTest.java b/space-d/src/test/java/com/dnd/spaced/core/word/presentation/WordControllerTest.java index dc4e5985..b9d60bc9 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/word/presentation/WordControllerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/word/presentation/WordControllerTest.java @@ -18,7 +18,7 @@ import com.dnd.spaced.config.common.CommonControllerSliceTest; import com.dnd.spaced.config.docs.link.DocumentLinkGenerator.DocsUrl; -import com.dnd.spaced.core.word.application.WordService; +import com.dnd.spaced.core.word.application.WordServiceFacade; import com.dnd.spaced.core.word.application.dto.request.SearchWordRequest; import com.dnd.spaced.core.word.application.dto.response.PopularWordCollectionResponse; import com.dnd.spaced.core.word.application.dto.response.WordCollectionResponse; @@ -27,7 +27,6 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; @@ -37,7 +36,7 @@ class WordControllerTest extends CommonControllerSliceTest { @Autowired - WordService wordService; + WordServiceFacade wordServiceFacade; @Test void 용어_조회_요청_성공_테스트() throws Exception { @@ -53,7 +52,7 @@ class WordControllerTest extends CommonControllerSliceTest { 1L ); - given(wordService.readWord(anyLong())).willReturn(wordResponse); + given(wordServiceFacade.readWord(anyLong())).willReturn(wordResponse); // when & then ResultActions resultActions = mockMvc.perform( @@ -112,7 +111,7 @@ class WordControllerTest extends CommonControllerSliceTest { 1L ); WordCollectionResponse wordCollectionResponse = new WordCollectionResponse(List.of(wordResponse), wordResponse.name()); - given(wordService.searchWord(any(SearchWordRequest.class), any(Pageable.class))).willReturn(wordCollectionResponse); + given(wordServiceFacade.searchWord(any(SearchWordRequest.class), any(Pageable.class))).willReturn(wordCollectionResponse); // when & then ResultActions resultActions = mockMvc.perform( @@ -183,7 +182,7 @@ class WordControllerTest extends CommonControllerSliceTest { 1L ); WordCollectionResponse wordCollectionResponse = new WordCollectionResponse(List.of(wordResponse), wordResponse.name()); - given(wordService.readWords(any(), any())).willReturn(wordCollectionResponse); + given(wordServiceFacade.readWords(any(), any())).willReturn(wordCollectionResponse); // when & then ResultActions resultActions = mockMvc.perform( @@ -240,7 +239,7 @@ class WordControllerTest extends CommonControllerSliceTest { // given PopularWordResponse popularWordResponse = new PopularWordResponse(1, 3L, "Authorization"); PopularWordCollectionResponse popularWordCollectionResponse = new PopularWordCollectionResponse(List.of(popularWordResponse)); - given(wordService.readPopularWords()).willReturn(popularWordCollectionResponse); + given(wordServiceFacade.readPopularWords()).willReturn(popularWordCollectionResponse); // when & then ResultActions resultActions = mockMvc.perform( From 7b75f38682121bcbc2e8ac676b6c5b6365586c00 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 13:25:24 +0900 Subject: [PATCH 103/115] =?UTF-8?q?refactor:=20=ED=80=B4=EC=A6=88=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4?= =?UTF-8?q?=EC=9E=90=EB=A5=BC=20package-private=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/quiz/application/CreateQuizService.java | 4 ++-- .../spaced/core/quiz/application/GradeQuizService.java | 4 ++-- .../spaced/core/quiz/application/ReadQuizService.java | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java index c37dcf19..9335f813 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/CreateQuizService.java @@ -25,7 +25,7 @@ @Service @RequiredArgsConstructor -public class CreateQuizService { +class CreateQuizService { private static final Long DEFAULT_WORD_METADATA_ID = 1L; private static final int QUIZ_QUESTION_WORD_COUNT = 5; @@ -40,7 +40,7 @@ public class CreateQuizService { private final WordMetadataRepository wordMetadataRepository; private final QuizQuestionProperties quizQuestionProperties; - Long createQuiz(Long accountId, CreateQuizRequest request) { + public Long createQuiz(Long accountId, CreateQuizRequest request) { QuizCategory quizCategory = findQuizCategory(request); validateQuizCreation(quizCategory); diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java index 2f2d9ee7..62d028fc 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeQuizService.java @@ -15,12 +15,12 @@ @Service @RequiredArgsConstructor -public class GradeQuizService { +class GradeQuizService { private final QuizRepository quizRepository; private final QuizGradedAnswerRepository quizGradedAnswerRepository; - void gradeQuiz(Long accountId, Long quizId, GradeQuizRequest request) { + public void gradeQuiz(Long accountId, Long quizId, GradeQuizRequest request) { Quiz quiz = findQuiz(quizId); validateQuiz(quiz); diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java index 1f7113f9..ec0b8fbf 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadQuizService.java @@ -15,12 +15,12 @@ @Service @RequiredArgsConstructor -public class ReadQuizService { +class ReadQuizService { private final QuizRepository quizRepository; private final QuizGradedAnswerRepository quizGradedAnswerRepository; - List readGradedAnswers( + public List readGradedAnswers( Long accountId, ReadQuizGradedAnswerSearchRequest request, Pageable pageable @@ -32,18 +32,18 @@ List readGradedAnswers( ); } - List readGradedAnswers(Long accountId, Long quizId) { + public List readGradedAnswers(Long accountId, Long quizId) { return quizGradedAnswerRepository.findAllBy(accountId, quizId); } - QuizDto readQuiz(Long accountId, Long quizId) { + public QuizDto readQuiz(Long accountId, Long quizId) { return quizRepository.findBy(quizId, accountId) .orElseThrow( () -> new QuizNotFoundException("지정한 id의 퀴즈를 찾지 못했습니다.") ); } - List readQuizzes(Long accountId, ReadAllQuizRequest request, Pageable pageable) { + public List readQuizzes(Long accountId, ReadAllQuizRequest request, Pageable pageable) { return quizRepository.findAllBy(accountId, request.lastQuizId(), pageable); } } From 3da0ce14c49a119c33b57a760817228751304ced Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 13:29:42 +0900 Subject: [PATCH 104/115] =?UTF-8?q?refactor:=20=EC=98=A4=EB=8A=98=EC=9D=98?= =?UTF-8?q?=20=ED=80=B4=EC=A6=88=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=EB=A5=BC=20package-private=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/quiz/application/GradeTodayQuizService.java | 4 ++-- .../core/quiz/application/ReadTodayQuizService.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java index 10ee6fee..d3e7a1d2 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/GradeTodayQuizService.java @@ -13,12 +13,12 @@ @Service @RequiredArgsConstructor -public class GradeTodayQuizService { +class GradeTodayQuizService { private final TodayQuizRepository todayQuizRepository; private final TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; - void gradeTodayQuiz(Long accountId, Long todayQuizId, GradeTodayQuizRequest request) { + public void gradeTodayQuiz(Long accountId, Long todayQuizId, GradeTodayQuizRequest request) { TodayQuiz todayQuiz = findTodayQuiz(todayQuizId); validateTodayQuizGradedAnswer(accountId, todayQuizId); diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java index 2b28fc5e..f97c4d4d 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/ReadTodayQuizService.java @@ -15,12 +15,12 @@ @Service @RequiredArgsConstructor -public class ReadTodayQuizService { +class ReadTodayQuizService { private final TodayQuizRepository todayQuizRepository; private final TodayQuizGradedAnswerRepository todayQuizGradedAnswerRepository; - SimpleTodayQuizDto readLatestTodayQuiz() { + public SimpleTodayQuizDto readLatestTodayQuiz() { return todayQuizRepository.findLatest() .orElseThrow( () -> new TodayQuizNotFoundException( @@ -28,7 +28,7 @@ SimpleTodayQuizDto readLatestTodayQuiz() { ); } - ReadTodayQuizDto readTodayQuiz(Long accountId, Long todayQuizId) { + public ReadTodayQuizDto readTodayQuiz(Long accountId, Long todayQuizId) { TodayQuiz todayQuiz = todayQuizRepository.findWithTodayQuizOptionBy(todayQuizId) .orElseThrow( () -> new TodayQuizNotFoundException( @@ -40,7 +40,7 @@ ReadTodayQuizDto readTodayQuiz(Long accountId, Long todayQuizId) { return new ReadTodayQuizDto(todayQuiz, solved); } - List readTodayQuizGradedAnswers( + public List readTodayQuizGradedAnswers( Long accountId, ReadTodayQuizGradedAnswerSearchRequest request, Pageable pageable @@ -52,7 +52,7 @@ List readTodayQuizGradedAnswers( ); } - TodayQuizGradedAnswer readTargetTodayQuizGradedAnswers(Long accountId, Long todayQuizId) { + public TodayQuizGradedAnswer readTargetTodayQuizGradedAnswers(Long accountId, Long todayQuizId) { return todayQuizGradedAnswerRepository.findBy(accountId, todayQuizId) .orElseThrow( () -> new TodayQuizNotFoundException( From 1dc740eddfdadcd1a7aa99eaa8bdecf37f3e2ca8 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 13:47:07 +0900 Subject: [PATCH 105/115] =?UTF-8?q?refactor:=20=ED=80=B4=EC=A6=88=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20Mapper=20=EC=9C=A0=ED=8B=B8=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=A7=81=20=EB=B9=88=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quiz/application/QuizServiceFacade.java | 11 +++++++---- .../mapper/QuizCollectionResponseMapper.java | 15 +++++++-------- ...uizGradedAnswerCollectionResponseMapper.java | 17 ++++++++--------- .../dto/mapper/QuizResponseMapper.java | 17 ++++++++--------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java index bfce16f7..4c27cb44 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/QuizServiceFacade.java @@ -29,6 +29,9 @@ public class QuizServiceFacade { private final CreateQuizService createQuizService; private final GradeQuizService gradeQuizService; private final ReadQuizService readQuizService; + private final QuizResponseMapper quizMapper; + private final QuizCollectionResponseMapper quizCollectionMapper; + private final QuizGradedAnswerCollectionResponseMapper gradedAnswerMapper; private final ApplicationEventPublisher eventPublisher; @Transactional @@ -55,24 +58,24 @@ public QuizGradedAnswerCollectionResponse readGradedAnswers( ) { List quizGradedAnswers = readQuizService.readGradedAnswers(accountId, request, pageable); - return QuizGradedAnswerCollectionResponseMapper.toCollectionDto(quizGradedAnswers); + return gradedAnswerMapper.toCollectionResponse(quizGradedAnswers); } public QuizGradedAnswerCollectionResponse readGradedAnswers(Long accountId, Long quizId) { List quizGradedAnswers = readQuizService.readGradedAnswers(accountId, quizId); - return QuizGradedAnswerCollectionResponseMapper.toCollectionDto(quizGradedAnswers); + return gradedAnswerMapper.toCollectionResponse(quizGradedAnswers); } public QuizResponse readQuiz(Long accountId, Long quizId) { QuizDto quizDto = readQuizService.readQuiz(quizId, accountId); - return QuizResponseMapper.toDto(quizDto); + return quizMapper.toResponse(quizDto); } public QuizCollectionResponse readQuizzes(Long accountId, ReadAllQuizRequest request, Pageable pageable) { List quizzes = readQuizService.readQuizzes(accountId, request, pageable); - return QuizCollectionResponseMapper.toCollectionResponse(quizzes); + return quizCollectionMapper.toCollectionResponse(quizzes); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java index 41d7843b..b89b2c4a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizCollectionResponseMapper.java @@ -6,15 +6,14 @@ import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse; import com.dnd.spaced.core.quiz.application.dto.response.QuizCollectionResponse.QuizResponse.QuizQuestionResponse; import com.dnd.spaced.core.quiz.domain.dto.SimpleQuizDto; +import com.dnd.spaced.global.mapper.Mapper; import java.util.Collections; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class QuizCollectionResponseMapper { +@Mapper +public class QuizCollectionResponseMapper { - public static QuizCollectionResponse toCollectionResponse(List quizzes) { + public QuizCollectionResponse toCollectionResponse(List quizzes) { if (quizzes == null || quizzes.isEmpty()) { return new QuizCollectionResponse( Collections.emptyList(), @@ -23,13 +22,13 @@ public static QuizCollectionResponse toCollectionResponse(List qu } List quizResponses = quizzes.stream() - .map(QuizCollectionResponseMapper::toQuizResponse) + .map(this::toQuizResponse) .toList(); return new QuizCollectionResponse(quizResponses, quizResponses.get(quizResponses.size() - 1).id()); } - private static QuizResponse toQuizResponse(SimpleQuizDto quiz) { + private QuizResponse toQuizResponse(SimpleQuizDto quiz) { return new QuizResponse( quiz.id(), quiz.accountId(), @@ -39,7 +38,7 @@ private static QuizResponse toQuizResponse(SimpleQuizDto quiz) { ); } - private static List toQuizQuestionResponse(List quizQuestions) { + private List toQuizQuestionResponse(List quizQuestions) { return quizQuestions.stream() .map( quizQuestion -> diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizGradedAnswerCollectionResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizGradedAnswerCollectionResponseMapper.java index 603956ea..b0b6acdc 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizGradedAnswerCollectionResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizGradedAnswerCollectionResponseMapper.java @@ -4,40 +4,39 @@ import com.dnd.spaced.core.quiz.application.dto.response.QuizGradedAnswerCollectionResponse.QuizGradedAnswerResponse; import com.dnd.spaced.core.quiz.domain.QuizGradedAnswer; import com.dnd.spaced.core.quiz.domain.QuizQuestion; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class QuizGradedAnswerCollectionResponseMapper { +@Mapper +public class QuizGradedAnswerCollectionResponseMapper { - public static QuizGradedAnswerCollectionResponse toCollectionDto(List quizGradedAnswers) { + public QuizGradedAnswerCollectionResponse toCollectionResponse(List quizGradedAnswers) { if (quizGradedAnswers.isEmpty()) { return new QuizGradedAnswerCollectionResponse(List.of(), null); } List responses = quizGradedAnswers.stream() - .map(QuizGradedAnswerCollectionResponseMapper::toDto) + .map(this::toResponse) .toList(); return new QuizGradedAnswerCollectionResponse(responses, responses.get(responses.size() - 1).id()); } - private static QuizGradedAnswerResponse toDto(QuizGradedAnswer quizGradedAnswer) { + private QuizGradedAnswerResponse toResponse(QuizGradedAnswer quizGradedAnswer) { QuizQuestion question = quizGradedAnswer.getQuizQuestion(); return new QuizGradedAnswerResponse( quizGradedAnswer.getId(), quizGradedAnswer.getAccountId(), quizGradedAnswer.getQuizId(), - toDto(question), + toResponse(question), question.getQuizAnswerOption().getAnswerContent(), quizGradedAnswer.getSelectedContent(), quizGradedAnswer.isCorrect() ); } - private static QuizGradedAnswerResponse.QuizQuestionResponse toDto(QuizQuestion quizQuestion) { + private QuizGradedAnswerResponse.QuizQuestionResponse toResponse(QuizQuestion quizQuestion) { return new QuizGradedAnswerResponse.QuizQuestionResponse( quizQuestion.getId(), quizQuestion.getQuizCategory().getName(), diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java index 34ed3b7f..ebb2f9a7 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/QuizResponseMapper.java @@ -6,17 +6,16 @@ import com.dnd.spaced.core.quiz.domain.dto.QuizDto; import com.dnd.spaced.core.quiz.domain.dto.QuizDto.QuizQuestionDto; import com.dnd.spaced.core.quiz.domain.dto.QuizDto.QuizQuestionDto.QuizOptionDto; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class QuizResponseMapper { +@Mapper +public class QuizResponseMapper { - public static QuizResponse toDto(QuizDto quiz) { + public QuizResponse toResponse(QuizDto quiz) { List quizQuestionResponses = quiz.quizQuestions() .stream() - .map(QuizResponseMapper::toQuizDto) + .map(this::toQuizQuestionResponse) .toList(); return new QuizResponse( @@ -26,10 +25,10 @@ public static QuizResponse toDto(QuizDto quiz) { ); } - private static QuizQuestionResponse toQuizDto(QuizQuestionDto quizQuestion) { + private QuizQuestionResponse toQuizQuestionResponse(QuizQuestionDto quizQuestion) { List quizOptionResponses = quizQuestion.quizOptions() .stream() - .map(QuizResponseMapper::toQuizOptionDto) + .map(this::toQuizOptionResponse) .toList(); return new QuizQuestionResponse( @@ -42,7 +41,7 @@ private static QuizQuestionResponse toQuizDto(QuizQuestionDto quizQuestion) { ); } - private static QuizOptionResponse toQuizOptionDto(QuizOptionDto quizOption) { + private QuizOptionResponse toQuizOptionResponse(QuizOptionDto quizOption) { return new QuizOptionResponse(quizOption.id(), quizOption.wordId(), quizOption.content()); } } From b7f27394c01669b0f2cc2b5da9aab23b2742cd0d Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 14:07:45 +0900 Subject: [PATCH 106/115] =?UTF-8?q?refactor:=20=EC=98=A4=EB=8A=98=EC=9D=98?= =?UTF-8?q?=20=ED=80=B4=EC=A6=88=20=EA=B4=80=EB=A0=A8=20Mapper=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84=EB=A7=81=20=EB=B9=88?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/TodayQuizServiceFacade.java | 9 ++++++--- .../mapper/SimpleTodayQuizResponseMapper.java | 9 ++++----- ...uizGradedAnswerCollectionResponseMapper.java | 17 ++++++++--------- .../dto/mapper/TodayQuizResponseMapper.java | 17 ++++++++--------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java index 3e4c78b3..66298f04 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/TodayQuizServiceFacade.java @@ -27,6 +27,9 @@ public class TodayQuizServiceFacade { private final ReadTodayQuizService readTodayQuizService; private final GradeTodayQuizService gradeTodayQuizService; + private final TodayQuizResponseMapper todayQuizMapper; + private final SimpleTodayQuizResponseMapper simpleTodayQuizMapper; + private final TodayQuizGradedAnswerCollectionResponseMapper gradedAnswerMapper; private final ApplicationEventPublisher eventPublisher; @Transactional @@ -43,13 +46,13 @@ public void gradeTodayQuiz(Long accountId, Long todayQuizId, GradeTodayQuizReque public SimpleTodayQuizResponse readLatestTodayQuiz() { SimpleTodayQuizDto simpleTodayQuizDto = readTodayQuizService.readLatestTodayQuiz(); - return SimpleTodayQuizResponseMapper.toDto(simpleTodayQuizDto); + return simpleTodayQuizMapper.toResponse(simpleTodayQuizDto); } public TodayQuizResponse readTodayQuiz(Long accountId, Long todayQuizId) { ReadTodayQuizDto readTodayQuizDto = readTodayQuizService.readTodayQuiz(accountId, todayQuizId); - return TodayQuizResponseMapper.toDto(readTodayQuizDto, accountId); + return todayQuizMapper.toResponse(readTodayQuizDto, accountId); } public TodayQuizGradedAnswerCollectionResponse readTodayQuizGradedAnswers( @@ -63,7 +66,7 @@ public TodayQuizGradedAnswerCollectionResponse readTodayQuizGradedAnswers( pageable ); - return TodayQuizGradedAnswerCollectionResponseMapper.toDto(todayQuizGradedAnswers); + return gradedAnswerMapper.toResponse(todayQuizGradedAnswers); } public TodayQuizGradedAnswerResponse readTargetTodayQuizGradedAnswers(Long accountId, Long todayQuizId) { diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java index 58ad4693..dcd273ad 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/SimpleTodayQuizResponseMapper.java @@ -3,13 +3,12 @@ import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse; import com.dnd.spaced.core.quiz.application.dto.response.SimpleTodayQuizResponse.TodayQuizQuestionResponse; import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; +import com.dnd.spaced.global.mapper.Mapper; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class SimpleTodayQuizResponseMapper { +@Mapper +public class SimpleTodayQuizResponseMapper { - public static SimpleTodayQuizResponse toDto(SimpleTodayQuizDto simpleTodayQuizDto) { + public SimpleTodayQuizResponse toResponse(SimpleTodayQuizDto simpleTodayQuizDto) { TodayQuizQuestionResponse todayQuizQuestionResponse = new TodayQuizQuestionResponse( simpleTodayQuizDto.quizCategory().getName(), simpleTodayQuizDto.question(), diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizGradedAnswerCollectionResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizGradedAnswerCollectionResponseMapper.java index 5f776915..0e8a6062 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizGradedAnswerCollectionResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizGradedAnswerCollectionResponseMapper.java @@ -6,22 +6,21 @@ import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.TodayQuizGradedAnswer; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class TodayQuizGradedAnswerCollectionResponseMapper { +@Mapper +public class TodayQuizGradedAnswerCollectionResponseMapper { - public static TodayQuizGradedAnswerCollectionResponse toDto(List todayQuizGradedAnswers) { + public TodayQuizGradedAnswerCollectionResponse toResponse(List todayQuizGradedAnswers) { List responses = todayQuizGradedAnswers.stream() - .map(TodayQuizGradedAnswerCollectionResponseMapper::toDto) + .map(this::toResponse) .toList(); return new TodayQuizGradedAnswerCollectionResponse(responses); } - public static TodayQuizGradedAnswerResponse toDto(TodayQuizGradedAnswer todayQuizGradedAnswer) { + private TodayQuizGradedAnswerResponse toResponse(TodayQuizGradedAnswer todayQuizGradedAnswer) { TodayQuiz quiz = todayQuizGradedAnswer.getTodayQuiz(); TodayQuizQuestion quizQuestion = quiz.getTodayQuizQuestion(); @@ -29,14 +28,14 @@ public static TodayQuizGradedAnswerResponse toDto(TodayQuizGradedAnswer todayQui todayQuizGradedAnswer.getId(), quiz.getId(), todayQuizGradedAnswer.getAccountId(), - toGradedAnswerDto(quizQuestion), + toTodayQuizQuestionResponse(quizQuestion), todayQuizGradedAnswer.getSelectedContent(), quizQuestion.getTodayQuizAnswerOption().getAnswerContent(), todayQuizGradedAnswer.isCorrect() ); } - private static TodayQuizQuestionResponse toGradedAnswerDto(TodayQuizQuestion quizQuestion) { + private TodayQuizQuestionResponse toTodayQuizQuestionResponse(TodayQuizQuestion quizQuestion) { return new TodayQuizQuestionResponse( quizQuestion.getQuizCategory().getName(), quizQuestion.getQuestion(), diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java index b60e348a..0a23cc71 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/dto/mapper/TodayQuizResponseMapper.java @@ -7,15 +7,14 @@ import com.dnd.spaced.core.quiz.application.dto.response.TodayQuizResponse.TodayQuizStatus; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; import com.dnd.spaced.global.consts.AuthConst; +import com.dnd.spaced.global.mapper.Mapper; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class TodayQuizResponseMapper { +@Mapper +public class TodayQuizResponseMapper { - public static TodayQuizResponse toDto(ReadTodayQuizDto todayQuizDto, Long accountId) { - TodayQuizQuestionResponse todayQuizQuestion = toTodayQuizQuestionDto( + public TodayQuizResponse toResponse(ReadTodayQuizDto todayQuizDto, Long accountId) { + TodayQuizQuestionResponse todayQuizQuestion = toTodayQuizQuestionResponse( todayQuizDto.todayQuiz().getTodayQuizQuestion() ); @@ -32,8 +31,8 @@ public static TodayQuizResponse toDto(ReadTodayQuizDto todayQuizDto, Long accoun return new TodayQuizResponse(todayQuizDto.todayQuiz().getId(), todayQuizQuestion, TodayQuizStatus.NOT_SOLVED); } - private static TodayQuizQuestionResponse toTodayQuizQuestionDto(TodayQuizQuestion todayQuizQuestion) { - List todayQuizOptionResponses = toTodayQuizOptionDto(todayQuizQuestion); + private TodayQuizQuestionResponse toTodayQuizQuestionResponse(TodayQuizQuestion todayQuizQuestion) { + List todayQuizOptionResponses = toTodayQuizOptionResponse(todayQuizQuestion); return new TodayQuizQuestionResponse( todayQuizQuestion.getQuizCategory().getName(), @@ -45,7 +44,7 @@ private static TodayQuizQuestionResponse toTodayQuizQuestionDto(TodayQuizQuestio ); } - private static List toTodayQuizOptionDto(TodayQuizQuestion todayQuizQuestion) { + private List toTodayQuizOptionResponse(TodayQuizQuestion todayQuizQuestion) { return todayQuizQuestion.getTodayQuizOptions() .stream() .map(todayQuizOption -> From 3f1f9764ed999e2a98d47b188da5474299f0ddd1 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 14:11:59 +0900 Subject: [PATCH 107/115] =?UTF-8?q?refactor:=20DeletedWordIdRepository=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/event/listener/AdminWordEventListener.java | 2 +- .../bookmark/application/schedule/DeleteBookmarkScheduler.java | 3 +-- .../application/{ => repository}/DeletedWordIdRepository.java | 2 +- .../persistence/DeletedWordGatewayIdRepository.java | 2 +- .../com/dnd/spaced/config/EventListenerSpyBeanTestConfig.java | 2 +- .../application/event/listener/AdminWordEventListenerTest.java | 2 +- .../application/schedule/DeleteBookmarkSchedulerTest.java | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/word/application/{ => repository}/DeletedWordIdRepository.java (81%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListener.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListener.java index 28d42350..53c81fc6 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListener.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListener.java @@ -1,7 +1,7 @@ package com.dnd.spaced.core.admin.application.event.listener; import com.dnd.spaced.core.admin.application.event.dto.DeletedWordEvent; -import com.dnd.spaced.core.word.application.DeletedWordIdRepository; +import com.dnd.spaced.core.word.application.repository.DeletedWordIdRepository; import com.dnd.spaced.core.word.domain.repository.PronunciationRepository; import com.dnd.spaced.core.word.domain.repository.WordExampleRepository; import java.time.Clock; diff --git a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkScheduler.java b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkScheduler.java index d008f0dd..2df2003b 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkScheduler.java +++ b/space-d/src/main/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkScheduler.java @@ -1,13 +1,12 @@ package com.dnd.spaced.core.bookmark.application.schedule; import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; -import com.dnd.spaced.core.word.application.DeletedWordIdRepository; +import com.dnd.spaced.core.word.application.repository.DeletedWordIdRepository; import java.time.Clock; import java.time.LocalDateTime; import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.retry.support.RetryTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/application/DeletedWordIdRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/application/repository/DeletedWordIdRepository.java similarity index 81% rename from space-d/src/main/java/com/dnd/spaced/core/word/application/DeletedWordIdRepository.java rename to space-d/src/main/java/com/dnd/spaced/core/word/application/repository/DeletedWordIdRepository.java index 6501e7ad..e3cc62e3 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/application/DeletedWordIdRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/application/repository/DeletedWordIdRepository.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.word.application; +package com.dnd.spaced.core.word.application.repository; import java.time.LocalDateTime; import java.util.Set; diff --git a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/DeletedWordGatewayIdRepository.java b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/DeletedWordGatewayIdRepository.java index 8b15de16..64a56a18 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/DeletedWordGatewayIdRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/word/infrastructure/persistence/DeletedWordGatewayIdRepository.java @@ -1,6 +1,6 @@ package com.dnd.spaced.core.word.infrastructure.persistence; -import com.dnd.spaced.core.word.application.DeletedWordIdRepository; +import com.dnd.spaced.core.word.application.repository.DeletedWordIdRepository; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Set; diff --git a/space-d/src/test/java/com/dnd/spaced/config/EventListenerSpyBeanTestConfig.java b/space-d/src/test/java/com/dnd/spaced/config/EventListenerSpyBeanTestConfig.java index f5fa7483..6db6a781 100644 --- a/space-d/src/test/java/com/dnd/spaced/config/EventListenerSpyBeanTestConfig.java +++ b/space-d/src/test/java/com/dnd/spaced/config/EventListenerSpyBeanTestConfig.java @@ -4,7 +4,7 @@ import com.dnd.spaced.core.skill.application.event.dto.FailedGradedQuizSkillEvent; import com.dnd.spaced.core.skill.application.event.dto.FailedGradedTodayQuizSkillEvent; import com.dnd.spaced.core.skill.domain.repository.SkillRepository; -import com.dnd.spaced.core.word.application.DeletedWordIdRepository; +import com.dnd.spaced.core.word.application.repository.DeletedWordIdRepository; import com.dnd.spaced.core.word.application.event.dto.FailedWordPersistedEvent; import com.dnd.spaced.core.word.domain.repository.PronunciationRepository; import com.dnd.spaced.core.word.domain.repository.WordExampleRepository; diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListenerTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListenerTest.java index 5454408a..74e007c7 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListenerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/event/listener/AdminWordEventListenerTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.verify; import com.dnd.spaced.core.admin.application.event.dto.DeletedWordEvent; -import com.dnd.spaced.core.word.application.DeletedWordIdRepository; +import com.dnd.spaced.core.word.application.repository.DeletedWordIdRepository; import com.dnd.spaced.core.word.domain.repository.PronunciationRepository; import com.dnd.spaced.core.word.domain.repository.WordExampleRepository; import java.time.LocalDateTime; diff --git a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java index 47b9b85b..d6538903 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/bookmark/application/schedule/DeleteBookmarkSchedulerTest.java @@ -8,7 +8,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import com.dnd.spaced.core.bookmark.domain.repository.BookmarkRepository; -import com.dnd.spaced.core.word.application.DeletedWordIdRepository; +import com.dnd.spaced.core.word.application.repository.DeletedWordIdRepository; import java.time.Clock; import java.time.LocalDateTime; import java.util.List; From 2b2cffe13dd012248976611f55fe3cb949c01a8f Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 14:25:49 +0900 Subject: [PATCH 108/115] =?UTF-8?q?refactor:=20ReadCommentService=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentServiceFacade.java | 8 +++++++- .../application/ReadCommentService.java | 9 ++------- .../CommentResponseCollectionMapper.java | 2 +- .../application/ReadCommentServiceTest.java | 19 ++++++++++--------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java index e6ea28f6..0660cdce 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/CommentServiceFacade.java @@ -1,8 +1,11 @@ package com.dnd.spaced.core.comment.application; +import com.dnd.spaced.core.comment.application.dto.mapper.CommentResponseCollectionMapper; import com.dnd.spaced.core.comment.application.dto.request.CreateCommentRequest; import com.dnd.spaced.core.comment.application.dto.request.UpdateCommentRequest; import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -16,6 +19,7 @@ public class CommentServiceFacade { private final ReadCommentService readCommentService; private final UpdateCommentService updateCommentService; private final DeleteCommentService deleteCommentService; + private final CommentResponseCollectionMapper mapper; @Transactional public void createComment(Long accountId, Long wordId, CreateCommentRequest request) { @@ -33,6 +37,8 @@ public void updateComment(Long accountId, Long commentId, UpdateCommentRequest r } public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { - return readCommentService.readComments(accountId, wordId, lastCommentId, pageable); + List comments = readCommentService.readComments(accountId, wordId, lastCommentId, pageable); + + return mapper.toResponse(comments); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java index 7e956cf1..939a4a65 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/ReadCommentService.java @@ -1,7 +1,5 @@ package com.dnd.spaced.core.comment.application; -import com.dnd.spaced.core.comment.application.dto.mapper.CommentResponseCollectionMapper; -import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; import com.dnd.spaced.core.comment.domain.dto.LikedComment; import com.dnd.spaced.core.comment.domain.repository.CommentRepository; import java.util.List; @@ -14,11 +12,8 @@ class ReadCommentService { private final CommentRepository commentRepository; - private final CommentResponseCollectionMapper mapper; - public CommentCollectionResponse readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { - List comments = commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); - - return mapper.toDto(comments); + public List readComments(Long accountId, Long wordId, Long lastCommentId, Pageable pageable) { + return commentRepository.findAllBy(accountId, wordId, lastCommentId, pageable); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java b/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java index 6c79dd6c..107cdf02 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/comment/application/dto/mapper/CommentResponseCollectionMapper.java @@ -12,7 +12,7 @@ @Mapper public class CommentResponseCollectionMapper { - public CommentCollectionResponse toDto(List comments) { + public CommentCollectionResponse toResponse(List comments) { if (comments.isEmpty()) { return new CommentCollectionResponse(List.of(), null); } diff --git a/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java index 8ffd7670..1653f390 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/comment/application/ReadCommentServiceTest.java @@ -3,7 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import com.dnd.spaced.core.comment.application.dto.response.CommentCollectionResponse; +import com.dnd.spaced.core.comment.domain.dto.LikedComment; +import java.util.List; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -33,7 +34,7 @@ class ReadCommentServiceTest { }) void 로그인_하지_않고_특정_용어의_댓글_목록을_조회한다() { // when - CommentCollectionResponse actual = readCommentService.readComments( + List actual = readCommentService.readComments( GUEST_ID, WORD_ID, null, @@ -42,9 +43,9 @@ class ReadCommentServiceTest { // then assertAll( - () -> assertThat(actual.comments()).hasSize(1), - () -> assertThat(actual.comments().get(0).commentContent().content()).isEqualTo("이 용어는 언제 쓰는건가요?"), - () -> assertThat(actual.comments().get(0).liked()).isFalse() + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).comment().getContent()).isEqualTo("이 용어는 언제 쓰는건가요?"), + () -> assertThat(actual.get(0).isLiked()).isFalse() ); } @@ -56,7 +57,7 @@ class ReadCommentServiceTest { }) void 로그인하고_특정_용어의_댓글_목록을_조회한다() { // when - CommentCollectionResponse actual = readCommentService.readComments( + List actual = readCommentService.readComments( READER_ID, WORD_ID, null, @@ -65,9 +66,9 @@ class ReadCommentServiceTest { // then assertAll( - () -> assertThat(actual.comments()).hasSize(1), - () -> assertThat(actual.comments().get(0).commentContent().content()).isEqualTo("이 용어는 언제 쓰는건가요?"), - () -> assertThat(actual.comments().get(0).liked()).isTrue() + () -> assertThat(actual).hasSize(1), + () -> assertThat(actual.get(0).comment().getContent()).isEqualTo("이 용어는 언제 쓰는건가요?"), + () -> assertThat(actual.get(0).isLiked()).isTrue() ); } } From ef3f157b044653e6724c781008c3418c8d773d9c Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 15:03:12 +0900 Subject: [PATCH 109/115] =?UTF-8?q?refactor:=20TodayQuizDtoMapper=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=94=84=EB=A7=81=20?= =?UTF-8?q?=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminTodayQuizServiceFacade.java | 5 ++- .../schedule/CreateTodayQuizScheduler.java | 5 ++- .../{TodayQuizInfo.java => TodayQuizDto.java} | 6 +-- ...nfoMapper.java => TodayQuizDtoMapper.java} | 39 ++++++++-------- .../TodayQuizGatewayRepository.java | 45 ++++++++++++++----- 5 files changed, 62 insertions(+), 38 deletions(-) rename space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/{TodayQuizInfo.java => TodayQuizDto.java} (68%) rename space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/{TodayQuizInfoMapper.java => TodayQuizDtoMapper.java} (62%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java index 829f6d00..0cccb0f1 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java @@ -2,7 +2,7 @@ import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; import com.dnd.spaced.core.quiz.domain.TodayQuiz; -import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizInfoMapper; +import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizDtoMapper; import com.dnd.spaced.global.consts.CacheConst; import lombok.RequiredArgsConstructor; import org.springframework.cache.Cache; @@ -17,6 +17,7 @@ public class AdminTodayQuizServiceFacade { private final CreateTodayQuizService createTodayQuizService; private final CacheManager memoryCacheManager; + private final TodayQuizDtoMapper mapper; private final ApplicationEventPublisher eventPublisher; @Transactional @@ -38,7 +39,7 @@ private void persistMemoryCache(TodayQuiz todayQuiz) { if (cache != null) { cache.clear(); - cache.put(CacheConst.TODAY_QUIZ_CACHE_NAME, TodayQuizInfoMapper.toDto(todayQuiz)); + cache.put(CacheConst.TODAY_QUIZ_CACHE_NAME, mapper.toDto(todayQuiz)); } } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java index ba6dc4c3..2540813a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java @@ -5,7 +5,7 @@ import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.TodayQuizOption; -import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizInfoMapper; +import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizDtoMapper; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizAnswerOption; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; @@ -43,6 +43,7 @@ public class CreateTodayQuizScheduler { private final TodayQuizOptionRepository todayQuizOptionRepository; private final QuizQuestionProperties quizQuestionProperties; private final CacheManager memoryCacheManager; + private final TodayQuizDtoMapper mapper; private final ApplicationEventPublisher eventPublisher; @Transactional @@ -122,7 +123,7 @@ private void persistMemoryCache(TodayQuiz todayQuiz) { if (cache != null) { cache.clear(); - cache.put(CacheConst.TODAY_QUIZ_CACHE_NAME, TodayQuizInfoMapper.toDto(todayQuiz)); + cache.put(CacheConst.TODAY_QUIZ_CACHE_NAME, mapper.toDto(todayQuiz)); } } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/TodayQuizInfo.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/TodayQuizDto.java similarity index 68% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/TodayQuizInfo.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/TodayQuizDto.java index c0fa2e27..bae66bb2 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/TodayQuizInfo.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/TodayQuizDto.java @@ -4,16 +4,16 @@ import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import java.util.List; -public record TodayQuizInfo( +public record TodayQuizDto( Long id, QuizCategory quizCategory, String question, String questionContent, TodayQuizAnswerOption todayQuizAnswerOption, - List todayQuizOptions + List todayQuizOptions ) { - public record TodayQuizOptionInfo(Long id, Long wordId, String content, int optionOrder) { + public record TodayQuizOptionDto(Long id, Long wordId, String content, int optionOrder) { } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizDtoMapper.java similarity index 62% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java rename to space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizDtoMapper.java index c4ab7fd0..38527b36 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizInfoMapper.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/domain/dto/mapper/TodayQuizDtoMapper.java @@ -3,20 +3,19 @@ import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.TodayQuizOption; import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; -import com.dnd.spaced.core.quiz.domain.dto.TodayQuizInfo; -import com.dnd.spaced.core.quiz.domain.dto.TodayQuizInfo.TodayQuizOptionInfo; +import com.dnd.spaced.core.quiz.domain.dto.TodayQuizDto; +import com.dnd.spaced.core.quiz.domain.dto.TodayQuizDto.TodayQuizOptionDto; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizAnswerOption; import com.dnd.spaced.core.quiz.domain.embed.TodayQuizQuestion; import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; +import com.dnd.spaced.global.mapper.Mapper; import java.time.LocalDateTime; import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class TodayQuizInfoMapper { +@Mapper +public class TodayQuizDtoMapper { - public static SimpleTodayQuizDto toDto(TodayQuiz todayQuiz) { + public SimpleTodayQuizDto toDto(TodayQuiz todayQuiz) { TodayQuizQuestion todayQuizQuestion = todayQuiz.getTodayQuizQuestion(); return new SimpleTodayQuizDto( @@ -29,7 +28,7 @@ public static SimpleTodayQuizDto toDto(TodayQuiz todayQuiz) { ); } - public static SimpleTodayQuizDto toDto( + public SimpleTodayQuizDto toDto( Long id, LocalDateTime createdAt, String question, @@ -43,24 +42,26 @@ public static SimpleTodayQuizDto toDto( return new SimpleTodayQuizDto(id, quizCategory, question, questionContent, todayQuizAnswerOption, createdAt); } - public static TodayQuizInfo toDto(TodayQuiz todayQuiz, List todayQuizOptions) { + public TodayQuizDto toDto(TodayQuiz todayQuiz, List todayQuizOptions) { TodayQuizQuestion todayQuizQuestion = todayQuiz.getTodayQuizQuestion(); - List todayQuizOptionInfos = todayQuizOptions.stream() - .map( - option -> new TodayQuizOptionInfo( - option.getId(), - option.getWordId(), option.getContent(), - option.getOptionOrder() - )) - .toList(); + List todayQuizOptionDtos = todayQuizOptions.stream() + .map( + option -> new TodayQuizOptionDto( + option.getId(), + option.getWordId(), + option.getContent(), + option.getOptionOrder() + ) + ) + .toList(); - return new TodayQuizInfo( + return new TodayQuizDto( todayQuiz.getId(), todayQuizQuestion.getQuizCategory(), todayQuizQuestion.getQuestion(), todayQuizQuestion.getPassage(), todayQuizQuestion.getTodayQuizAnswerOption(), - todayQuizOptionInfos + todayQuizOptionDtos ); } } diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java index 5eb028b3..5bd996d0 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java +++ b/space-d/src/main/java/com/dnd/spaced/core/quiz/infrastructure/persistence/TodayQuizGatewayRepository.java @@ -4,11 +4,14 @@ import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; -import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizInfoMapper; +import com.dnd.spaced.core.quiz.domain.embed.TodayQuizAnswerOption; import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; import com.dnd.spaced.global.consts.CacheConst; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -22,16 +25,7 @@ @RequiredArgsConstructor public class TodayQuizGatewayRepository implements TodayQuizRepository { - private static final RowMapper simpleTodayQuizInfoRowMapper = - (rs, ignoreRowNum) -> TodayQuizInfoMapper.toDto( - rs.getLong(1), - rs.getTimestamp(2).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), - rs.getString(3), - rs.getString(4), - QuizCategory.valueOf(rs.getString(5)), - rs.getString(6), - rs.getLong(7) - ); + private static final RowMapper simpleTodayQuizDtoRowMapper = new SimpleTodayQuizDtoMapper(); private final JdbcTemplate jdbcTemplate; private final JPAQueryFactory queryFactory; @@ -66,7 +60,7 @@ public Optional findLatest() { ) t left join today_quizzes tq ON t.id = tq.id; """; try { - SimpleTodayQuizDto simpleTodayQuizDto = jdbcTemplate.queryForObject(sql, simpleTodayQuizInfoRowMapper); + SimpleTodayQuizDto simpleTodayQuizDto = jdbcTemplate.queryForObject(sql, simpleTodayQuizDtoRowMapper); return Optional.of(simpleTodayQuizDto); } catch (IncorrectResultSizeDataAccessException ignored) { @@ -92,4 +86,31 @@ public Optional findWithTodayQuizOptionBy(Long todayQuizId) { return Optional.ofNullable(result); } + + private static class SimpleTodayQuizDtoMapper implements RowMapper { + + @Override + public SimpleTodayQuizDto mapRow(ResultSet rs, int ignoreRowNum) throws SQLException { + Long id = rs.getLong(1); + LocalDateTime createdAt = rs.getTimestamp(2) + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + String question = rs.getString(3); + String questionContent = rs.getString(4); + QuizCategory quizCategory = QuizCategory.valueOf(rs.getString(5)); + String answerContent = rs.getString(6); + Long answerWordId = rs.getLong(7); + TodayQuizAnswerOption todayQuizAnswerOption = new TodayQuizAnswerOption(answerWordId, answerContent); + + return new SimpleTodayQuizDto( + id, + quizCategory, + question, + questionContent, + todayQuizAnswerOption, + createdAt + ); + } + } } From 6d6b57b225978e2da866e0d9bed70bde3355f7e4 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 15:10:28 +0900 Subject: [PATCH 110/115] =?UTF-8?q?refactor:=20CreateTodayQuizScheduler=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/schedule/CreateTodayQuizScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename space-d/src/main/java/com/dnd/spaced/core/{quiz => admin}/application/schedule/CreateTodayQuizScheduler.java (99%) diff --git a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizScheduler.java similarity index 99% rename from space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java rename to space-d/src/main/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizScheduler.java index 2540813a..8773d55a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/quiz/application/schedule/CreateTodayQuizScheduler.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizScheduler.java @@ -1,4 +1,4 @@ -package com.dnd.spaced.core.quiz.application.schedule; +package com.dnd.spaced.core.admin.application.schedule; import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; From 522ae255b86f134f5e6cb90c881a2af6d5ed8259 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 15:33:59 +0900 Subject: [PATCH 111/115] =?UTF-8?q?test:=20CreateTodayQuizScheduler=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateTodayQuizSchedulerTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 space-d/src/test/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizSchedulerTest.java diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizSchedulerTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizSchedulerTest.java new file mode 100644 index 00000000..5c6d476d --- /dev/null +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/schedule/CreateTodayQuizSchedulerTest.java @@ -0,0 +1,55 @@ +package com.dnd.spaced.core.admin.application.schedule; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.dnd.spaced.core.quiz.domain.dto.SimpleTodayQuizDto; +import com.dnd.spaced.core.quiz.domain.repository.TodayQuizRepository; +import jakarta.persistence.EntityManager; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class CreateTodayQuizSchedulerTest { + + @Autowired + CreateTodayQuizScheduler createTodayQuizScheduler; + + @Autowired + TodayQuizRepository todayQuizRepository; + + @Autowired + EntityManager em; + + @Test + @Sql(scripts = { + "classpath:sql/admin/quiz/word_metadata.sql", + "classpath:sql/admin/quiz/quiz_metadata.sql", + "classpath:sql/admin/quiz/word.sql" + }) + void 오늘의_퀴즈를_생성한다() { + // given + Optional todayQuiz = todayQuizRepository.findLatest(); + + assertThat(todayQuiz).isEmpty(); + + // when + createTodayQuizScheduler.schedule(); + + // then + em.clear(); + + Optional actual = todayQuizRepository.findLatest(); + + assertThat(actual).hasValueSatisfying(value -> { + assertThat(value.id()).isEqualTo(1L); + }); + } +} From 8b72e8971c54e3ab1e9deec351af00f8270cd870 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 16:34:04 +0900 Subject: [PATCH 112/115] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=A9=EB=A6=AC=20=EB=A6=AC=EC=8A=A4=EB=84=88=20=EB=A9=94?= =?UTF-8?q?=EB=AA=A8=EB=A6=AC=20=EC=BA=90=EC=8B=9C=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clean/CleanupExecutionListener.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/space-d/src/test/java/com/dnd/spaced/config/clean/CleanupExecutionListener.java b/space-d/src/test/java/com/dnd/spaced/config/clean/CleanupExecutionListener.java index 8e75f925..eb0bd9f8 100644 --- a/space-d/src/test/java/com/dnd/spaced/config/clean/CleanupExecutionListener.java +++ b/space-d/src/test/java/com/dnd/spaced/config/clean/CleanupExecutionListener.java @@ -1,9 +1,12 @@ package com.dnd.spaced.config.clean; +import jakarta.persistence.EntityManager; import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; import org.redisson.spring.data.connection.RedissonConnectionFactory; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.ClassPathResource; @@ -22,20 +25,21 @@ public void beforeTestMethod(TestContext testContext) { return; } - cleanUpWithSql(testContext); - cleanUpWithRedis(testContext); + cleanupWithSql(testContext); + cleanupWithRedis(testContext); + cleanupWithMemoryCache(testContext); } @Override public int getOrder() { - return 4999; + return Ordered.HIGHEST_PRECEDENCE; } private boolean isNotIntegrationTest(TestContext testContext) { return AnnotationUtils.findAnnotation(testContext.getTestClass(), SpringBootTest.class) == null; } - private void cleanUpWithSql(TestContext testContext) { + private void cleanupWithSql(TestContext testContext) { DataSource dataSource = testContext.getApplicationContext().getBean(DataSource.class); ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); @@ -43,7 +47,7 @@ private void cleanUpWithSql(TestContext testContext) { populator.execute(dataSource); } - private void cleanUpWithRedis(TestContext testContext) { + private void cleanupWithRedis(TestContext testContext) { RedissonConnectionFactory lettuceConnectionFactory = findLettuceConnectionFactory(testContext); RedisConnection redisConnection = lettuceConnectionFactory.getConnection(); RedisServerCommands redisServerCommands = redisConnection.serverCommands(); @@ -51,6 +55,20 @@ private void cleanUpWithRedis(TestContext testContext) { redisServerCommands.flushAll(); } + private void cleanupWithMemoryCache(TestContext testContext) { + try { + CacheManager cacheManager = testContext.getApplicationContext().getBean(CacheManager.class); + cacheManager.getCacheNames().forEach(cacheName -> { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) { + cache.clear(); + } + }); + } catch (Exception e) { + log.warn("캐시 정리 중 오류 발생", e); + } + } + private RedissonConnectionFactory findLettuceConnectionFactory(TestContext testContext) { return testContext.getApplicationContext() .getBean(RedissonConnectionFactory.class); From a5dab73a200acca2c782d7d01f7734cc9252a442 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 16:37:23 +0900 Subject: [PATCH 113/115] =?UTF-8?q?fix:=20CreateTodayQuizService=20?= =?UTF-8?q?=EC=98=A4=EB=8A=98=EC=9D=98=20=ED=80=B4=EC=A6=88=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminTodayQuizServiceFacade.java | 14 ++++++++++++-- .../admin/application/CreateTodayQuizService.java | 10 ++-------- .../application/CreateTodayQuizServiceTest.java | 7 ++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java index 0cccb0f1..700077ab 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminTodayQuizServiceFacade.java @@ -3,6 +3,7 @@ import com.dnd.spaced.core.quiz.application.event.dto.AddedTodayQuizQuestionEvent; import com.dnd.spaced.core.quiz.domain.TodayQuiz; import com.dnd.spaced.core.quiz.domain.dto.mapper.TodayQuizDtoMapper; +import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import com.dnd.spaced.global.consts.CacheConst; import lombok.RequiredArgsConstructor; import org.springframework.cache.Cache; @@ -22,14 +23,23 @@ public class AdminTodayQuizServiceFacade { @Transactional public Long createTodayQuiz() { - TodayQuiz todayQuiz = createTodayQuizService.assembleTodayQuiz(); + TodayQuiz todayQuiz = generateRandomTodayQuiz(); publishAddedTodayQuizQuestionEvent(); persistMemoryCache(todayQuiz); - return todayQuiz.getId(); } + private TodayQuiz generateRandomTodayQuiz() { + QuizCategory quizCategory = findRandomQuizCategory(); + + return createTodayQuizService.createTodayQuiz(quizCategory); + } + + private QuizCategory findRandomQuizCategory() { + return QuizCategory.findRandom(); + } + private void publishAddedTodayQuizQuestionEvent() { eventPublisher.publishEvent(new AddedTodayQuizQuestionEvent()); } diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java index 06ce4c96..f03c2698 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java @@ -24,7 +24,7 @@ @Service @RequiredArgsConstructor -class CreateTodayQuizService { +public class CreateTodayQuizService { private static final Long DEFAULT_WORD_METADATA_ID = 1L; private static final int REQUIRED_TODAY_QUIZ_WORD_COUNT = 4; @@ -37,18 +37,12 @@ class CreateTodayQuizService { private final QuizQuestionProperties quizQuestionProperties; @Transactional - public TodayQuiz assembleTodayQuiz() { - QuizCategory quizCategory = findRandomQuizCategory(); - + public TodayQuiz createTodayQuiz(QuizCategory quizCategory) { validateQuizCreationRequirements(quizCategory); return assembleTodayQuiz(quizCategory); } - private QuizCategory findRandomQuizCategory() { - return QuizCategory.findRandom(); - } - private void validateQuizCreationRequirements(QuizCategory quizCategory) { WordMetadata wordMetadata = findWordMetadata(); diff --git a/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java index 00ec5a2a..2991abdf 100644 --- a/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java +++ b/space-d/src/test/java/com/dnd/spaced/core/admin/application/CreateTodayQuizServiceTest.java @@ -6,6 +6,7 @@ import com.dnd.spaced.core.admin.application.exception.WordMetadataNotFoundException; import com.dnd.spaced.core.quiz.application.exception.InvalidTodayQuizWordCountException; import com.dnd.spaced.core.quiz.domain.TodayQuiz; +import com.dnd.spaced.core.quiz.domain.enums.QuizCategory; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -25,7 +26,7 @@ class CreateTodayQuizServiceTest { @Test void 용어_메타데이터가_정상적으로_설정되지_않다면_오늘의_퀴즈를_생성할_수_없다() { // when & then - assertThatThrownBy(() -> createTodayQuizService.assembleTodayQuiz()) + assertThatThrownBy(() -> createTodayQuizService.createTodayQuiz(QuizCategory.DEVELOP)) .isInstanceOf(WordMetadataNotFoundException.class) .hasMessage("용어 메타데이터가 정상적으로 설정되지 않았습니다."); } @@ -37,7 +38,7 @@ class CreateTodayQuizServiceTest { }) void 등록된_용어_수가_퀴즈_생성_시_필요한_용어_수보다_적으면_퀴즈를_생성할_수_없다() { // when & then - assertThatThrownBy(() -> createTodayQuizService.assembleTodayQuiz()) + assertThatThrownBy(() -> createTodayQuizService.createTodayQuiz(QuizCategory.DEVELOP)) .isInstanceOf(InvalidTodayQuizWordCountException.class) .hasMessage("오늘의 퀴즈를 진행할 수 있는 용어 개수가 부족합니다."); } @@ -50,7 +51,7 @@ class CreateTodayQuizServiceTest { }) void 오늘의_퀴즈를_생성한다() { // when - TodayQuiz actual = createTodayQuizService.assembleTodayQuiz(); + TodayQuiz actual = createTodayQuizService.createTodayQuiz(QuizCategory.DEVELOP); // then assertThat(actual.getId()).isPositive(); From 3425f6d6c2f73729ca0f16e24f9c955e35b502a8 Mon Sep 17 00:00:00 2001 From: apptie Date: Sat, 26 Jul 2025 16:44:48 +0900 Subject: [PATCH 114/115] =?UTF-8?q?refactor:=20AdminWordServiceFacade=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/AdminWordServiceFacade.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java index 607ff44b..d508a17a 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/AdminWordServiceFacade.java @@ -20,7 +20,7 @@ public class AdminWordServiceFacade { @Transactional public Long createWord(CreateWordRequest createWordRequest) { - PersistWordDto wordDto = createWordservice.createWord(createWordRequest); + PersistWordDto wordDto = performWordCreation(createWordRequest); publishPersistedWordEvent(wordDto); return wordDto.id(); @@ -43,11 +43,19 @@ public void deletePronunciation(Long wordId, Long pronunciationId) { @Transactional public void deleteWord(Long wordId) { - deleteWordService.deleteWord(wordId); + performWordDeletion(wordId); publishDeletedWordEvent(wordId); } + private PersistWordDto performWordCreation(CreateWordRequest createWordRequest) { + return createWordservice.createWord(createWordRequest); + } + + private void performWordDeletion(Long wordId) { + deleteWordService.deleteWord(wordId); + } + private void publishDeletedWordEvent(Long wordId) { eventPublisher.publishEvent(new DeletedWordEvent(wordId)); } From cbff82d623a713fa2284253caa25124033c8da5c Mon Sep 17 00:00:00 2001 From: apptie Date: Tue, 5 Aug 2025 10:50:36 +0900 Subject: [PATCH 115/115] =?UTF-8?q?refactor:=20CreateTodayQuizService=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=EB=A5=BC=20package-private=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spaced/core/admin/application/CreateTodayQuizService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java index f03c2698..73000fbc 100644 --- a/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java +++ b/space-d/src/main/java/com/dnd/spaced/core/admin/application/CreateTodayQuizService.java @@ -24,7 +24,7 @@ @Service @RequiredArgsConstructor -public class CreateTodayQuizService { +class CreateTodayQuizService { private static final Long DEFAULT_WORD_METADATA_ID = 1L; private static final int REQUIRED_TODAY_QUIZ_WORD_COUNT = 4;