Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
@Data
public class EmailCodeVerificationResponse {
private final Boolean isVerified;
private final Boolean isRecoverable;

public static EmailCodeVerificationResponse from(EmailCodeVerificationVo vo) {
return new EmailCodeVerificationResponse(vo.getIsVerified());
return new EmailCodeVerificationResponse(vo.getIsVerified(), vo.getIsRecoverable());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.ftm.server.adapter.out.cache;

import com.ftm.server.application.port.out.cache.LoadTrendingPostsWithCachePort;
import com.ftm.server.application.port.out.persistence.post.LoadPostImagePort;
import com.ftm.server.application.port.out.persistence.post.LoadPostWithBookmarkCountPort;
import com.ftm.server.application.query.FindByIdsQuery;
import com.ftm.server.application.query.FindPostsByCreatedDateQuery;
import com.ftm.server.application.vo.post.PostWithBookmarkCountVo;
import com.ftm.server.application.vo.post.TrendingPostVo;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Primary
public class LoadTrendingPostsTestAdapter implements LoadTrendingPostsWithCachePort {

private final LoadPostWithBookmarkCountPort loadPostPort;
private final LoadPostImagePort loadPostImagePort;

private static final int N = 15;

@Override
public List<TrendingPostVo> loadTrendingPosts() {

// 현재보다 1주일 이전에 작성된 게시물만 조회(북마크 조회수 포함) (예전 게시물은 포함x)
List<PostWithBookmarkCountVo> rawPosts =
loadPostPort.loadAllPostsWithBookmarkCount(
FindPostsByCreatedDateQuery.of(LocalDate.now()));

if (rawPosts.isEmpty()) return List.of();

// 1. 최대값 계산 (정규화를 위해)
long maxView = 1, maxLike = 1, maxScrap = 1;
for (PostWithBookmarkCountVo post : rawPosts) {
maxView = Math.max(maxView, post.getViewCount());
maxLike = Math.max(maxLike, post.getLikeCount());
maxScrap = Math.max(maxScrap, post.getScrapCount());
}

// 2. 점수 계산 후 정렬
long finalMaxView = maxView;
long finalMaxLike = maxLike;
long finalMaxScrap = maxScrap;
List<PostWithBookmarkCountVo> topN =
rawPosts.stream()
.sorted(
Comparator.comparingDouble(
p ->
-((double) p.getViewCount() / finalMaxView
+ (double) p.getLikeCount()
/ finalMaxLike
+ (double) p.getScrapCount()
/ finalMaxScrap)
/ 3.0))
.limit(N)
.toList();

// 3. 각 게시물 이미지 조회 (없으면 null)
List<Long> postIds = topN.stream().map(PostWithBookmarkCountVo::getPostId).toList();

Map<Long, String> imageMap = new HashMap<>();
postIds.forEach(p -> imageMap.put(p, null));
loadPostImagePort
.loadRepresentativeImagesByPostIds(FindByIdsQuery.from(postIds))
.forEach(
postImage -> imageMap.put(postImage.getPostId(), postImage.getObjectKey()));

// 4. 결과 조합 (순위 부여)
return IntStream.range(0, topN.size())
.mapToObj(
i -> {
PostWithBookmarkCountVo post = topN.get(i);
String imageKey = imageMap.get(post.getPostId());
return TrendingPostVo.from(post, i + 1, imageKey); // rank = i+1
})
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class UserDomainPersistenceAdapter
implements LoadEmailVerificationLogPort,
SaveEmailVerificationLogPort,
UpdateEmailVerificationLogPort,
DeleteEmailVerificationLogPort,
CheckUserPort,
SaveUserPort,
SaveUserImagePort,
Expand Down Expand Up @@ -91,6 +92,11 @@ public void updateEmailVerificationLog(EmailVerificationLogs emailVerificationLo
emailVerificationLogsJpaEntity.updateFromDomainEntity(emailVerificationLogs);
}

@Override
public void deleteEmailVerificationLogsByEmail(DeleteByEmailCommand command) {
emailVerificationLogsRepository.deleteAllByEmail(command.getEmail());
}

@Override
public Boolean checksUserByEmail(FindByEmailQuery query) {
return userRepository.existsByEmail(query.getEmail());
Expand Down Expand Up @@ -154,6 +160,13 @@ public List<User> loadUserByDeleteOption(FindUserByDeleteOptionQuery query) {
.toList();
}

@Override
public Optional<User> loadDeletedUserByEmail(FindByEmailQuery query) {
Optional<UserJpaEntity> user =
userRepository.findByEmailAndIsDeleted(query.getEmail(), true);
return user.map(userMapper::toDomainEntity);
}

@Override
public UserImage loadUserImageByUserId(FindByUserIdQuery query) {
UserImageJpaEntity userImageJpaEntity =
Expand Down Expand Up @@ -307,4 +320,14 @@ public List<UserImage> loadUserImagesByUserIdIn(FindUserImagesByIdsQuery query)
.map(userImageMapper::toDomainEntity)
.toList();
}

@Override
public Boolean checksNotDeletedUserByEmail(FindByEmailQuery query) {
return userRepository.existsByEmailAndIsDeleted(query.getEmail(), false);
}

@Override
public Boolean checksUserSoftDeletedByEmail(FindByEmailQuery query) {
return userRepository.existsByEmailAndIsDeleted(query.getEmail(), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.ftm.server.adapter.out.persistence.model.EmailVerificationLogsJpaEntity;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
Expand All @@ -16,4 +19,8 @@ Optional<EmailVerificationLogsJpaEntity> findByVerificationCodeAndEmail(

Optional<EmailVerificationLogsJpaEntity> findByEmailAndIsVerified(
String email, Boolean isVerified);

@Modifying
@Query("DELETE FROM EmailVerificationLogsJpaEntity e WHERE e.email = :email")
void deleteAllByEmail(@Param("email") String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface UserRepository extends JpaRepository<UserJpaEntity, Long> {

Optional<UserJpaEntity> findByIdAndIsDeleted(Long userId, Boolean isDeleted);

Optional<UserJpaEntity> findByEmailAndIsDeleted(String email, Boolean isDeleted);

Boolean existsByEmail(String email);

Optional<UserJpaEntity> findByEmail(String email);
Expand All @@ -33,4 +35,6 @@ Optional<UserJpaEntity> findBySocialProviderAndSocialId(
@Query("SELECT u FROM UserJpaEntity u WHERE u.isDeleted = :isDeleted And u.deletedAt <=:end")
List<UserJpaEntity> findAllByDeletedBefore(
@Param("isDeleted") Boolean isDeleted, @Param("end") LocalDateTime end);

Boolean existsByEmailAndIsDeleted(String email, boolean b);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ftm.server.application.command.user;

import lombok.Data;

@Data
public class DeleteByEmailCommand {
private final String email;

public static DeleteByEmailCommand of(String email) {
return new DeleteByEmailCommand(email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ftm.server.application.command.user;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class DeleteUserByEmailCommand {
private final String email;

public static DeleteUserByEmailCommand of(String email) {
return new DeleteUserByEmailCommand(email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ftm.server.application.port.in.user;

import com.ftm.server.application.command.user.DeleteUserByEmailCommand;
import com.ftm.server.common.annotation.Port;

@Port
public interface UserHardDeleteByEmailUseCase {
void execute(DeleteUserByEmailCommand command);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ public interface CheckUserPort {
Boolean checksUserBySocialValue(FindBySocialValueQuery query);

Boolean checksUserById(FindByUserIdQuery query);

Boolean checksNotDeletedUserByEmail(FindByEmailQuery query);

Boolean checksUserSoftDeletedByEmail(FindByEmailQuery query);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ftm.server.application.port.out.persistence.user;

import com.ftm.server.application.command.user.DeleteByEmailCommand;
import com.ftm.server.common.annotation.Port;

@Port
public interface DeleteEmailVerificationLogPort {
void deleteEmailVerificationLogsByEmail(DeleteByEmailCommand command);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ftm.server.application.port.out.persistence.user;

import com.ftm.server.application.query.FindByEmailQuery;
import com.ftm.server.application.query.FindByUserIdQuery;
import com.ftm.server.application.query.FindUserByDeleteOptionQuery;
import com.ftm.server.application.query.FindUserByRoleQuery;
Expand All @@ -14,4 +15,6 @@ public interface LoadUserPort {
User loadUserByRole(FindUserByRoleQuery query);

List<User> loadUserByDeleteOption(FindUserByDeleteOptionQuery query);

Optional<User> loadDeletedUserByEmail(FindByEmailQuery query);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.ftm.server.application.service.user;

import com.ftm.server.application.port.in.user.EmailCodeVerificationUseCase;
import com.ftm.server.application.port.out.persistence.user.CheckUserPort;
import com.ftm.server.application.port.out.persistence.user.LoadEmailVerificationLogPort;
import com.ftm.server.application.port.out.persistence.user.UpdateEmailVerificationLogPort;
import com.ftm.server.application.query.EmailCodeVerificationQuery;
import com.ftm.server.application.query.FindByEmailQuery;
import com.ftm.server.application.vo.user.EmailCodeVerificationVo;
import com.ftm.server.domain.entity.EmailVerificationLogs;
import jakarta.transaction.Transactional;
Expand All @@ -17,6 +19,7 @@ public class EmailCodeVerificationService implements EmailCodeVerificationUseCas

private final LoadEmailVerificationLogPort loadEmailVerificationLogPort;
private final UpdateEmailVerificationLogPort updateEmailVerificationLogPort;
private final CheckUserPort checkUserPort;

@Override
@Transactional
Expand All @@ -28,8 +31,14 @@ public EmailCodeVerificationVo execute(EmailCodeVerificationQuery query) {
if (emailVerificationLogs.isEmpty()) { // 검증 코드가 일치하지 않음
return EmailCodeVerificationVo.of(false);
}

emailVerificationLogs.get().updateVerificationStatus(true); // 검증 코드가 일치함
updateEmailVerificationLogPort.updateEmailVerificationLog(emailVerificationLogs.get());
return EmailCodeVerificationVo.of(true);

// 해당 이메일의 계정 상태 확인 (soft delete 여부)
Boolean isRecoverable =
checkUserPort.checksUserSoftDeletedByEmail(FindByEmailQuery.of(query.getEmail()));

return EmailCodeVerificationVo.of(true, isRecoverable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ public class EmailDuplicationCheckService implements EmailDuplicationCheckUseCas

@Override
public EmailDuplicationVo execute(FindByEmailQuery query) {
return EmailDuplicationVo.of(checkUserPort.checksUserByEmail(query));
return EmailDuplicationVo.of(checkUserPort.checksNotDeletedUserByEmail(query));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.ftm.server.application.service.user;

import com.ftm.server.adapter.in.web.user.dto.response.GeneralUserSignupResponse;
import com.ftm.server.application.command.user.DeleteByEmailCommand;
import com.ftm.server.application.command.user.DeleteUserByEmailCommand;
import com.ftm.server.application.command.user.GeneralUserCreationCommand;
import com.ftm.server.application.command.user.GeneralUserSignupCommand;
import com.ftm.server.application.port.in.user.GeneralUserSignupUseCase;
import com.ftm.server.application.port.in.user.UserHardDeleteByEmailUseCase;
import com.ftm.server.application.port.out.persistence.user.CheckUserPort;
import com.ftm.server.application.port.out.persistence.user.DeleteEmailVerificationLogPort;
import com.ftm.server.application.port.out.persistence.user.LoadEmailVerificationLogPort;
import com.ftm.server.application.port.out.persistence.user.SaveUserImagePort;
import com.ftm.server.application.port.out.persistence.user.SaveUserPort;
Expand All @@ -25,8 +29,12 @@
@RequiredArgsConstructor
public class GeneralUserSignupService implements GeneralUserSignupUseCase {

// usecase
private final UserHardDeleteByEmailUseCase userHardDeleteByEmailUseCase;

// service
private final LoadEmailVerificationLogPort loadEmailVerificationLogPort;
private final DeleteEmailVerificationLogPort deleteEmailVerificationLogPort;
private final CheckUserPort checksUserPort;
private final SaveUserPort saveUserPort;
private final SaveUserImagePort saveUserImagePort;
Expand All @@ -42,14 +50,20 @@ public GeneralUserSignupResponse execute(GeneralUserSignupCommand command) {
loadEmailVerificationLogPort.loadEmailVerificationLogByEmail(
FindByEmailQuery.of(email));

if (checksUserPort.checksUserByEmail(FindByEmailQuery.of(email))) { // 기존에 가입된 회원인지 검사
if (checksUserPort.checksNotDeletedUserByEmail(
FindByEmailQuery.of(email))) { // 기존에 가입된 회원인지 검사(삭제되지 않은 user 에 한해서)
throw new CustomException(ErrorResponseCode.USER_ALREADY_EXISTS);
}

if (emailVerificationLogs.isEmpty()) { // 이메일 인증이 완료되지 않음.
throw new CustomException(ErrorResponseCode.EMAIL_NOT_VERIFIED);
}

// 회원 탈퇴 후 복구 없이 재가입하는 경우, 기존의 계정 정보 즉시 삭제
if (checksUserPort.checksUserSoftDeletedByEmail(FindByEmailQuery.of(email))) {
userHardDeleteByEmailUseCase.execute(DeleteUserByEmailCommand.of(email));
}

String nickname = RandomNickNameCreator.generateNickname(); // random 닉네임 생성

GeneralUserCreationCommand convertedCommand =
Expand All @@ -62,6 +76,11 @@ public GeneralUserSignupResponse execute(GeneralUserSignupCommand command) {

User user = saveUserPort.saveUser(User.createGeneralUser(convertedCommand));
saveUserImagePort.saveUserDefaultImage(UserImage.createUserImage(user.getId()));

// 회원가입 완료 후 해당 이메일의 인증 로그 삭제
deleteEmailVerificationLogPort.deleteEmailVerificationLogsByEmail(
DeleteByEmailCommand.of(email));

return GeneralUserSignupResponse.of(user.getId());
}
}
Loading
Loading