From f695c66818728e94c677b716849574b8213cf1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Sun, 21 Jul 2024 23:11:09 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor(BaseEntity):=20deleted=EB=A5=BC=20?= =?UTF-8?q?BaseEntity=EC=97=90=EC=84=9C=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stempo/api/domain/persistence/entity/ArticleEntity.java | 3 +++ .../com/stempo/api/domain/persistence/entity/BaseEntity.java | 3 --- .../com/stempo/api/domain/persistence/entity/BoardEntity.java | 3 +++ .../com/stempo/api/domain/persistence/entity/RecordEntity.java | 3 +++ .../api/domain/persistence/entity/UploadedFileEntity.java | 3 +++ .../com/stempo/api/domain/persistence/entity/UserEntity.java | 3 +++ .../com/stempo/api/domain/persistence/entity/VideoEntity.java | 3 +++ 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/ArticleEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/ArticleEntity.java index 29edb343..53092855 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/ArticleEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/ArticleEntity.java @@ -40,4 +40,7 @@ public class ArticleEntity extends BaseEntity { @Column(nullable = false) @URL(message = "Invalid URL") private String articleUrl; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/BaseEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/BaseEntity.java index 886579ec..acd6b404 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/BaseEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/BaseEntity.java @@ -24,9 +24,6 @@ public class BaseEntity { @LastModifiedDate private LocalDateTime updatedAt; - @Column(nullable = false) - private boolean deleted = false; - @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/BoardEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/BoardEntity.java index 707d6810..04a623f9 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/BoardEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/BoardEntity.java @@ -44,4 +44,7 @@ public class BoardEntity extends BaseEntity { @Column(nullable = false) @Size(min = 1, max = 10000, message = "Content must be between 1 and 10000 characters") private String content; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/RecordEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/RecordEntity.java index 2c0bb473..8cb2bd31 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/RecordEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/RecordEntity.java @@ -34,4 +34,7 @@ public class RecordEntity extends BaseEntity { private Integer duration; private Integer steps; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/UploadedFileEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/UploadedFileEntity.java index a3058603..0cabbf5a 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/UploadedFileEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/UploadedFileEntity.java @@ -40,4 +40,7 @@ public class UploadedFileEntity extends BaseEntity { @Column(nullable = false) private Long fileSize; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/UserEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/UserEntity.java index e9d7dbf2..c4bd8cb9 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/UserEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/UserEntity.java @@ -31,4 +31,7 @@ public class UserEntity extends BaseEntity { @Enumerated(EnumType.STRING) private Role role; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/VideoEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/VideoEntity.java index 89b2cf21..346e84f8 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/VideoEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/VideoEntity.java @@ -40,4 +40,7 @@ public class VideoEntity extends BaseEntity { @Column(nullable = false) @URL(message = "Invalid URL") private String videoUrl; + + @Column(nullable = false) + private boolean deleted = false; } From 55e93d72d846ee25082760b154ed6a7556ff02e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Sun, 21 Jul 2024 23:29:14 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(Achievement):=20=EC=97=85=EC=A0=81=20C?= =?UTF-8?q?RUD=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AchievementService.java | 18 +++++ .../service/AchievementServiceImpl.java | 47 +++++++++++++ .../api/domain/domain/model/Achievement.java | 35 ++++++++++ .../repository/AchievementRepository.java | 14 ++++ .../persistence/entity/AchievementEntity.java | 3 + .../mappper/AchievementMapper.java | 29 ++++++++ .../persistence/mappper/RecordMapper.java | 14 ++-- .../repository/AchievementJpaRepository.java | 16 +++++ .../repository/AchievementRepositoryImpl.java | 42 +++++++++++ .../presentation/AchievementController.java | 69 +++++++++++++++++++ .../dto/request/AchievementRequestDto.java | 32 +++++++++ .../request/AchievementUpdateRequestDto.java | 19 +++++ .../dto/response/AchievementResponseDto.java | 24 +++++++ 13 files changed, 355 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/stempo/api/domain/application/service/AchievementService.java create mode 100644 src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java create mode 100644 src/main/java/com/stempo/api/domain/domain/model/Achievement.java create mode 100644 src/main/java/com/stempo/api/domain/domain/repository/AchievementRepository.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/mappper/AchievementMapper.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java create mode 100644 src/main/java/com/stempo/api/domain/presentation/AchievementController.java create mode 100644 src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementRequestDto.java create mode 100644 src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementUpdateRequestDto.java create mode 100644 src/main/java/com/stempo/api/domain/presentation/dto/response/AchievementResponseDto.java diff --git a/src/main/java/com/stempo/api/domain/application/service/AchievementService.java b/src/main/java/com/stempo/api/domain/application/service/AchievementService.java new file mode 100644 index 00000000..7cd984b7 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/service/AchievementService.java @@ -0,0 +1,18 @@ +package com.stempo.api.domain.application.service; + +import com.stempo.api.domain.presentation.dto.request.AchievementRequestDto; +import com.stempo.api.domain.presentation.dto.request.AchievementUpdateRequestDto; +import com.stempo.api.domain.presentation.dto.response.AchievementResponseDto; + +import java.util.List; + +public interface AchievementService { + + Long registerAchievement(AchievementRequestDto requestDto); + + List getAchievements(); + + Long updateAchievement(Long achievementId, AchievementUpdateRequestDto requestDto); + + Long deleteAchievement(Long achievementId); +} diff --git a/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java b/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java new file mode 100644 index 00000000..c80db60f --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java @@ -0,0 +1,47 @@ +package com.stempo.api.domain.application.service; + +import com.stempo.api.domain.domain.model.Achievement; +import com.stempo.api.domain.domain.repository.AchievementRepository; +import com.stempo.api.domain.presentation.dto.request.AchievementRequestDto; +import com.stempo.api.domain.presentation.dto.request.AchievementUpdateRequestDto; +import com.stempo.api.domain.presentation.dto.response.AchievementResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AchievementServiceImpl implements AchievementService { + + private final AchievementRepository repository; + + + @Override + public Long registerAchievement(AchievementRequestDto requestDto) { + Achievement achievement = AchievementRequestDto.toDomain(requestDto); + return repository.save(achievement).getId(); + } + + @Override + public List getAchievements() { + List achievements = repository.findAll(); + return achievements.stream() + .map(AchievementResponseDto::toDto) + .toList(); + } + + @Override + public Long updateAchievement(Long achievementId, AchievementUpdateRequestDto requestDto) { + Achievement achievement = repository.findByIdOrThrow(achievementId); + achievement.update(requestDto); + return repository.save(achievement).getId(); + } + + @Override + public Long deleteAchievement(Long achievementId) { + Achievement achievement = repository.findByIdOrThrow(achievementId); + achievement.delete(); + return repository.save(achievement).getId(); + } +} diff --git a/src/main/java/com/stempo/api/domain/domain/model/Achievement.java b/src/main/java/com/stempo/api/domain/domain/model/Achievement.java new file mode 100644 index 00000000..6d1e8c34 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/domain/model/Achievement.java @@ -0,0 +1,35 @@ +package com.stempo.api.domain.domain.model; + +import com.stempo.api.domain.presentation.dto.request.AchievementUpdateRequestDto; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Optional; + +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class Achievement { + + private Long id; + private String name; + private String description; + private String imageUrl; + private boolean deleted; + + public void update(AchievementUpdateRequestDto requestDto) { + Optional.ofNullable(requestDto.getName()).ifPresent(this::setName); + Optional.ofNullable(requestDto.getDescription()).ifPresent(this::setDescription); + Optional.ofNullable(requestDto.getImageUrl()).ifPresent(this::setImageUrl); + } + + public void delete() { + setDeleted(true); + } +} diff --git a/src/main/java/com/stempo/api/domain/domain/repository/AchievementRepository.java b/src/main/java/com/stempo/api/domain/domain/repository/AchievementRepository.java new file mode 100644 index 00000000..4717deb6 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/domain/repository/AchievementRepository.java @@ -0,0 +1,14 @@ +package com.stempo.api.domain.domain.repository; + +import com.stempo.api.domain.domain.model.Achievement; + +import java.util.List; + +public interface AchievementRepository { + + Achievement findByIdOrThrow(Long achievementId); + + List findAll(); + + Achievement save(Achievement achievement); +} diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/AchievementEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/AchievementEntity.java index d6faf21e..40a135c4 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/AchievementEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/AchievementEntity.java @@ -36,4 +36,7 @@ public class AchievementEntity extends BaseEntity { @Column(nullable = false) private String imageUrl; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/mappper/AchievementMapper.java b/src/main/java/com/stempo/api/domain/persistence/mappper/AchievementMapper.java new file mode 100644 index 00000000..ef2e2af2 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/mappper/AchievementMapper.java @@ -0,0 +1,29 @@ +package com.stempo.api.domain.persistence.mappper; + +import com.stempo.api.domain.domain.model.Achievement; +import com.stempo.api.domain.persistence.entity.AchievementEntity; +import org.springframework.stereotype.Component; + +@Component +public class AchievementMapper { + + public AchievementEntity toEntity(Achievement achievement) { + return AchievementEntity.builder() + .id(achievement.getId()) + .name(achievement.getName()) + .description(achievement.getDescription()) + .imageUrl(achievement.getImageUrl()) + .deleted(achievement.isDeleted()) + .build(); + } + + public Achievement toDomain(AchievementEntity entity) { + return Achievement.builder() + .id(entity.getId()) + .name(entity.getName()) + .description(entity.getDescription()) + .imageUrl(entity.getImageUrl()) + .deleted(entity.isDeleted()) + .build(); + } +} diff --git a/src/main/java/com/stempo/api/domain/persistence/mappper/RecordMapper.java b/src/main/java/com/stempo/api/domain/persistence/mappper/RecordMapper.java index f3f39baa..9efa1c7e 100644 --- a/src/main/java/com/stempo/api/domain/persistence/mappper/RecordMapper.java +++ b/src/main/java/com/stempo/api/domain/persistence/mappper/RecordMapper.java @@ -17,14 +17,14 @@ public RecordEntity toEntity(Record record) { .build(); } - public Record toDomain(RecordEntity recordEntity) { + public Record toDomain(RecordEntity entity) { return Record.builder() - .id(recordEntity.getId()) - .deviceTag(recordEntity.getDeviceTag()) - .accuracy(recordEntity.getAccuracy()) - .duration(recordEntity.getDuration()) - .steps(recordEntity.getSteps()) - .createdAt(recordEntity.getCreatedAt()) + .id(entity.getId()) + .deviceTag(entity.getDeviceTag()) + .accuracy(entity.getAccuracy()) + .duration(entity.getDuration()) + .steps(entity.getSteps()) + .createdAt(entity.getCreatedAt()) .build(); } } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java new file mode 100644 index 00000000..bb574a45 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java @@ -0,0 +1,16 @@ +package com.stempo.api.domain.persistence.repository; + +import com.stempo.api.domain.persistence.entity.AchievementEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface AchievementJpaRepository extends JpaRepository { + + @Query("SELECT a " + + "FROM AchievementEntity a " + + "WHERE a.deleted = false " + + "ORDER BY a.createdAt ASC") + List findAllActiveAchievements(); +} diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java new file mode 100644 index 00000000..90c12fd0 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java @@ -0,0 +1,42 @@ +package com.stempo.api.domain.persistence.repository; + +import com.stempo.api.domain.domain.model.Achievement; +import com.stempo.api.domain.domain.repository.AchievementRepository; +import com.stempo.api.domain.persistence.entity.AchievementEntity; +import com.stempo.api.domain.persistence.mappper.AchievementMapper; +import com.stempo.api.global.exception.NotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class AchievementRepositoryImpl implements AchievementRepository { + + private final AchievementJpaRepository repository; + private final AchievementMapper mapper; + + + @Override + public Achievement findByIdOrThrow(Long achievementId) { + AchievementEntity entity = repository.findById(achievementId) + .orElseThrow(() -> new NotFoundException("[Achievement] id: " + achievementId + " not found")); + return mapper.toDomain(entity); + } + + @Override + public List findAll() { + List entities = repository.findAllActiveAchievements(); + return entities.stream() + .map(mapper::toDomain) + .toList(); + } + + @Override + public Achievement save(Achievement achievement) { + AchievementEntity jpaEntity = mapper.toEntity(achievement); + AchievementEntity savedEntity = repository.save(jpaEntity); + return mapper.toDomain(savedEntity); + } +} diff --git a/src/main/java/com/stempo/api/domain/presentation/AchievementController.java b/src/main/java/com/stempo/api/domain/presentation/AchievementController.java new file mode 100644 index 00000000..6ceaf137 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/AchievementController.java @@ -0,0 +1,69 @@ +package com.stempo.api.domain.presentation; + +import com.stempo.api.domain.application.service.AchievementService; +import com.stempo.api.domain.presentation.dto.request.AchievementRequestDto; +import com.stempo.api.domain.presentation.dto.request.AchievementUpdateRequestDto; +import com.stempo.api.domain.presentation.dto.response.AchievementResponseDto; +import com.stempo.api.global.common.dto.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Achievement", description = "업적") +public class AchievementController { + + private final AchievementService achievementService; + + @Operation(summary = "[A] 업적 추가", description = "ROLE_ADMIN 이상의 권한이 필요함") + @Secured({ "ROLE_ADMIN" }) + @PostMapping("/api/v1/achievements") + public ApiResponse registerAchievement( + @Valid @RequestBody AchievementRequestDto requestDto + ) { + Long id = achievementService.registerAchievement(requestDto); + return ApiResponse.success(id); + } + + @Operation(summary = "[U] 업적 목록 조회", description = "ROLE_USER 이상의 권한이 필요함") + @Secured({ "ROLE_USER", "ROLE_ADMIN" }) + @GetMapping("/api/v1/achievements") + public ApiResponse> getAchievements() { + List achievements = achievementService.getAchievements(); + return ApiResponse.success(achievements); + } + + @Operation(summary = "[A] 업적 수정", description = "ROLE_ADMIN 이상의 권한이 필요함") + @Secured({ "ROLE_ADMIN" }) + @PatchMapping("/api/v1/achievements/{achievementId}") + public ApiResponse updateAchievement( + @PathVariable(name = "achievementId") Long achievementId, + @Valid @RequestBody AchievementUpdateRequestDto requestDto + ) { + Long id = achievementService.updateAchievement(achievementId, requestDto); + return ApiResponse.success(id); + } + + @Operation(summary = "[A] 업적 삭제", description = "ROLE_ADMIN 이상의 권한이 필요함") + @Secured({ "ROLE_ADMIN" }) + @DeleteMapping("/api/v1/achievements/{achievementId}") + public ApiResponse deleteAchievement( + @PathVariable(name = "achievementId") Long achievementId + ) { + Long id = achievementService.deleteAchievement(achievementId); + return ApiResponse.success(id); + } + +} diff --git a/src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementRequestDto.java b/src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementRequestDto.java new file mode 100644 index 00000000..9fe4f45b --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementRequestDto.java @@ -0,0 +1,32 @@ +package com.stempo.api.domain.presentation.dto.request; + +import com.stempo.api.domain.domain.model.Achievement; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AchievementRequestDto { + + @NotNull + @Schema(description = "업적 이름", example = "업적 이름", minLength = 1, maxLength = 100) + private String name; + + @NotNull + @Schema(description = "업적 설명", example = "업적 설명", minLength = 1, maxLength = 255) + private String description; + + @NotNull + @Schema(description = "이미지 URL", example = "https://example.com/image.jpg", minLength = 1, maxLength = 255) + private String imageUrl; + + public static Achievement toDomain(AchievementRequestDto requestDto) { + return Achievement.builder() + .name(requestDto.getName()) + .description(requestDto.getDescription()) + .imageUrl(requestDto.getImageUrl()) + .build(); + } +} diff --git a/src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementUpdateRequestDto.java b/src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementUpdateRequestDto.java new file mode 100644 index 00000000..82549518 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/dto/request/AchievementUpdateRequestDto.java @@ -0,0 +1,19 @@ +package com.stempo.api.domain.presentation.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AchievementUpdateRequestDto { + + @Schema(description = "업적 이름", example = "업적 이름", minLength = 1, maxLength = 100) + private String name; + + @Schema(description = "업적 설명", example = "업적 설명", minLength = 1, maxLength = 255) + private String description; + + @Schema(description = "이미지 URL", example = "https://example.com/image.jpg", minLength = 1, maxLength = 255) + private String imageUrl; +} diff --git a/src/main/java/com/stempo/api/domain/presentation/dto/response/AchievementResponseDto.java b/src/main/java/com/stempo/api/domain/presentation/dto/response/AchievementResponseDto.java new file mode 100644 index 00000000..fba8dd3d --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/dto/response/AchievementResponseDto.java @@ -0,0 +1,24 @@ +package com.stempo.api.domain.presentation.dto.response; + +import com.stempo.api.domain.domain.model.Achievement; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class AchievementResponseDto { + + private Long id; + private String name; + private String description; + private String imageUrl; + + public static AchievementResponseDto toDto(Achievement achievement) { + return AchievementResponseDto.builder() + .id(achievement.getId()) + .name(achievement.getName()) + .description(achievement.getDescription()) + .imageUrl(achievement.getImageUrl()) + .build(); + } +} From 7d19221612692d9eef9faab37606ce3553f0b7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Mon, 22 Jul 2024 00:11:40 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat(Achievement):=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=97=85=EC=A0=81=20=EB=93=B1=EB=A1=9D,=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/UserAchievementService.java | 12 +++++ .../service/UserAchievementServiceImpl.java | 49 +++++++++++++++++++ .../domain/domain/model/UserAchievement.java | 30 ++++++++++++ .../repository/UserAchievementRepository.java | 16 ++++++ .../entity/UserAchievementEntity.java | 38 ++++++++++++++ .../mappper/UserAchievementMapper.java | 26 ++++++++++ .../repository/AchievementJpaRepository.java | 7 +++ .../repository/AchievementRepositoryImpl.java | 2 +- .../UserAchievementJpaRepository.java | 14 ++++++ .../UserAchievementRepositoryImpl.java | 36 ++++++++++++++ .../UserAchievementController.java | 41 ++++++++++++++++ .../response/UserAchievementResponseDto.java | 22 +++++++++ 12 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/stempo/api/domain/application/service/UserAchievementService.java create mode 100644 src/main/java/com/stempo/api/domain/application/service/UserAchievementServiceImpl.java create mode 100644 src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java create mode 100644 src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java create mode 100644 src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java create mode 100644 src/main/java/com/stempo/api/domain/presentation/UserAchievementController.java create mode 100644 src/main/java/com/stempo/api/domain/presentation/dto/response/UserAchievementResponseDto.java diff --git a/src/main/java/com/stempo/api/domain/application/service/UserAchievementService.java b/src/main/java/com/stempo/api/domain/application/service/UserAchievementService.java new file mode 100644 index 00000000..ed196846 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/service/UserAchievementService.java @@ -0,0 +1,12 @@ +package com.stempo.api.domain.application.service; + +import com.stempo.api.domain.presentation.dto.response.UserAchievementResponseDto; + +import java.util.List; + +public interface UserAchievementService { + + Long registerUserAchievement(Long achievementId); + + List getUserAchievements(); +} diff --git a/src/main/java/com/stempo/api/domain/application/service/UserAchievementServiceImpl.java b/src/main/java/com/stempo/api/domain/application/service/UserAchievementServiceImpl.java new file mode 100644 index 00000000..6e9670ac --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/service/UserAchievementServiceImpl.java @@ -0,0 +1,49 @@ +package com.stempo.api.domain.application.service; + +import com.stempo.api.domain.domain.model.Achievement; +import com.stempo.api.domain.domain.model.UserAchievement; +import com.stempo.api.domain.domain.repository.AchievementRepository; +import com.stempo.api.domain.domain.repository.UserAchievementRepository; +import com.stempo.api.domain.persistence.entity.UserAchievementEntity; +import com.stempo.api.domain.presentation.dto.response.UserAchievementResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class UserAchievementServiceImpl implements UserAchievementService { + + private final UserService userService; + private final UserAchievementRepository userAchievementRepository; + private final AchievementRepository achievementRepository; + + @Override + public Long registerUserAchievement(Long achievementId) { + String deviceTag = userService.getCurrentDeviceTag(); + Optional existingUserAchievement = userAchievementRepository.findByDeviceTagAndAchievementId(deviceTag, achievementId); + + if (existingUserAchievement.isPresent()) { + return existingUserAchievement.get().getId(); + } + + UserAchievement userAchievement = UserAchievement.create(achievementId, deviceTag); + return userAchievementRepository.save(userAchievement).getId(); + } + + @Override + public List getUserAchievements() { + String deviceTag = userService.getCurrentDeviceTag(); + List userAchievements = userAchievementRepository.findByDeviceTag(deviceTag); + return userAchievements.stream() + .map(this::getUserAchievementResponseDto) + .toList(); + } + + private UserAchievementResponseDto getUserAchievementResponseDto(UserAchievementEntity ua) { + Achievement achievement = achievementRepository.findByIdOrThrow(ua.getAchievementId()); + return UserAchievementResponseDto.toDto(achievement); + } +} diff --git a/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java b/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java new file mode 100644 index 00000000..e0e44079 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java @@ -0,0 +1,30 @@ +package com.stempo.api.domain.domain.model; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class UserAchievement { + + private Long id; + private String deviceTag; + private Long achievementId; + private LocalDateTime createdAt; + + public static UserAchievement create(Long achievementId, String deviceTag) { + return UserAchievement.builder() + .achievementId(achievementId) + .deviceTag(deviceTag) + .build(); + } +} diff --git a/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java b/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java new file mode 100644 index 00000000..dfdb7c2b --- /dev/null +++ b/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java @@ -0,0 +1,16 @@ +package com.stempo.api.domain.domain.repository; + +import com.stempo.api.domain.domain.model.UserAchievement; +import com.stempo.api.domain.persistence.entity.UserAchievementEntity; + +import java.util.List; +import java.util.Optional; + +public interface UserAchievementRepository { + + UserAchievement save(UserAchievement userAchievement); + + List findByDeviceTag(String deviceTag); + + Optional findByDeviceTagAndAchievementId(String deviceTag, Long achievementId); +} diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java new file mode 100644 index 00000000..e055f7a3 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java @@ -0,0 +1,38 @@ +package com.stempo.api.domain.persistence.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "user_achievement", indexes = { + @Index(name = "idx_user_achievement_device_tag", columnList = "deviceTag"), + @Index(name = "idx_user_achievement_achievement_id", columnList = "achievementId") +}) +public class UserAchievementEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String deviceTag; + + @Column(nullable = false) + private Long achievementId; +} diff --git a/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java b/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java new file mode 100644 index 00000000..571d6f1f --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java @@ -0,0 +1,26 @@ +package com.stempo.api.domain.persistence.mappper; + +import com.stempo.api.domain.domain.model.UserAchievement; +import com.stempo.api.domain.persistence.entity.UserAchievementEntity; +import org.springframework.stereotype.Component; + +@Component +public class UserAchievementMapper { + + public UserAchievementEntity toEntity(UserAchievement userAchievement) { + return UserAchievementEntity.builder() + .id(userAchievement.getId()) + .deviceTag(userAchievement.getDeviceTag()) + .achievementId(userAchievement.getAchievementId()) + .build(); + } + + public UserAchievement toDomain(UserAchievementEntity userAchievementEntity) { + return UserAchievement.builder() + .id(userAchievementEntity.getId()) + .deviceTag(userAchievementEntity.getDeviceTag()) + .achievementId(userAchievementEntity.getAchievementId()) + .createdAt(userAchievementEntity.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java index bb574a45..3a2f7efa 100644 --- a/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java +++ b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementJpaRepository.java @@ -3,8 +3,10 @@ import com.stempo.api.domain.persistence.entity.AchievementEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface AchievementJpaRepository extends JpaRepository { @@ -13,4 +15,9 @@ public interface AchievementJpaRepository extends JpaRepository findAllActiveAchievements(); + + @Query("SELECT a " + + "FROM AchievementEntity a " + + "WHERE a.id = :achievementId AND a.deleted = false") + Optional findByIdAndNotDeleted(@Param("achievementId") Long achievementId); } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java index 90c12fd0..69c08f78 100644 --- a/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java +++ b/src/main/java/com/stempo/api/domain/persistence/repository/AchievementRepositoryImpl.java @@ -20,7 +20,7 @@ public class AchievementRepositoryImpl implements AchievementRepository { @Override public Achievement findByIdOrThrow(Long achievementId) { - AchievementEntity entity = repository.findById(achievementId) + AchievementEntity entity = repository.findByIdAndNotDeleted(achievementId) .orElseThrow(() -> new NotFoundException("[Achievement] id: " + achievementId + " not found")); return mapper.toDomain(entity); } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java new file mode 100644 index 00000000..0ec8e565 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java @@ -0,0 +1,14 @@ +package com.stempo.api.domain.persistence.repository; + +import com.stempo.api.domain.persistence.entity.UserAchievementEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface UserAchievementJpaRepository extends JpaRepository { + + List findByDeviceTagOrderByCreatedAtDesc(String deviceTag); + + Optional findByDeviceTagAndAchievementId(String deviceTag, Long achievementId); +} diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java new file mode 100644 index 00000000..ed94a74a --- /dev/null +++ b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java @@ -0,0 +1,36 @@ +package com.stempo.api.domain.persistence.repository; + +import com.stempo.api.domain.domain.model.UserAchievement; +import com.stempo.api.domain.domain.repository.UserAchievementRepository; +import com.stempo.api.domain.persistence.entity.UserAchievementEntity; +import com.stempo.api.domain.persistence.mappper.UserAchievementMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class UserAchievementRepositoryImpl implements UserAchievementRepository { + + private final UserAchievementJpaRepository repository; + private final UserAchievementMapper mapper; + + @Override + public UserAchievement save(UserAchievement userAchievement) { + UserAchievementEntity jpaEntity = mapper.toEntity(userAchievement); + UserAchievementEntity savedEntity = repository.save(jpaEntity); + return mapper.toDomain(savedEntity); + } + + @Override + public List findByDeviceTag(String deviceTag) { + return repository.findByDeviceTagOrderByCreatedAtDesc(deviceTag); + } + + @Override + public Optional findByDeviceTagAndAchievementId(String deviceTag, Long achievementId) { + return repository.findByDeviceTagAndAchievementId(deviceTag, achievementId); + } +} diff --git a/src/main/java/com/stempo/api/domain/presentation/UserAchievementController.java b/src/main/java/com/stempo/api/domain/presentation/UserAchievementController.java new file mode 100644 index 00000000..b7b1103a --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/UserAchievementController.java @@ -0,0 +1,41 @@ +package com.stempo.api.domain.presentation; + +import com.stempo.api.domain.application.service.UserAchievementService; +import com.stempo.api.domain.presentation.dto.response.UserAchievementResponseDto; +import com.stempo.api.global.common.dto.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Achievement - User", description = "유저 업적") +public class UserAchievementController { + + private final UserAchievementService userAchievementService; + + @Operation(summary = "[U] 유저 업적 등록", description = "ROLE_USER 이상의 권한이 필요함") + @Secured({ "ROLE_USER", "ROLE_ADMIN" }) + @PostMapping("/api/v1/achievements/user/{achievementId}") + public ApiResponse registerUserAchievement( + @PathVariable(name = "achievementId") Long achievementId + ) { + Long id = userAchievementService.registerUserAchievement(achievementId); + return ApiResponse.success(id); + } + + @Operation(summary = "[U] 내 업적 조회", description = "ROLE_USER 이상의 권한이 필요함") + @Secured({ "ROLE_USER", "ROLE_ADMIN" }) + @GetMapping("/api/v1/achievements/user") + public ApiResponse> getUserAchievements() { + List myAchievements = userAchievementService.getUserAchievements(); + return ApiResponse.success(myAchievements); + } +} diff --git a/src/main/java/com/stempo/api/domain/presentation/dto/response/UserAchievementResponseDto.java b/src/main/java/com/stempo/api/domain/presentation/dto/response/UserAchievementResponseDto.java new file mode 100644 index 00000000..42ba6851 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/dto/response/UserAchievementResponseDto.java @@ -0,0 +1,22 @@ +package com.stempo.api.domain.presentation.dto.response; + +import com.stempo.api.domain.domain.model.Achievement; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class UserAchievementResponseDto { + + private final String name; + private final String description; + private final String imageUrl; + + public static UserAchievementResponseDto toDto(Achievement achievement) { + return UserAchievementResponseDto.builder() + .name(achievement.getName()) + .description(achievement.getDescription()) + .imageUrl(achievement.getImageUrl()) + .build(); + } +} From 590c02fc0f34c5c34343e712db7315af11e98c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Mon, 22 Jul 2024 00:37:07 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat(Achievement):=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=9D=B4=EB=B2=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 --- .../event/AchievementDeletedEvent.java | 15 ++++++++++ .../event/AchievementEventDispatcher.java | 24 +++++++++++++++ .../event/AchievementEventProcessor.java | 8 +++++ .../AchievementEventProcessorRegistry.java | 21 ++++++++++++++ .../event/AchievementUpdatedEvent.java | 15 ++++++++++ .../event/UserAchievementEventProcessor.java | 29 +++++++++++++++++++ .../service/AchievementServiceImpl.java | 13 +++++++-- .../domain/domain/model/UserAchievement.java | 5 ++++ .../repository/UserAchievementRepository.java | 4 +++ .../entity/UserAchievementEntity.java | 3 ++ .../mappper/UserAchievementMapper.java | 2 ++ .../UserAchievementJpaRepository.java | 9 +++++- .../UserAchievementRepositoryImpl.java | 18 +++++++++++- 13 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/stempo/api/domain/application/event/AchievementDeletedEvent.java create mode 100644 src/main/java/com/stempo/api/domain/application/event/AchievementEventDispatcher.java create mode 100644 src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessor.java create mode 100644 src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessorRegistry.java create mode 100644 src/main/java/com/stempo/api/domain/application/event/AchievementUpdatedEvent.java create mode 100644 src/main/java/com/stempo/api/domain/application/event/UserAchievementEventProcessor.java diff --git a/src/main/java/com/stempo/api/domain/application/event/AchievementDeletedEvent.java b/src/main/java/com/stempo/api/domain/application/event/AchievementDeletedEvent.java new file mode 100644 index 00000000..415b44b2 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/event/AchievementDeletedEvent.java @@ -0,0 +1,15 @@ +package com.stempo.api.domain.application.event; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class AchievementDeletedEvent extends ApplicationEvent { + + private final Long achievementId; + + public AchievementDeletedEvent(Object source, Long achievementId) { + super(source); + this.achievementId = achievementId; + } +} diff --git a/src/main/java/com/stempo/api/domain/application/event/AchievementEventDispatcher.java b/src/main/java/com/stempo/api/domain/application/event/AchievementEventDispatcher.java new file mode 100644 index 00000000..941bd45b --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/event/AchievementEventDispatcher.java @@ -0,0 +1,24 @@ +package com.stempo.api.domain.application.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class AchievementEventDispatcher { + + private final List processors; + + @EventListener + public void handleAchievementDeletedEvent(AchievementDeletedEvent event) { + processors.forEach(processor -> processor.processAchievementDeleted(event.getAchievementId())); + } + + @EventListener + public void handleAchievementUpdatedEvent(AchievementUpdatedEvent event) { + processors.forEach(processor -> processor.processAchievementUpdated(event.getAchievementId())); + } +} diff --git a/src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessor.java b/src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessor.java new file mode 100644 index 00000000..487e6594 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessor.java @@ -0,0 +1,8 @@ +package com.stempo.api.domain.application.event; + +public interface AchievementEventProcessor { + + void processAchievementDeleted(Long achievementId); + + void processAchievementUpdated(Long achievementId); +} diff --git a/src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessorRegistry.java b/src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessorRegistry.java new file mode 100644 index 00000000..eda0b45d --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/event/AchievementEventProcessorRegistry.java @@ -0,0 +1,21 @@ +package com.stempo.api.domain.application.event; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Component +public class AchievementEventProcessorRegistry { + + private final List processors = new ArrayList<>(); + + public void register(AchievementEventProcessor processor) { + processors.add(processor); + } + + public List getProcessors() { + return Collections.unmodifiableList(processors); + } +} diff --git a/src/main/java/com/stempo/api/domain/application/event/AchievementUpdatedEvent.java b/src/main/java/com/stempo/api/domain/application/event/AchievementUpdatedEvent.java new file mode 100644 index 00000000..81da03ef --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/event/AchievementUpdatedEvent.java @@ -0,0 +1,15 @@ +package com.stempo.api.domain.application.event; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class AchievementUpdatedEvent extends ApplicationEvent { + + private final Long achievementId; + + public AchievementUpdatedEvent(Object source, Long achievementId) { + super(source); + this.achievementId = achievementId; + } +} diff --git a/src/main/java/com/stempo/api/domain/application/event/UserAchievementEventProcessor.java b/src/main/java/com/stempo/api/domain/application/event/UserAchievementEventProcessor.java new file mode 100644 index 00000000..fa93b307 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/application/event/UserAchievementEventProcessor.java @@ -0,0 +1,29 @@ +package com.stempo.api.domain.application.event; + +import com.stempo.api.domain.domain.model.UserAchievement; +import com.stempo.api.domain.domain.repository.UserAchievementRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class UserAchievementEventProcessor implements AchievementEventProcessor { + + private final UserAchievementRepository userAchievementRepository; + + @Override + @Transactional + public void processAchievementDeleted(Long achievementId) { + List achievements = userAchievementRepository.findByAchievementId(achievementId); + achievements.forEach(UserAchievement::delete); + userAchievementRepository.saveAll(achievements); + } + + @Override + public void processAchievementUpdated(Long achievementId) { + // do nothing + } +} diff --git a/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java b/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java index c80db60f..209c4f28 100644 --- a/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java +++ b/src/main/java/com/stempo/api/domain/application/service/AchievementServiceImpl.java @@ -1,11 +1,14 @@ package com.stempo.api.domain.application.service; +import com.stempo.api.domain.application.event.AchievementDeletedEvent; +import com.stempo.api.domain.application.event.AchievementUpdatedEvent; import com.stempo.api.domain.domain.model.Achievement; import com.stempo.api.domain.domain.repository.AchievementRepository; import com.stempo.api.domain.presentation.dto.request.AchievementRequestDto; import com.stempo.api.domain.presentation.dto.request.AchievementUpdateRequestDto; import com.stempo.api.domain.presentation.dto.response.AchievementResponseDto; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import java.util.List; @@ -15,7 +18,7 @@ public class AchievementServiceImpl implements AchievementService { private final AchievementRepository repository; - + private final ApplicationEventPublisher eventPublisher; @Override public Long registerAchievement(AchievementRequestDto requestDto) { @@ -35,13 +38,17 @@ public List getAchievements() { public Long updateAchievement(Long achievementId, AchievementUpdateRequestDto requestDto) { Achievement achievement = repository.findByIdOrThrow(achievementId); achievement.update(requestDto); - return repository.save(achievement).getId(); + repository.save(achievement); + eventPublisher.publishEvent(new AchievementUpdatedEvent(this, achievement.getId())); + return achievement.getId(); } @Override public Long deleteAchievement(Long achievementId) { Achievement achievement = repository.findByIdOrThrow(achievementId); achievement.delete(); - return repository.save(achievement).getId(); + repository.save(achievement); + eventPublisher.publishEvent(new AchievementDeletedEvent(this, achievement.getId())); + return achievement.getId(); } } diff --git a/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java b/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java index e0e44079..8e806e56 100644 --- a/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java +++ b/src/main/java/com/stempo/api/domain/domain/model/UserAchievement.java @@ -20,6 +20,7 @@ public class UserAchievement { private String deviceTag; private Long achievementId; private LocalDateTime createdAt; + private boolean deleted; public static UserAchievement create(Long achievementId, String deviceTag) { return UserAchievement.builder() @@ -27,4 +28,8 @@ public static UserAchievement create(Long achievementId, String deviceTag) { .deviceTag(deviceTag) .build(); } + + public void delete() { + setDeleted(true); + } } diff --git a/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java b/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java index dfdb7c2b..cdc8b41b 100644 --- a/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java +++ b/src/main/java/com/stempo/api/domain/domain/repository/UserAchievementRepository.java @@ -10,7 +10,11 @@ public interface UserAchievementRepository { UserAchievement save(UserAchievement userAchievement); + void saveAll(List achievements); + List findByDeviceTag(String deviceTag); Optional findByDeviceTagAndAchievementId(String deviceTag, Long achievementId); + + List findByAchievementId(Long achievementId); } diff --git a/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java b/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java index e055f7a3..55a98682 100644 --- a/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java +++ b/src/main/java/com/stempo/api/domain/persistence/entity/UserAchievementEntity.java @@ -35,4 +35,7 @@ public class UserAchievementEntity extends BaseEntity { @Column(nullable = false) private Long achievementId; + + @Column(nullable = false) + private boolean deleted = false; } diff --git a/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java b/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java index 571d6f1f..1087bd99 100644 --- a/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java +++ b/src/main/java/com/stempo/api/domain/persistence/mappper/UserAchievementMapper.java @@ -12,6 +12,7 @@ public UserAchievementEntity toEntity(UserAchievement userAchievement) { .id(userAchievement.getId()) .deviceTag(userAchievement.getDeviceTag()) .achievementId(userAchievement.getAchievementId()) + .deleted(userAchievement.isDeleted()) .build(); } @@ -21,6 +22,7 @@ public UserAchievement toDomain(UserAchievementEntity userAchievementEntity) { .deviceTag(userAchievementEntity.getDeviceTag()) .achievementId(userAchievementEntity.getAchievementId()) .createdAt(userAchievementEntity.getCreatedAt()) + .deleted(userAchievementEntity.isDeleted()) .build(); } } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java index 0ec8e565..c1fc1c6d 100644 --- a/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java +++ b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementJpaRepository.java @@ -2,13 +2,20 @@ import com.stempo.api.domain.persistence.entity.UserAchievementEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; public interface UserAchievementJpaRepository extends JpaRepository { - List findByDeviceTagOrderByCreatedAtDesc(String deviceTag); + @Query("SELECT ua " + + "FROM UserAchievementEntity ua " + + "WHERE ua.deviceTag = :deviceTag AND ua.deleted = false " + + "ORDER BY ua.createdAt DESC") + List findByDeviceTag(String deviceTag); Optional findByDeviceTagAndAchievementId(String deviceTag, Long achievementId); + + List findByAchievementId(Long achievementId); } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java index ed94a74a..715393ac 100644 --- a/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java +++ b/src/main/java/com/stempo/api/domain/persistence/repository/UserAchievementRepositoryImpl.java @@ -24,13 +24,29 @@ public UserAchievement save(UserAchievement userAchievement) { return mapper.toDomain(savedEntity); } + @Override + public void saveAll(List achievements) { + List jpaEntities = achievements.stream() + .map(mapper::toEntity) + .toList(); + repository.saveAll(jpaEntities); + } + @Override public List findByDeviceTag(String deviceTag) { - return repository.findByDeviceTagOrderByCreatedAtDesc(deviceTag); + return repository.findByDeviceTag(deviceTag); } @Override public Optional findByDeviceTagAndAchievementId(String deviceTag, Long achievementId) { return repository.findByDeviceTagAndAchievementId(deviceTag, achievementId); } + + @Override + public List findByAchievementId(Long achievementId) { + List jpaEntities = repository.findByAchievementId(achievementId); + return jpaEntities.stream() + .map(mapper::toDomain) + .toList(); + } }